🧑💻 本系列文章采用 Torchtext 0.13.1 版本
目录
- 前言
- 一、创建词典
-
- 1.1 根据有序字典进行创建
- 1.2 根据可迭代对象进行创建
-
- 1.2.1 从生成器中创建
- 二、Vocab的用法
-
- 2.1 获取词元到索引的映射/索引到词元的映射
- 2.2 正/反向查询
-
- 2.2.1 根据词元查询索引
- 2.2.2 根据索引查询词元
- 2.3 设置默认索引
- 2.4 添加词元
- 2.5 其他用法
- 附录
前言
词典(Vocab)是NLP任务中最为重要的工具之一,本文将详细介绍Torchtext中的词典类及其使用方法。
安装Torchtext:
conda install -c pytorch torchtext
导入本文所需要的所有包:
from collections import Counter, OrderedDict
from torchtext.vocab import vocab, build_vocab_from_iterator
一、创建词典
Torchtext中创建词典有两种方法,第一种是根据有序字典(OrderedDict)进行创建,第二种是根据生成器(Generator)/可迭代对象(Iterable)进行创建。
1.1 根据有序字典进行创建
NLP任务中,绝大多数时候 tokens
是一个二维列表,即 tokens[0]
代表一个句子,tokens[0][0]
代表一个词元(单词)。为了使用 Counter()
统计词频,我们需要先将 tokens
展平成一维列表(事实上展平成一维的可迭代对象即可),最常用的方法如下:
from tkinter import _flatten
tokens = _flatten(tokens) # 这里tokens是一个一维元组,是可迭代对象
如果 tokens
本身就是个一维列表,则 _flatten(tokens)
仍然会返回一维元组,因此我们可以总是使用 _flatten(tokens)
。
之后我们可以对这个一维可迭代对象使用 Counter()
统计词频,并将其从高到低进行过排序
>>> tokens = [["I", "am", "very", "happy"], ["I", "seem", "to", "have", "lost", "something"]]
>>> sorted(Counter(_flatten(tokens)).items(), key=lambda x: x[1], reverse=True)
[('I', 2), ('am', 1), ('very', 1), ('happy', 1), ('seem', 1), ('to', 1), ('have', 1), ('lost', 1), ('something', 1)]
可以看出输出结果是形如 List[Tuple[str, int]]
这样的类型,我们可以根据此结果来构造有序字典
tokens = [["I", "am", "very", "happy"], ["I", "seem", "to", "have", "lost", "something"]]
ordered_dict = OrderedDict(sorted(Counter(_flatten(tokens)).items(), key=lambda x: x[1], reverse=True))
print(ordered_dict)
# OrderedDict([('I', 2), ('am', 1), ('very', 1), ('happy', 1), ('seem', 1), ('to', 1), ('have', 1), ('lost', 1), ('something', 1)])
然后基于有序字典创建词典
v = vocab(ordered_dict)
print(type(v))
# <class 'torchtext.vocab.vocab.Vocab'>
⚠️ 这里的
vocab()
是一个函数,创建得到的v
是Vocab
类的实例。第二章节我们会详细介绍Vocab
类的用法。
我们还可以向 vocab()
函数传入其他参数。例如如果一词元的出现次数低于 2 就丢弃,则可设置
v = vocab(ordered_dict, min_freq=2)
在NMT(机器翻译)任务中,我们往往需要一些特殊词元,这时可以使用 specials
参数进行指定
v = vocab(ordered_dict, min_freq=2, specials=['<pad>', '<unk>', '<bos>', '<eos>'])
此时这些特殊词元会被添加到词表的最上方。如果需要将特殊词元添加到词表的最下方,则可指定
v = vocab(ordered_dict, min_freq=2, specials=['<pad>', '<unk>', '<bos>', '<eos>'], special_first=False)
1.2 根据可迭代对象进行创建
根据可迭代对象创建词典需要用到以下函数
build_vocab_from_iterator(iterator: Iterable,
min_freq: int = 1,
specials: Optional[List[str]] = None,
special_first: bool = True,
max_tokens: Optional[int] = None)
接下来重点讲解第一个和最后一个参数。
首先 iterator
是一个可迭代对象,观察 build_vocab_from_iterator
源码的 9~11 行可知(见本文附录),Counter
的实例每次会 update iterator
中的一个元素,而 Counter
的 update
方法源码如下:
def update(self, iterable=None, /, **kwds):
if iterable is not None:
if isinstance(iterable, _collections_abc.Mapping):
if self:
self_get = self.get
for elem, count in iterable.items():
self[elem] = count + self_get(elem, 0)
else:
# fast path when counter is empty
super().update(iterable)
else:
_count_elements(self, iterable)
if kwds:
self.update(kwds)
这说明 iterator
中的每一个元素仍是一个可迭代对象,所以我们可以直接向 iterator
传入二维列表 tokens
,如下:
tokens = [["I", "am", "very", "happy"], ["I", "seem", "to", "have", "lost", "something"]]
v = build_vocab_from_iterator(tokens)
print(v.get_stoi())
# {'to': 7, 'seem': 5, 'very': 8, 'something': 6, 'lost': 4, 'have': 3, 'happy': 2, 'am': 1, 'I': 0}
我们还可以指定词典的大小(算上特殊词元的大小)
v = build_vocab_from_iterator(tokens, max_tokens=3)
print(v.get_stoi())
# {'happy': 2, 'am': 1, 'I': 0}
⚠️
max_tokens
不能小于特殊词元的数量,否则词典中将只含特殊词元。
1.2.1 从生成器中创建
假如 ./data.txt
中的内容为
I am very happy
I seem to have lost something
则我们可以构造一个生成器,然后使用它来创建词典。
def yield_tokens(path):
with open(path) as f:
for line in f.readlines():
yield line.strip().split()
v = build_vocab_from_iterator(yield_tokens("./data.txt"))
二、Vocab的用法
无论使用 vocab()
函数还是 build_vocab_from_iterator()
函数,返回的结果都是一个 Vocab
实例。
2.1 获取词元到索引的映射/索引到词元的映射
获取词元到索引到映射(字典)
tokens = [['a', 'a', 'b'], ['c', 'c', 'd', 'd', 'd']]
v = build_vocab_from_iterator(tokens, specials=['<pad>', '<unk>'])
print(v.get_stoi())
# {'b': 5, 'd': 2, '<pad>': 0, '<unk>': 1, 'c': 4, 'a': 3}
获取索引到词元的映射(列表)
print(v.get_itos())
# ['<pad>', '<unk>', 'd', 'a', 'c', 'b']
2.2 正/反向查询
2.2.1 根据词元查询索引
根据单个词元查询其索引
print(v['a'])
# 3
根据多个词元查询它们对应的索引(常用)
print(v(['c', 'd', '<unk>']))
# [4, 2, 1]
2.2.2 根据索引查询词元
根据单个索引查询其词元
print(v.lookup_token(4))
# c
根据多个索引查询它们对应的词元
print(v.lookup_tokens([0, 5, 2]))
# ['<pad>', 'b', 'd']
2.3 设置默认索引
在实际应用中,我们难免会遇到OOV(Out Of Vocabulary)词元,这时如果直接查询其索引会报错
print(v['f'])
# RuntimeError: Token f not found and default index is not set
因此我们需要设置一个默认索引,所有OOV词元都会被映射到该索引上。通常来讲,我们会将默认索引设置为未知词元的索引,即
v.set_default_index(v['<unk>'])
print(v['f'])
# 1
如果要获取默认索引,可调用 get_default_index()
方法。
2.4 添加词元
如果一个词元不在词典当中,我们可以将其追加到词典的末尾
v.append_token('e')
print(v.get_stoi())
# {'b': 5, 'd': 2, '<pad>': 0, '<unk>': 1, 'e': 6, 'c': 4, 'a': 3}
除了追加到末尾之外,我们还可以在词典中的任意位置插入新词元,此时需要同时提供词元和索引
v.insert_token('e', 3)
print(v.get_stoi())
# {'b': 6, 'c': 5, 'e': 3, 'd': 2, '<pad>': 0, '<unk>': 1, 'a': 4}
2.5 其他用法
获取词典大小
print(len(v))
# 6
判断词元是否在词典当中
print('c' in v)
# True
print('k' in v)
# False
附录
vocab
函数源码:
def vocab(ordered_dict: Dict,
min_freq: int = 1,
specials: Optional[List[str]] = None,
special_first: bool = True
) -> Vocab:
specials = specials or []
for token in specials:
ordered_dict.pop(token, None)
tokens = []
# Save room for special tokens
for token, freq in ordered_dict.items():
if freq >= min_freq:
tokens.append(token)
if special_first:
tokens[0:0] = specials
else:
tokens.extend(specials)
return Vocab(VocabPybind(tokens, None)) # 这里的VocabPybind是C++对象
build_vocab_from_iterator
函数源码:
def build_vocab_from_iterator(
iterator: Iterable,
min_freq: int = 1,
specials: Optional[List[str]] = None,
special_first: bool = True,
max_tokens: Optional[int] = None,
) -> Vocab:
counter = Counter()
for tokens in iterator:
counter.update(tokens)
specials = specials or []
# First sort by descending frequency, then lexicographically
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: (-x[1], x[0]))
if max_tokens is None:
ordered_dict = OrderedDict(sorted_by_freq_tuples)
else:
assert len(specials) < max_tokens, "len(specials) >= max_tokens, so the vocab will be entirely special tokens."
ordered_dict = OrderedDict(sorted_by_freq_tuples[:max_tokens - len(specials)])
word_vocab = vocab(ordered_dict, min_freq=min_freq, specials=specials, special_first=special_first)
return word_vocab