对中文进行NLP,首先要对要处理的文本进行分词,再对每一个词建立向量空间,即word2Vec。
简单的词向量-one-hot vector
最简单的词向量可采用one-hot vector,对每个词用单位向量来进行表示。比如对所有的词
w
1
w_1
w1,
w
2
w_2
w2, …
w
∣
V
∣
∈
V
w_{|V|} \in V
w∣V∣∈V,对其中的词
w
i
w_i
wi,表示的词向量为
(
0
,
0
,
.
.
.
,
1
,
0
,
.
.
.
0
)
T
∈
{
0
,
1
}
∣
V
∣
(0,0,...,1,0,...0)^T \in \{0,1\}^{|V|}
(0,0,...,1,0,...0)T∈{0,1}∣V∣,其中向量在第
i
i
i个位置为1,其它为0。
这种词向量会导致建立的维度太大,矩阵过于稀疏,现在用的不多了。
基于频度的共生矩阵的方法
我们可以先建立一个 R ∣ V ∣ × ∣ V ∣ R^{|V| \times |V|} R∣V∣×∣V∣的矩阵。比如以下三句话:
- 我 热爱 祖国
- 我 热爱 人民
以每句话中的每一个词,作为中心词,然后看与中心词相邻的词一起出现了几次。假设我们的训练集只有这三句话,也就是 { 我 , 热 爱 , 祖 国 , 人 民 } = V \{我,热爱,祖国,人民\} = V {我,热爱,祖国,人民}=V;同时假定我们只看相邻距离为1的词。比如我+热爱,出现了2次;我+祖国出现了1次;我+人民出现了1次等等。这样建立的矩阵就为
我 热 爱 祖 国 人 民 X = ( 0 2 0 0 2 0 1 1 0 1 0 0 0 1 0 0 ) 我 热 爱 祖 国 人 民 \begin{aligned} &\begin{matrix} 我&热爱&祖国&人民 \end{matrix} \\ X=&\begin{pmatrix} 0 & 2 & 0 & 0 \\ 2 & 0 & 1 & 1 \\ 0 & 1 & 0 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix} \begin{matrix} 我\\热爱\\祖国\\人民 \end{matrix} \end{aligned} X=我热爱祖国人民⎝⎜⎜⎛0200201101000100⎠⎟⎟⎞我热爱祖国人民
再对该矩阵进行奇异值分解(SVD),即 X = U Σ V T X=U\Sigma V^T X=UΣVT,其中矩阵 U , Σ , V ∈ R ∣ V ∣ × ∣ V ∣ U , \Sigma, V\in R^{|V| \times |V|} U,Σ,V∈R∣V∣×∣V∣, U = ( u 1 , u 2 , . . . , u ∣ V ∣ ) U=(u_1,u_2, ..., u_{|V|}) U=(u1,u2,...,u∣V∣), Σ = d i a g ( σ 1 , σ 2 , . . . , σ ∣ V ∣ ) \Sigma=diag(\sigma_1,\sigma_2,...,\sigma_{|V|}) Σ=diag(σ1,σ2,...,σ∣V∣), V = ( v 1 , . . . , v ∣ V ∣ ) V=(v_1,...,v_{|V|}) V=(v1,...,v∣V∣),其中 u i u_i ui, v i v_i vi均为 ∣ V ∣ |V| ∣V∣维的向量。
然后再进行降维至 K K K维,即 U K = ( u 1 , u 2 , . . , u K ) U_K=(u_1,u_2,..,u_K) UK=(u1,u2,..,uK), V K = ( v 1 , v 2 , . . , v K ) V_K=(v_1, v_2, .., v_K) VK=(v1,v2,..,vK), Σ K = ( σ 1 , . . . , σ k ) \Sigma_K=(\sigma_1,...,\sigma_k) ΣK=(σ1,...,σk),这样 X K = U K Σ V K T X_K=U_K\Sigma V_K^T XK=UKΣVKT,即为 ∣ V ∣ × K |V| \times K ∣V∣×K的矩阵,矩阵 X K X_K XK的第 i i i行可认为是单词 i i i对应的词向量 x i T x_i^T xiT。
注意到对 X X X进行SVD,计算复杂度可高达 O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3),因此现在用的也少。
基于Word2Vec的方法
CBOW法
将每一句话的每一个单词看作是一个中心词,和中心词相邻的距离为 m m m的词对应词为上下文词。CBOW法是已知上下文词,推测中心词为哪个词。比如假定 m = 1 m=1 m=1,看以下这句话
- 我 明天 就 回家 了
比如对中心词“就”,和它相邻距离为1的词为"明天"和“回家”。CBOW的中心思想是,已知"明天"和“回家”,求所有的词向量,使得"就“出现的概率最大。
假定我们就考虑
{
我
,
明
天
,
就
,
回
家
,
了
}
=
V
\{我,明天,就,回家,了\}=V
{我,明天,就,回家,了}=V,可以建立一个只有一层隐藏层(两层全连接层)的神经网络。神经网络的输入、输出均采用one-hot vector编码,即对”我“编码为
(
1
,
0
,
0
,
0
,
0
)
T
(1,0,0,0,0)^T
(1,0,0,0,0)T,明天编码为
(
0
,
1
,
0
,
0
,
0
)
T
(0,1,0,0,0)^T
(0,1,0,0,0)T,依次类推。那么神经网络的结构可以表示为
其中
K
K
K为我们需要计算的词向量的长度。隐藏层之前的
W
∣
V
∣
×
K
W_{|V| \times K}
W∣V∣×K的weight matrix定义为我们想要的输入词矩阵,其中矩阵的每一行对应一个单词的输入词向量
w
i
T
w_i^T
wiT,隐藏层之后的
U
K
×
∣
V
∣
U_{K \times |V|}
UK×∣V∣定义为我们想要的输出词矩阵,其中矩阵的每一列对应一个单词的输出词向量
u
i
u_i
ui。
很明显,这个并不是一个标准的神经网络,因为隐藏层的输出并没有激活,而是对输出的结果各行求了平均。输入到隐藏层的矩阵为
W
~
2
m
∗
K
\tilde{W}_{2m*K}
W~2m∗K,其中
W
~
\tilde{W}
W~的每一行正好对应的是输入的上下文词的输入词向量,即
(
w
j
c
−
m
,
.
.
.
,
w
j
c
−
1
,
w
j
c
+
1
,
.
.
.
,
w
j
c
+
m
)
T
(w_{j_{c-m}},...,w_{j_{c-1}},w_{j_{c+1}},...,w_{j_{c+m}})^T
(wjc−m,...,wjc−1,wjc+1,...,wjc+m)T,求完平均后矩阵为
w
c
T
ˉ
=
1
2
m
∑
i
=
c
−
m
i
=
c
+
m
w
j
i
T
\bar{w_c^T }= \frac{1}{2m}\sum_{i=c-m}^{i=c+m}w_{j_i}^T
wcTˉ=2m1i=c−m∑i=c+mwjiT
其中,
w
c
T
ˉ
\bar{w_c^T}
wcTˉ为
K
K
K维向量。对数据求
s
o
f
t
m
a
x
softmax
softmax,即
o
u
t
p
u
t
=
s
o
f
t
m
a
x
(
w
c
T
ˉ
u
1
,
.
.
.
,
w
c
T
ˉ
u
∣
V
∣
)
=
(
y
^
1
,
.
.
.
,
y
^
∣
V
∣
)
\begin{aligned} output &= softmax(\bar{w_c^T}u_1, ..., \bar{w_c^T}u_{|V|}) \\ &= (\hat{y}_1,...,\hat{y}_{|V|}) \end{aligned}
output=softmax(wcTˉu1,...,wcTˉu∣V∣)=(y^1,...,y^∣V∣)
训练集里,输出应为
(
y
1
,
.
.
.
,
y
∣
V
∣
)
(y_1, ..., y_{|V|})
(y1,...,y∣V∣),定义输出的损失函数为
L
=
−
∑
i
y
i
log
y
^
i
L=-\sum_i y_i \log{\hat{y}_i}
L=−i∑yilogy^i
注意到
(
y
1
,
.
.
.
,
y
∣
V
∣
)
(y_1, ..., y_{|V|})
(y1,...,y∣V∣)采用的是one-hot vector编码,所以对于输入的中心词
c
c
c,其对应的
y
c
=
1
y_c=1
yc=1,同时
y
i
=
0
,
∀
i
≠
c
y_i=0, \forall i \neq c
yi=0,∀i=c。这样损失函数可以简化为
L
=
−
log
y
^
c
=
−
w
c
T
ˉ
u
c
+
log
∑
i
=
1
∣
V
∣
exp
w
c
T
ˉ
u
i
\begin{aligned} L&=-\log{\hat{y}_c} \\ &=-\bar{w_c^T}u_c+\log{\sum_{i=1}^{|V|}{ \exp{\bar{w_c^T}u_i } }} \end{aligned}
L=−logy^c=−wcTˉuc+logi=1∑∣V∣expwcTˉui
Skip-Gram法
Skip-Gram与CBOW正好相反,是输入中心词的one-hot vector,输出上下文词的one-hot vector,对应的神经网络的结构为
隐藏层的输出为
w
c
T
w_c^T
wcT为
K
K
K维向量,这样到输出层的向量为
w
c
T
(
u
1
,
.
.
.
,
u
∣
V
∣
)
=
(
w
c
T
u
1
,
.
.
.
,
w
c
T
u
∣
V
∣
)
\begin{aligned} &w_c^T(u_1,...,u_{|V|}) \\ &=(w_c^Tu_1,...,w_c^Tu_{|V|}) \end{aligned}
wcT(u1,...,u∣V∣)=(wcTu1,...,wcTu∣V∣)
其中输出的第
i
i
i个分量,对应的损失函数为
L
i
=
−
log
y
^
c
−
m
+
i
=
−
w
c
T
u
j
c
−
m
+
i
+
log
∑
i
=
1
∣
V
∣
exp
w
c
T
u
i
\begin{aligned} L_i &= -\log{\hat{y}_{c-m+i}} \\ &= -w_c^Tu_{j_{c-m+i}} + \log{\sum_{i=1}^{|V|}\exp{w_c^Tu_i} } \end{aligned}
Li=−logy^c−m+i=−wcTujc−m+i+logi=1∑∣V∣expwcTui
总损失函数为
L
=
∑
i
=
0
,
i
≠
m
2
m
L
i
=
−
∑
i
=
0
,
i
≠
m
2
m
w
c
T
u
j
c
−
m
+
i
+
2
m
log
∑
i
=
1
∣
V
∣
exp
w
c
T
u
i
\begin{aligned} L&=\sum_{i=0,i \neq m}^{2m}L_i \\ &=-\sum_{i=0,i \neq m}^{2m}w_c^Tu_{j_{c-m+i}} + 2m\log{\sum_{i=1}^{|V|}\exp{w_c^Tu_i} } \end{aligned}
L=i=0,i=m∑2mLi=−i=0,i=m∑2mwcTujc−m+i+2mlogi=1∑∣V∣expwcTui
有了损失函数,就可以将
L
L
L分别对
u
i
u_i
ui和
w
i
w_i
wi取偏导,通过随机梯度法的迭代,去解出词向量矩阵的最优值。
改进1——Negative Sampling
上述方法中的
L
L
L,都涉及到要对
∣
V
∣
|V|
∣V∣个数求和。这样每次采用梯度法迭代时,对每一个分量求梯度都会有
∣
V
∣
|V|
∣V∣个求和运算。由于汉语的词汇量很大,达几十万个。这种方法会导致每次迭代的运算量过大,收敛慢。因此,需要考虑定义一个更加有效的损失函数
L
L
L。
令
D
\mathcal{D}
D为语料库中的中心词和上下文词构成的集合。现在我们考虑另一个集合
D
^
\mathcal{\hat{D}}
D^,表示所有中心词不对应的上下文词构成的集合。我们取求
U
U
U和
W
W
W,使得以下值最大
L
=
−
log
∏
(
c
,
a
)
∈
D
P
(
D
=
1
∣
c
,
a
,
U
,
W
)
∏
(
c
,
a
)
∈
D
^
P
(
D
=
0
∣
c
,
a
,
U
,
W
)
L=-\log{ \prod_{(c,a) \in \mathcal{D}}P(D=1|c,a,U,W)\prod_{(c,a) \in \mathcal{\hat{D}}}P(D=0|c,a,U,W) }
L=−log(c,a)∈D∏P(D=1∣c,a,U,W)(c,a)∈D^∏P(D=0∣c,a,U,W)
其中
P
(
D
=
1
∣
c
,
a
,
U
,
W
)
P(D=1|c,a,U,W)
P(D=1∣c,a,U,W)为
(
c
,
a
)
(c,a)
(c,a)作为中心词和上下文词出现在语料库的概率,利用
s
o
f
t
m
a
x
softmax
softmax可将其定义为
P
(
D
=
1
∣
c
,
a
,
U
,
W
)
≡
1
1
+
exp
(
−
w
c
T
u
a
)
≡
σ
(
w
c
T
u
a
)
P(D=1|c,a,U,W) \equiv \frac{1}{1+\exp{(-w_c^Tu_a)}} \equiv \sigma(w_c^Tu_a)
P(D=1∣c,a,U,W)≡1+exp(−wcTua)1≡σ(wcTua)
这样,
L
L
L可以化简为
L
=
−
∑
(
c
,
a
)
∈
D
log
σ
(
w
c
T
u
a
)
−
∑
(
c
,
a
)
∈
D
^
log
σ
(
−
w
c
T
u
a
)
L=-\sum_{(c,a) \in \mathcal{D}}\log\sigma(w_c^Tu_a)-\sum_{(c,a) \in \mathcal{\hat{D}}} \log \sigma(-w_c^Tu_a)
L=−(c,a)∈D∑logσ(wcTua)−(c,a)∈D^∑logσ(−wcTua)
在实际中
D
^
\mathcal{\hat{D}}
D^可以通过随机取样获得。注意到skip-gram的损失函数
L
i
L_i
Li为
L
i
=
−
log
exp
w
c
T
u
j
c
−
m
+
i
+
log
∑
i
=
1
∣
V
∣
exp
w
c
T
u
i
L_i= -\log \exp w_c^Tu_{j_{c-m+i}} + \log{\sum_{i=1}^{|V|}\exp{w_c^Tu_i} }
Li=−logexpwcTujc−m+i+logi=1∑∣V∣expwcTui
可以改写为
L
i
=
−
log
σ
(
w
c
T
u
j
c
−
m
+
i
)
−
log
∑
(
c
,
a
)
∈
D
^
σ
(
−
w
c
T
u
a
)
L_i=-\log \sigma{(w_c^Tu_{j_{c-m+i}})} - \log{\sum_{(c,a) \in \mathcal{\hat{D}}}}\sigma{(-w_c^Tu_a)}
Li=−logσ(wcTujc−m+i)−log(c,a)∈D^∑σ(−wcTua)
而CBOW的损失函数
L
L
L则可以改写为
L
=
−
log
σ
(
w
c
T
ˉ
u
c
)
−
log
∑
(
c
,
a
)
∈
D
^
σ
(
−
w
c
T
ˉ
u
a
)
L=-\log\sigma(\bar{w_c^T}u_c)-\log{\sum_{(c,a) \in \mathcal{\hat{D}}}{ \sigma (-{\bar{w_c^T}u_a }) }}
L=−logσ(wcTˉuc)−log(c,a)∈D^∑σ(−wcTˉua)
这样,对应每个中心词c,我们只需要随机找出一部分不是它上下文的词a就可以。同时要注意到随机取a时也要考虑到a本身在语料库中出现的频率,令
f
r
e
q
(
a
)
=
c
o
u
n
t
(
a
)
/
∣
T
e
x
t
∣
freq(a)=count(a)/|Text|
freq(a)=count(a)/∣Text∣,那取a的概率建议为
f
r
e
q
(
a
)
3
/
4
freq(a)^{3/4}
freq(a)3/4。所以negative sampling算法在为常见词建立词向量空间,是适合的。
改进2-Hierarchical Softmax
针对于非常见词,更合适的改进算法是Hierarchical softmax方法。
图中给出了Hierarchical softmax的算法。首先,将语料库的所有词建成一个二叉树的结构,其中每一个词都是这个树的叶子节点。(算法作者建议构建成Huffman树,频率高的词位于层数低的叶子节点,频率低的词位于层数高的叶子节点)。
图里以skip-gram为例,给定要训练的中心词
c
c
c和上下文词
a
a
a,找从根节点到
a
a
a的路径。定义给定中心词
c
c
c,出现上下文词
a
a
a的概率为
P
(
a
∣
c
)
=
P
(
a
在
节
点
6
的
右
边
∣
c
)
∗
P
(
a
在
节
点
4
左
边
∣
c
)
∗
P
(
a
在
节
点
3
右
边
∣
c
)
=
σ
(
v
6
T
v
c
)
∗
σ
(
v
4
T
v
c
)
∗
σ
(
−
v
3
T
v
c
)
\begin{aligned} P(a|c)&=P(a在节点6的右边|c)*P(a在节点4左边|c)*P(a在节点3右边|c) \\ &=\sigma(v_6^Tv_c)*\sigma(v_4^Tv_c)*\sigma(-v_3^Tv_c) \end{aligned}
P(a∣c)=P(a在节点6的右边∣c)∗P(a在节点4左边∣c)∗P(a在节点3右边∣c)=σ(v6Tvc)∗σ(v4Tvc)∗σ(−v3Tvc)
这里利用了
σ
(
x
)
+
σ
(
−
x
)
=
1
\sigma(x)+\sigma(-x)=1
σ(x)+σ(−x)=1的性质。
于是损失函数即为
L
i
=
−
log
P
(
a
∣
c
)
=
−
∑
n
为
到
a
的
节
点
log
σ
(
v
n
T
v
c
∗
s
n
)
s
n
=
{
1
a
在
节
点
n
的
右
边
−
1
a
在
节
点
n
的
左
边
\begin{aligned} L_i &= -\log P(a|c)=-\sum_{n为到a的节点}\log\sigma(v_n^Tv_c*s_n) \\ s_n&=\begin{cases} 1 & a在节点n的右边\\ -1 & a在节点n的左边 \end{cases} \end{aligned}
Lisn=−logP(a∣c)=−n为到a的节点∑logσ(vnTvc∗sn)={1−1a在节点n的右边a在节点n的左边
和skip-gram原算法的
L
i
L_i
Li进行比较可以发现,这里的计算
L
i
L_i
Li复杂度取决于树的高度,即
O
(
log
2
∣
V
∣
)
O(\log_2|V|)
O(log2∣V∣),而不再是整个词汇的数目
O
(
∣
V
∣
)
O(|V|)
O(∣V∣),大大减少了计算的复杂度。