class _NamespaceDependentDefaultDict(defaultdict)
记录non_padded_namespaces(哪些namespace不需要pad,例如tags,labels),以及如何进行pad(padded_function),或者不pad(non_padded_function)
def missing(self, key: str): 处理
如果key在non_padded_namespaces中,返回_non_padded_function
如果key不在non_padded_namespaces中,返回_padded_function
defaultdict类的初始化函数接受一个类型作为参数,当所访问的键不存在的时候,可以实例化一个值作为默认值
defaultdict类除了接受类型名称作为初始化函数的参数之外,还可以使用任何不带参数的可调用函数,到时该函数的返回结果作为默认值,这样使得默认值的取值更加灵活。
defaultdict类中通过__missing__()实现默认值的功能,当访问不存在的键时,dict[key]会调用__missing__()方法取得默认值。
python中defaultdict方法的使用
class _TokenToIndexDefaultDict
从_NamespaceDependentDefaultDict派生,padded_function为lambda: {padding_token: 0, oov_token: 1}
,即pad(无意义的填充词)为0,oov(词典中没有的词)为1
class _IndexToTokenDefaultDict
从_NamespaceDependentDefaultDict派生,padded_function为lambda: {0: padding_token, 1: oov_token}
,即0为pad(无意义的填充词),1为oov(词典中没有的词)
以上两个词典实际为两层的词典,例如{"labels":{"@@PADDING@@":0,"@@UNKNOWN@@":1}}
,{"tokens":{"word":10}}
。
一个巧妙的地方在于,基类的__missing__()方法中,判断如果某个命名空间不在词典中,则在添加命名空间时,判断它是不是需要pad,如果需要pad,则先把{"@@PADDING@@":0,"@@UNKNOWN@@":1}设置为这个命名空间的值。
_read_pretrained_tokens(embeddings_file_uri: str)
从txt或者压缩文件(zip/tar/…)获得所有token,返回值tokens: List[str]
文件中存embedding的形式为word XXXXXX
,即词与对应的embedding中间有一个空格,一行是一个词的。
def pop_max_vocab_size(params: Params)
从配置中获得词典中值的数量,返回int或Dict[str, int](多个词典)
fields.field.Field
(各种Field的基类), 此类中的大多数方法都允许传入命名空间;默认使用’tokens’命名空间,可以省略命名空间参数,只使用默认值。Vocabulary中用两个词典记录token到index,index到token。
他们是两层的词典,第一层是{命名空间:词典},第二层词典是token和index的对应。
self._token_to_index = _TokenToIndexDefaultDict
self._index_to_token = _IndexToTokenDefaultDict
_extend():向初始化的Vocabulary传数据建立词典,或拓展已有的词典,把tokens填充进词典,需要注意的是,用来创建词典的词有三个来源:1.数据集(instances)得到的(counter),2.预训练词嵌入文件(pretrained_files), 3.手动添加的词(tokens_to_add),3是最后加入词典的。
如果数据集中得到token先用词数进行排序,因此出现多的词在前面。
预训练词嵌入文件在counter中没有出现的词,没有加入词典。
add_token_to_namespace(self, token: str, namespace: str = ‘tokens’):把单词(按顺序,而不是插入加入到指定的命名空间中,
def from_files(cls, directory: str) -> 'Vocabulary’
读取vocabulary目录下的所有文件,例如non_padded_namespaces.txt,tokens.txt,token_characters.txt。。。最后调用vocab.set_from_file(filename, is_padded, namespace=namespace)
,由一个个命名空间构造出词典。
def set_from_file(self,
filename: str,
is_padded: bool = True,
oov_token: str = DEFAULT_OOV_TOKEN,
namespace: str = "tokens")
filename为non_padded_namespaces.txt这些文件的路径
is_padded指这个命名空间是不是需要pad的(tags与labels为False),如果需要pad,则词典的0号位置为@@PADDING@@
oov_token用哪个词代表OOV词,默认为@@UNKNOWN@@,在txt文件中可以看到第一个位置是@@UNKNOWN@@,如果is_padded=True,则@@UNKNOWN@@在1号位置。
namespace表示在那个命名空间。
读取embedding文件是逐行进行的,加入到词典时也是按照顺序的,因此词典中的词与文件中词的顺序对应,用来进行词嵌入。
Instance(Mapping[str, Field]),Instance包含若干个Field,Field的count_vocab_items方法各自不同。
每个instance调用count_vocab_items
namespace_token_counts: Dict[str(命名空间), Dict[str(词), int(数量)]]
instance.count_vocab_items(namespace_token_counts)
-------------->count_vocab_items()内部调用每个field的count_vocab_items()
for field in self.fields.values():
field.count_vocab_items(counter)
例如TextField,类中包括List[Token],token_indexers: Dict[str, TokenIndexer]。
token_indexers可以将tokens进行不同的转换,例如cat可以转换为34,也可以根据字符转换为[23, 10, 18],还有其他的方式。。。
def count_vocab_items(self, counter: Dict[str, Dict[str, int]]):
for indexer in self._token_indexers.values():
for token in self.tokens:
indexer.count_vocab_items(token, counter)
indexs下次单独学习,这里只是了解一下count_vocab_items做了什么。
以SingleIdTokenIndexer为例,在这里词作为一个整体。count_vocab_items方法中用一个计数器对单词进行计数,计数结果存在indexer设置的namespace中。
counter[self.namespace][text] += 1
读取配置文件创建Vocabulary部分可以在trainer.py的TrainerPieces类的from_params方法中找到。
if recover and os.path.exists(os.path.join(serialization_dir, "vocabulary")):
vocab = Vocabulary.from_files(os.path.join(serialization_dir, "vocabulary"))
params.pop("vocabulary", {})
else:
vocab = Vocabulary.from_params(
params.pop("vocabulary", {}),
(instance for key, dataset in all_datasets.items()
for instance in dataset
if key in datasets_for_vocab_creation)
)