『论文复现系列』2.word2vec
★★★ 本文源自AI Studio社区精品项目,【点击此处】查看更多精品内容 >>>
Word2Vec
论文 | word2vec Parameter Learning Explained
链接 | https://arxiv.org/abs/1411.2738
作者 | Xin Rong
发布时间 | 2014
1、引言
Mikolov 等人的 word2vec 模型和应用引来极大关注,并且该类单词的向量表示在各种NLP任务中十分有用且被证明具有语义含义,本篇笔记主要目的是:
解释两种Word2Vec模型的参数更新方程:
- 连续词袋模型(continuous bag-of-word CBOW)
- 跳字模型(skip-gram SG)
介绍两种高性能的优化方式:
- 层序softmax(hierarchical softmax)
- 负采样(negative sampling)
2、模型原理讲解
跳字模型

在skip-gram模型中,我们用一个词来预测它在文本序列周围的词,定义中,当前选中的词被称为中心词,邻近的词被称为背景词。例如,给定文本序列"小",“明”,“爱”,“中”,“国” (白板推导串戏)。设置背景窗口大小为2,那么跳字模型所做的,就是在给定"爱",生成它邻近词"小",“明”,“中”,"国"的概率。(此时"爱"是中心词,“小”,“明”,“中”,"国"是背景词),即:
P
(
小
,
明
,
中
,
国
∣
爱
)
P(小,明,中,国\; | \;爱)
P(小,明,中,国∣爱)
假定在给定中心词的条件下,背景词的生成是独立的,那么上式可改写成
P
(
小
∣
爱
)
⋅
P
(
明
∣
爱
)
⋅
P
(
中
∣
爱
)
⋅
P
(
国
∣
爱
)
P(小\;| \;爱) \cdot P(明\;| \;爱) \cdot P(中\;| \;爱) \cdot P(国\;| \;爱)
P(小∣爱)⋅P(明∣爱)⋅P(中∣爱)⋅P(国∣爱)
接下来我们给定一些模型定义:
-
假设词典大小为|V|。
-
词典中的每个词与0 到 |V| - 1的整数一一对应,当成词典索引,构建词典索引集 V = { 0 , 1 , . . . , ∣ V ∣ − 1 } V = \{0,1,...,|V|-1\} V={0,1,...,∣V∣−1}。每一个词对应的那个整数称为该词的索引。
给定一个长度为
T
T
T 的文本序列,
t
t
t 时刻的词为
w
(
t
)
w^{(t)}
w(t)。当背景窗口大小为 m 时,跳字模型需要最大化给定任一中心词生成背景词的概率:
∏
t
=
1
T
∏
−
m
≤
j
≤
m
,
j
≠
0
P
(
w
(
t
+
j
)
∣
w
(
t
)
)
\prod_{t=1}^T \prod_{-m \leq j \leq m, j \neq 0} P\left(w^{(t+j)} \mid w^{(t)}\right)
t=1∏T−m≤j≤m,j=0∏P(w(t+j)∣w(t))
上式得最大似然估计与最小化以下损失函数等价
−
1
T
∑
t
=
1
T
∑
−
m
≤
j
≤
m
,
j
≠
0
log
P
(
w
(
t
+
j
)
∣
w
(
t
)
)
-\frac{1}{T} \sum_{t=1}^T \sum_{-m \leq j \leq m, j \neq 0} \log P\left(w^{(t+j)} \mid w^{(t)}\right)
−T1t=1∑T−m≤j≤m,j=0∑logP(w(t+j)∣w(t))
我们可以用
v
v
v代表中心词的词向量,
u
u
u 代表背景词的词向量。换言之,对于词典中一个索引为
i
i
i 的词,它本身有两个向量
v
i
v_i
vi 和
u
i
u_i
ui 进行表示,在计算的过程中,根据其所处的角色不同,选择不同的词向量。词典中所有词的这两种向量正是跳字模型所需要学习的参数。为了将模型参数植入损失函数,我们需要使用模型参数表达损失函数中的中心词生成背景词的概率。假设中心词的概率是相互独立的。给定中心词
w
c
w_c
wc 在词典中的索引为
c
c
c,背景词
w
0
w_0
w0 在词典中的索引为
o
o
o,损失函数中中心词生成背景词的概率可以使用 softmax 函数进行定义:
P
(
w
o
∣
w
c
)
=
exp
(
u
o
T
v
c
)
∑
i
∈
V
exp
(
u
i
T
v
c
)
P\left(w_o \mid w_c\right)=\frac{\exp \left(\boldsymbol{u}_o^T \boldsymbol{v}_c\right)}{\sum_{i \in V} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)}
P(wo∣wc)=∑i∈Vexp(uiTvc)exp(uoTvc)
当序列长度
T
T
T 较大时,我们通常随机采样一个较小的子序列来计算损失函数并使用 SGD 优化该损失函数。通过求导,我们可以计算出上式生成概率的对数关于中心词向量
v
c
\boldsymbol{v}_c
vc 的梯度为:
∂
log
P
(
w
o
∣
w
c
)
∂
v
c
=
∂
∂
v
c
log
exp
(
u
o
T
v
c
)
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
=
∂
∂
v
c
log
exp
(
u
o
T
v
c
)
⏟
1
−
∂
∂
v
c
log
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
⏟
2
\begin{aligned} \frac{\partial \log P\left(w_o \mid w_c\right)}{\partial \boldsymbol{v}_c} & =\frac{\partial}{\partial \boldsymbol{v}_c} \log \frac{\exp \left(\boldsymbol{u}_o^T \boldsymbol{v}_c\right)}{\sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)} \\ & =\underbrace{\frac{\partial}{\partial \boldsymbol{v}_c} \log \exp \left(\boldsymbol{u}_o^T \boldsymbol{v}_c\right)}_1-\underbrace{\frac{\partial}{\partial \boldsymbol{v}_c} \log \sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)}_2 \end{aligned}
∂vc∂logP(wo∣wc)=∂vc∂log∑i=1∣V∣exp(uiTvc)exp(uoTvc)=1
∂vc∂logexp(uoTvc)−2
∂vc∂logi=1∑∣V∣exp(uiTvc)
第一部分推导
∂
∂
v
c
log
exp
(
u
o
T
v
c
)
=
∂
∂
v
c
u
o
T
v
c
=
u
o
\frac{\partial}{\partial \boldsymbol{v}_c} \textcolor{Red}{\log \exp \left(\boldsymbol{u}_o^T \boldsymbol{v}_c\right)} =\frac{\partial}{\partial \boldsymbol{v}_c} \textcolor{Red}{\boldsymbol{u}_o^T \boldsymbol{v}_c}=\boldsymbol{u}_{\mathbf{o}}
∂vc∂logexp(uoTvc)=∂vc∂uoTvc=uo
第二部分推导
∂
∂
v
c
log
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
=
1
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
⋅
∂
∂
v
c
∑
x
=
1
∣
V
∣
exp
(
u
x
T
v
c
)
=
1
A
⋅
∑
x
=
1
∣
V
∣
∂
∂
v
c
exp
(
u
x
T
v
c
)
=
1
A
⋅
∑
x
=
1
∣
V
∣
exp
(
u
x
T
v
c
)
∂
∂
v
c
u
x
T
v
c
=
1
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
∑
x
=
1
∣
V
∣
exp
(
u
x
T
v
c
)
u
x
=
∑
x
=
1
∣
V
∣
exp
(
u
x
T
v
c
)
∑
i
=
1
∣
V
∣
exp
(
u
i
T
v
c
)
u
x
=
∑
x
=
1
∣
V
∣
P
(
w
x
∣
w
c
)
u
x
\begin{aligned} \frac{\partial}{\partial \boldsymbol{v}_c} \log \sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right) & =\frac{1}{\sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)} \cdot \textcolor{Red}{ \frac{\partial}{\partial \boldsymbol{v}_c} \sum_{x=1}^{|V|} \exp \left(\boldsymbol{u}_x^T \boldsymbol{v}_c\right)} \\ & =\frac{1}{A} \cdot \sum_{x=1}^{|V|} \textcolor{red}{\frac{\partial}{\partial \boldsymbol{v}_c} \exp \left(\boldsymbol{u}_x^T \boldsymbol{v}_c\right)} \\ & =\frac{1}{A} \cdot \sum_{x=1}^{|V|} \exp \left(\boldsymbol{u}_x^T \boldsymbol{v}_c\right) \textcolor{red}{\frac{\partial}{\partial \boldsymbol{v}_c} \boldsymbol{u}_x^T \boldsymbol{v}_c} \\ & =\frac{1}{\sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)} \sum_{x=1}^{|V|} \exp \left(\boldsymbol{u}_x^T \boldsymbol{v}_c\right) \textcolor{red}{\boldsymbol{u}_x} \\ & =\sum_{x=1}^{|V|} \textcolor{red}{\frac{\exp \left(\boldsymbol{u}_x^T \boldsymbol{v}_c\right)}{\sum_{i=1}^{|V|} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)}} \boldsymbol{u}_x \\ & =\sum_{x=1}^{|V|} \textcolor{red}{P\left(w_x \mid w_c\right)} \boldsymbol{u}_x \end{aligned}
∂vc∂logi=1∑∣V∣exp(uiTvc)=∑i=1∣V∣exp(uiTvc)1⋅∂vc∂x=1∑∣V∣exp(uxTvc)=A1⋅x=1∑∣V∣∂vc∂exp(uxTvc)=A1⋅x=1∑∣V∣exp(uxTvc)∂vc∂uxTvc=∑i=1∣V∣exp(uiTvc)1x=1∑∣V∣exp(uxTvc)ux=x=1∑∣V∣∑i=1∣V∣exp(uiTvc)exp(uxTvc)ux=x=1∑∣V∣P(wx∣wc)ux
综上所述
∂
log
P
(
w
o
∣
w
c
)
∂
v
c
=
u
o
−
∑
j
∈
V
P
(
w
j
∣
w
c
)
u
j
\frac{\partial \log P\left(w_o \mid w_c\right)}{\partial \boldsymbol{v}_c}=\boldsymbol{u}_o-\sum_{j \in V} P\left(w_j \mid w_c\right) \boldsymbol{u}_j
∂vc∂logP(wo∣wc)=uo−j∈V∑P(wj∣wc)uj
通过上面计算得到梯度后,我们可以使用随机梯度下降来不断迭代模型参数
v
c
\boldsymbol{v}_c
vc 。其它模型参数
u
o
\boldsymbol{u}_o
uo 的迭代方式同理可得。最终,对于词典中任一索引为
i
i
i 的词,我们均得到该词作为中心词和背景词的两组词向量
v
i
\boldsymbol{v}_i
vi 和
u
i
\boldsymbol{u}_i
ui
时间复杂度Q = C × ( D + D × log 2 ( V )), 其中C为window size,V为词典大小,D为每个词设置的维度。通过反向传播优化,每次更新当前的词向量。可见,相比NNLM,word2vec主要是减少了hidden layer,并且引入log-linear classifier(也就是hierarchical softmax),大大减少了训练的计算量。
连续词袋模型

连续词袋模型与跳字模型类似。与跳字模型最大的不同是,连续词袋模型是用一个中心词在文本序列周围的词来预测中心词。简单的说就是,跳字模型是用中心词预测周围的词;连续词袋模型是用周围的词预测中心词。例如,给定文本 “小”,“明”,“爱”,“中”,“国”,连续词袋模型所关心的是,邻近词 “小”,“明”,“中”,“国” 一起生成中心词 “爱” 的概率
连续词袋模型需要最大化由背景词生成任一中心词的概率:
∏
t
=
1
T
P
(
w
(
t
)
∣
w
(
t
−
m
)
,
…
,
w
(
t
−
1
)
,
w
(
t
+
1
)
,
…
,
w
(
t
+
m
)
)
\prod_{t=1}^T P\left(w^{(t)} \mid w^{(t-m)}, \ldots, w^{(t-1)}, w^{(t+1)}, \ldots, w^{(t+m)}\right)
t=1∏TP(w(t)∣w(t−m),…,w(t−1),w(t+1),…,w(t+m))
上式得最大似然估计与最小化以下损失函数等价
−
∑
t
=
1
T
log
P
(
w
(
t
)
∣
w
(
t
−
m
)
,
…
,
w
(
t
−
1
)
,
w
(
t
+
1
)
,
…
,
w
(
t
+
m
)
)
-\sum_{t=1}^T \log P\left(w^{(t)} \mid w^{(t-m)}, \ldots, w^{(t-1)}, w^{(t+1)}, \ldots, w^{(t+m)}\right)
−t=1∑TlogP(w(t)∣w(t−m),…,w(t−1),w(t+1),…,w(t+m))
我们可以用
v
\boldsymbol{v}
v 和
u
\boldsymbol{u}
u 分别代表背景词和中心词的向量(注意符号和跳字模型相反,这能确保公式相似)。给定中心词
w
c
w_c
wc 在词典中的索引为
c
c
c ,背景词
w
o
1
,
…
,
w
o
2
m
w_{o_1}, \ldots, w_{o_{2 m}}
wo1,…,wo2m 在词典中的索引为
o
1
,
…
,
o
2
m
o_1, \ldots, o_{2 m}
o1,…,o2m ,损失函数中的背景词生成中心词的概率可以使用 softmax 函数定义为
P
(
w
c
∣
w
o
1
,
…
,
w
o
2
m
)
=
exp
[
u
c
T
(
v
o
1
+
…
+
v
o
2
m
)
/
(
2
m
)
]
∑
j
∈
V
exp
[
u
j
T
(
v
o
1
+
…
+
v
o
2
m
)
/
(
2
m
)
]
P\left(w_c \mid w_{o_1}, \ldots, w_{o_{2 m}}\right)=\frac{\exp \left[\boldsymbol{u}_c^T\left(\boldsymbol{v}_{o_1}+\ldots+\boldsymbol{v}_{o_{2 m}}\right) /(2 m)\right]}{\sum_{j \in V} \exp \left[\boldsymbol{u}_j^T\left(\boldsymbol{v}_{o_1}+\ldots+\boldsymbol{v}_{o_{2 m}}\right) /(2 m)\right]}
P(wc∣wo1,…,wo2m)=∑j∈Vexp[ujT(vo1+…+vo2m)/(2m)]exp[ucT(vo1+…+vo2m)/(2m)]
同样,当序列长度
T
T
T 较大时,我们通常随机采样一个较小的子序列来计算损失函数,并使用随机梯度下降优化该损失函数,通过微分,我们可以计算出上式生成概率的对数关于任一背景词向量
v
o
i
(
i
=
1
,
…
,
2
m
)
\boldsymbol{v}_{o_i}(i=1, \ldots, 2 m)
voi(i=1,…,2m) 的梯度为:
∂
log
P
(
w
c
∣
w
o
1
,
…
,
w
o
2
m
)
∂
v
o
i
=
1
2
m
(
u
c
−
∑
j
∈
V
exp
(
u
j
T
v
c
)
∑
i
∈
V
exp
(
u
i
T
v
c
)
u
j
)
\frac{\partial \log P\left(w_c \mid w_{o_1}, \ldots, w_{o_{2 m}}\right)}{\partial \boldsymbol{v}_{o_i}}=\frac{1}{2 m}\left(\boldsymbol{u}_c-\sum_{j \in V} \frac{\exp \left(\boldsymbol{u}_j^T \boldsymbol{v}_c\right)}{\sum_{i \in V} \exp \left(\boldsymbol{u}_i^T \boldsymbol{v}_c\right)} \boldsymbol{u}_j\right)
∂voi∂logP(wc∣wo1,…,wo2m)=2m1
uc−j∈V∑∑i∈Vexp(uiTvc)exp(ujTvc)uj
而上式与下式等价:
∂
log
P
(
w
c
∣
w
o
1
,
…
,
w
o
2
m
)
∂
v
o
i
=
1
2
m
(
u
c
−
∑
j
∈
V
P
(
w
j
∣
w
c
)
u
j
)
\frac{\partial \log P\left(w_c \mid w_{o_1}, \ldots, w_{o_{2 m}}\right)}{\partial \boldsymbol{v}_{o_i}}=\frac{1}{2 m}\left(\boldsymbol{u}_c-\sum_{j \in V} P\left(w_j \mid w_c\right) \boldsymbol{u}_j\right)
∂voi∂logP(wc∣wo1,…,wo2m)=2m1
uc−j∈V∑P(wj∣wc)uj
3、优化方法
可以看到,无论是跳字模型还是连续词袋模型,每一步梯度计算的开销与词典 V V V 的大小呈正相关。显然,当词典较大时,这种训练方法的计算开销会很大。所以使用上述训练方法在实际中是由难度的。我们可以使用近似的方法来计算这些梯度,从而减小计算开销。常用的近似训练法包括负采样和层序 softmax
负采样
其本质是反向传播训练时只更新一部分权重,降低计算量。
以跳字模型为例讨论负采样。词典
V
V
V 的大小之所以会在目标函数中出现,是因为中心词
w
c
w_c
wc 生成背景词
w
o
w_o
wo 的概率
P
(
w
o
∣
w
c
)
P\left(w_o \mid w_c\right)
P(wo∣wc) 使用了 softmax,而 softmax 考虑到了背景词可能是词典中任意词,并体现在了 softmax 的分母上
我们不妨换个角度,假设中心词
w
c
w_c
wc 生成背景词
w
o
w_o
wo 由以下两个互相独立的联合事件组成来近似:
- 中心词 w c w_c wc 和背景词 w o w_o wo 同时出现在该训练数据窗口
- 中心词
w
c
w_c
wc 和噪声词不同时出现在该训练数据窗口
- 中心词 w c w_c wc 和第 1 个噪声词 w 1 w_1 w1 不同时出现在训练数据窗口 (噪声词 w 1 w_1 w1 按噪声词分布 P ( w ) P(w) P(w) 随机生成)
- …
- 中心词 w c w_c wc 和第 K K K 个噪声词 w k w_k wk 不同时出现在训练数据窗口(噪声词 w K w_K wK 按噪声词分布 P ( w ) P(w) P(w) 随机生成)
我们可以使用
σ
(
x
)
=
1
1
+
exp
(
−
x
)
\sigma(x)=\frac{1}{1+\exp (-x)}
σ(x)=1+exp(−x)1 函数来表达中心词
w
c
w_c
wc 和背景词
w
o
w_o
wo 同时出现在训练数据窗口的概率:
P
(
D
=
1
∣
w
o
,
w
c
)
=
σ
(
u
o
T
,
v
c
)
P\left(D=1 \mid w_o, w_c\right)=\sigma\left(\boldsymbol{u}_o^T, \boldsymbol{v}_c\right)
P(D=1∣wo,wc)=σ(uoT,vc)
那么,中心词
w
c
w_c
wc 生成背景词
w
o
w_o
wo 的对数概率可以近似为
log
P
(
w
o
∣
w
c
)
=
log
[
P
(
D
=
1
∣
w
o
,
w
c
)
∏
k
=
1
,
w
k
∼
P
(
w
)
K
P
(
D
=
0
∣
w
k
,
w
c
)
]
\log P\left(w_o \mid w_c\right)=\log \left[P\left(D=1 \mid w_o, w_c\right) \prod_{k=1, w_k \sim P(w)}^K P\left(D=0 \mid w_k, w_c\right)\right]
logP(wo∣wc)=log
P(D=1∣wo,wc)k=1,wk∼P(w)∏KP(D=0∣wk,wc)
假设噪声词
w
k
w_k
wk 在词典中的索引为
i
k
i_k
ik , 上式可改写为
log
P
(
w
o
∣
w
c
)
=
log
1
1
+
exp
(
−
u
o
T
v
c
)
+
∑
k
=
1
,
w
k
∼
P
(
w
)
K
log
[
1
−
1
1
+
exp
(
−
u
i
k
T
v
c
)
]
\log P\left(w_o \mid w_c\right)=\log \frac{1}{1+\exp \left(-\boldsymbol{u}_o^T \boldsymbol{v}_c\right)}+\sum_{k=1, w_k \sim P(w)}^K \log \left[1-\frac{1}{1+\exp \left(-\boldsymbol{u}_{i_k}^T \boldsymbol{v}_c\right)}\right]
logP(wo∣wc)=log1+exp(−uoTvc)1+k=1,wk∼P(w)∑Klog[1−1+exp(−uikTvc)1]
因此,有关中心词
w
c
w_c
wc 生成背景词
w
o
w_o
wo 的损失函数是
−
log
P
(
w
o
∣
w
c
)
=
−
log
1
1
+
exp
(
−
u
o
T
v
c
)
−
∑
k
=
1
,
w
k
∼
P
(
w
)
K
log
1
1
+
exp
(
u
i
k
T
v
c
)
-\log P\left(w_o \mid w_c\right)=-\log \frac{1}{1+\exp \left(-\boldsymbol{u}_o^T \boldsymbol{v}_c\right)}-\sum_{k=1, w_k \sim P(w)}^K \log \frac{1}{1+\exp \left(\boldsymbol{u}_{i_k}^T \boldsymbol{v}_c\right)}
−logP(wo∣wc)=−log1+exp(−uoTvc)1−k=1,wk∼P(w)∑Klog1+exp(uikTvc)1
现在,训练中每一步的梯度计算开销不再与词典大小相关,而与
K
K
K 线性相关。当
K
K
K 取较小的常数时, 负采样的每一步梯度计算开销也较小。
同理,也可以对连续词袋模型进行负采样。有关背景词
w
(
t
−
m
)
,
…
,
w
(
t
−
1
)
,
w
(
t
+
1
)
,
…
,
w
(
t
+
m
)
w^{(t-m)}, \ldots, w^{(t-1)}, w^{(t+1)}, \ldots, w^{(t+m)}
w(t−m),…,w(t−1),w(t+1),…,w(t+m)
生成中心词
w
c
w_c
wc 的损失函数
−
log
P
(
w
(
t
)
∣
w
(
t
−
m
)
,
…
,
w
(
t
−
1
)
,
w
(
t
+
1
)
,
…
,
w
(
t
+
m
)
)
-\log P\left(w^{(t)} \mid w^{(t-m)}, \ldots, w^{(t-1)}, w^{(t+1)}, \ldots, w^{(t+m)}\right)
−logP(w(t)∣w(t−m),…,w(t−1),w(t+1),…,w(t+m))
在负采样中可以近似为
−
log
1
1
+
exp
[
−
u
c
T
(
v
o
1
+
…
+
v
o
2
m
)
/
(
2
m
)
]
−
∑
k
=
1
,
w
k
∼
P
(
w
)
K
log
1
1
+
exp
[
u
i
k
T
(
v
o
1
+
…
+
v
o
2
m
)
/
(
2
m
)
]
-\log \frac{1}{1+\exp \left[-\boldsymbol{u}_c^T\left(\boldsymbol{v}_{o_1}+\ldots+\boldsymbol{v}_{o_{2 m}}\right) /(2 m)\right]}-\sum_{k=1, w_k \sim P(w)}^K \log \frac{1}{1+\exp \left[\boldsymbol{u}_{i_k}^T\left(\boldsymbol{v}_{o_1}+\ldots+\boldsymbol{v}_{o_{2 m}}\right)\right./(2m)]}
−log1+exp[−ucT(vo1+…+vo2m)/(2m)]1−k=1,wk∼P(w)∑Klog1+exp[uikT(vo1+…+vo2m)/(2m)]1
层序 softmax
首先,不要被名字误解,我个人认为这个和softmax没任何关系。
层序 softmax 利用了二叉树。树的每个叶子节点代表着词典 V V V 中的每个词。每个词 w i w_i wi 对应的词向量为 v i v_i vi。我们以下图为例,来描述层序 softmax 的工作机制

设
L
(
w
)
L(w)
L(w) 为从二叉树根节点到代表词
w
w
w 的叶子节点的路径上的节点数,并设
n
(
w
,
i
)
n(w, i)
n(w,i) 为该路径上第
i
i
i 个节点,该节点的向量为
u
n
(
w
,
j
)
\boldsymbol{u}_{n(w, j)}
un(w,j) 。以上图为例,
L
(
w
3
)
=
4
L\left(w_3\right)=4
L(w3)=4 。那么,跳字模型和连续词袋模型所需要计算的任意词
w
i
w_i
wi 生成词
w
w
w 的概率为:
P
(
w
∣
w
i
)
=
∏
j
=
1
L
(
w
)
−
1
σ
(
[
n
(
w
,
j
+
1
)
=
left
child
(
n
(
w
,
j
)
)
]
⋅
u
n
(
w
,
j
)
T
v
i
)
P\left(w \mid w_i\right)=\prod_{j=1}^{L(w)-1} \sigma\left([n(w, j+1)=\operatorname{left} \operatorname{child}(n(w, j))] \cdot \boldsymbol{u}_{n(w, j)}^T \boldsymbol{v}_i\right)
P(w∣wi)=j=1∏L(w)−1σ([n(w,j+1)=leftchild(n(w,j))]⋅un(w,j)Tvi)
其中,如果
x
x
x 为真,
[
x
]
=
1
[x]=1
[x]=1 ;反之
[
x
]
=
−
1
[x]=-1
[x]=−1
由于
σ
(
x
)
+
σ
(
−
x
)
=
1
,
w
i
\sigma(x)+\sigma(-x)=1 , w_i
σ(x)+σ(−x)=1,wi 生成词典中任何词的概率之和为 1 :
∑
w
=
1
V
P
(
w
∣
w
i
)
=
1
\sum_{w=1}^V P\left(w \mid w_i\right)=1
w=1∑VP(w∣wi)=1
上面公式可能比较抽象,下面举个具体的例子,计算
w
i
w_i
wi 生成
w
3
w_3
w3 的概率,由于在二叉树中由根到
w
3
w_3
w3 的路径需要向左、向右、再向左地遍历,所以得到
P
(
w
3
∣
w
i
)
=
σ
(
u
n
(
w
3
,
1
)
T
v
i
)
⋅
σ
(
−
u
n
(
w
3
,
2
)
T
v
i
)
⋅
σ
(
u
n
(
w
3
,
3
)
T
v
i
)
P\left(w_3 \mid w_i\right)=\sigma\left(\boldsymbol{u}_{n\left(w_3, 1\right)}^T \boldsymbol{v}_i\right) \cdot \sigma\left(-\boldsymbol{u}_{n\left(w_3, 2\right)}^T \boldsymbol{v}_i\right) \cdot \sigma\left(\boldsymbol{u}_{n\left(w_3, 3\right)}^T \boldsymbol{v}_i\right)
P(w3∣wi)=σ(un(w3,1)Tvi)⋅σ(−un(w3,2)Tvi)⋅σ(un(w3,3)Tvi)
由此,我们就可以使用随机梯度下降在跳字模型和连续词袋模型中不断迭代计算词典中所有词向量
v
\boldsymbol{v}
v 和 非叶子节点的向量
u
\boldsymbol{u}
u 。每次迭代的计算开销由
O
(
∣
V
∣
)
O(|V|)
O(∣V∣) 降为二叉树的高度
O
(
log
∣
V
∣
)
O(\log |V|)
O(log∣V∣),即为树的高度。
最后一个问题,层序 softmax 的二叉树是如何建立的?
这里的二叉树是Huffman 树,权重是语料库中 word 出现的频率。
简单说明下霍夫曼树的构建方法,计算出词典中所有词的词频作为节点的权重,首先将权重最小的两个节点合并,父节点的权重为两个叶子节点的值之和,之后再在剩下的节点(之前合并的两个节点不在这里考虑中,用他们合并的父节点代替)里选最小的两个重复以上操作,当直到只有一个节点停止迭代。
这样做的好处是,词频高的词会离根节点近,模型实际计算的时候大大减少运算量。
4、代码实现
基于Skip-gram的word2vec
仅乞丐版实现QWQ,理解思想最重要
import paddle
import numpy as np
import paddle.optimizer as optimizer
import paddle.io as Data
import paddle.nn as nn
# 初始化简易句子
sentences = ["panda like one", "monkey hate three", "horse undrink milk", "He is tall and strong",
"She sings beautifully", "movie was thrilling", " weather is warm and sunny", "music is upbeat"]
sentence_list = " ".join(sentences).split() # ['jack', 'like', 'dog', 'jack', 'like', 'cat', 'animal',...]
vocab = list(set(sentence_list))
word2idx = {w:i for i, w in enumerate(vocab)}
vocab_size = len(vocab)
word2idx
{'panda': 0,
'He': 1,
'hate': 2,
'and': 3,
'undrink': 4,
'tall': 5,
'music': 6,
'warm': 7,
'beautifully': 8,
'three': 9,
'horse': 10,
'was': 11,
'strong': 12,
'sings': 13,
'weather': 14,
'movie': 15,
'sunny': 16,
'She': 17,
'thrilling': 18,
'milk': 19,
'upbeat': 20,
'one': 21,
'like': 22,
'is': 23,
'monkey': 24}
# model parameters
C = 2 # window size
batch_size = 8
m = 2 # word embedding dim
skip_grams = []
for idx in range(C, len(sentence_list) - C):
center = word2idx[sentence_list[idx]]
context_idx = list(range(idx - C, idx)) + list(range(idx + 1, idx + C + 1))
context = [word2idx[sentence_list[i]] for i in context_idx]
for w in context:
skip_grams.append([center, w])
len(skip_grams)
96
def make_data(skip_grams):
input_data = []
output_data = []
for a, b in skip_grams:
input_data.append(np.eye(vocab_size)[a])
output_data.append(b)
return input_data, output_data
input_data, output_data = make_data(skip_grams)
input_data, output_data = paddle.to_tensor(input_data), paddle.to_tensor(output_data)
# TensorDataset 可以用来对 tensor 进行打包,就好像 python 中的 zip 功能。该类通过每一个 tensor 的第一个维度进行索引。
dataset = Data.TensorDataset([input_data, output_data])
loader = Data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
def createParameter(dim1, dim2):
x = paddle.randn([dim1, dim2], dtype="float32")
param = paddle.create_parameter(shape=x.shape,
dtype=str(x.numpy().dtype),
default_initializer=paddle.nn.initializer.Assign(x))
param.stop_gradient = True
return param
class Word2Vec(nn.Layer):
def __init__(self) -> None:
super(Word2Vec, self).__init__()
self.W = createParameter(vocab_size, m)
self.V = createParameter(m, vocab_size)
def forward(self, X):
# X : [batch_size, vocab_size]
hidden = paddle.mm(X, self.W) # [batch_size, m]
output = paddle.mm(hidden, self.V) # [batch_size, vocab_size]
return output
# 定义损失函数和优化器
model = Word2Vec()
loss_fn = nn.CrossEntropyLoss()
optim = optimizer.Adam(parameters=model.parameters(), learning_rate=1e-3)
W0202 14:53:18.384579 322 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W0202 14:53:18.388543 322 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
# 训练
for epoch in range(10000):
for i, (batch_x, batch_y) in enumerate(loader):
batch_x = batch_x
batch_y = batch_y
pred = model(batch_x)
loss = loss_fn(pred, batch_y)
if (epoch + 1) % 5000 == 0:
print(epoch + 1, i, loss.item())
optim.clear_grad()
loss.backward()
optim.step()
5000 0 5.13233757019043
5000 1 4.557521343231201
5000 2 4.677734375
5000 3 5.151454448699951
5000 4 4.975801944732666
5000 5 4.329399108886719
5000 6 3.4836997985839844
5000 7 4.278501987457275
5000 8 4.9019927978515625
5000 9 4.992636680603027
5000 10 4.900193214416504
5000 11 3.9632203578948975
10000 0 5.36308479309082
10000 1 3.973039150238037
10000 2 4.780693054199219
10000 3 4.952635765075684
10000 4 4.985872268676758
10000 5 4.532427787780762
10000 6 4.118497848510742
10000 7 4.413473129272461
10000 8 4.123109817504883
10000 9 4.427910804748535
10000 10 4.218624591827393
10000 11 5.4551239013671875
000 11 5.4551239013671875
# 由于定义的tensor为二维,故可以展示最后的结果在图上
import matplotlib.pyplot as plt
for i, label in enumerate(vocab):
W, WT = model.parameters()
x,y = float(W[i][0]), float(W[i][1])
plt.scatter(x, y)
plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')
plt
<module 'matplotlib.pyplot' from '/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/pyplot.py'>

参考资料
更多推荐



所有评论(0)