在传统监督学习(supervised learning)中,词元的特征是手动添加的。手动添加的特征往往太过具体、不够全面、耗时长且表现一般。
例如:“good"这个单词,我们可能主观认为与其相似的单词有"fine”,“expert”,"beneficial"等,但是这会造成一些问题:
(1)在同义词层面上,会遗漏细微差别(不够全面);
(2)在同义词词集里面添加单词是非常主观的选择(太过具体);
(3)打标签费时费力(耗时长)。
于是有了自监督学习,也称为无监督学习(unsupervised learning)。
无监督学习常常用于解决聚类、主成分分析、因果关系、概率图、生成对抗网络等问题。
而我们接下来要讲的word2vec模型,它所要解决的就属于聚类问题。
我们逐步了解这个模型,分为三个部分:词义、word2vec原理、训练word2vec。
文章目录
(一)词义
词义是人类判断词相似性的依据,对于word2vec同样如此。
我们采用词向量来表示词元的特征,具体来讲就是:
给定这样一句话"Today is a fine day.",我们可以句子做如下的编码:
today -> 0
is -> 1
a -> 2
fine -> 3
day -> 4
然后采用独热编码去生成"fine"的词向量:[0, 0, 0, 1, 0]
。
这是一个长度为 5 5 5的张量(tensor),独热编码长度由词元种类数量决定;除了一个位置是 1 1 1之外,其余为 0 0 0,故得名独热编码。
从以上这个简单例子中,我们会发现独热编码的几个问题:
(1)独热编码可能会很长
对于一个庞大的数据集来说,其中词元的种类可能多达十万个,那么此时独热编码生成的词向量长度会很长。
(2)独热编码没有保留词元间的相似性
对于上面的句子来说,我们知道"today"和"day"的意义是相似的,但是二者的词向量点积(常常用于衡量相关性和相似性)是 0 0 0。
显然二者没有任何相关性,这并不是我们希望看到的结果。
那么如何解决独热编码词向量的问题?
对于问题(1)有一个简单的解决方案就是去掉出现频率过低的词元,同时改变句子编码策略,将"today"拆开为"to"和"day"后分别编码。
而对于问题(2),解决方案就是word2vec模型。
(二)word2vec原理
我们应该早就已经见过这个模型,只不过一直以来它都是以黑盒的形式出现在代码之中:
self.embedding = nn.Embedding(vocab_size, embed_size)
对的,这就是嵌入层。
word2vec,顾名思义,就是将词元转化为向量(word to vector)。
这也就是模型的目标:学习每个词元的词向量表示。
将词元转化为向量的过程被称为词嵌入,这就是嵌入层得名的原因。
接下来我们去了解一下黑盒是如何把词元转化为词向量的。
1.理论
词嵌入技术有一个核心的理论支撑,分布相似性理论:
直观来讲,就是一句话中的每个单词,都具有一定的上下文,也就是说我们可以通过一个单词去推断其上下文出现的概率。
根据分布相似性理论,我们可以学习词元的分布式表示。
有这样一句话评价词元分布式表示的价值:
词汇的分布式表示非常有价值,可以预测上下文,只要得到好的分布式表示,甚至不需要构建可以预测的概率语言模型,只需要找到一种学习单词表示的方法。
2.算法
这里给出两种学习词元分布式表示的算法:
(1)跳元模型(SG,Skip-Gram)
(2)连续词袋模型(CBOW,Continuous Bag Of Words)
(1)跳元模型(SG,Skip-Gram)
跳元模型条件概率公式(
w
t
w_t
wt出现的前提下,上下文出现的条件概率):
P
(
c
o
n
t
e
x
t
∣
w
t
)
=
.
.
.
P(context|w_t)=...
P(context∣wt)=...
在之前的例子中,条件概率公式表现为
P
(
T
o
d
a
y
,
i
s
,
f
i
n
e
,
d
a
y
∣
a
)
P(Today,\ is,\ fine,\ day\ |\ a)
P(Today, is, fine, day ∣ a)。
在这个模型中,每个词元都有两个 d d d维向量表示,用于计算条件概率。
具体的说,对于词表中索引为 i i i的任何词 w i w_i wi,有两个向量表示 v i v_i vi和 u i u_i ui,其中 v i v_i vi代表着中心词, u i u_i ui代表着上下文词。
为什么要用两个向量而不是一个向量表示?
因为这样,同一个词元的两个分布式表示是相互独立的,在优化的时候不会互相耦合。
我们将跳元模型条件概率公式拆解:
P
(
c
o
n
t
e
x
t
∣
w
t
)
=
∏
−
m
≤
j
≤
m
,
j
≠
0
P
(
w
t
+
j
∣
w
t
)
P(context|w_t)={\prod_{-m\le j\le m, j\ne 0}{P(w_{t+j}|w_t)}}
P(context∣wt)=−m≤j≤m,j=0∏P(wt+j∣wt)
其中
T
T
T代表着文本序列长度,
m
m
m是窗口长度,二者均为超参数;
P
(
w
t
+
j
∣
w
t
)
P(w_{t+j}|w_t)
P(wt+j∣wt)代表在
w
t
+
j
w_{t+j}
wt+j出现的前提下
w
t
w_t
wt出现的概率:
P
(
w
o
∣
w
c
)
=
e
x
p
(
u
o
T
v
c
)
∑
i
∈
V
e
x
p
(
u
i
T
v
c
)
P(w_o|w_c)=\frac{exp(u_o^Tv_c)}{\sum_{i\in V}exp(u_i^Tv_c)}
P(wo∣wc)=∑i∈Vexp(uiTvc)exp(uoTvc)
其中
u
o
u_o
uo为上下文词向量,
v
c
v_c
vc为中心词向量,
V
=
{
0
,
1
,
.
.
.
,
∣
V
∣
−
1
}
V=\{0,1,...,|V|-1\}
V={0,1,...,∣V∣−1}为词表索引集。
这里的条件概率采用了softmax处理。
注意softmax处理后的概率分布与原来的分布不同。我们都知道什么是指数爆炸,所以很自然的,softmax会放大最大值所占的比重,这也是为什么它的名字中有"max"。
接下来我们尝试将文本序列中的每一个词作为中心词,进行累乘操作:
∏
t
=
1
T
P
(
c
o
n
t
e
x
t
∣
w
t
)
=
∏
t
=
1
T
∏
−
m
≤
j
≤
m
,
j
≠
0
P
(
w
t
+
j
∣
w
t
)
\prod_{t=1}^TP(context|w_t)=\prod_{t=1}^T{\prod_{-m\le j\le m, j\ne 0}{P(w_{t+j}|w_t)}}
t=1∏TP(context∣wt)=t=1∏T−m≤j≤m,j=0∏P(wt+j∣wt)
我们的目标是让这个公式的结果尽可能的大。
但是累乘是很麻烦的,我们可以取负对数,将其变成累加:
−
∑
t
=
1
T
∑
−
m
≤
j
≤
m
,
j
≤
0
l
o
g
P
(
u
t
+
j
∣
v
t
)
-\sum_{t=1}^T{\sum_{-m\le j\le m,j\le 0}log\ P(u_{t+j}|v_t)}
−t=1∑T−m≤j≤m,j≤0∑log P(ut+j∣vt)
现在我们的目标变为使公式的值尽可能的小(因为是负对数似然),这个公式就是我们的目标函数。
注意,对于每个中心词,其上下文只有一个概率分布,不同的中心词的上下文具有不同的概率分布。也就是说,目标概率分布与上下文和中心词之间的距离无关,所以我们称这个跳元模型算法是位置独立的。
(2)连续词袋模型(CBOW,Continuous Bag Of Words)
连续词袋模型的预测方法恰好是跳元模型的逆过程:根据上下文预测中心词元。
连续词袋模型条件概率公式(上下文出现的前提下,
w
t
w_t
wt出现的概率):
P
(
w
t
∣
c
o
n
t
e
x
t
)
=
.
.
.
P(w_t|context)=...
P(wt∣context)=...
在之前的例子中,可以表现为
P
(
a
∣
T
o
d
a
y
,
i
s
,
f
i
n
e
,
d
a
y
)
P(a\ |\ Today,\ is,\ fine,\ day)
P(a ∣ Today, is, fine, day)。
在这个模型中,各个符号所代表的意义与跳元模型相同。
与之前不同是,连续词袋模型条件概率公式无法拆解:
P
(
w
t
∣
c
o
n
t
e
x
t
)
=
P
(
w
t
∣
w
o
1
,
.
.
.
,
w
o
2
m
)
P(w_t|context)=P(w_t|w_{o_1},...,w_{o_{2m}})
P(wt∣context)=P(wt∣wo1,...,wo2m)
其中
m
m
m为窗口长度,是超参数。
为了表示方便,我们设 W t = { w t − m , . . . , w t − 1 , w t + 1 , . . . , w t + m } W_t=\{w_{t-m},...,w_{t-1},w_{t+1},...,w_{t+m}\} Wt={wt−m,...,wt−1,wt+1,...,wt+m}。
因为存在多个上下文词,我们需要对 2 m 2m 2m个上下文词取均值。
为了表示方便,设 u ˉ t = 1 2 m ( w t − m , . . . , w t − 1 , w t + 1 , . . . , w t + m ) \bar u_t=\frac{1}{2m}(w_{t-m},...,w_{t-1},w_{t+1},...,w_{t+m}) uˉt=2m1(wt−m,...,wt−1,wt+1,...,wt+m)。
则上下文出现的前提下,
w
t
w_t
wt出现的概率:
P
(
w
t
∣
W
t
)
=
e
x
p
(
v
t
T
u
ˉ
t
)
∑
i
∈
V
e
x
p
(
v
i
T
u
ˉ
t
)
P(w_t|W_t)=\frac{exp(v_t^T\bar u_t)}{\sum_{i\in V}exp(v_i^T\bar u_t)}
P(wt∣Wt)=∑i∈Vexp(viTuˉt)exp(vtTuˉt)
V = { 0 , 1 , . . . , ∣ V ∣ − 1 } V=\{0,1,...,|V|-1\} V={0,1,...,∣V∣−1}为词表索引集。
接下来我们尝试预测文本序列中的每一个词,进行累乘操作:
∏
t
=
1
T
P
(
w
t
∣
W
t
)
\prod_{t=1}^{T} P(w_t|W_t)
t=1∏TP(wt∣Wt)
由于累乘过于繁琐,我们取负对数:
−
∑
t
=
1
T
l
o
g
P
(
w
t
∣
W
t
)
-\sum_{t=1}^{T} log\ P(w_t|W_t)
−t=1∑Tlog P(wt∣Wt)
我们的目标就是使上式的值最小,它就是我们的目标函数。
(三)训练word2vec
嵌入层的基本训练方式与常规神经网络并无不同:
数据处理、训练数据输入、计算损失、反向传播更新梯度、优化模型。
训练方法有以下两种:
(1)层次softmax(Hierarchical softmax);
(2)负采样(Negative sampling)。
但是这里不介绍这两种方法,因为本文的主要目的是介绍word2vec的基本原理。
这里只说明一个非常简单的训练方法。
1.初始化参数
简单取两个比较小的数值,然后在这个范围内随机取值进行初始化即可。
2.优化模型
优化算法的核心是梯度。(求导过程省略,实际应用时只需要反向传播即可,感兴趣可以自行求导)
(1)跳元模型(SG,Skip-Gram)
根据跳元模型的条件概率公式:
P
(
w
o
∣
w
c
)
=
e
x
p
(
u
o
T
v
c
)
∑
i
∈
V
e
x
p
(
u
i
T
v
c
)
P(w_o|w_c)=\frac{exp(u_o^Tv_c)}{\sum_{i\in V}exp(u_i^Tv_c)}
P(wo∣wc)=∑i∈Vexp(uiTvc)exp(uoTvc)
通过求微分,我们可以获得其对于中心词向量
v
c
v_c
vc的梯度:
∂
l
o
g
P
(
w
o
∣
w
c
)
∂
v
c
=
u
o
−
∑
j
∈
V
P
(
w
j
∣
w
c
)
u
j
\frac{\partial log\ P(w_o|w_c)}{\partial v_c}=u_o-\sum_{j\in V}P(w_j|w_c)u_j
∂vc∂log P(wo∣wc)=uo−j∈V∑P(wj∣wc)uj
(2)连续词袋模型(CBOW,Continuous Bag Of Words)
根据连续词袋模型的条件概率公式:
P
(
w
t
∣
W
t
)
=
e
x
p
(
v
t
T
u
ˉ
t
)
∑
i
∈
V
e
x
p
(
v
i
T
u
ˉ
t
)
P(w_t|W_t)=\frac{exp(v_t^T\bar u_t)}{\sum_{i\in V}exp(v_i^T\bar u_t)}
P(wt∣Wt)=∑i∈Vexp(viTuˉt)exp(vtTuˉt)
通过求微分,我们可以获得其对于
m
m
m个上下文词向量的梯度:
∂
P
(
w
t
∣
W
t
)
∂
u
i
=
1
2
m
(
v
t
−
∑
i
∈
V
P
(
w
i
∣
W
t
)
v
i
)
\frac{\partial P(w_t|W_t)}{\partial u_i}=\frac{1}{2m}(v_t-\sum_{i\in V}P(w_i|W_t)v_i)
∂ui∂P(wt∣Wt)=2m1(vt−i∈V∑P(wi∣Wt)vi)
因为数据集往往很大,所以计算并存储整个数据集的梯度然后反向传播是不实际的(反向传播本质就是偏导数+存储),而且效果也并不好。
所以我们使用小批量随机梯度下降(SGD,Stochastic Gradient Descent)。
因为是随机取样,所以最终计算得到的梯度很好代表了整个样本的均值,并没有影响模型收敛。
参考:《动手学深度学习 Pytorch版》
【中英字幕】吹爆!最全斯坦福CS224n《深度学习自然语言处理》课程!我愿称之为2022最强NLP课程!超重量级,赶紧收藏!—人工智能/深度学习/机器学习