Vocabulary
是包含字或词与index的关系的类,用于将文本转化为index。
构建Vocabulary
from fastNLP import Vocabulary
vocab = Vocabulary()
vocab.add_word_lst(['复', '旦', '大', '学']) # 加入新的字
vocab.add_word('上海') # `上海`会作为一个整体
vocab.to_index('复') # 应该会为3
vocab.to_index('我') # 会输出1,Vocabulary中默认pad的index为0, unk(没有找到的词)的index为1
# 在构建target的Vocabulary时,词表中应该用不上pad和unk,可以通过以下的初始化
vocab = Vocabulary(unknown=None, padding=None)
vocab.add_word_lst(['positive', 'negative'])
vocab.to_index('positive') # 输出0
vocab.to_index('neutral') # 会报错,因为没有unk这种情况
除了直接使用一个列表来构建Vocabulary
,还可以使用DataSet
的某一个Field
来构建,然后还能将field的内容转为index。
from fastNLP import Vocabulary
from fastNLP import DataSet
dataset = DataSet({'chars': [
['今', '天', '天', '气', '很', '好', '。'],
['被', '这', '部', '电', '影', '浪', '费', '了', '两', '个', '小', '时', '。']
],
'target': ['neutral', 'negative']
})
vocab = Vocabulary()
# 从该dataset中的chars列建立词表
vocab.from_dataset(dataset, field_name='chars')
# 使用vocabulary将chars列转换为index
vocab.index_dataset(dataset, field_name='chars')
target_vocab = Vocabulary(padding=None, unknown=None)
target_vocab.from_dataset(dataset, field_name='target')
target_vocab.index_dataset(dataset, field_name='target')
print(dataset)
输出:
+---------------------------------------------------+--------+
| chars | target |
+---------------------------------------------------+--------+
| [4, 2, 2, 5, 6, 7, 3] | 0 |
| [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 3] | 1 |
+---------------------------------------------------+--------+
一些使用tips
教程中这一部分主要探讨no_create_entry
参数的作用。
建议使用的dev
集和test
集可以设这个参数。这个参数的作用是指定某个集合中的词如果没出现在预训练 embedding 中,会随机 embedding 还是设为 <UNK>
。
from fastNLP import Vocabulary
from fastNLP import DataSet
tr_data = DataSet({'chars': [
['今', '天', '心', '情', '很', '好', '。'],
['被', '这', '部', '电', '影', '浪', '费', '了', '两', '个', '小', '时', '。']
],
'target': ['positive', 'negative']
})
dev_data = DataSet({'chars': [
['住', '宿', '条', '件', '还', '不', '错'],
['糟', '糕', '的', '天', '气', ',', '无', '法', '出', '行', '。']
],
'target': ['positive', 'negative']
})
vocab = Vocabulary()
# 将验证集或者测试集在建立词表是放入no_create_entry_dataset这个参数中。
vocab.from_dataset(tr_data, field_name='chars', no_create_entry_dataset=[dev_data])
上面的这个例子就为dev集的词也使用了embedding。
这样做的好处在于一些词没有在与训练的 embedding 中,但是在test集中出现,我们通过 finetune 可以提高对这个词的理解。若test集或dev集中有这个词,可以达到更好的训练效果。如果test或dev集有只在这两个集合中出现,且没有在预训练出现的词,则按照<UNK>
处理。
如果一个词出现在了train中,但是没在预训练模型中,embedding会为随机初始化,且它单独的一个vector,如果finetune embedding的话, 这个词在更新之后可能会有更好的表示; 而如果这个词仅出现在了dev或test中,那么就不能为它们单独建立vector,而应该让它指向unk这个vector的 值(当unk的值更新时,这个词也使用的是更新之后的vector)。所以被认为是no_create_entry的token,将首先从预训练的词表中寻找它的表示,如 果找到了,就使用该表示; 如果没有找到,则认为该词的表示应该为unk的表示。
下面这个例子帮助理解这个参数的作用。
import torch
from fastNLP.embeddings import StaticEmbedding
from fastNLP import Vocabulary
vocab = Vocabulary()
vocab.add_word('train')
vocab.add_word('only_in_train') # 仅在train出现,但肯定在预训练词表中不存在
vocab.add_word('test', no_create_entry=True) # 该词只在dev或test中出现
vocab.add_word('only_in_test', no_create_entry=True) # 这个词在预训练的词表中找不到
embed = StaticEmbedding(vocab, model_dir_or_name='en-glove-6b-50d')
print(embed(torch.LongTensor([vocab.to_index('train')])))
print(embed(torch.LongTensor([vocab.to_index('only_in_train')])))
print(embed(torch.LongTensor([vocab.to_index('test')])))
print(embed(torch.LongTensor([vocab.to_index('only_in_test')])))
print(embed(torch.LongTensor([vocab.unknown_idx])))
输出:
tensor([[ 0.9497, 0.3433, 0.8450, -0.8852, ...]], grad_fn=<EmbeddingBackward>) # train,en-glove-6b-50d,找到了该词
tensor([[ 0.0540, -0.0557, -0.0514, -0.1688, ...]], grad_fn=<EmbeddingBackward>) # only_in_train,en-glove-6b-50d,使用了随机初始化
tensor([[ 0.1318, -0.2552, -0.0679, 0.2619, ...]], grad_fn=<EmbeddingBackward>) # test,在en-glove-6b-50d中找到了这个词
tensor([[0., 0., 0., 0., 0., ...]], grad_fn=<EmbeddingBackward>) # only_in_test, en-glove-6b-50d中找不到这个词,使用unk的vector
tensor([[0., 0., 0., 0., 0., ...]], grad_fn=<EmbeddingBackward>) # unk,使用zero初始化