-
CBOW
2017-07-12 10:13:17本文简述了以下内容: ... word2vec:CBOW / Skip-gram,直接以得到词表示为目标的模型 (一)原始CBOW(Continuous Bag-of-Words)模型 (二)原始Skip-gram模型 (三)word analogy 神经本文简述了以下内容:
神经概率语言模型NPLM,训练语言模型并同时得到词表示
word2vec:CBOW / Skip-gram,直接以得到词表示为目标的模型
(一)原始CBOW(Continuous Bag-of-Words)模型
(二)原始Skip-gram模型
(三)word analogy
神经概率语言模型NPLM
上篇文简单整理了一下不同视角下的词表示模型。近年来,word embedding可以说已经成为了各种神经网络方法(CNN、RNN乃至各种网络结构,深层也好不深也罢)处理NLP任务的标配。word embedding(词嵌入;词向量)是指基于神经网络来得到词向量的模型(如CBOW、Skip-gram等,几乎无一例外都是浅层的)所train出来的词的向量表示,这种向量表示被称为是分布式表示distributed representation,大概就是说单独看其中一维的话没什么含义,但是组合到一起的vector就表达了这个词的语义信息(粒度上看的话,不止词,字、句子乃至篇章都可以有分布式表示;而且,例如网络节点、知识图谱中的三元组等都可以有自己的embedding,各种“xx2vec”层出不穷)。这种基于神经网络的模型又被称作是基于预测(predict)的模型,超参数往往要多于基于计数(count)的模型,因此灵活性要强一些,超参数起到的作用可能并不逊于模型本身。尽管有一批paper去证明了这类神经网络得到词表示模型的本质其实就是矩阵分解,但这并不妨碍它们的广泛应用。
下面就简要介绍利用神经网络来得到词表示的非常早期的工作——神经概率语言模型(NPLM, Neural Probabilistic Language Model),通过训练语言模型,同时得到词表示。
语言模型是指一个词串
{wt}Tt=1=wT1=w1,w2,...,wT {wt}t=1T=w1T=w1,w2,...,wT 是自然语言的概率P(wT1) P(w1T) 。 词wt wt的下标t t 表示其是词串中的第t t 个词。根据乘法公式,有P(w1,w2,...,wT)=P(w1)P(w2|w1)P(w3|w1,w2)...P(wT|w1,w2,...,wT−1) P(w1,w2,...,wT)=P(w1)P(w2|w1)P(w3|w1,w2)...P(wT|w1,w2,...,wT−1)因此要想计算出这个概率,那就要计算出
P(wt|w1,w2,...,wt−1),t∈{1,2,...,T} P(wt|w1,w2,...,wt−1),t∈{1,2,...,T} 。传统方式是利用频数估计:P(wt|w1,w2,...,wt−1)=count(w1,w2,...,wt−1,wt)count(w1,w2,...,wt−1) P(wt|w1,w2,...,wt−1)=count(w1,w2,...,wt−1,wt)count(w1,w2,...,wt−1)count()是指词串在语料中出现的次数。暂且抛开数据稀疏(如果分子为零那么概率为零,这个零合理吗?如果分母为零,又怎么办?)不谈,如果词串的长度很长的话,这个计算会非常费时。n-gram模型是一种近似策略,作了一个
n−1 n−1 阶马尔可夫假设:认为目标词wt wt的条件概率只与其之前的n−1 n−1 个词有关:P(wt|w1,w2,...,wt−1)≈P(wt|wt−(n−1),wt−(n−2),...,wt−1)=count(wt−(n−1),wt−(n−2),...,wt−1,wt)count(wt−(n−1),wt−(n−2),...,wt−1) P(wt|w1,w2,...,wt−1)≈P(wt|wt−(n−1),wt−(n−2),...,wt−1)=count(wt−(n−1),wt−(n−2),...,wt−1,wt)count(wt−(n−1),wt−(n−2),...,wt−1)神经概率语言模型NPLM延续了n-gram的假设:认为目标词
wt wt 的条件概率与其之前的n−1 n−1 个词有关。但其在计算P(wt|w1,w2,...,wt−1) P(wt|w1,w2,...,wt−1) 时,则使用的是机器学习的套路,而不使用上面count()的方式。那么它是如何在训练语言模型的同时又得到了词表示的呢?图片来源:[1],加了几个符号
设训练语料为
D D ,提取出的词表为V={w1−,w2−,...,w|V|−−−} V={w1_,w2_,...,w|V|_} 。词wi− wi_ 的下标i− i_ 表示其是词表中的第i i 个词,区别于不带下划线的下标。大致说来,NPLM将语料中的一个词串wtt−(n−1) wt−(n−1)t 的目标词wt wt 之前的n−1 n−1 个词的词向量(即word embedding,设维度为m m )按顺序首尾拼接得到一个“长”的列向量x x ,作为输入层(也就是说共(n−1)m (n−1)m 个神经元)。然后经过权重矩阵Hh×(n−1)m Hh×(n−1)m 来到隐层(神经元数为h h ),并用tanh函数激活。之后再经过权重矩阵U|V|×h U|V|×h 来到输出层(神经元数当然为|V| |V| ),并使用softmax()将其归一化为概率。另外存在一个从输入层直连输出层的权重矩阵W|V|×(n−1)m W|V|×(n−1)m 。所以网络的输出如下(隐层和输出层加了偏置):z=Utanh(Hx+d)+b+Wx z=Utanh(Hx+d)+b+Wxy^i−=P(wi−|wt−(n−1),wt−(n−2),...,wt−1)=softmax(zi−)=expzi−∑k=1|V|expzk−,wi−∈V y^i_=P(wi_|wt−(n−1),wt−(n−2),...,wt−1)=softmax(zi_)=expzi_∑k=1|V|expzk_,wi_∈Vy^i− y^i_ 表示目标词是词表中第i i 个词wi− wi_ 的概率。expzi− expzi_ 表示前n−1 n−1 个词对词表中第i i 个词wi− wi_ 的能量聚集。词表中的每个词的词向量都存在一个矩阵
C C 中,look-up操作就是从矩阵中取出需要的词向量。由此可以看出,NPLM模型和传统神经网络的区别在于,传统神经网络需要学习的参数是权重和偏置;而NPLM模型除了需要学习权重和偏置外,还需要对输入(也就是词向量)进行学习。那么,模型的参数就有:
C,U,H,W,b,d C,U,H,W,b,d 。使用交叉熵损失函数,模型对目标词
wt wt 的损失为L=−logy^t=−logP(wt|wt−(n−1),wt−(n−2),...,wt−1)=−logsoftmax(zt) L=−logy^t=−logP(wt|wt−(n−1),wt−(n−2),...,wt−1)=−logsoftmax(zt)那么模型的经验风险为(省略了常系数)
L=−∑wtt−(n−1)∈Dlogy^t=−∑wtt−(n−1)∈DlogP(wt|wt−(n−1),wt−(n−2),...,wt−1)=−∑wtt−(n−1)∈Dlogsoftmax(zt) L=−∑wt−(n−1)t∈Dlogy^t=−∑wt−(n−1)t∈DlogP(wt|wt−(n−1),wt−(n−2),...,wt−1)=−∑wt−(n−1)t∈Dlogsoftmax(zt)所以接下来就可以使用梯度下降等方法来迭代求取参数了。这样便同时训练了语言模型和词向量。
word2vec:CBOW / Skip-gram
上面介绍的NPLM以训练语言模型为目标,同时得到了词表示。2013年的开源工具包word2vec则包含了CBOW和Skip-gram这两个直接以得到词向量为目标的模型。
像SGNS这些新兴的获得embedding的模型其实不属于字面含义上的“深度”学习,因为这些模型本身都是很浅层的神经网络。但得到它们后,通常会作为输入各种神经网络结构的初始值(也就是预训练,而不采用随机初始化),并随网络参数一起迭代更新进行fine-tuning。就我做过的实验来说,预训练做初始值时通常可以提升任务上的效果,而且fine-tuning也是必要的,不要直接用初始值而不更新了。
首先它获取word embedding(Distributed representation)的方式是无监督的,只需要语料本身,而不需要任何标注信息,训练时所使用的监督信息并不来自外部标注;但之前的pLSA什么的也是无监督的啊,也是稠密向量表示啊。所以我觉得word2vec之所以引爆了DL在NLP中的应用更可能是因为它在语义方面的一些优良性质,比如相似度方面和词类比(word analogy)现象,便于神经网络从它开始继续去提取一些high level的东西,进而去完成复杂的任务。
这里先介绍两种模型的没有加速策略的原始形式(也就是输出层是softmax的那种。对于Skip-gram模型,作者在paper中称之为“impractical”),两种加速策略将在下篇文中介绍。
与NPLM不同,在CBOW / Skip-gram模型中,目标词
wt wt 是一个词串中间的词而不是最后一个词,其拥有的上下文(context)为前后各m m 个词:wt−m,...,wt−1,wt+1,...,wt+m wt−m,...,wt−1,wt+1,...,wt+m 。NPLM基于n-gram,相当于目标词只有上文。后文中,“目标词”和“中心词”是同一概念,“周围词”和“上下文”是同一概念。在原始的CBOW / Skip-gram模型中,任一个词
wi− wi_ 将得到两个word embedding(设维度为n n ):作为中心词时的词向量,也称为输出词向量vi−∈Rn×1 vi_∈Rn×1 ;以及作为周围词时的词向量,也称为输入词向量ui−∈Rn×1 ui_∈Rn×1 。词向量的下标和词的下标相对应,比如说目标词wt wt 的词向量就对应为vt vt 和ut ut 。与NPLM类似,词表中每个词的词向量都存在一个矩阵中。由于存在两套词向量,因此就有两个矩阵:输入词矩阵
Vn×|V|=[v1−,...,v|V|−−−] Vn×|V|=[v1_,...,v|V|_] ,其每一列都是一个词作为周围词时的词向量;输出词矩阵U|V|×n=[u⊤1−;...;u⊤|V|−−−] U|V|×n=[u1_⊤;...;u|V|_⊤] ,其每一行都是一个词作为中心词时的词向量。比如说若想取出词作为周围词时的词向量,只要知道词在词表中的编号即可,取出的操作相当于用输入词矩阵乘以词的one-hot representation。(一)CBOW(Continuous Bag-of-Words)
不带加速的CBOW模型是一个两层结构,相比于NPLM来说CBOW模型没有隐层,通过上下文来预测中心词,并且抛弃了词序信息——
输入层:
n n 个节点,上下文共2m 2m 个词的词向量的平均值;输入层到输出层的连接边:输出词矩阵
U|V|×n U|V|×n ;输出层:
|V| |V| 个节点。第i i 个节点代表中心词是词wi− wi_ 的概率。如果要视作三层结构的话,可以认为——
输入层:
2m×|V| 2m×|V|个节点,上下文共2m 2m 个词的one-hot representation输入层到投影层到连接边:输入词矩阵
Vn×|V| Vn×|V| ;投影层::
n n 个节点,上下文共2m 2m 个词的词向量的平均值;投影层到输出层的连接边:输出词矩阵
U|V|×n U|V|×n ;输出层:
|V| |V| 个节点。第i i 个节点代表中心词是词wi− wi_ 的概率。这样表述相对清楚,将one-hot到word embedding那一步描述了出来。这里的投影层并没有做任何的非线性激活操作,直接就是Softmax层。换句话说,如果只看投影层到输出层的话,其实就是个Softmax回归模型,但标记信息是词串中心词,而不是外部标注。
图片来源:[5],把记号都改成和本文一致
首先,将中心词
wt wt 的上下文ct ct :wt−m,...,wt−1,wt+1,...,wt+m wt−m,...,wt−1,wt+1,...,wt+m 由one-hot representation(xt+j xt+j )转为输入词向量(vt+j vt+j ):vt+j=Vxt+j,j∈{−m,...,m}∖{0} vt+j=Vxt+j,j∈{−m,...,m}∖{0}进而将上下文的输入词向量
vt−m,...,vt−1,vt+1,...,vt+m vt−m,...,vt−1,vt+1,...,vt+m 求平均值,作为模型输入:v^t=12m∑jvt+j,j∈{−m,...,m}∖{0} v^t=12m∑jvt+j,j∈{−m,...,m}∖{0}这一步叫投影(projection)。可以看出,CBOW像词袋模型(BoW)一样抛弃了词序信息,然后窗口在语料上滑动,就成了连续词袋= =。丢掉词序看起来不太好,不过开个玩笑的话:“研表究明,汉字的序顺并不定一能影阅响读,事证实明了当你看这完句话之后才发字现都乱是的”。
与NPLM不同,CBOW模型没有隐藏层,投影之后就用softmax()输出目标词是某个词的概率,进而减少了计算时间:
z=Uv^t z=Uv^ty^i−=P(wi−|wt−m,...,wt−1,wt+1,...,wt+m)=softmax(zi−)=softmax(u⊤i−v^t),wi−∈V y^i_=P(wi_|wt−m,...,wt−1,wt+1,...,wt+m)=softmax(zi_)=softmax(ui_⊤v^t),wi_∈V那么模型的参数就是两个词向量矩阵:
U,V U,V 。对于中心词
wt wt ,模型对它的损失为L=−logy^t=−logP(wt|wt−m,...,wt−1,wt+1,...,wt+m)=−logsoftmax(zt)=−logexp(u⊤tv^t)∑|V|k=1exp(u⊤k−v^t)=−u⊤tv^t+log∑k=1|V|exp(u⊤k−v^t)=−zt+log∑k=1|V|expzk− L=−logy^t=−logP(wt|wt−m,...,wt−1,wt+1,...,wt+m)=−logsoftmax(zt)=−logexp(ut⊤v^t)∑k=1|V|exp(uk_⊤v^t)=−ut⊤v^t+log∑k=1|V|exp(uk_⊤v^t)=−zt+log∑k=1|V|expzk_所以模型的经验风险为
L=−∑wt+mt−m∈Dlogyt^=−∑wt+mt−m∈DlogP(wt|wt−m,...,wt−1,wt+1,...,wt+m)=−∑wt+mt−m∈Dlogsoftmax(zt) L=−∑wt−mt+m∈Dlogyt^=−∑wt−mt+m∈DlogP(wt|wt−m,...,wt−1,wt+1,...,wt+m)=−∑wt−mt+m∈Dlogsoftmax(zt)做文本的各位同好应该都知道fastText,它相比于CBOW有两个比较重要的区别:首先,fastText是一个端到端的分类器,用全部窗口词取平均去预测文档的标签,而不是预测窗口中心词;另外一个,是它引入了局部词序,也就是 n-gram 特征,所以train出来的词向量和word2vec有一些不一样的特点。因为Hierarchical Softmax还有其他的trick,它的速度快到难以置信,而且精度并不低,没用过fastText的各位可以跑下实验感受一下。
下面开始是非常无聊的求导练习。。。
如果用SGD来更新参数的话,只需求出模型对一个样本的损失的梯度。也就是说上式的求和号可以没有,直接对
L L 求梯度,来更新参数。I. 首先是对输出词矩阵
U⊤=[u1−,...,u|V|−−−] U⊤=[u1_,...,u|V|_] :这部分和Softmax回归模型的梯度推导过程是一样一样的。有很多种方法,下面介绍最按部就班的方法。
因为
zi−=u⊤i−v^t zi_=ui_⊤v^t ,所以∂L∂ui−=∂zi−∂ui−∂L∂zi−=v^t∂L∂zi− ∂L∂ui_=∂zi_∂ui_∂L∂zi_=v^t∂L∂zi_ (这里的∂L∂zi− ∂L∂zi_ 其实就是BP算法中的δ δ ),那么先求∂L∂zi− ∂L∂zi_ :(1) 对
∀wi−∈V∖{wt} ∀wi_∈V∖{wt} ,有yi−=0 yi_=0,那么∂L∂zi−=∂(−zt+log∑k=1|V|expzk−)∂zi=0+∂∑|V|k=1expzk−∂zi−∑k=1|V|expzk−=expzi−∑k=1|V|expzk−=y^i−=y^i−−yi− ∂L∂zi_=∂(−zt+log∑k=1|V|expzk_)∂zi=0+∂∑k=1|V|expzk_∂zi_∑k=1|V|expzk_=expzi_∑k=1|V|expzk_=y^i_=y^i_−yi_(2) 对
wi−=wt wi_=wt ,有yi−=1 yi_=1,那么∂L∂zi−=∂L∂zt=−1+y^t=y^i−−yi− ∂L∂zi_=∂L∂zt=−1+y^t=y^i_−yi_可见两种情形的结果是统一的,就是误差项。
因此有
∂L∂ui−=(y^i−−yi−)v^t,wi−∈V ∂L∂ui_=(y^i_−yi_)v^t,wi_∈V那么对于词表中的任一个词
wi− wi_ ,其输出词向量的更新迭代式为:ui−=ui−−α(y^i−−yi−)v^t,wi−∈V ui_=ui_−α(y^i_−yi_)v^t,wi_∈V不妨把它们拼接成对矩阵的梯度:
∂L∂U⊤=[∂L∂u1−,...,∂L∂u|V|−−−]=v^t(y^−y)⊤ ∂L∂U⊤=[∂L∂u1_,...,∂L∂u|V|_]=v^t(y^−y)⊤U⊤=U⊤−αv^t(y^−y)⊤ U⊤=U⊤−αv^t(y^−y)⊤II. 接下来是对输入词矩阵
V=[v1−,...,v|V|−−−] V=[v1_,...,v|V|_] :因为
v^t=12m∑jvt+j v^t=12m∑jvt+j ,所以∂L∂vt+j=∂v^t∂vt+j∂L∂v^t=12mI∂L∂v^t ∂L∂vt+j=∂v^t∂vt+j∂L∂v^t=12mI∂L∂v^t,那么求∂L∂v