在《(一)理解word2vec:原理篇》中,我已经介绍了word2vec的相关应用和原理。在这篇博客中,我主要介绍word2vec的实践。
本篇博客的基础实践代码仍然参考刘新建老师的博客,在他文章的基础上,我又扩展了一些功能。
我用的实现word2vec的包是gensim(官方github)。gensim是一款开源的第三方Python工具包,用于从原始的非结构化的文本中,无监督地学习到文本隐层的主题向量表达。它支持包括TF-IDF,LSA,LDA,和word2vec在内的多种主题模型算法,支持流式训练,并提供了诸如相似度计算,信息检索等一些常用任务的API接口。
目录
4.1 word2vec是word embedding 最好的工具吗?
一、gensim word2vec API概述
在gensim中,word2vec 相关的API都在包gensim.models.word2vec中。和算法有关的参数都在类gensim.models.word2vec.Word2Vec中。相关主页:
word2vec,keyedvectors
算法需要注意的参数有:
1) sentences: 我们要分析的语料,可以是一个列表,或者从文件中遍历读出。后面我们会有从文件读出的例子。
2) size: 词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。
3) window:即词向量上下文最大距离,这个参数在我们的算法原理篇中标记为cc,window越大,则和某一词较远的词也会产生上下文关系。默认值为5。在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5,10]之间。
4) sg: 即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型,是1则是Skip-Gram模型,默认是0即CBOW模型。
5) hs: 即我们的word2vec两个解法的选择了,如果是0, 则是Negative Sampling,是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。
6) negative:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。
7) cbow_mean: 仅用于CBOW在做投影的时候,为0,则算法中的xwxw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。在原理篇中,我们用平均值来表示,默认值也是1,不推荐修改默认值。
8) min_count:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。
9) iter: 随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。
10) alpha: 在随机梯度下降法中迭代的初始步长。算法原理篇中标记为,默认是0.025。
11) min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步长值。随机梯度下降中每轮的迭代步长可以由iter,alpha, min_alpha一起得出。这部分由于不是word2vec算法的核心内容,因此在原理篇我们没有提到。对于大语料,需要对alpha, min_alpha,iter一起调参,来选择合适的三个值。
以上就是gensim word2vec的主要的参数,下面我们用一个实际的例子来学习word2vec。
二、对原始语料分词
刘新建老师选择《人民的名义》的小说原文作为语料,语料原文在这里。在这里,我也用这个语料。由于这个原始语料就是小说,因此,我们应该先分词。采用jieba。
代码如下:
import jieba
import jieba.analyse
jieba.suggest_freq('沙瑞金', True) # 加入一些词,使得jieba分词准确率更高
jieba.suggest_freq('田国富', True)
jieba.suggest_freq('高育良', True)
jieba.suggest_freq('侯亮平', True)
jieba.suggest_freq('钟小艾', True)
jieba.suggest_freq('陈岩石', True)
jieba.suggest_freq('欧阳菁', True)
jieba.suggest_freq('易学习', True)
jieba.suggest_freq('王大路', True)
jieba.suggest_freq('蔡成功', True)
jieba.suggest_freq('孙连城', True)
jieba.suggest_freq('季昌明', True)
jieba.suggest_freq('丁义珍', True)
jieba.suggest_freq('郑西坡', True)
jieba.suggest_freq('赵东来', True)
jieba.suggest_freq('高小琴', True)
jieba.suggest_freq('赵瑞龙', True)
jieba.suggest_freq('林华华', True)
jieba.suggest_freq('陆亦可', True)
jieba.suggest_freq('刘新建', True)
jieba.suggest_freq('刘庆祝', True)
jieba.suggest_freq('赵德汉', True)
with open('in_the_name_of_people.txt', 'rb') as f:
document = f.read()
document_cut = jieba.cut(document)
result = ' '.join(document_cut)
result = result.encode('utf-8')
with open('in_the_name_of_people_segment.txt', 'wb') as f2:
f2.write(result)
f.close()
f2.close()
三、利用gensim实现word2vec
拿到了分词后的文件,在一般的NLP处理中,会需要去停用词。由于word2vec的算法依赖于上下文,而上下文有可能就是停词。因此对于word2vec,我们可以不用去停词。
现在我们可以直接读分词后的文件到内存。这里使用了word2vec提供的LineSentence类来读文件,然后套用word2vec的模型。在实际应用中,可以调参提高词的embedding的效果。
① 训练模型
# import modules & set up logging
import logging
from gensim.models import word2vec
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = word2vec.LineSentence('in_the_name_of_people_segment.txt')
model = word2vec.Word2Vec(sentences, hs=1, min_count=10, window=3, size=100)
② 选出最相似的5个词
# 选出最相似的5个词,方法一
for e in model.wv.similar_by_word('沙瑞金', topn =5):
print (e[0], e[1])
'''
高育良 0.9234411716461182
田国富 0.8858301043510437
亮 0.8368291854858398
李达康 0.822664737701416
指示 0.8102874755859375
'''
print ('*'*50)
# 选出最相似的5个词,方法二
for e in model.wv.most_similar(positive=['沙瑞金'], topn=5):
print(e[0], e[1])
'''
高育良 0.9234411716461182
田国富 0.8858301043510437
亮 0.8368291854858398
李达康 0.822664737701416
指示 0.8102874755859375
'''
2个方法的结果一样,可以看到能找到几个相关的词,但是也有噪音
③ 选出词汇相对于另一组词汇的对应词
# 选出词汇相对于另一组词汇的对应词
model.wv.most_similar(positive=['李达康', '欧阳菁'], negative=['侯亮平'])
'''
[('一个', 0.8564327359199524),
('为', 0.8563842177391052),
('老婆', 0.8343011140823364),
('中国', 0.8328956961631775),
('问题', 0.8306251764297485),
('班子', 0.8267861604690552),
('发展', 0.8258910179138184),
('干部', 0.8231020569801331),
('思路', 0.8212059140205383),
('重要', 0.8195070028305054)]
'''
第3个是老婆,看来还是能get到欧阳菁是李达康的老婆。但是却找不到侯亮平的老婆?
④ 计算两个词的相似度
# 计算两个词的相似度
print(model.wv.similarity('沙瑞金', '季昌明'))
print(model.wv.similarity('沙瑞金', '田国富'))
'''
0.69518
0.88583
'''
沙瑞金和田国富都是书记,而季昌明是检察长,因此沙瑞金和田国富更相关。
⑤ 计算两个集合的相似度
# 计算两个集合的相似度
list1 = ['李达康', '欧阳菁']
list2 = ['侯亮平', '钟小艾']
print(model.wv.n_similarity(list1, list2))
'''
0.811746
'''
⑥ 选出集合中不同类的词语
# 选出集合中不同类的词语
list3 = ['沙瑞金', '李达康', '季昌明', '刘新建']
print(model.wv.doesnt_match(list3))
'''
刘新建
'''
刘新建明显跟他们3个不是一类的。
⑦ 查看词的向量值
# 查看词的向量值
print(model.wv.__getitem__(['沙瑞金'])) # 输出 一行100列的向量
⑧ 保存并读出训练好的模型
# 保存训练好的模型
model.save('model.bin')
# 读出训练好的模型
new_model = word2vec.Word2Vec.load('model.bin')
在这篇文章里有更多模型保存和读入的例子。
上面实现了一个很简单的例子,在实际应用中,我们可以对word2vec进行调参提高embedding的准确性。有时间,自己会整理一个调参的代码。这里有一个调参的参考。
四、关于实践word2vec的一些问题
4.1 word2vec是word embedding 最好的工具吗?
word2vec并非是效果最好的word embedding 工具。最容易看出的就是word2vec没有考虑语序,这里会有训练效果损失。
由于 word2vec训练速度快 ,易用,google出品 等,使得word2vec使用的人多。
训练快是因为word2vec只有输入层和输出层,砍去了神经网络中,隐藏层的耗时计算(所以word2vec并不算是一个深度学习算法)。另外,阅读word2vec的google的源码,会发现里面有一些提速的trick。如 sigmod函数,采用一次计算,以后查表,减去了大量的重复计算。如词典hash存储, 层次softmax等。
易用是因为word2vec公布了word2vec的代码。在tensorflow,gensim,spark mllib包中都有集成,使用方便。
4.2 word2vec 训练结果的差异主要来自什么因素?
4.2.1 语料影响最大
语料的场景,比如微博的语料和新闻语料训练的结果差别很大。因为微博属于个人发帖,比较随意。而新闻比较官方正式,另外新闻句式相对复杂。经过训练对比:微博这种短文,训练的相似词更多是同级别的相关词。比如 深圳 相关的是 广州 。而用新闻语料,训练得到 深圳 相关的词 更多是与 深圳 有关联的词,比如 深圳大学。
4.2.2 算法参数的影响
算法参数对总体效果影响不大。相对来说,比较重要的参数有以下:
① 子采样(subsampling)
子采样越低,对高频词越不利,对低频词有利。可以这么理解,本来高频词被迭代50次,低频词迭代10次,如果采样频率降低一半,高频词失去了25次迭代,而低频词只失去了5次。一般设置成le-5。个人觉得,降采样有类似tf-idf的功能,降低高频词对上下文影响的权重。
② 语言模型
skip-gram 和cbow,之前有对比,切词效果偏重各不相同。
从效果来看,感觉cbow对词频低的词更有利。这是因为cbow是基于周围词来预测某个词,虽然这个词词频低,但是它是基于周围词训练的基础上,通过算法来得到这个词的向量。通过周围词的影响,周围词训练的充分,这个词就会收益。
③ 窗口大小
窗口大小影响词和前后多少个词的关系,和语料中语句长度有关,建议可以统计一下语料中,句子长度的分布,再来设置window大小。
④ min-count
最小词频训练阀值,这个根据训练语料大小设置,只有词频超过这个阀值的词才能被训练。
根据经验,如果切词效果不好,会切错一些词,比如 “在深圳”,毕竟切错的是少数情况,使得这种错词词频不高,可以通过设置相对大一点的 min-count 过滤掉切错的词。
⑤ 向量维度
如果词量大,训练得到的词向量还要做语义层面的叠加,比如句子的向量表示用词的向量叠加,为了有区分度,语义空间应该要设置大一些,所以维度要偏大。一般情况下200维够用。
⑥ 其他参数
比如学习率,可以根据需要调。
4.3 word2vec 影响速度的因素有哪些?
4.3.1 语言模型:cbow 比skip-gram 更快
为什么 cbow更快,很重要的一个原因,cbow是基于周围词来预测这个单词本身 。而skip-gram是基于本身词去预测周围词。 那么,cbow只要 把窗口内的其他词相加一次作为输入来预测 一个单词。不管窗口多大,只需要一次运算。而skip-gram直接受窗口影响,窗口越大,需要预测的周围词越多。在训练中,通过调整窗口大小明显感觉到训练速度受到很大影响。
4.3.2 迭代次数
影响训练次数,语料不够的情况下,可以调大迭代次数。
4.3.3 线程数
单机版(google word2vec)可以通过设置多线程跑,集群版(spark mllib)可以设置多个partitions。但是从经验来看,在集群上设置partitions 过多,会影响训练的效果。
4.3.4 其他参数
采样频率 影响词的训练频率
min-count 最小词频 影响 训练词的数量
向量维度 维度决定了训练过程中计算的维度
4.4 怎样评估word2vec训练的好坏?
该篇论文介绍了word embedding 的评估方法。
4.4.1 词聚类
可以采用 kmeans 聚类,看聚类簇的分布
4.4.2 词cos 相关性
查找cos相近的词
4.4.3 Analogy对比
a:b 与 c:d的cos距离 (man-king woman-queen )
4.4.4 使用tnse,pca等降维可视化展示
在参考文献【3】中有用pca展示的代码。
参考文献
【1】15分钟入门Gensim
【2】python︱gensim训练word2vec及相关函数与功能理解