肥宅程序员aka不会游泳的鱼

肥宅程序员aka不会游泳的鱼

前言

做自然语言处理(Natural Language Processing,NLP)这个领域的小伙伴们肯定对word2vec这个模型很熟悉了,它就是一种最为常见的文本表示的算法,是将文本数据转换成计算机能够运算的数字或者向量。在自然语言处理领域,文本表示是处理流程的第一步,主要是将文本转换为计算机可以运算的数字。

最传统的文本表示方法就是大名鼎鼎的One-Hot编码,就是用一个很长的向量来表示一个词,向量的长度为词典的大小,向量中只有一个1, 其他全为 0,1 的位置对应该词在词典中的位置。举个例子:I like writing code,那么转换成独热编码就是:
I —[1 0 0 0], like—[0 1 0 0], writing—[0 0 1 0], code—[0 0 0 1]
One-Hot编码的文本表示方法,确实非常简单易懂,但是以下4个缺点:
(1)实际工业上词汇表一般都非常大,比如达到百万级别,这样每个词都用百万维的向量来表示,简直是内存的灾难。
(2)这样表示数据,数据非常稀疏,稀疏数据的训练效率比较低,我们通常需要更多地数据来训练。
维度高且稀疏的数据常常会导致训练过程中梯度消失,模型无法收敛。
(3)无法体现出词之间的关系,所有转化后的向量都只存在相互正交的关系,无法用余弦相似度的方法求解词之间的相似关系。
(4)这样的向量其实除了一个位置是1,其余的位置全部都是0,表达的效率不高,本质上来说就是这个向量包含的信息太少了,因为这个文本表示的向量就是用来表达文本信息,这显然不是最好的文本表示的方法。

正是因为One-Hot编码的方式存在各种问题,nlp界各位大佬们又提出了word2vec的文本表示算法,这里的word2vec算法其实指的是其背后用于计算word vector的CBoW模型和Skip-gram模型。

word2vec算法的背景

任何经典算法的提出,在之前肯定都有无数大佬的努力,大佬们思想的碰撞,灵感的激发,最后成就一个经典的算法,流传于世,被后人敬仰,膜拜,然后封装成接口,内置于框架中,方便地被大家使用。比如CV领域经典的模型alexnet,在alexnet提出之前,卷积操作早就被广泛的用于图像处理领域,而神经网络也早就在上世纪七八十年底被人提出并使用。后来受益于硬件领域的技术发展,Alex Krizhevsky 等人在前人的基础上创造性的提出这个alexnet,并一举在夺下了2012 年 ImageNet LSVRC 的冠军,引 起了很大的轰动,也就开创后来的人工智能领域的盛况。
说回到word2vec这个算法,对这个算法有一些影响的基础研究主要包含以下几个部分:

统计语言模型

统计语言模型是用来计算一个句子的概率的概率模型,它通常基于一个语料库来构建。那什么叫做一个句子的概率呢?假设 W = ( w 1 , w 1 , . . . . w T ) W = (w_{1}, w_{1},.... w_{T} ) W=(w1,w1,....wT)表示由T个词 w 1 , w 1 , . . . . w T w_{1}, w_{1},.... w_{T} w1,w1,....wT按顺序构成的一个句子,则 w 1 , w 1 , . . . . w T w_{1}, w_{1},.... w_{T} w1,w1,....wT 的联合概率为: p ( W ) = p ( w 1 , w 1 , . . . . w T ) p(W) = p(w_{1}, w_{1},.... w_{T} ) p(W)=p(w1,w1,....wT)
p(W)被称为语言模型,即用来计算这个句子概率的模型。而根据利用Bayes公式,上式可以被链式地分解为: p ( W ) = p ( w 1 ) p ( w 2 ∣ w 1 ) p ( w 3 ∣ w 1 , w 2 ) . . . p ( w T ∣ w 1 , w 2 , . . . w T − 1 ) p(W) = p(w_{1})p(w_{2}\mid w_{1})p(w_{3}\mid w_{1},w_{2})...p(w_{T}\mid w_{1},w_{2},...w_{T-1}) p(W)=p(w1)p(w2w1)p(w3w1,w2)...p(wTw1,w2,...wT1)
其中的条件概率 p ( w 1 ) p ( w 2 ∣ w 1 ) p ( w 3 ∣ w 1 , w 2 ) . . . p ( w T ∣ w 1 , w 2 , . . . w T − 1 ) p(w_{1})p(w_{2}\mid w_{1})p(w_{3}\mid w_{1},w_{2})...p(w_{T}\mid w_{1},w_{2},...w_{T-1}) p(w1)p(w2w1)p(w3w1,w2)...p(wTw1,w2,...wT1)就是语言模型的参数。

其实这里说到统计语言模型,说到条件概率包括说到语言模型参数,就是想引出来一条结论:一个词出现的概率与它前面的所有词都相关。如果假定一个词出现的概率只与它前面固定数目的词相关呢?这就是n-gram模型的基本思想。至于n-gram模型,这里就不详细介绍了,总之,word2vec模型会用到统计语言模型的结论经验,同时也是简化版本的,一个词的出现的概率和其附近几个词关系最为密切。

向量空间模型

用一个连续的稠密向量去刻画一个词的特征就叫着词的分布式表示。词的分布式表示不仅可以直接刻画词与词之间的相似度,还可以建立一个从向量到概率的平滑函数模型,使得相似的词向量可以映射到相近的概率空间上。

向量空间模型(Vector Space Models)可以将字词转化为连续值向量的分布表示,并且其中意思相近的词将被映射到向量空间中相近的位置。向量空间模型在NLP中主要依赖的假设为Distributional Hypothesis,即在相同语境中出现的词其语义也相近。向量空间模型可以分为两类:
(1)计数模型,比如LSA;计数模型统计在语料库中,相邻出现的词的频率,再把这些计数统计结果转为小而稠密的矩阵;
(2)预测模型(比如Neural Probabilisitc Language Models)。而预测模型则根据一个词周围相邻的词推测出这个词,以及它的向量空间。

神经网络语言模型

2003年,Bengio等人发表了一篇开创性的文章:A neural probabilistic language model。在这篇文章里,作者总结出了一套用神经网络建立统计语言模型的框架(Neural Network Language Model)即神经网络语言模型,并首次提出了word embedding的概念,从而奠定了包括word2vec在内后续研究word representation learning的基础。神经网络语言模型的基本思想可以概括如下:
(1)假定词表中的每一个word都对应着一个连续的特征向量;
(2)假定一个连续平滑的概率模型,输入一段词向量的序列,可以输出这段序列的联合概率;
(3)同时学习词向量的权重和概率模型里的参数。
可以看出,基本上神经网络语言模型,是将向量空间模型和统计语言模型结合起来,并加入了神经网络这个结构,从而达到同时学习词向量的权重以及概率模型的参数的效果。
大白话讲懂word2vec原理和如何使用-LMLPHP
这是神经网络模型的结构图,其中 w t − n + 1 w_{t-n+1} wtn+1, w t − 2 w_{t-2} wt2, w t − 1 w_{t-1} wt1是输入词,index for w t − n + 1 w_{t-n+1} wtn+1则是该词的one-hot词向量,Matrix C则是词向量的权重。

word2vec算法

Word2vec主要包含两个模型,连续词袋模型(CBOW)和 Skip-Gram模型。这两个模型非常相似,其区别为CBOW根据源词上下文词汇来预测目标词汇,而Skip-Gram模型做法相反,它通过目标词汇来预测源词汇。举例说明,对同样一个句子:Shenzhen is a nice city。我们要构造一个语境与目标词汇的映射关系,其实就是input与label的关系。 这里假设滑窗尺寸为1

CBOW可以制造的映射关系为:[Shenzhen,a]—>is,[is,nice]—>a,[a,city]—>nice
Skip-Gram可以制造的映射关系为 is—>[Shenzhen,a],a—>[is,nice],nice —>[a, city]

这里的映射就在模型中,就是单词的one-hot编码。

大白话讲懂word2vec原理和如何使用-LMLPHP
大白话讲懂word2vec原理和如何使用-LMLPHP
其实这两张图基本上就把word2vec表的很清楚了,就是输入一个词,输出其相邻的词,中间的框就是一个浅的隐藏层,也是我们想要得到的word embedding权重矩阵。对于word2vec这个算法来说,这个设计很巧妙,模型的输入和输出的都是词的one-hot编码,非常简单,而每个词都有多个预测结果来不断的更隐藏层的参数,达到优化权重矩阵的效果,对了,这个模型的损失函数是交叉熵损失,这是比较明显的,而训练保存到权重矩阵是我们想要,而不是整个前向传播的过程。这点和一般的神经网络任务不一样,一般的神经网络任务想要达到的效果就是,给一个输入,经过前向传播,得到想要输出,任务完成。

这里还需要说明一下,通过word embedding权重矩阵如何得到想要的词向量表示:
大白话讲懂word2vec原理和如何使用-LMLPHP
上图中35的矩阵就是权重矩阵,15的矩阵则是输入的词向量编码,通过索引1的位置,就可以获取到这个词的向量表示。当然,如果词向量维度是1000,而我们想要学习到的词向量维度是300的话,那么这个权重矩阵维度则是1000*300,像下图所示:
大白话讲懂word2vec原理和如何使用-LMLPHP

gensim中word2vec的使用

gensim(generate similarity)是一个简单高效的自然语言处理Python库,用于抽取文档的语义主题(semantic topics)。Gensim的输入是原始的、无结构的数字文本(纯文本),内置的算法包括Word2Vec,FastText,潜在语义分析(Latent Semantic Analysis,LSA),潜在狄利克雷分布(Latent Dirichlet Allocation,LDA)等等。

使用gensim训练英文word2vec模型十分简单,这里用的是gensim中自带的文本数据。

from gensim.test.utils import common_texts
from gensim.models import Word2Vec

print(common_texts[:200])
model = Word2Vec(sentences=common_texts, vector_size=100,
                 window=5, min_count=1, workers=4)
print(model)

打印结果如下:
[['human', 'interface', 'computer'], ['survey', 'user', 'computer', 'system', 'response', 'time'], ['eps', 'user', 'interface', 'system'], ['system', 'human', 'system', 'eps'], ['user', 'response', 'time'], ['trees'], ['graph', 'trees'], ['graph', 'minors', 'trees'], ['graph', 'minors', 'survey']]
<gensim.models.word2vec.Word2Vec at 0x7fd9d0c67b20>

模型保存和继续训练的代码如下:

model.save("word2vec.model")

# 先保存,再继续接力训练
model = Word2Vec.load("word2vec.model")
model.train([["hello", "world"]], total_examples=1, epochs=1)
print(model)

打印结果如下:
<gensim.models.word2vec.Word2Vec at 0x7fd9ce7681c0>

获取词向量

vector1 = model.wv['computer']  # get numpy vector of a word
print(vector1)

打印结果如下:
array([-0.00515774, -0.00667028, -0.0077791 ,  0.00831315, -0.00198292,
       -0.00685696, -0.0041556 ,  0.00514562, -0.00286997, -0.00375075,
        0.0016219 , -0.0027771 , -0.00158482,  0.0010748 , -0.00297881,
        0.00852176,  0.00391207, -0.00996176,  0.00626142, -0.00675622,
        0.00076966,  0.00440552, -0.00510486, -0.00211128,  0.00809783,
       -0.00424503, -0.00763848,  0.00926061, -0.00215612, -0.00472081,
        0.00857329,  0.00428458,  0.0043261 ,  0.00928722, -0.00845554,
        0.00525685,  0.00203994,  0.0041895 ,  0.00169839,  0.00446543,
        0.00448759,  0.0061063 , -0.00320303, -0.00457706, -0.00042664,
        0.00253447, -0.00326412,  0.00605948,  0.00415534,  0.00776685,
        0.00257002,  0.00811904, -0.00138761,  0.00808028,  0.0037181 ,
       -0.00804967, -0.00393476, -0.0024726 ,  0.00489447, -0.00087241,
       -0.00283173,  0.00783599,  0.00932561, -0.0016154 , -0.00516075,
       -0.00470313, -0.00484746, -0.00960562,  0.00137242, -0.00422615,
        0.00252744,  0.00561612, -0.00406709, -0.00959937,  0.00154715,
       -0.00670207,  0.0024959 , -0.00378173,  0.00708048,  0.00064041,
        0.00356198, -0.00273993, -0.00171105,  0.00765502,  0.00140809,
       -0.00585215, -0.00783678,  0.00123304,  0.00645651,  0.00555797,
       -0.00897966,  0.00859466,  0.00404815,  0.00747178,  0.00974917,
       -0.0072917 , -0.00904259,  0.0058377 ,  0.00939395,  0.00350795],
      dtype=float32)

获取相似的词向量:

sims = model.wv.most_similar('computer', topn=10)  # get other similar words
print(sims)

打印结果:
[('system', 0.21617142856121063),
 ('survey', 0.044689200818538666),
 ('interface', 0.01520337350666523),
 ('time', 0.0019510575802996755),
 ('trees', -0.03284314647316933),
 ('human', -0.0742427185177803),
 ('response', -0.09317588806152344),
 ('graph', -0.09575346857309341),
 ('eps', -0.10513805598020554),
 ('user', -0.16911622881889343)]

参考文献

word2vec原理及gensim中word2vec的使用
word2vec前世今生
Word2vec Tutorial

12-15 12:37