|
|
@@ -11,6 +11,7 @@ __all__ = [ |
|
|
|
from collections import Counter |
|
|
|
from functools import partial |
|
|
|
from functools import wraps |
|
|
|
from typing import List, Callable, Union |
|
|
|
|
|
|
|
from fastNLP.core.dataset import DataSet |
|
|
|
from fastNLP.core.utils.utils import Option |
|
|
@@ -20,6 +21,9 @@ import io |
|
|
|
|
|
|
|
|
|
|
|
class VocabularyOption(Option): |
|
|
|
""" |
|
|
|
|
|
|
|
""" |
|
|
|
def __init__(self, |
|
|
|
max_size=None, |
|
|
|
min_freq=None, |
|
|
@@ -33,8 +37,11 @@ class VocabularyOption(Option): |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def _check_build_vocab(func): |
|
|
|
r"""A decorator to make sure the indexing is built before used. |
|
|
|
def _check_build_vocab(func: Callable): |
|
|
|
r""" |
|
|
|
A decorator to make sure the indexing is built before used. |
|
|
|
|
|
|
|
:param func: 传入的callable函数 |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
@@ -48,7 +55,10 @@ def _check_build_vocab(func): |
|
|
|
|
|
|
|
|
|
|
|
def _check_build_status(func): |
|
|
|
r"""A decorator to check whether the vocabulary updates after the last build. |
|
|
|
r""" |
|
|
|
A decorator to check whether the vocabulary updates after the last build. |
|
|
|
|
|
|
|
:param func: 用户传入要修饰的callable函数 |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
@@ -69,27 +79,30 @@ class Vocabulary(object): |
|
|
|
r""" |
|
|
|
用于构建, 存储和使用 `str` 到 `int` 的一一映射:: |
|
|
|
|
|
|
|
from fastNLP.core import Vocabulary |
|
|
|
vocab = Vocabulary() |
|
|
|
word_list = "this is a word list".split() |
|
|
|
# vocab更新自己的字典,输入为list列表 |
|
|
|
vocab.update(word_list) |
|
|
|
vocab["word"] # str to int |
|
|
|
vocab.to_word(5) # int to str |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
def __init__(self, max_size=None, min_freq=None, padding='<pad>', unknown='<unk>'): |
|
|
|
def __init__(self, max_size:int=None, min_freq:int=None, padding:str='<pad>', unknown:str='<unk>'): |
|
|
|
r""" |
|
|
|
|
|
|
|
:param int max_size: `Vocabulary` 的最大大小, 即能存储词的最大数量 |
|
|
|
:param max_size: `Vocabulary` 的最大大小, 即能存储词的最大数量 |
|
|
|
若为 ``None`` , 则不限制大小. Default: ``None`` |
|
|
|
:param int min_freq: 能被记录下的词在文本中的最小出现频率, 应大于或等于 1. |
|
|
|
:param min_freq: 能被记录下的词在文本中的最小出现频率, 应大于或等于 1. |
|
|
|
若小于该频率, 词语将被视为 `unknown`. 若为 ``None`` , 所有文本中的词都被记录. Default: ``None`` |
|
|
|
:param str optional padding: padding的字符. 如果设置为 ``None`` , |
|
|
|
:param padding: padding的字符. 如果设置为 ``None`` , |
|
|
|
则vocabulary中不考虑padding, 也不计入词表大小,为 ``None`` 的情况多在为label建立Vocabulary的情况. |
|
|
|
Default: '<pad>' |
|
|
|
:param str optional unknown: unknown的字符,所有未被记录的词在转为 `int` 时将被视为unknown. |
|
|
|
:param unknown: unknown的字符,所有未被记录的词在转为 `int` 时将被视为unknown. |
|
|
|
如果设置为 ``None`` ,则vocabulary中不考虑unknow, 也不计入词表大小. |
|
|
|
为 ``None`` 的情况多在为label建立Vocabulary的情况. |
|
|
|
Default: '<unk>' |
|
|
|
|
|
|
|
""" |
|
|
|
self.max_size = max_size |
|
|
|
self.min_freq = min_freq |
|
|
@@ -121,45 +134,50 @@ class Vocabulary(object): |
|
|
|
self._word2idx = value |
|
|
|
|
|
|
|
@_check_build_status |
|
|
|
def update(self, word_lst, no_create_entry=False): |
|
|
|
r"""依次增加序列中词在词典中的出现频率 |
|
|
|
def update(self, word_lst: list, no_create_entry:bool=False): |
|
|
|
r""" |
|
|
|
依次增加序列中词在词典中的出现频率 |
|
|
|
|
|
|
|
:param list word_lst: a list of strings |
|
|
|
:param bool no_create_entry: 如果词语来自于非训练集建议设置为True。在使用fastNLP.TokenEmbedding加载预训练模型时,没有从预训练词表中找到这个词的处理方式。 |
|
|
|
:param word_lst: 列表形式的词语,如word_list=['I', 'am', 'a', 'Chinese'],列表中的每个词会计算出现频率并加入到词典中。 |
|
|
|
:param no_create_entry: 如果词语来自于非训练集建议设置为True。 |
|
|
|
如果为True,则不会有这个词语创建一个单独的entry,它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独 |
|
|
|
的entry。如果这个word来自于dev或者test,一般设置为True,如果来自与train一般设置为False。以下两种情况: 如果新 |
|
|
|
加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary中且并不是no_create_entry的,则还是会为这 |
|
|
|
个词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary中且并不是no_create_entry的, |
|
|
|
则这个词将认为是需要创建单独的vector的。 |
|
|
|
|
|
|
|
""" |
|
|
|
self._add_no_create_entry(word_lst, no_create_entry) |
|
|
|
self.word_count.update(word_lst) |
|
|
|
return self |
|
|
|
|
|
|
|
@_check_build_status |
|
|
|
def add(self, word, no_create_entry=False): |
|
|
|
def add(self, word:str, no_create_entry:bool=False): |
|
|
|
r""" |
|
|
|
增加一个新词在词典中的出现频率 |
|
|
|
|
|
|
|
:param str word: 新词 |
|
|
|
:param bool no_create_entry: 如果词语来自于非训练集建议设置为True。在使用fastNLP.TokenEmbedding加载预训练模型时,没有从预训练词表中找到这个词的处理方式。 |
|
|
|
:param word: 要添加进字典的新词, word为一个字符串 |
|
|
|
:param no_create_entry: 如果词语来自于非训练集建议设置为True。 |
|
|
|
如果为True,则不会有这个词语创建一个单独的entry,它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独 |
|
|
|
的entry。如果这个word来自于dev或者test,一般设置为True,如果来自与train一般设置为False。以下两种情况: 如果新 |
|
|
|
加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary中且并不是no_create_entry的,则还是会为这 |
|
|
|
个词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary中且并不是no_create_entry的, |
|
|
|
则这个词将认为是需要创建单独的vector的。 |
|
|
|
|
|
|
|
""" |
|
|
|
self._add_no_create_entry(word, no_create_entry) |
|
|
|
self.word_count[word] += 1 |
|
|
|
return self |
|
|
|
|
|
|
|
def _add_no_create_entry(self, word, no_create_entry): |
|
|
|
def _add_no_create_entry(self, word:Union[str, List[str]], no_create_entry:bool): |
|
|
|
r""" |
|
|
|
在新加入word时,检查_no_create_word的设置。 |
|
|
|
|
|
|
|
:param str List[str] word: |
|
|
|
:param bool no_create_entry: |
|
|
|
:param word: 要添加的新词或者是List类型的新词,如word='I'或者word=['I', 'am', 'a', 'Chinese']均可 |
|
|
|
:param no_create_entry: 如果词语来自于非训练集建议设置为True。如果为True,则不会有这个词语创建一个单独的entry, |
|
|
|
它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独的entry |
|
|
|
:return: |
|
|
|
|
|
|
|
""" |
|
|
|
if isinstance(word, str) or not _is_iterable(word): |
|
|
|
word = [word] |
|
|
@@ -170,32 +188,32 @@ class Vocabulary(object): |
|
|
|
self._no_create_word.pop(w) |
|
|
|
|
|
|
|
@_check_build_status |
|
|
|
def add_word(self, word, no_create_entry=False): |
|
|
|
def add_word(self, word:str, no_create_entry:bool=False): |
|
|
|
r""" |
|
|
|
增加一个新词在词典中的出现频率 |
|
|
|
|
|
|
|
:param str word: 新词 |
|
|
|
:param bool no_create_entry: 如果词语来自于非训练集建议设置为True。在使用fastNLP.TokenEmbedding加载预训练模型时,没有从预训练词表中找到这个词的处理方式。 |
|
|
|
如果为True,则不会有这个词语创建一个单独的entry,它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独 |
|
|
|
的entry。如果这个word来自于dev或者test,一般设置为True,如果来自与train一般设置为False。以下两种情况: 如果新 |
|
|
|
加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary中且并不是no_create_entry的,则还是会为这 |
|
|
|
个词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary中且并不是no_create_entry的, |
|
|
|
则这个词将认为是需要创建单独的vector的。 |
|
|
|
:param word: 要添加进字典的新词, word为一个字符串 |
|
|
|
:param no_create_entry: 如果词语来自于非训练集建议设置为True。如果为True,则不会有这个词语创建一个单独的entry, |
|
|
|
它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独的entry。如果这个word来自于dev或者test,一般设置为True, |
|
|
|
如果来自与train一般设置为False。以下两种情况: 如果新加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary |
|
|
|
中且并不是no_create_entry的,则还是会为这词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary |
|
|
|
中且并不是no_create_entry的,则这个词将认为是需要创建单独的vector的。 |
|
|
|
|
|
|
|
""" |
|
|
|
self.add(word, no_create_entry=no_create_entry) |
|
|
|
|
|
|
|
@_check_build_status |
|
|
|
def add_word_lst(self, word_lst, no_create_entry=False): |
|
|
|
def add_word_lst(self, word_lst: List[str], no_create_entry:bool=False): |
|
|
|
r""" |
|
|
|
依次增加序列中词在词典中的出现频率 |
|
|
|
|
|
|
|
:param list[str] word_lst: 词的序列 |
|
|
|
:param bool no_create_entry: 如果词语来自于非训练集建议设置为True。在使用fastNLP.TokenEmbedding加载预训练模型时,没有从预训练词表中找到这个词的处理方式。 |
|
|
|
如果为True,则不会有这个词语创建一个单独的entry,它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独 |
|
|
|
的entry。如果这个word来自于dev或者test,一般设置为True,如果来自与train一般设置为False。以下两种情况: 如果新 |
|
|
|
加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary中且并不是no_create_entry的,则还是会为这 |
|
|
|
个词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary中且并不是no_create_entry的, |
|
|
|
则这个词将认为是需要创建单独的vector的。 |
|
|
|
:param word_lst: 需要添加的新词的list序列,如word_lst=['I', 'am', 'a', 'Chinese'] |
|
|
|
:param no_create_entry: 如果词语来自于非训练集建议设置为True。如果为True,则不会有这个词语创建一个单独的entry, |
|
|
|
它将一直被指向unk的表示; 如果为False,则为这个词创建一个单独的entry。如果这个word来自于dev或者test,一般设置为True, |
|
|
|
如果来自与train一般设置为False。以下两种情况: 如果新加入一个word,且no_create_entry为True,但这个词之前已经在Vocabulary |
|
|
|
中且并不是no_create_entry的,则还是会为这词创建一个单独的vector; 如果no_create_entry为False,但这个词之前已经在Vocabulary |
|
|
|
中且并不是no_create_entry的,则这个词将认为是需要创建单独的vector的。 |
|
|
|
|
|
|
|
""" |
|
|
|
self.update(word_lst, no_create_entry=no_create_entry) |
|
|
|
return self |
|
|
@@ -238,7 +256,7 @@ class Vocabulary(object): |
|
|
|
return len(self._word2idx) |
|
|
|
|
|
|
|
@_check_build_vocab |
|
|
|
def __contains__(self, item): |
|
|
|
def __contains__(self, item:str): |
|
|
|
r""" |
|
|
|
检查词是否被记录 |
|
|
|
|
|
|
@@ -247,7 +265,7 @@ class Vocabulary(object): |
|
|
|
""" |
|
|
|
return item in self._word2idx |
|
|
|
|
|
|
|
def has_word(self, w): |
|
|
|
def has_word(self, w:str): |
|
|
|
r""" |
|
|
|
检查词是否被记录:: |
|
|
|
|
|
|
@@ -255,7 +273,7 @@ class Vocabulary(object): |
|
|
|
# equals to |
|
|
|
has_abc = 'abc' in vocab |
|
|
|
|
|
|
|
:param item: the word |
|
|
|
:param item: 输入的str类型的词 |
|
|
|
:return: ``True`` or ``False`` |
|
|
|
""" |
|
|
|
return self.__contains__(w) |
|
|
@@ -263,7 +281,7 @@ class Vocabulary(object): |
|
|
|
@_check_build_vocab |
|
|
|
def __getitem__(self, w): |
|
|
|
r""" |
|
|
|
To support usage like:: |
|
|
|
支持从字典中直接得到词语的index,例如:: |
|
|
|
|
|
|
|
vocab[w] |
|
|
|
""" |
|
|
@@ -275,15 +293,15 @@ class Vocabulary(object): |
|
|
|
raise ValueError("word `{}` not in vocabulary".format(w)) |
|
|
|
|
|
|
|
@_check_build_vocab |
|
|
|
def index_dataset(self, *datasets, field_name, new_field_name=None): |
|
|
|
def index_dataset(self, *datasets, field_name:Union[List, str], new_field_name:Union[List, str, None]=None): |
|
|
|
r""" |
|
|
|
将DataSet中对应field的词转为数字,Example:: |
|
|
|
|
|
|
|
# remember to use `field_name` |
|
|
|
vocab.index_dataset(train_data, dev_data, test_data, field_name='words') |
|
|
|
|
|
|
|
:param ~fastNLP.DataSet,List[~fastNLP.DataSet] datasets: 需要转index的一个或多个数据集 |
|
|
|
:param list,str field_name: 需要转index的field, 若有多个 DataSet, 每个DataSet都必须有此 field. |
|
|
|
:param datasets: 其类型为:~fastNLP.core.Dataset或者List[~fastNLP.core.Dataset] 需要转index的一个或多个数据集 |
|
|
|
:param field_name: 需要转index的field, 若有多个 DataSet, 每个DataSet都必须有此 field. |
|
|
|
目前支持 ``str`` , ``List[str]`` |
|
|
|
:param list,str new_field_name: 保存结果的field_name. 若为 ``None`` , 将覆盖原field. |
|
|
|
Default: ``None``. |
|
|
@@ -333,17 +351,16 @@ class Vocabulary(object): |
|
|
|
def _no_create_word_length(self): |
|
|
|
return len(self._no_create_word) |
|
|
|
|
|
|
|
def from_dataset(self, *datasets, field_name, no_create_entry_dataset=None): |
|
|
|
def from_dataset(self, *datasets, field_name:Union[str,List[str]], no_create_entry_dataset=None): |
|
|
|
r""" |
|
|
|
使用dataset的对应field中词构建词典:: |
|
|
|
|
|
|
|
# remember to use `field_name` |
|
|
|
vocab.from_dataset(train_data1, train_data2, field_name='words') |
|
|
|
vocab.from_dataset(train_data1, train_data2, field_name='words', no_create_entry_dataset=[test_data1, test_data2]) |
|
|
|
|
|
|
|
:param ~fastNLP.DataSet,List[~fastNLP.DataSet] datasets: 需要转index的一个或多个数据集 |
|
|
|
:param str,List[str] field_name: 可为 ``str`` 或 ``List[str]`` . |
|
|
|
构建词典所使用的 field(s), 支持一个或多个field,若有多个 DataSet, 每个DataSet都必须有这些field. 目前支持的field结构 |
|
|
|
: ``str`` , ``List[str]`` |
|
|
|
:param 其类型为:~fastNLP.core.Dataset或者List[~fastNLP.core.Dataset] 需要转index的一个或多个数据集 |
|
|
|
:param field_name: 构建词典所使用的 field(s), 支持一个或多个field,若有多个 DataSet, 每个DataSet都必须有这些field. |
|
|
|
目前支持的field结构: ``str`` , ``List[str]`` |
|
|
|
:param no_create_entry_dataset: 可以传入DataSet, List[DataSet]或者None(默认), 建议直接将非训练数据都传入到这个参数。该选项用在接下来的模型会使用pretrain |
|
|
|
的embedding(包括glove, word2vec, elmo与bert)且会finetune的情况。如果仅使用来自于train的数据建立vocabulary,会导致test与dev |
|
|
|
中的数据无法充分利用到来自于预训练embedding的信息,所以在建立词表的时候将test与dev考虑进来会使得最终的结果更好。 |
|
|
@@ -351,7 +368,8 @@ class Vocabulary(object): |
|
|
|
finetune embedding的话,这个词在更新之后可能会有更好的表示; 而如果这个词仅出现在了dev或test中,那么就不能为它们单独建立vector, |
|
|
|
而应该让它指向unk这个vector的值。所以只位于no_create_entry_dataset中的token,将首先从预训练的词表中寻找它的表示, |
|
|
|
如果找到了,就使用该表示; 如果没有找到,则认为该词的表示应该为unk的表示。 |
|
|
|
:return self: |
|
|
|
:return Vocabulary自身 |
|
|
|
|
|
|
|
""" |
|
|
|
if isinstance(field_name, str): |
|
|
|
field_name = [field_name] |
|
|
@@ -395,15 +413,16 @@ class Vocabulary(object): |
|
|
|
dataset.apply(partial_construct_vocab) |
|
|
|
return self |
|
|
|
|
|
|
|
def _is_word_no_create_entry(self, word): |
|
|
|
def _is_word_no_create_entry(self, word:str): |
|
|
|
r""" |
|
|
|
判断当前的word是否是不需要创建entry的,具体参见from_dataset的说明 |
|
|
|
:param word: str |
|
|
|
:return: bool |
|
|
|
|
|
|
|
:param word: 输入的str类型的词语 |
|
|
|
:return: bool值的判断结果 |
|
|
|
""" |
|
|
|
return word in self._no_create_word |
|
|
|
|
|
|
|
def to_index(self, w): |
|
|
|
def to_index(self, w:str): |
|
|
|
r""" |
|
|
|
将词转为数字. 若词不再词典中被记录, 将视为 unknown, 若 ``unknown=None`` , 将抛出 ``ValueError`` :: |
|
|
|
|
|
|
@@ -411,8 +430,8 @@ class Vocabulary(object): |
|
|
|
# equals to |
|
|
|
index = vocab['abc'] |
|
|
|
|
|
|
|
:param str w: a word |
|
|
|
:return int index: the number |
|
|
|
:param w: 需要输入的词语 |
|
|
|
:return 词语w对应的int类型的index |
|
|
|
""" |
|
|
|
return self.__getitem__(w) |
|
|
|
|
|
|
@@ -420,7 +439,7 @@ class Vocabulary(object): |
|
|
|
@_check_build_vocab |
|
|
|
def unknown_idx(self): |
|
|
|
r""" |
|
|
|
unknown 对应的数字. |
|
|
|
获得unknown 对应的数字. |
|
|
|
""" |
|
|
|
if self.unknown is None: |
|
|
|
return None |
|
|
@@ -430,14 +449,14 @@ class Vocabulary(object): |
|
|
|
@_check_build_vocab |
|
|
|
def padding_idx(self): |
|
|
|
r""" |
|
|
|
padding 对应的数字 |
|
|
|
获得padding 对应的数字 |
|
|
|
""" |
|
|
|
if self.padding is None: |
|
|
|
return None |
|
|
|
return self._word2idx[self.padding] |
|
|
|
|
|
|
|
@_check_build_vocab |
|
|
|
def to_word(self, idx): |
|
|
|
def to_word(self, idx: int): |
|
|
|
r""" |
|
|
|
给定一个数字, 将其转为对应的词. |
|
|
|
|
|
|
@@ -460,7 +479,8 @@ class Vocabulary(object): |
|
|
|
return self |
|
|
|
|
|
|
|
def __getstate__(self): |
|
|
|
r"""Use to prepare data for pickle. |
|
|
|
r""" |
|
|
|
用来从pickle中加载data |
|
|
|
|
|
|
|
""" |
|
|
|
len(self) # make sure vocab has been built |
|
|
@@ -470,7 +490,8 @@ class Vocabulary(object): |
|
|
|
return state |
|
|
|
|
|
|
|
def __setstate__(self, state): |
|
|
|
r"""Use to restore state from pickle. |
|
|
|
r""" |
|
|
|
支持pickle的保存,保存到pickle的data state |
|
|
|
|
|
|
|
""" |
|
|
|
self.__dict__.update(state) |
|
|
@@ -485,11 +506,11 @@ class Vocabulary(object): |
|
|
|
for index in range(len(self._word2idx)): |
|
|
|
yield self.to_word(index), index |
|
|
|
|
|
|
|
def save(self, filepath): |
|
|
|
def save(self, filepath: [str, io.StringIO]): |
|
|
|
r""" |
|
|
|
|
|
|
|
:param str,io.StringIO filepath: Vocabulary的储存路径 |
|
|
|
:param filepath: Vocabulary的储存路径 |
|
|
|
:return: |
|
|
|
|
|
|
|
""" |
|
|
|
if isinstance(filepath, io.IOBase): |
|
|
|
assert filepath.writable() |
|
|
@@ -521,10 +542,11 @@ class Vocabulary(object): |
|
|
|
f.close() |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def load(filepath): |
|
|
|
def load(filepath: Union[str,io.StringIO]): |
|
|
|
r""" |
|
|
|
从文件路径中加载数据 |
|
|
|
|
|
|
|
:param str,io.StringIO filepath: Vocabulary的读取路径 |
|
|
|
:param filepath: Vocabulary的读取路径 |
|
|
|
:return: Vocabulary |
|
|
|
""" |
|
|
|
if isinstance(filepath, io.IOBase): |
|
|
|