为什么会有这篇博文

  自己在了解 word2vec 的时候参考的是 《动手学深度学习》 这本书,看完 word2vec 章节之后感觉应该有所领悟。这之后按照之前的学习习惯会把样例敲一遍。
  在敲样例的时候一度感觉自己没有理解代码,原以为能通过代码具体实现验证自己的理解并进一步加深认识,但是没想到变得更困惑了,重复看了几遍相关章节以及代码,并参考了其他资料之后感觉应该是有些理解了。
  在目前遍地都是 word2vec、词嵌入内容讲解的今天重复写一篇这方面的讲解博文还有啥意义呢?说实话大概对于我个人加深印象、提升写作会更有意义吧。当然,如果有幸成为某位朋友学习相关内容时的参考文本,那么就再好不过了。

模型

  出于表示更多信息与能够表示更多词的考虑,使用一个 n 维的向量来表示一个词,这里的词并不局限于我们一般认知上的单词、词汇。这里表示一个词元,可以认为是在之后的处理中的最小单位,在英语中你当然可以使用单词作为词元,
  使用字母作为词元看上去没有什么关系,但是在通过文本进行训练的时候这样的做法看上去不一定是一个好的选择。而且在 word2vec 的考虑中,一个词元也往往是一个单词。这样,我们现在可以认为我们通过一个向量表示了一个词。
  现在,在我们的期望中一个表示良好的词向量除去映射到一个词元上的功能之外,也应该有 “在含义上更接近的词,其向量表示会更接近。而那些在文本中经常出现在一起的词汇,我们也能通过其向量表示度量他们的相关性” 这样的性质。
  接下来将讲一下 word2vec 中两个表示词向量相关性的模型,之后介绍如何通过这两个模型更新优化,得到我们期望的词向量。但是在这之前需要谈一下之后两个模型都会依赖的基础设定。
  每次建模的时候我们从原始文本中提取一段连续的词序列进行建模,每次提取序列时以一个词 $W_a$ 为中心,取其两侧长度为 L 的子串构成一个长度为 2L + 1 的序列用来建模,这里,词 $W_a$ 称作中心词,其他的 2L 个词称作背景词。
  而每个词会映射到两个不同的向量,分别是该词作为中心词的向量,以及该词作为背景词的向量。在之后的叙述中,第 i 个词的中心词向量记为 $V_{ci}$ 然后第 i 个词的背景词向量记为 $V_{bi}$ 而之前提到的单侧子串长度 L 称作为窗口长度

skip-gram

  skip-gram 的思想是,给定中心词 $W_c$ ,一个词 $W_b$ 作为背景词出现的概率可以表示为 $P(W_b | W_c)$, 使用 softmax 计算其大小
$$
P(W_b | W_c) = softmax(U_{b}^T \cdot V_c) = \frac{exp(U_b^T \cdot V_c)}{\sum_{n = 1}^{v}exp(U_n^T \cdot V_c) }
$$
  假设一个序列中每个背景词出现的事件之间是独立的,那么给定一个中心词 $V_c$ 给定窗口大小为 L 的序列,该序列的出现概率就可以表示为
$$
\prod_{n = c-L}^{c+L} P(W_n|W_c)
$$
  在这之后考虑所有中心词及背景词构成的序列,产生该上下文的概率为,其中 T 为中心词集合的大小
$$
\prod_{c = 1}^{T}\prod_{n = c-L}^{c+L} P(W_n|W_c)
$$
  到这里为止,看上去已经设计出了模型,并可以基于此模型进行计算,但是这里存在一个问题:为了计算一个背景词出现的概率,需要让词表中的所有词向量参与计算,这意味着如果就这样计算分母那么总体的复杂度是 $O(n^2)$, 而且我们是通过模型反过来更新词向量,词表是会发生变化的。这样来看现有的模型将难以计算。那么这么看来需要做一些妥协,选择一种方法进行近似计算。
  一种方法是,将原本的概率函数 $P(W_b|W_c)$ 进行修改,修改为 $P(D = 1|W_b, W_c) = \sigma(U_{b}^{T}V_c)$ 其中 $\sigma$ 为 sigmoid 函数,即 $\sigma (x) = \frac{1}{1 + exp(-x)}$,而 D = 1 表示 $W_b$ 作为背景词出现在 $W_c$ 的上下文中这个事件发生。
  不过使用这样的函数来计算概率再通过之后极大似然来迭代词向量的话,最终所有词向量都将趋向于无穷大,为避免这样的情况需要引入另一种函数来描述其他词没有作为背景词出现在当前中心词上下文的情况。使用的函数表示为 $P(D = 0|W_k, W_c) = 1 - \sigma (U_{k}^{T}V_c) = \sigma(-U_{k}^{T}V_c) $。通过引入负样例的方式可以回避所有词向量趋近无穷大的情况发生。最终用于描述一个词作为背景词出现在中心词上下文这个事件的模型可以表示为

$$
P(D = 1|W_b, W_c) \cdot \prod_{w_k \in W_K}P(D = 0|w_k, W_c) = \sigma(U_{b}^{T}V_c)\cdot \prod_{u_k \in U_K}\sigma(-u_{k}^{T}V_c)
$$

  其中,$U_K$ 表示选择作为负样例引入的词的背景词向量构成的集合,该集合大小为 K,之后我们会看到 K 是一个可调参数。
  建立这样的模型之后我们可以在训练数据上计算该概率作为文本出现的似然函数,然后通过最大似然估计来更新优化词向量矩阵。之后在下游任务的应用中我们使用一个词作为中心词的词向量来表示该词。

连续词袋 CBOW

  skip-gram 模型的核心是通过指定的中心词来预测固定窗口中其他词出现的概率。而 连续词袋 模型则相反,它通过指定固定窗口中的背景词,来预测中心词出现的概率。而在之后下游任务中 CBOW 模型使用词的背景词向量来表示该词。给定中心词向量 $V_c$ 与背景词向量 $U_1$、$U_2$ … $U_n$ 以及负采样得到的噪声词 $V_1$、$V_2$ … $V_m$ 通过背景词预测中心词出现概率的计算可以表示为
$$
P(V_c|U_1, U_2, … , U_n) = P(D = 1 | V_c, h) \cdot \prod_{V_i \in V_{neg}}P(D = 0 | V_i, h)
$$
  其中 $V_{neg}$ 是经过负采样获得的噪声词集合,h 是背景词计算得到的隐藏状态,一般的,会使用 $h = \frac{1}{n} \sum_{i = 1}^{n} U_i$ 来计算 h

关于负采样的补充

  需要补充的一点是,进行负采样获取噪声词的时候,噪声词不能是背景词。且各词的采样概率为各词在文本中的分布率作为采样率,且在 Word2Vec 论文中推荐取分布律的 3/4 次幂作为最终的采样率。

初始化

  假设使用的词表大小为 v,且设置映射后的词向量维数为 d,那么我们需要初始化一个大小为 $v \times d$ 的矩阵。初始化的方案有多种。比如使用基于正态分布的随机初始化。

损失函数

  因为通过 sigmoid 来计算每个词出现在中心词上下文中作为背景词的概率,故在损失函数方面使用交叉熵损失函数是一种自然的选择。
  在训练集上,设 $y_i$ 为位置为 i 上的标注数据,$\hat{y_i}$ 为模型预测该位置的结果。如果该位置上的词作为背景词出现,那么有 $y_i = 1$,如果该词是引入的噪声词则有 $y_i = 0$。记中心词所在位置为 c 那么在一个窗口长度为 L 的训练文本上损失函数输出可记为 l,计算函数为

$$
l = -\sum_{i = c - L}^{c + L}y_i \cdot log(\hat{y_i})
$$

  那么在一次批量训练中、那么对这一批量中各文本的损失取平均得到该次训练的损失。

出于实现考虑的细节

  受台大李宏毅教授有关 Transformer 的教学 (bilibili YouTube) 的启发,通过一些图示描述一下在实现中相关的问题,比如矩阵的形状等。在阅读书籍相关部分的时候有过一时间不太能理解实现样例的经历,所以在这里尝试做一些说明(可能只是我单纯比较笨)。在接下来的讨论中以使用 skip-gram 模型为例。

vec

  让我们来考虑下计算 skip-gram 中讨论的预测概率的计算。在选定一段文本序列并确定中心词之后,为计算确认该中心词之后序列产生的概率,我们需要计算中心词向量与各背景词和噪声词的词向量的点积,然后再代入 sigmoid。
  假设词向量维数为 d,首先只考虑中心词向量与一个背景词向量做点积的情况,这个时候可以看做是 1 x d 的矩阵与 1 x d 矩阵的转置做矩阵乘法,上图中上方的图示展示了这种情况。接下来考虑中心词与文本中其他词向量同时做点积的情况,我们可以通过扩展第二个矩阵,将背景词与噪声词的词向量按行拼接,若背景词与噪声词的数量总计 m 那么我们拼接词向量之后能得到一个 m x d 的矩阵,再让中心词向量构成的 1 x d 矩阵与拼接矩阵的转置做矩阵乘法可以得到一个 1 x m 的矩阵每一列即为中心词与对应背景词的点积结果。上图中下方的图示描述了这种情况。

expanded_vec

  在一个文本序列中其他的词同时也可以做中心词,对所有 中心词-背景词 对求得的损失求和得到该文本序列基于当前词向量求出的损失函数。那么我们开始对中心词向量构成的矩阵在另一个维度上做拼接,n 个中心词按行拼接可以得到 n x 1 x d 的数据,与此同时背景词构成的矩阵需要在新的维度上进行拼接,拼接之后构成 n x m x d 的数据,由 n 个中心词各自对应的上下文词向量拼接成。之后在进行矩阵计算的时候需要按第一维批量做之前提到的矩阵乘法操作,计算完成之后我们将得到 n x 1 x m 的数据,对应为 n 个中心词与各自对应的 m 个上下文词向量进行点积得到的结果。

  至此,我们描述了基于一个文本序列计算各向量点积时在具体实现中可能会有的数据形状。如果考虑小批量计算的情况,我们只需要将批量中其他文本序列构成的数据在第一维上进行拼接。