9.6 fastText网络#
在「第9.2节 Word2Vec原理:词向量训练与语义表示方法」和「第9.4节 GloVe词向量:全局统计词向量建模方法」内容中我们分介绍了Word2Vec和GloVe这两种词向量模型的原理,可以发现它们有一个共同的特点就是都是以词为粒度来训练其对应的词向量。在接下来的这节内容中,我们将会介绍另外一种从此的形态学来训练词向量的模型。
9.6.1 fastText动机#
尽管同一个词根可以派生得到不同词,但是在Word2Vec和GloVe这两种模型中并没有从形态学的角度来考虑词向量的生成。例如对于interesting和interested这两个词来说,虽然它们都有共同的词根interest,但是在Word2Vec和GloVe中却将这三者看成了完全不同的3个词来对待,而这将导致如果某些词在语料中出现的频次过低,那么模型将难以准确学到这些词对应的词嵌入表示。
基于这样的动机,元宇宙公司研究院(Facebook AI Research, FAIR)博扬诺夫斯基(Bojanowski)等人[1]于2017年基于第9.2.4节˙中的跳元模型提出了一种从形态学角度来考虑词向量的子词(Subword)嵌入模型——fastText。fastText模型的核心思想是将一个词以N-gram的形式分割成若干字词部分,然后利用跳元模型的思想为每个子词学习得到一个嵌入式表示,最后将每个词各个子词对应的向量累加起来作为该词的向量化表示。通过这样的方式让具有相似结构的词之间共享来自子词的向量表示,fastText便可以捕捉到更加细粒度的语义信息从而提高了词向量的表达能力,同时对于词表中未出现的词也可以通过子词间的组合得到该词的词向量表示。
9.6.2 fastText原理#
整体上来说fastText的原理是在跳元模型的基础上引入了字符级别的$N$-gram子词嵌入策略,即将一个词以$N$-gram($3\leq N \leq 6$)的方式划分成若干子词,然后采用模型为每个子词训练得到一个词向两,最后将该词所有子词的词向量求和得到最终的词向量表示。
对于语料中的每个词来说,首先需要在其首尾分别加上<和>符以区分与其它单词的前后缀;然后再以不同取值$N$将其划分为$N$-gram子词并同该词本身构成整个语料对应的词表。以单词where为例,首先在首尾分别加上<和>即<where>;然后分别构建当$N=3$时其对应的子词集<wh、whe、her、ere、re>,当$N=4$时的子词集<whe、wher、here、ere>,当$N=5$时的子词集<wher、where、here>,当$N=6$时的子词集<where、where>,以及特殊子词<where>。这样便得到了单词where的子词集。最后以同样的方式构造得到所有单词的子词集便形成了最终的词表。
在fastText中对于任意词$w$来说,用$\mathcal{G}_w$表示其对应的$N$-gram子词及其对应的特殊子词的合集,并假设$z_g$是词表中子词$g$对应的向量,则在跳元模型模型中,词$w$的词向量$v_w$是其所有子词向量的和[2]
$$ v_w=\sum_{g\in\mathcal{G_w}}z_g\tag{9-25} $$对于其它部分的建模过程fastText与第9.2.4节中介绍的跳元模型模型一致,这里就不再赘述。
同时,对于中文语料来说fastText模型会先使用斯坦福大学开源的斯坦福分词模型(Stanford Word Segmenter)[3] [4]进行普通的分词处理;然后同样也在每个词的首尾加上符号<和 > ;最后对于每个词来说以$N$-gram构建子词集,例如当$N=6$时,词<跟我一起学深度学习>的子词有<跟我一起学、跟我一起学深、我一起学深度、一起学深度学、起学深度学习和学深度学习>。
9.6.3 fastText库介绍#
对于fastText模型的使用有两种途径,第1种是借助Gensim库中的gensim.models.FastText模块,其包括预训练词向量的载入和训练等,使用方法同「第9.3节 Word2Vec训练与使用:CBOW、Skip-gram 与词向量应用」类似这里就不再赘述,各位读者可以直接查阅官方文档[5];第2种则是借助Facebook AI研究院开源的fastText库[6]。
fastText是由FAIR开发的一个用于文本分类和词向量学习的开源库,支持多语言文本处理并且在低资源情况下的文本分类任务中有良好的性能。除此之外fastText库还提供了简单易用的API和命令行工具,使得模型的训练和预测非常方便。在大规模数据集上,fastText具有高效的训练和推理速度,使其成为处理大规模文本数据的强大工具。
9.6.4 词向量的使用与训练#
1. 词向量使用
在fastText官网[6]中一共开源了157种语言的预训练模型,且均包含txt和bin两种格式,前者同第9.3节中Word2Vec一样因此也可以通过Gensim库里的KeyedVectors.load_word2vec_format()函数载入,后者则只能通过fastText库中的fasttext.load_model()函数载入使用。在使用之前需要通过命令pip install --upgrade fasttext安装fastText库,然后便可以通过如下方式进行载入使用:
1 def load_fasttext_model():
2 path_to_model = os.path.join(DATA_HOME, 'Pretrained', 'fasttext', 'cc.zh.300.bin')
3 ft = fasttext.load_model(path_to_model)
4 logging.info(f"词向量的维度: {ft.get_dimension()}")
5 logging.info(f"中国: {ft.get_word_vector('中国')}")
6 logging.info(f"与中国最相似的5个词为: {ft.get_nearest_neighbors('中国', k=5)}")
7 reduce_model(ft, 100)
8 logging.info(f"词向量的维度: {ft.get_dimension()}")
9 path_to_model = os.path.join(DATA_HOME, 'Pretrained', 'fasttext', 'cc.zh.100.bin')
10 ft.save_model(path_to_model)在上述代码中,第2行是构造预训练词向量的路径。第3行是载入预训练词向量。第4~6行是分别输出相应的结果。第7行是对词向量进行降维。第9~10行是保存降维后的模型,后续可直接载入使用。
上述代码运行结束后的输出结果类似如下所示:
1 词向量的维度: 300
2 中国: [ 0.01141522 -0.020438 0.350222 -0.036417 -0.113030 0.010370...
3 与中国最相似的5个词为: [(0.691871, '中國'), (0.673008, '大陆'), (0.664995, '美国'),
4 (0.664802, '・中国'), (0.663499, '我国')]
5 词向量的维度: 100以上完整示例代码可参见Code/Chapter09/C05_FastText/main.py文件。
2. 词向量类比
相比于Word2Vec和GloVe词向量来说,通过fastText模型训练得到词向量所具备的另外一个优点便是对于词表中没有出现的词一样可以通过$N$-gram计算得到,同时fastText库还支持词语的类比推断,示例代码如下所示:
1 def get_get_analogies():
2 path_to_model = os.path.join(DATA_HOME, 'Pretrained', 'fasttext', 'cc.zh.300.bin')
3 ft = fasttext.load_model(path_to_model)
4 logging.info('有凤来仪' in ft.words)
5 logging.info(f"与有凤来仪最相似的3个词为: {ft.get_nearest_neighbors('有凤来仪', k=3)}")
6 logging.info(ft.get_analogies("柏林", "德国", "法国", k=3))
7
8 path_to_model = os.path.join(DATA_HOME, 'Pretrained', 'fasttext', 'cc.en.300.bin')
9 ft = fasttext.load_model(path_to_model)
10 logging.info('accomodtion' in ft.words)
11 logging.info(f"与accomodtion最相似的3个词为: {ft.get_nearest_neighbors('accomodation', k=3)}")
12 logging.info(ft.get_analogies("berlin", "germany", "france", k=3))在上述代码中,第5行是取与词“有凤来仪”最相似的5个词,此时“有凤来仪”并不在词表中。第6行则是根据“柏林”—“德国”这一类比来推断得到“法国”与另外一个也能满足类似类比关系的词。第8~12行则是换成了英文词向量模型来完成上述实验。
上述代码运行结果如下:
1 False
2 与有凤来仪最相似的3个词为: [(0.45718, 'Viscosity'), (0.45417, 'viscosity'), (0.36153, 'thb')]
3 [(0.74381, '巴黎'), (0.58383, '里昂'), (0.55554, '法國')]
4
5 False
6 与accomodtion最相似的3个词为: [(0.85873, 'accomadation'), (0.82801, 'acommodation'), (0.82264, 'accommodation')]
7 [(0.73037, 'paris'), (0.64085, 'france.'), (0.63933, 'aignon')]在上述结果中,对于中文词向量来说不在词表中的词“有凤来仪”来说,模型找到的与之最为相似的3个词都是英文单词这并不是理想的结果,对于类比计算来说“柏林”—“德国”的类比模型找到的是“法国”—“巴黎”这一类比。对于英文词向量来说,未知词“accomodtion”(正确应该是“accommodation”)来说,模型找到的与之最为相似的3个词效果明显好于中文情况,同时对于类比计算也符合理想情况。
根据上述结果可以看出,由于中文汉字最小的单位并不是单个的字,所以仅仅只是通过字粒度的$N$-gram来推测词表之外的词效果明显会差于拉丁语系这类以字母构成且以字母粒度为$N$-gram的情况。
3. 词向量训练
进一步可以借助fastText库来根据自定义语料完成词向量的训练。这里依旧以「第9.3.4节 Word2Vec训练与使用:CBOW、Skip-gram 与词向量应用」中介绍的搜狗新闻语料为例进行示例。同Gensim中的词向训练模块一样,fastText中提供的模型训练接口所接受的格式依旧是一个本地文件,每一行为一个段落或一篇文档。
由于在第9.3.4节中已经介绍了数据集的构建过程,这里就不再赘述,最终通过如下所示代码便可完成词向量的训练:
1 def train_fasttext(config):
2 data_loader = SougoNews()
3 model = fasttext.train_unsupervised(intput=data_loader.corpus_path,
4 dim=config.vector_size,minCount=config.min_count,
5 epoch=config.epochs,lr=config.learning_rate, ws=config.window,
6 neg=config.neg,maxn=config.maxn, minn=config.minn)
7 model.save_model(config.model_save_path)
8 vec = model.get_word_vector("中国")
9 logging.info(f"词向量维度为: {model.get_dimension()}")
10 logging.info(model.get_subwords("跟我一起学深度学习"))
11 logging.info(vec)在上述代码中,第2行是返回数据集预处理结束后的实例化对象。第3~6行则是完成模型的实例化和词向量训练过程,其中input用于指定语料的路径,dim用于指定词向量的维度,minCount表示最小词频,ws表示上下文窗口的大小,neg表示负采样的样本数,minn和maxn表示$N$-gram的范围。第7行是将训练好的模型进行保存。第8~9行取对应的词向量和维度。第10行是输出词跟我一起学深度学习的子词。
在模型训练结束后可以得到类似如下输出结果:
1 Read 12M, words Number of words: 54292, Number of labels: 0
2 Progress: 100.0% words/sec/thread: 135249 lr: 0.00 avg.loss: 2.324 ETA: 0h 0m 0s
3 词向量维度为: 50
4 (['<跟我', '<跟我一', '<跟我一起', '<跟我一起学', '跟我一', '跟我一起', '跟我一起学', '跟我一起学深',
5 '我一起', '我一起学', '我一起学深', '我一起学深度', '一起学', '一起学深', '一起学深度', '一起学深度学',
6 '起学深', '起学深度', '起学深度学', '起学深度学习', '学深度', '学深度学', '学深度学习', '学深度学习>',
7 '深度学', '深度学习', '深度学习>', '度学习', '度学习>', '学习>'],
8 array([1376956, 301584, 727144, 902360, 1307098, 2021986, 489546, 942384, 791226, 1564290,
9 1298616, 1315371, 1946851, 1373235, 1391906, 1629962, 369161, 905820, 1958412, 985705, 1296310,
10 1193678, 1257647, 1113811, 2027440, 389293, 805625, 1204825, 595509, 206362]))
11 [-0.2524777 0.3646668 0.29871824 0.3780552 -0.18824375 -0.30125538...在上述输出结果中,第2行是模型训练时的输出信息。第3行是打印词向量维度。第4~7行是词跟我一起学深度学习的所有子词集合。第8~10行是子词在词表中对应的索引。第11行是输出词"中国"对应的词向量。
在词向量训练完毕后便可以通过本小节开始介绍的方法进行使用,以上完整示例代码可参见Code/Chapter09/C05_FastText/train.py文件。
9.6.5 fastText文本分类#
除了词向量训练之外,基于子词叠加来得到词向量的思想,fastText库还提供了一种快速进行文本分类的模块[7]。下面分别就其原理和使用方案进行介绍,以下完整示例代码可参见Code/Chapter09/C05_FastText/text_cla.py文件。
1. 模型原理
整体来看fastText中的分类模型是一个简单的两层神经网络,第1个是词嵌入层,第2个是分类层,结构类似于第9.2.3节中的CBOW模型。在建模时,每个词将在各自内部以$N$-gram的方式划分为子词构建词表,即上面训练词向量时的做法。进一步,在通过将子词相加的方式便可以得到每个词的词向量表示。最后,再将所有词的词向量相加取均值便得到了一个样本的向量化表示。
但上述做法存在一个弊端,那就是忽略了词与词之间的先后关系,而这对于时序数据来说是一个不可忽视的地方。因此,一个改进的办法就是在词与词之间也引入$N$-gram,使得模型能够在一定程度上提取到局部的时序特征。此时也可以看出当不考虑词与词之间的顺序时,对应的就是当$N=1$时的情况。具体地,对于每个样本来说除了得到每个单词的向量表示之外,还要根据词之间$N$-gram的结果得到每个部分的向量表示,最后再对$1$-gram和$N$-gram的向量取均值得到整个样本的向量化表示。在得到整个句子的向量化表示之后,再经过一个分类层即可完成整个分类任务。
例如对于样本“深度学习、和、人工智能”来说,其一共包含有3个词,假定词与词之间的$N$-gram中$N=2$,那么最终将会得到“深度学习”、“和”、“人工智能”、“深度学习 和”、“和 人工智能”这个5个词的向量表示,再取均值后便得到了原始样本的向量化表示。
2. 数据集构建
在利用fastText进行文本分类时只需要将预处理好的文本存放至本地,然后将文件路径作为参数传入分类模块即可完成整个分类任务。具体地,在分类时需要将数据处理成如下格式:
1 __label__Class1 __label__Class2 word1 word2 word3由于fastText支持多标签分类,因此对于样本中的每个标签需要以特定前缀组合标签的方式构成,且每个标签之间以空格分隔。例如此处__label__Class1和__label__Class2分别表示两个不同的标签。同时,最后一个标签之后便是样本且不需要进行处理。需要注意的是,特定前缀也可以通过参数指定。
这里依旧以「第7.2.4节 时序数据建模:RNN 适合处理什么样的序列任务」中介绍的今日头条分类数据集为例进行介绍。由于此处需要对标签形式进行改变,因此需要将以下文本格式
1 三农玉米穗期栽培管理技巧!提高你的产量和品质!收藏备用!_!_三农
2 李逵和蒋门神谁更厉害?为什么?_!_文化转换为符合要求的格式
1 _!_三农 玉米穗期栽培管理技巧!提高你的产量和品质!收藏备用!
2 _!_文化 李逵和蒋门神谁更厉害?为什么?格式化示例代码可参见Code/data/toutiao/format_for_fasttext.py文件,这里就不再赘述。
3. 模型训练
在完成数据集构建之后,便可以通过如下方法进行分类模型的训练:
1 def train(config):
2 model = fasttext.train_supervised(input=config.data_path[0],
3 lr=config.learning_rate,dim=config.vector_size,
4 epoch=config.epochs, minCount=config.min_count,
5 minn=config.minn, maxn=config.maxn, label=config.label,
6 wordNgrams=config.word_ngrams)
7 logging.info(model.test(config.data_path[1]))
8 model.save_model("fasttext.bin")在上述代码中,第2~6行便是实例化并训练整个模型,其中input表示指定训练集路径,minCount表示指定最小词频,label表示指定标签前缀,wordNgrams表示指定词与词之间的$N$-gram。第7行是输出模型在验证集上的评估结果。第8行是保存训练完成的模型。
上述代码运行结束后便可得到类似如下结果:
1 Read 4M words
2 Number of words: 144220
3 Number of labels: 15
4 Progress: 100.0% words/sec/thread: 773672 lr: 0.000000 avg.loss: 0.501598 ETA: 0h 0m 0s
5 (76537, 0.8793002077426604, 0.8793002077426604)在上述结果最后一行中,第1个值表示样本数量,第2个值表示精确率,第3个值表示召回率。
最后,模型训练完成后可以通过如下方式进行新样本推理预测:
1 def inference(config):
2 model = fasttext.load_model(config.model_save_path)
3 result = model.predict(['小米 生态 链出 新品 , 智能 聪明 : 有 了 它 , 老婆 都 变懒 了',
4 '哪些 瞬间 是 NBA 球员 回想起来 最 自豪 的 和 最 懊恼 的 ?'], k=2)
5
6 logging.info(result)在上述代码中,第3行中k=2表示返回概率值最大的两个预测标签。
输出结果如下:
1 ([['_!_科技', '_!_汽车'], ['_!_体育', '_!_汽车']],
2 [array([0.92186487, 0.05834499], dtype=float32), array([9.994085e-01, 2.704422e-04], dtype=float32)])根据预测结果可知,第1个样本属于类别“科技”的概率值最大为0.9218。
9.6.6 小结#
在本节内容中,我们首先详细介绍了fastText中词向量的构建思想和原理;然后介绍了如何借助开源的fastText库来快速完成词向量的训练和使用;最后介绍了如何利用fastText中分类模型的构建原理,并一步一步介绍了如何从数据集构建到模型的训练和推理过程。
引用#
[1] Bojanowski P, Grave E, Joulin A, et al. Enriching word vectors with subword information[J]. Transactions of the association for computational linguistics, 2017, 5: 135-146.
[2] 阿斯顿·张、李沐、扎卡里 C. 立顿等,动手学深度学习[M],2版. 北京:人民邮电出版社, 2019.
[3] Grave E, Bojanowski P, Gupta P,et al. Learning Word Vectors for 157 Languages[C]. In Proceedings of the Eleventh International Conference on LREC, Miyazaki, Japan, 2018.
[4] https://nlp.stanford.edu/software/segmenter.html
[5] https://radimrehurek.com/gensim/
[7] Joulin A, Grave E, Bojanowski P, et al. Bag of Tricks for Efficient Text Classification.[C] In Proceedings of the 15th Conference of the European Chapter of the Association for Computational Linguistics. Valencia, Spain, 2017, 2: 427–431.