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](多个词典)
class Vocabulary(Registrable)
- 词汇表将字符串映射到整数,允许将字符串映射到词典外的标记(OOV)。
- 词汇表匹配特定的数据集,用它来判断哪些token是在词典内。
- 词汇表还允许使用多个不同的命名空间,因此可以将“a”作为单词和“a”作为单词使用单独的索引,因此可以使用此命名空间将标记和标签字符串映射到索引(通过Field.index方法)。统一地,class:
fields.field.Field
(各种Field的基类), 此类中的大多数方法都允许传入命名空间;默认使用’tokens’命名空间,可以省略命名空间参数,只使用默认值。 - 命名空间:Instance的key(根据Instance向词典添加元素时获得命名空间),Indexer的namespace属性(token转index时添加命名空间)
- Vocabulary类只是管理token与index的关系,OOV,PAD,而index,计数,转tensor的工作在Field类中进行。
构造参数
- counter: Dict[str, Dict[str, int]] = None,计数器,用来初始化内部两个词典,具体地,Dict[str(命名空间), Dict[str(词), int(数量)]] 。Vocabulary只是记录token和index的关系,用这个counter传入instance最终在Field进行计数。
- min_count: Dict[str, int] = None,对于某个词的计数少于一个值时,不加入词典;可以对不同的命名空间设置不同的最小值
- max_vocab_size: Union[int, Dict[str, int]] = None,限制词典最大词数;可以对不同的命名空间设置不同的最大值
- non_padded_namespaces: Iterable[str] = DEFAULT_NON_PADDED_NAMESPACES,默认为("*tags", “*labels”),命名空间为tags、labels或后缀是tags/labels,在后面的处理中,不进行pad
- pretrained_files: Optional[Dict[str, str]] = None,命名空间中的词都可以从已有的embedding文件获取。如果6为False,那么数据集中新出现的词会加入词典;如果为True,则词典中只用embedding文件中加载的词。
- only_include_pretrained_words: bool = False
- tokens_to_add: Dict[str, List[str]] = None,手动向命名空间添加词
- min_pretrained_embeddings: Dict[str, int] = None,如果不为None,则从命名空间对应的预训练embedding文件中取出前k个词,加入词典
词典
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文件是逐行进行的,加入到词典时也是按照顺序的,因此词典中的词与文件中词的顺序对应,用来进行词嵌入。
二、从实例(instances)创建词典
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)
)
- 如果上次训练中断,这次训练设置-r,即恢复之前的训练,则词典通过Vocabulary.from_files读取建立好的词典文件建立。
- 如果直接建立词典,则先读取数据集,词典通过Vocabulary.from_params,把Vocabulary的参数和读取数据集得到的Instances传入from_params。在from_params中进行一系列参数设置,进入Vocabulary.from_instances,通过数据集,建立词典。