从零实现Word2Vec(上)

引言

本文是对近日学习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模型。

[Simple CBOW模型的图片]

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

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

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

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

W W W V × N V \times N V×N h h h的维度是 N × 1 N \times 1 N×1

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

W V × N = [ w 11 w 12 ⋯ w 1 N w 21 w 22 ⋯ w 2 N ⋮ ⋮ ⋱ ⋮ w V 1 w V 2 ⋯ w V N ] 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

x x x

x = [ x 1 x 2 ⋮ x V ] x = \left[ \begin{matrix} x_1 \\ x_2 \\ \vdots \\ x_V \end{matrix} \right] x=x1x2xV

h = W T x = [ w 11 w 21 ⋯ w k 1 ⋯ w V 1 w 12 w 22 ⋯ w k 2 ⋯ w V 2 ⋮ ⋮ ⋱ ⋮ ⋮ w 1 N w 2 N ⋯ w k N ⋯ w V N ] N × V [ x 1 x 2 ⋮ x k ⋮ x V ] = [ w k 1 w k 2 ⋮ w k N ] 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

W W W的第 i i i行用 v w v_w vw表示,相当于是 w w w的词向量,是 1 × N 1 \times N 1×N的。

W T x W^T x WTx得到 N × 1 N \times 1 N×1的列向量(注意这里是 W W W的转置, W T W^T WT的列对应的就是 W W W的行),相当于是 W W W x k = 1 x_k=1 xk=1对应的那一行。

基本上就是拷贝了 W W W的第 k k k行到 h h h去了。

输入单词 w I w_I wI的向量表示是 v w I v_{w_I} vwI,维度是 N × 1 N \times 1 N×1

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

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

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

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

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

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

p ( w j ∣ w I ) = y j = e x p ( u j ) ∑ j ′ = 1 V e x p ( u j ′ ) (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)

y j y_j yj是输出层第 j j j个单元的输出。把 ( 1 ) (1) (1), ( 2 ) (2) (2)代入到 ( 3 ) (3) (3)得:

p ( w j ∣ w I ) = e x p ( v w j ′ T ⋅ v w I ) ∑ j ′ = 1 V e x p ( v w j ′ ′ T v w I ) (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)

这里要注意的是:

  • 输入单词 x x x和输出单词 y y y都是one-hot向量
  • v w v_w vw v w ′ v^′_w vw是输入单词 w w w的两种表示,分别称为输入向量和输出向量
  • v w v_w vw来自 W W W的行
  • v w ′ v^′_w vw来自 W ′ W^′ W的列

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

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

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

max ⁡ p ( w O ∣ w I ) = max ⁡   y j ∗ = max ⁡   log ⁡   y j ∗ = max ⁡   log ⁡ exp ⁡ ( u j ∗ ) − log ⁡ ∑ j ′ = 1 V e x p ( u j ′ ) = u j ∗ − l o g ∑ j ′ = 1 V e x p ( u j ′ ) : = − 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 -E E,也就是 E = − log ⁡   p ( w O ∣ w I ) E = -\log \, p(w_O|w_I) E=logp(wOwI),因为我们习惯最小化损失函数。

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

下面求 E E E u j u_j uj的偏导,得到了

∂ E ∂ u j = y j − t j : = e j (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时, t j = 1 t_j=1 tj=1,否则 t j = 0 t_j=0 tj=0

下面给出公式推导:

∂ E ∂ u j = − ∂ ( u j ∗ − l o g ∑ j ′ = 1 V e x p ( u j ′ ) ) ∂ u j = − ∂ u j ∗ ∂ u j + ∂ ( log ⁡ ∑ j ′ = 1 V exp ⁡ ( u j ′ ) ) ∂ u j = − t j + e x p ( u j ) ∑ j ′ = 1 V e x p ( u j ) = y j − t j \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 ′ = 1 V exp ⁡ ( u j ′ ) ) ∂ u j \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 ′ = 1 V exp ⁡ ( u j ′ ) f(x)=\sum_{j^{\prime} = 1}^V \exp (u_{j^{\prime}}) f(x)=j=1Vexp(uj)

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

∂ ( e x p ( u 1 ) + e x p ( u 2 ) + ⋯ + e x p ( u j ) + ⋯ + e x p ( u V ) ) ∂ u j = e x p ( u j ) \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)

u j u_j uj看成一个变量,其他 u 1 , u 2 , ⋯ u_1,u_2, \cdots u1,u2,都是与 u j u_j uj无关的,因此求导结果为0。

根据公式 ( 3 ) (3) (3)就可以化简为 y j − t j y_j - t_j yjtj

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

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

来看下 ∂ u j ∂ w i j ′ \frac{\partial u_j}{\partial w^′_{ij}} wijuj

由公式 ( 2 ) (2) (2)知道 u j u_j uj w i j ′ w^′_{ij} wij的关系。 h = v w I = [ h 1 , h 2 , ⋯   , h N ] h=v_{w_I}=[h_1,h_2,\cdots,h_N] h=vwI=[h1,h2,,hN]

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

u j = h 1 ⋅ w 1 j ′ + h 2 ⋅ w 2 j ′ + ⋯ + h i ⋅ w i j ′ + ⋯ + h N ⋅ w N j ′ 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

所以
∂ u j ∂ w i j ′ = h i \frac{\partial u_j}{\partial w^′_{ij}} = h_i wijuj=hi

∂ E ∂ w i j ′ = ∂ E ∂ u j ⋅ ∂ u j ∂ w i j ′ = e j ⋅ h i (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)

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

h i h_i hi是隐藏层的第 i i i个单元, v ′ w j v′_{w_j} vwj是单词 w j w_j wj的输出向量。对每个训练样本都需要做一次复杂度为 V V V的操作去更新 W ′ W^′ W

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

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

∂ E ∂ h i = ∑ j = 1 V ∂ E ∂ u j ⋅ ∂ u j ∂ h i = ∑ j = 1 V e j ⋅ w i j ′ : = E H i \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

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

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

然后我们用链式法则来求 E E E W W W的导数:
∂ E ∂ w k i = ∂ E ∂ h i ⋅ ∂ h i ∂ w k i = E H i ⋅ x k \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

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

这样就得到了一个 V × N V \times N V×N的矩阵,因为 x x x向量中只有一个元素为 1 1 1,其他都为 0 0 0,所以在 ∂ E ∂ W \frac{\partial E}{\partial W} WE的矩阵中,只有一行是非零的。并且这一行的值是 E H T EH^T EHT

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

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

CBOW模型

CBOW模型的图示如下:

[CBOW模型图片]

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

h = 1 C ( x 1 T + x 2 T + ⋯ + x C T ) W = 1 C ( v w 1 + v w 2 + ⋯ + v w C ) 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)

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

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

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

v w I , c = v w I , c − 1 C ⋅ η ⋅ E H T      f o r   c = 1 , 2 , ⋯   , C v_{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

这里每次会更新 W W W中的 C C C行。

Skipgram模型

在这里插入图片描述

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

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

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

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

p ( w c , j ∣ w I ) = y c , j = e x p ( u c , j ) ∑ j ′ = 1 V e x p ( u j ′ ) 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)

需要注意的是,这 C C C个输出是相互独立的。 w c , j w_{c,j} wc,j是第 c c c个panel(输出)中的第 j j j个单词。 w I w_I wI是输入单词。 y c , j y_{c,j} yc,j是第 c c c个输出层中的第 j j j个单元。
u c , j u_{c,j} uc,j是第 c c c个输出的第 j j j个单元的得分。因为这些输出都共享同样的权重,因此
u c , j = u j = v w j ′ T ⋅ h     f o r   c = 1 , 2 , ⋯   , C u_{c,j} = u_j = {v^′_{w_j}}^T \cdot h \,\, \, for \, c = 1,2,\cdots,C uc,j=uj=vwjThforc=1,2,,C

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

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

E = − log ⁡ p ( w O , 1 , w O , 2 , ⋯   , w O , C ∣ w I ) = − log ⁡ ∏ c = 1 C P ( w O , c ∣ w i ) = − log ⁡ ∏ c = 1 C e x p ( u c , j c ∗ ) ∑ j ′ = 1 V e x p ( u j ′ ) = − log ⁡ ∏ c = 1 C e x p ( u c , j c ∗ ) + log ⁡ ∏ c = 1 C ∑ j ′ = 1 V e x p ( u j ′ ) = − ∑ c = 1 C u j c ∗ + log ⁡ ( ∑ j ′ = 1 V e x p ( u j ′ ) ) C = − ∑ c = 1 C u j c ∗ + C ⋅ log ⁡ ∑ j ′ = 1 V e x p ( u j ′ ) \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)

w O , c w_{O,c} wO,c代表第 c c c个输出单词, j c ∗ j^*_c jc表示第 c c c个输出单词的索引。
因为这 C C C个输出是相互独立的,因此 p ( w O , 1 , w O , 2 , ⋯   , w O , C ∣ w I ) = ∏ P ( w O , c ∣ w I ) 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)

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

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

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

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

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

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

∂ E ∂ w i j ′ = ∑ c = 1 C ∂ E ∂ u c , j ⋅ ∂ u c , j ∂ w i j ′ = E I j ⋅ h i \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

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

w i j ′ = w i j ′ − η ⋅ E I j ⋅ h i w^\prime_{ij} = w^\prime_{ij} -\eta \cdot EI_j \cdot h_i wij=wijηEIjhi
或者
v w j ′ = v w j ′ − η ⋅ E I j ⋅ h     f o r   j = 1 , 2 , ⋯   , V v^\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 ∂ h i = ∑ c = 1 C ∑ j = 1 V ∂ E ∂ u c , j ∂ u c , j ∂ h i = ∑ c = 1 C ∑ j = 1 V e c , j ⋅ w i j ′ = ∑ j = 1 V E I j ⋅ w i j ′ : = E H i \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 = E H T \frac{\partial E}{\partial h} = EH^T hE=EHT

由于输入只有一个词, h = v w I T h=v_{w_I}^T h=vwIT,每次也是更新 W W W的一行:

v w I = v w I − η ⋅ E H T v_{w_I} = v_{w_I} - \eta \cdot EH^T vwI=vwIηEHT

简单代码实现

# -*- coding: utf-8 -*-
# @Author  : Jue

from collections import defaultdict

import numpy as np


class 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:skipgram
		self.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

		# 词典大小V
		self.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 n
		self.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_j
				if self.cbow:
					e = y_pred - w_t  # dE/du
				else:
					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) -> nx1
		u = np.dot(self.w2.T, h)  # (v x n) (n x 1)   -> vx1 计算每个单词的得分
		y_c = self.softmax(u)  # 通过softmax进行归一化,得到每个单词对应的概率
		return y_c, h, u

	def 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)->nx1

		self.w1 -= self.eta * dw1
		self.w2 -= self.eta * dw2

	def word2onehot(self, word):
		word_vec = np.zeros((self.v_count, 1))
		word_vec[self.word_index[word]] = 1
		return word_vec

	def 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 embeddings
	settings['window_size'] = 2  # context window +/- center word
	settings['min_count'] = 0  # minimum word count
	settings['epochs'] = 5000  # number of training epochs
	settings['neg_samp'] = 5  # number of negative words to use during training
	settings['learning_rate'] = 0.1  # learning rate
	settings['model'] = 'skipgram'  # cbow or skipgram
	np.random.seed(0)  # set the seed for reproducibility

	corpus = [['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 friends
	w2v = 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 plt

	plt.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. 自然语言处理与词嵌入
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
本课程隶属于自然语言处理(NLP)实战系列。自然语言处理(NLP)是数据科学里的一个分支,它的主要覆盖的内容是:以一种智能与高效的方式,对文本数据进行系统化分析、理解与信息提取的过程。通过使用NLP以及它的组件,我们可以管理非常大块的文本数据,或者执行大量的自动化任务,并且解决各式各样的问题,如自动摘要,机器翻译,命名实体识别,关系提取,情感分析,语音识别,以及主题分割等等。一般情况下一个初级NLP工程师的工资从15万-35万不等,所以掌握NLP技术,对于人工智能学习者来讲是非常关键的一个环节。【超实用课程内容】 课程从自然语言处理的基本概念与基本任务出发,对目前主流的自然语言处理应用进行全面细致的讲解,包括文本分类,文本摘要提取,文本相似度,文本情感分析,文本特征提取等,同时算法方面包括经典算法与深度学习算法的结合,例如LSTM,BiLSTM等,并结合京东电商评论分类、豆瓣电影摘要提取、今日头条舆情挖掘、饿了么情感分析等过个案例,帮助大家熟悉自然语言处理工程师在工作中会接触到的常见应用的实施的基本实施流程,从0-1入门变成自然语言处理研发工程师。 【课程如何观看?】 PC端:https://edu.csdn.net/course/detail/25649 移动端:CSDN 学院APP(注意不是CSDN APP哦)本课程为录播课,课程2年有效观看时长,大家可以抓紧时间学习后一起讨论哦~【学员专享增值服务】 源码开放课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化下载方式:电脑登录https://edu.csdn.net/course/detail/25649,点击右下方课程资料、代码、课件等打包下载通过第二课时下载材料

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值