word2vec 参数详解

1. 背景知识

在NLP中,我们处理文本的最细粒度的是词语,所以我们需要将词语转换成向量的形式以进行各式各样的计算。

最初也是最简单的一种词向量表达方式是 One-hot 编码词语,例如:

text: I love you
Vocab: { I: 0, love: 1, you: 2}

Wordvector: {I:[1,0,0], love: [0,1,0], you: [0,0,1]}

可以看到这种表达方式很直观,但是有缺陷:

  1. 词向量的维度 = vocab中词语数量。后者在实际中往往是几万甚至几十几百万,因此会有维度爆炸
  2. 只能单纯的表示词语在vocab的索引,无法表示词语之间的内在联系

有许多研究者就词向量的获取方式作了改进,我们今天讲的word2vec便是其中一种。wordvec是google团队在2013年发布的一个训练获取词向量的工具包,包含了两种训练训练词向量的模型(skip-gram, COBW)和训练优化工具(negative samping, hierarchy softmax)

2. CBOW, Skip-Gram介绍

2.1 以单个词语为输入的情况

word2vec 参数详解

​ 从最简单的情况开始讲起,假设输入的文本只有一个单词。我们设 V = vocab size,N = hidden size。Input layer是一个 (V*1) 大小的embedding layer : {x1,x2,...,xVx_1,x_2,...,x_V}。xix_i为1代表该词语在输入文本中出现。以单个词语为输入时,意味着{x1,x2,...,xVx_1,x_2,...,x_V}只有一个1值。

WV×NW_{V\times N}是input 和 hidden之间的权重矩阵,每一行代表一个与input word相关的N维向量,我们用vwv_w表示。对于单个词语的输入,假设xk=1x_k=1,我们有:

h=WTx=W(k,.)T:=vwITh = W^Tx = W^T_{(k,.)}:= v^T_{w_I}

注意到xk=1x_k=1,所以h其实就是W的第k行,vwIv_{w_I}便是输入词wIw_I的向量表示。

​ 通过hidden layer到output layer中的权重矩阵W=wijW'={w'_{ij}},我们可以计算出词汇表中每个词语的得分:

uj=vwjThu_j = {v'_{w_j}}^Th

vwjv'_{w_j}WW’的第j列,然后我们用softmax得到所有词语的后验概率:

word2vec 参数详解

yjy_j便是对应output layer的第j个词。

​ 我们注意到,对于输入词wIw_I,他在W和W’中有两种向量形式:vwI,vwIv_{w_I}, v'_{w_I},接下来我们把分别称为词语wIw_I输入向量(input vector)和输出向量(output vector)。

更新输出向量:

​ 我们要最大化P(wowi)P(w_o|w_i)

word2vec 参数详解
E=log p(wOwI)E = -log\ p(w_O|w_I)便是我们的损失函数, jj^*是实际输出词语的index。然后反向传播更新输出向量

word2vec 参数详解

从式子中可以看出,更新一次,我们要检查vocab中所有词的概率。

更新输入向量:

word2vec 参数详解

E对输入向量W求导,得到一个V*N的矩阵,因为只有一个输入词,所以该矩阵只有一行是非0的,值为EHTEH^T,然后便可以对W进行更新:

word2vec 参数详解

注意,只有vwIv_{w_I}那一行得到了更新。

讲过多次训练,最终,输入词的词向量维wj=h=WTxjw_j=h = W^Tx_j

2.2 CBOW

word2vec 参数详解

直观来看,CBOW实际上是以上下文的方式来预测输出的词,我们需要设置一个上下文词语数量skip_window来决定窗口值C, 比如

Text: I love you three thousands.

Window size: skip_window = 2,C=2*skip_window+1=5

假设我们的输入词是 {I, love, three, thousands}, 我们的预测就应该是{you}。参数与2.1中的参数设置基本相同,hidden layer的值为:

word2vec 参数详解

C为窗口值5,损失函数也随即成了:

word2vec 参数详解

输出输入向量更新为:

word2vec 参数详解word2vec 参数详解

注意,对于所有的输入词,输入向量WW都是同一个矩阵。

2.3 Skip-Gram

word2vec 参数详解

直观来看,skip-gram实际上利用单个词预测上下文。我们设置skip_window和num_skip两个参数,前者代表上文或者下文的个数,后者代表从窗口中选取输出词的数量。同样对于文本"I love you three thousand":

设 skip_window = 1, num_skip=3, 意味着我们的窗口值(包括输入词)为span = 2*1+1 = 5, 输出词个数为3,即:

Input: “you” output=[“I love you”, “love you three”, “you three thousand”)]

损失函数为:
word2vec 参数详解

其中word2vec 参数详解为输出层所有词的错误和。

输出向量更新公式:

word2vec 参数详解

输入向量更新公式:
word2vec 参数详解word2vec 参数详解

3. 优化计算效率的两种方法

我们注意到,输入向量的学习是很轻松的,但是输出向量的更新却很繁重。对于每个训练示例(training instance),我们都需要遍历一遍vocab中的所有词语,计算每个词语的概率,损失,然后更新相对应输出向量,这对于动辄几万几十万的vocab来说计算成本巨大,所以有一个优化的思路是减少每次训练是需要更新的输出向量。

3.1 Hierarchical Softmax

Hierarchical Softmax是一个利用霍夫曼树来表示vocab中所有词来达到优化softmax计算的方法。首先,将原先模型的输出向量WW'矩阵替代成如下的霍夫曼树:

word2vec 参数详解

白色的叶结点代表输出层的词,共V个; 黑色结点为内部节点,共V-1个。图中显示从隐藏层到输出层w2w_2的路径P2P_2,路径长度L(w2)=4L(w_2)=4n(w2,j)j=1,2,3n(w_2,j):j=1,2,3代表该路径上的第i个内部节点。

原先用输出向量表示输出词w2w_2,现在变成了用P2P_2路径上每个节点表示。树中每一个内部节点都有一个输出向量vn(w,j)v'_{n(w,j)},所以相应的,输出词的概率也变成了:

word2vec 参数详解

ch(n(w,j))ch(n(w,j))表示节点n(w,j)n(w,j)的左节点,word2vec 参数详解

直观的理解就是预测值ww为实际值wOw_O的概率是在P2P_2的每一个节点都取左节点的概率。

更新的式子为:word2vec 参数详解

其中
word2vec 参数详解
就是路径上每个词语的内部节点的预测error。我们根据词频构建霍夫曼树,出现频率越高的词拥有越短的路径(内部节点越少),所需要更新的vjv'_j也越少。因此,我们的更新参数时的计算复杂度从原来的O(V)减少到了O(logV)。当然,我们的总参数量并没有显著减少(N×V(V1)×NN \times V \to (V-1)\times N)。

3.2 Negative Sampling

Negative Sampling的优化思路简单暴力,我们称输出词为正样本,非输出词为负样本,我们基于经验选择特定的概率分布作为样本的噪音分布Pn(w)P_n(w),基于Pn(w)P_n(w)选择K个负样本:word2vec 参数详解

word2vec 参数详解

更新方程:

word2vec 参数详解

所以,每次更新,我们只需要更新正样本和K个负样本就好了,显著地减少了计算时间。

参考资料:
Rong, Xin. “word2vec parameter learning explained.” arXiv preprint arXiv:1411.2738 (2014).