引言

本文是对近日学习word2vec的一个总结,期间看了不少博客和论文。

word2vec是一种高效的训练词向量的模型,基于上下文相似的两个词,它们的词向量也应该相似, 比如,“A dog is running in the room"和"A cat is running in the room”。这两个句子,只是"cat"和"dog"不同,word2vec认为它们是相似的,而n-gram模型做不到这一点。

word2vec有两个模型:CBOW(COntinuous Bag of Words)和Skip-Gram。

CBOW模型中,通过一个上下文(比如说一个句子)来预测目标词;而Skip-Gram模型则相反,根据给定的输入词来预测上下文。

Skip-Gram:能够很好地处理少量的训练数据,而且能够很好地表示不常见的单词或短语
CBOW:比skip-gram训练快几倍,对出现频率高的单词的准确度稍微更好一些

Simple CBOW模型

要想理解CBOW和SkipGram模型,我们先从最简单版本的CBOW模型开始介绍,又被称为One Word模型,上下文只有一个单词,目标词也是一个单词。
意味着给定一个上下文词来预测一个目标词。有点类似bigram模型。

在上图中VVV是词典大小,NNN是一个超参数,是隐藏层中单元数量,也是我们要学的词向量的维度,一般最多设置到300。

输入向量xxxV×1V \times 1V×1的one-hot向量,只有xk=1\color{red}{ x_k=1}xk=1,其他都是000

输入层和输出层之间的权重是一个V×NV \times NV×N的矩阵WWW,给定一个上下文单词,隐藏层hhh计算如下:

h=WTx=W(k,⋅)T:=vwIT(1)h = W^T x = W_{(k,\cdot)}^T := v_{w_I}^T \tag{1} h=WTx=W(k,)T:=vwIT(1)

WWWV×NV \times NV×Nhhh的维度是N×1N \times 1N×1

这个公式详细描述一下,展开上面的WWW矩阵:

WV×N=[w11w12⋯w1Nw21w22⋯w2N⋮⋮⋱⋮wV1wV2⋯wVN]W_{V \times N} = \left[ \begin{matrix} w_{11} & w_{12} & \cdots & w_{1N} \\ w_{21} & w_{22} & \cdots & w_{2N} \\ \vdots & \vdots & \ddots & \vdots \\ w_{V1} & w_{V2} & \cdots & w_{VN} \end{matrix} \right] WV×N=w11w21wV1w12w22wV2w1Nw2NwVN

xxx

x=[x1x2⋮xV]x = \left[ \begin{matrix} x_1 \\ x_2 \\ \vdots \\ x_V \end{matrix} \right] x=x1x2xV

h=WTx=[w11w21⋯wk1⋯wV1w12w22⋯wk2⋯wV2⋮⋮⋱⋮⋮w1Nw2N⋯wkN⋯wVN]N×V[x1x2⋮xk⋮xV]=[wk1wk2⋮wkN]h = W^T x = \left[ \begin{matrix} w_{11} & w_{21} & \cdots & w_{k1} \cdots & w_{V1} \\ w_{12} & w_{22} & \cdots & w_{k2} \cdots & w_{V2} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ w_{1N} & w_{2N} & \cdots & w_{kN} \cdots & w_{VN} \end{matrix} \right]_{N \times V} \left[ \begin{matrix} x_1 \\ x_2 \\ \vdots \\ x_k \\ \vdots \\ x_V \end{matrix} \right] = \left[ \begin{matrix} w_{k1} \\ w_{k2} \\ \vdots \\ w_{kN} \end{matrix} \right] \\ h=WTx=w11w12w1Nw21w22w2Nwk1wk2wkNwV1wV2wVNN×Vx1x2xkxV=wk1wk2wkN

WWW的第iii行用vwv_wvw表示,相当于是www的词向量,是1×N1 \times N1×N的。

WTxW^T xWTx得到N×1N \times 1N×1的列向量(注意这里是WWW的转置,WTW^TWT的列对应的就是WWW的行),相当于是WWWxk=1x_k=1xk=1对应的那一行。

基本上就是拷贝了WWW的第kkk行到hhh去了。

输入单词wIw_IwI的向量表示是vwIv_{w_I}vwI,维度是N×1N \times 1N×1

从隐藏层到输出层,有一个不同的权重矩阵W′W^′W,它是N×VN \times VN×V的。使用这个权重矩阵,可以计算第jjj个单词的得分uju_juj:

uj=vwj′T⋅h(2)u_j = {v^{\prime} _{w_j}}^T \cdot h \tag{2} uj=vwjTh(2)

vwj′v^′_{w_j}vwj是矩阵W′W^′W的第jjj列,维度是N×1N \times 1N×1的, vwj′T{v^′_{w_j}}^TvwjT维度就是1×N1 \times N1×N。因此uju_juj是这两个向量的内积,结果是一个标量,代表某个单词的分数。

这个得分可以理解为衡量中心词与输出词的相似度,hhh其实就是输入词的向量vwIv_{w_I}vwI

我们可以一次性求出所有单词的得分:u=W′T⋅hu = {W^′}^T \cdot hu=WTh,得到的是V×1V \times 1V×1的向量,VVV是词典大小。

接着对uuu进行softmax就可以得到每个单词得分的概率分布:

p(wj∣wI)=yj=exp(uj)∑j′=1Vexp(uj′)(3)p(w_j|w_I) = y_j = \frac{exp(u_j)}{\sum_{j^{\prime} = 1}^V exp(u_{j^{\prime}})} \tag{3} p(wjwI)=yj=j=1Vexp(uj)exp(uj)(3)

yjy_jyj是输出层第jjj个单元的输出。把(1)(1)(1),(2)(2)(2)代入到(3)(3)(3)得:

p(wj∣wI)=exp(vwj′T⋅vwI)∑j′=1Vexp(vwj′′TvwI)(4)p(w_j|w_I) = \frac{ exp ({v^{\prime} _{w_j}}^T \cdot v_{w_I} )}{ \sum^V_{j^′=1} exp({v^{\prime} _{w_{j^′}}}^T v_{w_I} ) } \tag{4} p(wjwI)=j=1Vexp(vwjTvwI)exp(vwjTvwI)(4)

这里要注意的是:

  • 输入单词xxx和输出单词yyy都是one-hot向量
  • vwv_wvwvw′v^′_wvw是输入单词www的两种表示,分别称为输入向量和输出向量
  • vwv_wvw来自WWW的行
  • vw′v^′_wvw来自W′W^′W的列

更新权重:隐藏层到输出层

下面我们就可以根据上面的式子来求梯度了。

训练目标是最大化公式(4)(4)(4),即给定输入单词wIw_IwI,最大化观察到输出单词wOw_OwO的条件概率(用j∗j^*j表示它输出层的索引)。

max⁡p(wO∣wI)=max⁡yj∗=max⁡log⁡yj∗=max⁡log⁡exp⁡(uj∗)−log⁡∑j′=1Vexp(uj′)=uj∗−log∑j′=1Vexp(uj′):=−E\begin{aligned} \max p(w_O|w_I) &= \max \, y_{j^*} \\ &= \max \, \log \, y_{j^*} \\ &= \max \, \log \exp (u_{j^*}) - \log \sum_{j^{\prime} = 1}^V exp(u_{j^{\prime}}) \\ &= u_j^* - log \sum_{j^{\prime} = 1}^V exp(u_{j^{\prime}}) := -E \end{aligned} maxp(wOwI)=maxyj=maxlogyj=maxlogexp(uj)logj=1Vexp(uj)=ujlogj=1Vexp(uj):=E

:=:=:=是记作的意思,即整个式子记作−E-EE,也就是E=−log⁡p(wO∣wI)E = -\log \, p(w_O|w_I)E=logp(wOwI),因为我们习惯最小化损失函数。

现在我们更新隐藏层和输出层之间的权重。

下面求EEEuju_juj的偏导,得到了

∂E∂uj=yj−tj:=ej(5)\frac{\partial E}{\partial u_j} = y_j - t_j := e_j \tag{5} ujE=yjtj:=ej(5)

j=j∗j=j^*j=j时,tj=1t_j=1tj=1,否则tj=0t_j=0tj=0

下面给出公式推导:

∂E∂uj=−∂(uj∗−log∑j′=1Vexp(uj′))∂uj=−∂uj∗∂uj+∂(log⁡∑j′=1Vexp⁡(uj′))∂uj=−tj+exp(uj)∑j′=1Vexp(uj)=yj−tj\begin{aligned} \frac{\partial E}{\partial u_j} &=- \frac{ \partial \left( u_j^* - log \sum_{j^{\prime} = 1}^V exp(u_{j^{\prime}}) \right) }{\partial u_j} \\ &= -\frac{\partial u_{j^*}}{\partial u_j} + \frac{\partial \left(\log \sum_{j^{\prime} = 1}^V \exp (u_{j^{\prime}}) \right)}{\partial u_j} \\ &= - t_j + \frac{exp(u_j)}{\sum_{j^{\prime} = 1}^V exp(u_j)} \\ &= y_j - t_j \end{aligned} ujE=uj(ujlogj=1Vexp(uj))=ujuj+uj(logj=1Vexp(uj))=tj+j=1Vexp(uj)exp(uj)=yjtj

其中

∂(log⁡∑j′=1Vexp⁡(uj′))∂uj\frac{\partial \left(\log \sum_{j^{\prime} = 1}^V \exp (u_{j^{\prime}}) \right)}{\partial u_j} uj(logj=1Vexp(uj))
是通过复合函数的求导法则来求的,∂log⁡f(x)∂x=f(x)′f(x)\frac{\partial \log f(x)}{\partial x} = \frac{f(x)^{\prime}}{f(x)}xlogf(x)=f(x)f(x) ,这里把f(x)=∑j′=1Vexp⁡(uj′)f(x)=\sum_{j^{\prime} = 1}^V \exp (u_{j^{\prime}})f(x)=j=1Vexp(uj)

要求∑j′=1Vexp⁡(uj′)\sum_{j^{\prime} = 1}^V \exp (u_{j^{\prime}})j=1Vexp(uj)uju_juj的偏导,其实很简单,把求和符号展开即可。

∂(exp(u1)+exp(u2)+⋯+exp(uj)+⋯+exp(uV))∂uj=exp(uj)\frac{ \partial \left(exp(u_1) + exp(u_2) + \cdots + exp(u_j) + \cdots +exp(u_V) \right)}{\partial u_j} = exp(u_j) uj(exp(u1)+exp(u2)++exp(uj)++exp(uV))=exp(uj)

uju_juj看成一个变量,其他u1,u2,⋯u_1,u_2, \cdotsu1,u2,都是与uju_juj无关的,因此求导结果为0。

根据公式(3)(3)(3)就可以化简为yj−tjy_j - t_jyjtj

结果简单地就是预测值与真实值之差。

下一步就是对wij′w^′_{ij}wij求导来获取它的梯度。

来看下∂uj∂wij′\frac{\partial u_j}{\partial w^′_{ij}}wijuj

由公式(2)(2)(2)知道uju_jujwij′w^′_{ij}wij的关系。h=vwI=[h1,h2,⋯,hN]h=v_{w_I}=[h_1,h_2,\cdots,h_N]h=vwI=[h1,h2,,hN]

vwj′T=[w1j′,w2j′,⋯,w1N′]{v^′_{w_j}}^T = [w^′_{1j},w^′_{2j},\cdots,w^′_{1N}]vwjT=[w1j,w2j,,w1N]

uj=h1⋅w1j′+h2⋅w2j′+⋯+hi⋅wij′+⋯+hN⋅wNj′u_j = h_1 \cdot w^′_{1j} + h_2 \cdot w^′_{2j} + \cdots + h_i \cdot w^′_{ij} + \cdots + h_N \cdot w^′_{Nj}uj=h1w1j+h2w2j++hiwij++hNwNj

所以
∂uj∂wij′=hi\frac{\partial u_j}{\partial w^′_{ij}} = h_iwijuj=hi

∂E∂wij′=∂E∂uj⋅∂uj∂wij′=ej⋅hi(6)\frac{\partial E}{\partial w^′_{ij}} = \frac{\partial E}{\partial u_j} \cdot \frac{\partial u_j}{\partial w^′_{ij}} = e_j \cdot h_i \tag{6} wijE=ujEwijuj=ejhi(6)

现在就可以使用梯度下降来更新隐藏层到输出层的权重:
wij′=wij′−η⋅ej⋅hiw^′_{ij} = w^′_{ij} - \eta \cdot e_j \cdot h_i wij=wijηejhi
或者向量的形式为:
vwj′=vwj′−η⋅ej⋅hv^′_{w_j} = v^′_{w_j} - \eta \cdot e_j \cdot h vwj=vwjηejh

hih_ihi是隐藏层的第iii个单元,v′wjv′_{w_j}vwj是单词wjw_jwj的输出向量。对每个训练样本都需要做一次复杂度为VVV的操作去更新W′W^′W

更新权重:输入层到隐藏层

接着我们关注输入层到隐藏层的权重。首先求∂E∂hi\frac{\partial E}{\partial h_i}hiE

∂E∂hi=∑j=1V∂E∂uj⋅∂uj∂hi=∑j=1Vej⋅wij′:=EHi\frac{\partial E}{\partial h_i} = \sum_{j=1}^V \frac{\partial E}{\partial u_j} \cdot \frac{\partial u_j}{\partial h_i} \\ = \sum_{j=1}^V e_j \cdot w^′_{ij}\\ := EH_i hiE=j=1VujEhiuj=j=1Vejwij:=EHi

EHEHEH是一个NNN维的向量(N×1N \times 1N×1),就是所有输出单词的权重之和,权重是它们的预测错误。

下一步就是要求EEEWWW的导数,首先回顾下隐藏层就是输入层的线性变换:
hi=∑k=1Vxk⋅wkih_i = \sum_{k=1}^V x_k \cdot w_{ki} hi=k=1Vxkwki

然后我们用链式法则来求EEEWWW的导数:
∂E∂wki=∂E∂hi⋅∂hi∂wki=EHi⋅xk\frac{\partial E}{\partial w_{ki}} = \frac{\partial E}{\partial h_i} \cdot \frac{\partial h_i}{\partial w_{ki}} \\ = EH_i \cdot x_k wkiE=hiEwkihi=EHixk

向量化形式等价于xxxEHEHEH的张量积:
∂E∂W=x⊗EH=x⋅EHT\frac{\partial E}{\partial W} = x \otimes EH = x \cdot EH^T WE=xEH=xEHT

这样就得到了一个V×NV \times NV×N的矩阵,因为xxx向量中只有一个元素为111,其他都为000,所以在∂E∂W\frac{\partial E}{\partial W}WE的矩阵中,只有一行是非零的。并且这一行的值是EHTEH^TEHT

现在我们就可以写出WWW的更新式子了:
vwI=vwI−η⋅EHTv_{w_I} = v_{w_I} - \eta \cdot EH^T vwI=vwIηEHT

因为只有一行是非零的,所以一次也只会更新一行。

CBOW模型

CBOW模型的图示如下:

CBOW模型由多个单词作为输入,每个输入都是one-hot模型,同样输出一个单词。由多个上下文单词来预测中心词。计算隐藏层的时候,取输入单词的平均向量,然后乘以权重WWW作为输出:

h=1C(x1T+x2T+⋯+xCT)W=1C(vw1+vw2+⋯+vwC)h = \frac{1}{C} (x_1^T + x_2^T + \cdots + x_C^T) W \\ = \frac{1}{C}(v_{w_1} + v_{w_2} + \dots + v_{w_C}) h=C1(x1T+x2T++xCT)W=C1(vw1+vw2++vwC)

CCC是上下文单词数量,因为是把CCC个输入单词的平均向量作为输入向量,损失函数的定义和上面一个单词的模型一样。

更新隐藏层到输出层的式子也是一样的:
vwj′=vwj′−η⋅ej⋅hforj=1,2,⋯,Vv^′_{w_j} = v^′_{w_j} - \eta \cdot e_j \cdot h \,\,\,\, for\, j = 1,2, \cdots,V vwj=vwjηejhforj=1,2,,V

更新输入层到隐藏层的权重和之前一样,除了我们需要将梯度均摊到每个输入单词上:

vwI,c=vwI,c−1C⋅η⋅EHTforc=1,2,⋯,Cv_{w_{I,c}} = v_{w_{I,c}} - \frac{1}{C} \cdot \eta \cdot EH^T \,\,\,\, for\, c = 1,2,\cdots,C vwI,c=vwI,cC1ηEHTforc=1,2,,C

这里每次会更新WWW中的CCC行。

Skipgram模型

Skip-Gram模型和CBOW模型相反,把中心词放到输入层中,输出层输出的是上下文词。即用中心词来预测上下文词。

我们仍然使用vwIv_{w_I}vwI来表示Skip-gram模型的唯一输入向量。然后隐藏层输出hhh的定义也和(1)(1)(1)一样。

h=WTx=W(k,⋅)T:=vwITh = W^T x = W_{(k,\cdot)}^T := v_{w_I}^T h=WTx=W(k,)T:=vwIT

在输出层,不是输出一个多项式分布,而是输出CCC个多项式分布。但每个分布使用同样的权重矩阵来计算:

p(wc,j∣wI)=yc,j=exp(uc,j)∑j′=1Vexp(uj′)p(w_{c,j}|w_I) = y_{c,j} = \frac{exp(u_{c,j})}{\sum_{j^′=1}^V exp(u_{j^′})} p(wc,jwI)=yc,j=j=1Vexp(uj)exp(uc,j)

需要注意的是,这CCC个输出是相互独立的。wc,jw_{c,j}wc,j是第ccc个panel(输出)中的第jjj个单词。wIw_IwI是输入单词。yc,jy_{c,j}yc,j是第ccc个输出层中的第jjj个单元。
uc,ju_{c,j}uc,j是第ccc个输出的第jjj个单元的得分。因为这些输出都共享同样的权重,因此
uc,j=uj=vwj′T⋅hforc=1,2,⋯,Cu_{c,j} = u_j = {v^′_{w_j}}^T \cdot h \,\, \, for \, c = 1,2,\cdots,C uc,j=uj=vwjThforc=1,2,,C

vwj′v^′_{w_j}vwj是词典中第jjj个单词的输出向量,它是矩阵W′W^′W中的第jjj列。

参数更新的式子和简单CBOW模型有点不同,

E=−log⁡p(wO,1,wO,2,⋯,wO,C∣wI)=−log⁡∏c=1CP(wO,c∣wi)=−log⁡∏c=1Cexp(uc,jc∗)∑j′=1Vexp(uj′)=−log⁡∏c=1Cexp(uc,jc∗)+log⁡∏c=1C∑j′=1Vexp(uj′)=−∑c=1Cujc∗+log⁡(∑j′=1Vexp(uj′))C=−∑c=1Cujc∗+C⋅log⁡∑j′=1Vexp(uj′)\begin{aligned} E &= -\log p(w_{O,1},w_{O,2},\cdots,w_{O,C}|w_I) \\ &= - \log \prod_{c=1}^C P(w_{O,c}|w_i) \\ &= - \log \prod_{c=1}^C \frac{exp(u_{c,j^*_c})}{\sum_{j^′=1}^V exp(u_{j^′})} \\ &= - \log \prod_{c=1}^C exp(u_{c,j^*_c}) + \log \prod_{c=1}^C \sum_{j^′=1}^V exp(u_{j^′})\\ &= - \sum_{c=1}^C u_{j^*_c} + \log (\sum_{j^′=1}^V exp(u_{j^′}))^C\\ &= - \sum_{c=1} ^ C u_{j^*_c} + C \cdot \log \sum_{j^′=1}^V exp(u_{j^′}) \end{aligned} E=logp(wO,1,wO,2,,wO,CwI)=logc=1CP(wO,cwi)=logc=1Cj=1Vexp(uj)exp(uc,jc)=logc=1Cexp(uc,jc)+logc=1Cj=1Vexp(uj)=c=1Cujc+log(j=1Vexp(uj))C=c=1Cujc+Clogj=1Vexp(uj)

wO,cw_{O,c}wO,c代表第ccc个输出单词,jc∗j^*_cjc表示第ccc个输出单词的索引。
因为这CCC个输出是相互独立的,因此p(wO,1,wO,2,⋯,wO,C∣wI)=∏P(wO,c∣wI)p(w_{O,1},w_{O,2},\cdots,w_{O,C}|w_I) = \prod P(w_{O,c}|w_I)p(wO,1,wO,2,,wO,CwI)=P(wO,cwI)

下面我们求梯度,对第ccc个多项分布的第jjj项的梯度为:

∂E∂uc,j=yc,j−tc,j:=ec,j\frac{\partial E}{\partial u_{c,j}} = y_{c,j} - t_{c,j} := e_{c,j} uc,jE=yc,jtc,j:=ec,j

就是某个输出的预测错误,考虑到CCC个多项分布产生的影响,所以需要求和。

为了简化,我们定义一个VVV维的向量EI=EI1,⋯,EIVEI = {EI_1,\cdots,EI_V}EI=EI1,,EIV作为所有上下文单词的预测错误之和。

对第jjj个单词的预测错误之和为:
EIj=∑c=1Cec,jEI_j = \sum_{c=1}^C e_{c,j} EIj=c=1Cec,j

接下来,对隐藏层到输出层矩阵W′W^\primeW求导:

∂E∂wij′=∑c=1C∂E∂uc,j⋅∂uc,j∂wij′=EIj⋅hi\frac{\partial E}{\partial w^\prime_{ij}} = \sum_{c=1}^C \frac{\partial E}{\partial u_{c,j}} \cdot \frac{\partial u_{c,j}}{\partial w^\prime_{ij}} = EI_j \cdot h_i wijE=c=1Cuc,jEwijuc,j=EIjhi

所以更新隐藏层到输出层权重的式子为:

wij′=wij′−η⋅EIj⋅hiw^\prime_{ij} = w^\prime_{ij} -\eta \cdot EI_j \cdot h_i wij=wijηEIjhi
或者
vwj′=vwj′−η⋅EIj⋅hforj=1,2,⋯,Vv^\prime_{w_j} = v^\prime_{w_j} - \eta \cdot EI_j \cdot h \,\,\, for\, j=1,2,\cdots,V vwj=vwjηEIjhforj=1,2,,V

下面考虑对隐藏层的梯度:
∂E∂hi=∑c=1C∑j=1V∂E∂uc,j∂uc,j∂hi=∑c=1C∑j=1Vec,j⋅wij′=∑j=1VEIj⋅wij′:=EHi\begin{aligned} \frac{\partial E}{\partial h_i} &= \sum_{c=1}^C \sum_{j=1}^V \frac{\partial E}{\partial u_{c,j}} \frac{\partial u_{c,j}}{\partial h_i } \\ &= \sum_{c=1}^C \sum_{j=1}^V e_{c,j} \cdot w^\prime_{ij} \\ &= \sum_{j=1}^V EI_j \cdot w^\prime_{ij} := EH_i \end{aligned} hiE=c=1Cj=1Vuc,jEhiuc,j=c=1Cj=1Vec,jwij=j=1VEIjwij:=EHi

和简单CBOW模型一样,整成向量化的形式为:
∂E∂h=EHT\frac{\partial E}{\partial h} = EH^T hE=EHT

由于输入只有一个词,h=vwITh=v_{w_I}^Th=vwIT,每次也是更新WWW的一行:

vwI=vwI−η⋅EHTv_{w_I} = v_{w_I} - \eta \cdot EH^T vwI=vwIηEHT

简单代码实现

# -*- coding: utf-8 -*-
# @Author  : Juefrom collections import defaultdictimport numpy as npclass word2vec:def __init__(self, settings):self.n = settings['n']self.eta = settings['learning_rate']self.epochs = settings['epochs']self.window = settings['window_size']# true:cbow ; false:skipgramself.cbow = settings['model'] == 'cbow'def generate_training_data(self, corpus):# 单词计数word_counts = defaultdict(int)for row in corpus:for word in row:word_counts[word] += 1# 词典大小Vself.v_count = len(word_counts.keys())# 生成LOOKUP 词典self.words_list = sorted(list(word_counts.keys()), reverse=False)# 单词对应的索引self.word_index = dict((word, i) for i, word in enumerate(self.words_list))# 索引对应的单词self.index_word = dict((i, word) for word, i in self.word_index.items())training_data = []for sentence in corpus:sent_len = len(sentence)for i, word in enumerate(sentence):# 目标词w_target = self.word2onehot(sentence[i])# 上下文词w_context = []for j in range(i - self.window, i + self.window + 1):if j != i and sent_len - 1 >= j >= 0:w_context.append(self.word2onehot(sentence[j]))training_data.append([w_target, w_context])  # 中心词,上下文词return np.array(training_data, dtype=object)def train(self, training_data, debug=False):# 初始化权重矩阵self.w1 = np.random.uniform(-0.8, 0.8, (self.v_count, self.n))  # 目标词矩阵 W v x nself.w2 = np.random.uniform(-0.8, 0.8, (self.n, self.v_count))  # 上下文词矩阵  W′ n x v# 迭代epochs次for i in range(self.epochs):self.loss = 0# 中心词,上下文词for w_t, w_c in training_data:if self.cbow:x = np.mean(w_c, axis=0)else:x = w_t# 前向传播y_pred, h, u = self.forward_pass(x)# 计算损失 e_jif self.cbow:e = y_pred - w_t  # dE/duelse:e = np.sum([np.subtract(y_pred, word) for word in w_c], axis=0)# 反向传播self.backprop(e, h, x)if self.cbow:self.loss += -float(u[w_t == 1]) + np.log(np.sum(np.exp(u)))else:self.loss += -np.sum([u[word == 1] for word in w_c]) + len(w_c) * np.log(np.sum(np.exp(u)))if i % 100 == 0 and debug:print('EPOCH:', i, 'LOSS:', self.loss)def forward_pass(self, x):''':param x:  vx1 one-hot向量:return:'''h = np.dot(self.w1.T, x)  # (nxv)  (vx1) -> nx1u = np.dot(self.w2.T, h)  # (v x n) (n x 1)   -> vx1 计算每个单词的得分y_c = self.softmax(u)  # 通过softmax进行归一化,得到每个单词对应的概率return y_c, h, udef backprop(self, e, h, x):''':param e: v x 1:param h: n x 1:param x: v x 1:return:'''dw2 = np.outer(h, e)  # n x v    W′的梯度dw1 = np.outer(x, np.dot(self.w2, e))  # (vx1)  (nxv vx1)->nx1self.w1 -= self.eta * dw1self.w2 -= self.eta * dw2def word2onehot(self, word):word_vec = np.zeros((self.v_count, 1))word_vec[self.word_index[word]] = 1return word_vecdef softmax(self, x):e_x = np.exp(x - np.max(x))return e_x / e_x.sum(axis=0)def word_2_vec(self, word):w_index = self.word_index[word]return self.w1[w_index]def cos_similarity(v1, v2):return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))if __name__ == '__main__':settings = {}settings['n'] = 2  # dimension of word embeddingssettings['window_size'] = 2  # context window +/- center wordsettings['min_count'] = 0  # minimum word countsettings['epochs'] = 5000  # number of training epochssettings['neg_samp'] = 5  # number of negative words to use during trainingsettings['learning_rate'] = 0.1  # learning ratesettings['model'] = 'skipgram'  # cbow or skipgramnp.random.seed(0)  # set the seed for reproducibilitycorpus = [['A', 'dog', 'is', 'running', 'in', 'the', 'room'],['A', 'cat', 'is', 'running', 'in', 'the', 'room']]# corpus = []# corpus = [['natural', 'language', 'processing', 'and', 'machine', 'learning', 'is', 'fun', 'and', 'exciting']]# I like playing football with my friendsw2v = word2vec(settings)# 生成训练数据training_data = w2v.generate_training_data(corpus)# print(training_data)# 训练w2v.train(training_data, debug=True)for w1 in w2v.word_index.keys():for w2 in w2v.word_index.keys():print("%s & %s similarity is %s" % (w1, w2, cos_similarity(w2v.word_2_vec(w1), w2v.word_2_vec(w2))))vecs = np.array([w2v.word_2_vec(vec) for vec in w2v.word_index.keys()])import matplotlib.pyplot as pltplt.scatter(vecs[:, 0], vecs[:, 1])words = list(w2v.word_index.keys())for i, word in enumerate(words):plt.annotate(word, xy=(vecs[i, 0], vecs[i, 1]))plt.show()

至此我们知道了word2vec的原理和代码实现,但训练效率低是它的一个缺点,在下篇文章将会介绍两种优化的方法。

参考

  1. Word2vec from Scratch with Python and NumPy
  2. word2vec Parameter Learning Explained
  3. 自然语言处理与词嵌入

从零实现Word2Vec(上)相关推荐

  1. 打包node服务端_如何基于NodeJS从零构建线上自动化打包工作流?

    前言 NodeJS在前端领域正扮演着越越重要的地位,它不仅可以让前端工作者使用javascript编写后端代码,还能方便地搭建响应速度快.易于扩展的网络应用.Node.js 使用事件驱动,非阻塞I/O ...

  2. 从零在FPG上实现OFDM(一)

    目录 前言 一.OFDM介绍 二.ODFM时域与频域变化 1.时域变化: 1.频域变化: 总结 前言 最近有空学习了一些关于OFDM的类容,并且想准备在FPGA实现,所以准备记录下这一过程,准备从零在 ...

  3. 《实变函数简明教程》,第四章:Lebesgue积分,零测集上的任意非负简单函数Lebesgue可积且积分值为0

    <实变函数简明教程>,第四章:Lebesgue积分,零测集上的任意非负简单函数Lebesgue可积且积分值为0 待分析命题 证明过程 一点注记 待分析命题   设E⊂RnE\subset ...

  4. 《实变函数简明教程》,第四章:Lebesgue积分,零测集上的任意非负实值函数Lebesgue可积且积分值为0

    <实变函数简明教程>,第四章:Lebesgue积分,零测集上的任意非负实值函数Lebesgue可积且积分值为0 待分析命题 证明过程 待分析命题   设E⊂RnE\subset {{\ma ...

  5. 备胎的自我修养 | (2)第二卷 当爱已成往事--01 由零开始(上)

    第二卷 当爱已成往事 往事不要再提,人生已多风雨,纵然记忆抹不去爱与恨都还在心底,真的要断了过去,让明天好好继续,你就不要再苦苦追问我的消息,爱情他是个难题,让人目眩神迷,忘了痛或许可以忘了你却太不容 ...

  6. 绿色创新+绿色质造,群硕OI在“2022国际绿色零碳节”上获奖

    八月末的北京,因零碳节又增添了一抹清凉绿意.8月30日,由数央网.数央公益联合国内众多媒体共同主办的2022国际绿色零碳节暨2022ESG领袖峰会如约而至. 大会上,群硕软件凭借数字化在助力产业绿色低 ...

  7. git常用命令/mac上从零完成本地上传和下载github代码

    安装 mac上安装Xcode很简单,直接苹果商店下载Xcode 上传文件 输入git init 在你新建的文件夹中输入这个命令 git add ***** 输入要上传的文件 git commit -m ...

  8. 从零至壹上贰弃叁得肆之综合渗透贯穿始终

    目录 描述 拓扑图 整体思路 内网渗透思路 内网渗透的思维 内网--渗透思路

  9. 【word2vec】算法原理 公式推导

    前言 近两年来由Mikolov等人提出的word2vec模型和应用引起了社会的极大的关注.使用word2vec模型学习的单词的向量表示已经被证明能够携带语义信息,且在各种NLP任务中都是有用的.越来越 ...

  10. 词嵌入之 Word2Vec

    词嵌入基础 循环神经网络的从零开始实现中使用 one-hot 向量表示单词,虽然它们构造起来很容易,但通常并不是一个好选择. 原因: one-hot 词向量无法准确表达不同词之间的相似度,如我们常常使 ...

最新文章

  1. linux命令--提升
  2. GetLastError()和FormatMessage()
  3. android listview item点击时更改textview的颜色 代码中实现
  4. python删除指定天数前的文件_python 删除指定时间间隔之前的文件实例
  5. c++基础语句代码(循环语句)
  6. 《转》TCP的三次握手与四次挥手(详解+动图)
  7. opencv-api getStructuringElement
  8. python中requests库入门及写入文件
  9. Linux编译安装PHP Mysql Nginx
  10. bzoj 2212 Tree Rotations
  11. 最好的PDF阅读器,Foxit Reader绿色V8.0
  12. 粒子滤波算法理解及实现
  13. 阿铭Linux_公有云学习笔记20190117
  14. excel中查找两列数据中的重复数据
  15. 创建LV报错/dev/vgdata/data: not found: device not cleared Aborting. Failed to wipe start of new LV.
  16. ad走开窗线_奇怪的用户,您不存在。 走开。
  17. 美团机器学习InAction系列—实例详解机器学习如何解决问题
  18. 通用视觉预训练大模型巡礼系列(一):UFO大模型
  19. 红外线探测报警器的简单设计
  20. 【GEE】批量下载全球降水量GPM数据 (NASA)

热门文章

  1. get与post在技术上的区别
  2. 跟我一起认识axure(二)
  3. Python+Selenium开发工具安装及下载
  4. 获取汉字首字母,拼音,可实现拼音字母搜索----npm js-pinyin
  5. SD卡, EMMC固化 ,关于bootloader linux
  6. SharePoint 备忘录(一)
  7. 面向接口的开发到面向对象的编程
  8. perl 安装 ZooKeeper模块
  9. JavaScript从父页面获取子页面的值(子页面又如何访问父页面)
  10. IOS开发学习笔记027-UITableView 使用模型对象