第三部分 · 用数字表示词义

word2vec:靠预测学出词向量

训练一个分类器去猜哪些词会出现在一起,再把它学到的权重留下来,作为又短又稠密的词向量。

相关课次第 7 讲Sep 15

能不能不数长向量,而是直接学出短向量? Lec 7 · PDF pp. 16–18

第 6 讲从计数得到的词向量,每个上下文词占一个坐标。词表有两万到五万个词,向量就有那么长,而且大部分位置是零。第 7 讲重提另一条路:只用 50 到 1000 个数字表示一个词,并且大部分位置都不为零。这种向量叫稠密向量(dense vector)。

课件给出四个理由。向量短,后面的分类器要调的权重就少。稠密向量在新样本上的表现,可能比直接用计数更好。它还能更合理地处理同义词:在计数向量里,car 和 automobile 是两个独立的坐标,一个常和 car 一起出现的词,和另一个常和 automobile 一起出现的词,看起来毫无关系,可我们希望它们相似。最后一个理由很实际:稠密向量在实践中效果更好。

课件列出三条得到稠密向量的路线,本页只讲第一条。

  • 受神经语言模型启发的方法:word2vec(有 skip-gram 和 CBOW 两种变体)和 GloVe。GloVe 在下一页讲。
  • 奇异值分解(singular value decomposition,SVD):直接压缩计数矩阵。潜在语义分析(LSA)是它的一个特例。课件只点名,没有展开。
  • 上下文相关的词向量(contextual embeddings):根据所在句子,给一个词的每次出现分别计算向量。这是课程后面的内容。

word2vec 的思路:不去数,而是去预测 Lec 7 · PDF pp. 20–23

word2vec 给每种词型一个短而稠密的向量。这个向量不随句子变化,所以属于静态词向量(static embedding)。课件提到两种训练算法:带负采样的 skip-gram(skip-gram with negative sampling,SGNS)和连续词袋(continuous bag of words,CBOW),但只展开讲第一种。训练好的 word2vec 向量很容易下载来直接用,这也是它流行的原因之一。出处是 Mikolov 等人 2013 年的两篇论文。

计数问的是:"每个词在 cherry 旁边出现了多少次?"word2vec 换了一个问题,并训练一个分类器来回答:这个词有没有可能出现在 cherry 旁边?

我们其实并不关心这个是非题本身。我们要的是分类器为了答对这个问题而学到的那些数字。这些学到的权重,就是词向量。

训练用的标签从哪里来?

分类器需要带正确答案的样本。这里的答案由文本自己提供:如果一个词在语料里真的出现在 cherry 旁边,这一对就是一个"是"的样本。不需要任何人手工标注。这种由数据自己提供标签的学习方式叫自监督(self-supervision)。课件注明这个想法来自 Bengio 等人(2003)和 Collobert 等人(2011)。

整个方法可以概括为四步:

  1. 把目标词和真正出现在它附近的词配成一对,作为正样本。
  2. 从词表里随机抽一些别的词,配成负样本。
  3. 训练一个逻辑回归分类器,区分这两类词对。
  4. 把学到的权重当作词向量保留下来。

跟着一个句子走一遍 Lec 7 · PDF pp. 24–29

假设窗口向左右各延伸两个词。课件用的这段训练文本,目标词是 apricot:

... lemon, a [tablespoon  of  apricot  jam,  a] pinch ...
               c1         c2     w      c3   c4

方括号里的四个词是上下文词 \(c_1\) 到 \(c_4\)。每一个都和目标词组成一个正样本:(apricot, tablespoon)、(apricot, of)、(apricot, jam)、(apricot, a)。随机抽出来的词,比如 aardvark,则组成负样本 (apricot, aardvark)。

对任何一个候选词对 \((w,c)\),分类器输出"\(c\) 确实是 \(w\) 的邻居"的概率。两个概率加起来等于 1:

\[ P(+\mid w,c) \qquad\qquad P(-\mid w,c)=1-P(+\mid w,c) \]

把两个向量的相似程度变成概率 Lec 7 · PDF pp. 30–31

核心想法是:让这个概率取决于两个词的向量有多相似。两个向量越相似,这个词就越可能出现在目标词旁边。分类器用点积(dot product)来衡量相似程度,记作 \(\mathbf{c}\cdot\mathbf{w}\)。注意 \(\mathbf{w}\) 和 \(\mathbf{c}\) 都是向量,不是单个数字。

点积可以是任何实数,还不是概率。余弦相似度也解决不了这个问题:余弦只是把点积除以两个长度,仍然不是概率。解决办法和逻辑回归一样:把分数送进 sigmoid 函数,它能把任何数压到 0 和 1 之间:

向量越相似,这个值越接近 1
\[ P(+\mid w,c)=\sigma(\mathbf{c}\cdot\mathbf{w})=\frac{1}{1+\exp(-\mathbf{c}\cdot\mathbf{w})} \]
"不是邻居"拿到剩下的概率
\[ P(-\mid w,c)=1-P(+\mid w,c)=\sigma(-\mathbf{c}\cdot\mathbf{w})=\frac{1}{1+\exp(\mathbf{c}\cdot\mathbf{w})} \]

这正是一个逻辑回归分类器,区别只在于数字从哪里来。第 4 讲里,特征是固定的,只学权重;这里点积里的两个向量都是学出来的。

一个窗口里有好几个上下文词 Lec 7 · PDF pp. 32–33

上面的公式只处理一个上下文词。一个窗口里有 \(L\) 个上下文词,\(L\) 由窗口大小决定。模型做了一个简化假设:这些上下文词彼此独立。于是概率可以相乘,取对数后乘积变成求和:

\[ P(+\mid w,c_{1:L})=\prod_{i=1}^{L}\sigma(\mathbf{c}_i\cdot\mathbf{w}) \qquad\Rightarrow\qquad \log P(+\mid w,c_{1:L})=\sum_{i=1}^{L}\log\sigma(\mathbf{c}_i\cdot\mathbf{w}) \]

课件用文字说明了独立假设和相乘,这里的公式按指定阅读材料 Jurafsky & Martin 第 6 章写出。负样本的上下文词也按同样的方式处理。

所以,skip-gram 分类器接收一个目标词和一个含 \(L\) 个上下文词的窗口,根据目标词向量与每个上下文向量的相似程度,估计这个窗口出现的可能性。要算这些,只需要给每个词一个向量。

每个词有两个向量 Lec 7 · PDF p. 34

一个词可以扮演两种角色,模型为两种角色分别保存表示:作为目标词时用一个向量,作为上下文词时用另一个向量。所有目标词向量组成矩阵 \(W\),所有上下文向量组成矩阵 \(C\)。词表有 \(|V|\) 个词,模型就保存 \(2|V|\) 个长度为 \(d\) 的向量。

构造训练样本 Lec 7 · PDF p. 36

每个正样本都配上一组负样本。负样本里的词是随机抽的,抽取概率按一元词频加权。课件对目标词 apricot 给出这些例子:

正样本 \((w, c)\)负样本 \((w, c_{neg})\)
apricot, tablespoonapricot, aardvark
apricot, ofapricot, zebra
apricot, jamapricot, where
apricot, aapricot, adversarial

每个正样本配多少个负样本,是我们自己选的设置。损失函数那页写作 \(k\),后面比较方法那页写作 \(K\)。

补充自阅读材料:"加权"是什么意思

课件只说抽样是加权的。Jurafsky & Martin 第 6 章给出了常用的公式:先把每个词的出现次数取 \(\alpha\) 次方(通常取 \(0.75\)),再换算成概率:

\[ P_\alpha(w)=\frac{\operatorname{count}(w)^{\alpha}}{\sum_{w'}\operatorname{count}(w')^{\alpha}} \]

指数小于 1,罕见词被抽中的机会会略微增加。比如两个词的概率原本是 \(0.99\) 和 \(0.01\),取 \(\alpha=0.75\) 后大约变成 \(0.97\) 和 \(0.03\)。第 7 讲第 56 页把这个权重列为 word2vec 的超参数之一。

训练想达到什么 Lec 7 · PDF pp. 37–38

训练从正负样本和 \(2|V|\) 个随机初始化的向量开始,然后朝两个目标调整这些向量:

  • 让目标词向量与真正出现在它窗口里的词的向量更相似。
  • 让目标词向量与随机抽出的负样本词的向量更不相似。

对一个正样本和它的 \(k\) 个负样本,我们希望分类器对真邻居说"是",对每个抽出来的词说"否"。假设这些判断相互独立,全部判对的概率就是一个乘积。损失取这个乘积的负对数,也就是又一次用到了交叉熵损失:

一个正样本和 k 个负样本的负对数似然
\[ L_{CE}=-\log\Big[P(+\mid w,c_{pos})\prod_{i=1}^{k}P(-\mid w,c_{neg_i})\Big] \] \[ =-\Big[\log P(+\mid w,c_{pos})+\sum_{i=1}^{k}\log P(-\mid w,c_{neg_i})\Big] \] \[ =-\Big[\log P(+\mid w,c_{pos})+\sum_{i=1}^{k}\log\big(1-P(+\mid w,c_{neg_i})\big)\Big] \]
代入 sigmoid
\[ =-\Big[\log\sigma(\mathbf{c}_{pos}\cdot\mathbf{w})+\sum_{i=1}^{k}\log\sigma(-\mathbf{c}_{neg_i}\cdot\mathbf{w})\Big] \]

目标词和真邻居的点积越大,第一项越小。目标词和某个抽出来的词的点积越小甚至为负,求和里对应的那一项就越小。所以最小化损失同时推动两个目标。整个语料的总损失,就是把每个目标词与上下文词对的这个量加起来。

用梯度下降调整向量 Lec 7 · PDF pp. 39–43

向量用随机梯度下降学习。从随机值出发,对每个训练样本计算损失对参数的梯度,再朝相反方向走一小步。学习率 \(\eta\) 决定步长,学习率越高,向量移动得越快。当参数或损失不再有明显变化时停止。在整个训练集上,正样本会变得更可能,负样本会变得更不可能。

一个训练样本涉及三类参数:正样本词的上下文向量、每个负样本词的上下文向量,以及目标词向量。它们的梯度是:

\[ \frac{\partial L_{CE}}{\partial \mathbf{c}_{pos}}=\big[\sigma(\mathbf{c}_{pos}\cdot\mathbf{w})-1\big]\,\mathbf{w} \qquad\qquad \frac{\partial L_{CE}}{\partial \mathbf{c}_{neg}}=\big[\sigma(\mathbf{c}_{neg}\cdot\mathbf{w})\big]\,\mathbf{w} \] \[ \frac{\partial L_{CE}}{\partial \mathbf{w}}=\big[\sigma(\mathbf{c}_{pos}\cdot\mathbf{w})-1\big]\,\mathbf{c}_{pos}+\sum_{i=1}^{k}\big[\sigma(\mathbf{c}_{neg_i}\cdot\mathbf{w})\big]\,\mathbf{c}_{neg_i} \]

这和逻辑回归的梯度形状一样:(预测概率 − 正确标签)× 输入。正样本的标签是 1,所以出现 \(\sigma-1\);负样本的标签是 0,所以只剩 \(\sigma\)。从第 \(t\) 步到第 \(t+1\) 步的更新是:

\[ \mathbf{c}_{pos}^{t+1}=\mathbf{c}_{pos}^{t}-\eta\big[\sigma(\mathbf{c}_{pos}^{t}\cdot\mathbf{w}^{t})-1\big]\mathbf{w}^{t} \qquad\qquad \mathbf{c}_{neg}^{t+1}=\mathbf{c}_{neg}^{t}-\eta\big[\sigma(\mathbf{c}_{neg}^{t}\cdot\mathbf{w}^{t})\big]\mathbf{w}^{t} \] \[ \mathbf{w}^{t+1}=\mathbf{w}^{t}-\eta\Big[\big[\sigma(\mathbf{c}_{pos}\cdot\mathbf{w}^{t})-1\big]\mathbf{c}_{pos}+\sum_{i=1}^{k}\big[\sigma(\mathbf{c}_{neg_i}\cdot\mathbf{w}^{t})\big]\mathbf{c}_{neg_i}\Big] \]

看一下符号。\(\sigma-1\) 是负数,所以更新会给正样本的上下文向量加上一点 \(\mathbf{w}\),把两者拉近。\(\sigma\) 是正数,所以更新会从每个负样本的上下文向量里减去一点 \(\mathbf{w}\),把它们推开。课件里"一步梯度下降"的示意图画的就是这件事:apricot 朝 jam 靠近,远离抽出来的那些词。

补充的小数字例子(课件里没有)

取二维向量 \(\mathbf{w}=[1,\,0.5]\)、\(\mathbf{c}_{pos}=[0.5,\,1]\),以及一个负样本 \(\mathbf{c}_{neg}=[1,\,-0.5]\),学习率 \(\eta=0.1\)。

点积分别是 \(\mathbf{c}_{pos}\cdot\mathbf{w}=1.0\) 和 \(\mathbf{c}_{neg}\cdot\mathbf{w}=0.75\)。分类器给真邻居的概率是 \(\sigma(1.0)\approx0.731\),给抽出来的词 \(\sigma(0.75)\approx0.679\)。对负样本来说,第二个值太高了。用自然对数算,损失是 \(-[\log 0.731+\log(1-0.679)]\approx1.450\)。

更新后,\(\mathbf{c}_{pos}\) 变成 \([0.527,\,1.013]\),\(\mathbf{c}_{neg}\) 变成 \([0.932,\,-0.534]\),\(\mathbf{w}\) 变成 \([0.946,\,0.561]\)。正样本的点积升到约 \(1.067\),负样本的点积降到约 \(0.582\),损失降到约 \(1.322\)。只走一步,两个词对就都朝正确方向动了。

最后保留哪个向量? Lec 7 · PDF p. 44

训练得到两套词向量:目标词矩阵 \(W\) 和上下文矩阵 \(C\)。课件说,常见做法是把两者相加,用 \(\mathbf{w}_i+\mathbf{c}_i\) 表示第 \(i\) 个词。

这一页要记住什么
  • word2vec 通过训练一个分类器来学出短、稠密、静态的词向量,最后留下权重,丢掉分类器。
  • 标签由文本自己提供:真邻居是正样本,随机抽出的词是负样本。
  • 分类器就是作用在点积上的逻辑回归:\(P(+\mid w,c)=\sigma(\mathbf{c}\cdot\mathbf{w})\),两个向量都是学出来的。
  • 损失是一个正样本加 \(k\) 个负样本的交叉熵。随机梯度下降把真邻居拉近,把抽出来的词对推开。
  • 每个词有一个目标词向量和一个上下文向量,常见做法是把两者相加。