能不能不数长向量,而是直接学出短向量? 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)。
整个方法可以概括为四步:
- 把目标词和真正出现在它附近的词配成一对,作为正样本。
- 从词表里随机抽一些别的词,配成负样本。
- 训练一个逻辑回归分类器,区分这两类词对。
- 把学到的权重当作词向量保留下来。
跟着一个句子走一遍 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:
把两个向量的相似程度变成概率 Lec 7 · PDF pp. 30–31
核心想法是:让这个概率取决于两个词的向量有多相似。两个向量越相似,这个词就越可能出现在目标词旁边。分类器用点积(dot product)来衡量相似程度,记作 \(\mathbf{c}\cdot\mathbf{w}\)。注意 \(\mathbf{w}\) 和 \(\mathbf{c}\) 都是向量,不是单个数字。
点积可以是任何实数,还不是概率。余弦相似度也解决不了这个问题:余弦只是把点积除以两个长度,仍然不是概率。解决办法和逻辑回归一样:把分数送进 sigmoid 函数,它能把任何数压到 0 和 1 之间:
这正是一个逻辑回归分类器,区别只在于数字从哪里来。第 4 讲里,特征是固定的,只学权重;这里点积里的两个向量都是学出来的。
一个窗口里有好几个上下文词 Lec 7 · PDF pp. 32–33
上面的公式只处理一个上下文词。一个窗口里有 \(L\) 个上下文词,\(L\) 由窗口大小决定。模型做了一个简化假设:这些上下文词彼此独立。于是概率可以相乘,取对数后乘积变成求和:
课件用文字说明了独立假设和相乘,这里的公式按指定阅读材料 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, tablespoon | apricot, aardvark |
| apricot, of | apricot, zebra |
| apricot, jam | apricot, where |
| apricot, a | apricot, 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\) 个负样本,我们希望分类器对真邻居说"是",对每个抽出来的词说"否"。假设这些判断相互独立,全部判对的概率就是一个乘积。损失取这个乘积的负对数,也就是又一次用到了交叉熵损失:
目标词和真邻居的点积越大,第一项越小。目标词和某个抽出来的词的点积越小甚至为负,求和里对应的那一项就越小。所以最小化损失同时推动两个目标。整个语料的总损失,就是把每个目标词与上下文词对的这个量加起来。
用梯度下降调整向量 Lec 7 · PDF pp. 39–43
向量用随机梯度下降学习。从随机值出发,对每个训练样本计算损失对参数的梯度,再朝相反方向走一小步。学习率 \(\eta\) 决定步长,学习率越高,向量移动得越快。当参数或损失不再有明显变化时停止。在整个训练集上,正样本会变得更可能,负样本会变得更不可能。
一个训练样本涉及三类参数:正样本词的上下文向量、每个负样本词的上下文向量,以及目标词向量。它们的梯度是:
这和逻辑回归的梯度形状一样:(预测概率 − 正确标签)× 输入。正样本的标签是 1,所以出现 \(\sigma-1\);负样本的标签是 0,所以只剩 \(\sigma\)。从第 \(t\) 步到第 \(t+1\) 步的更新是:
看一下符号。\(\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\) 个负样本的交叉熵。随机梯度下降把真邻居拉近,把抽出来的词对推开。
- 每个词有一个目标词向量和一个上下文向量,常见做法是把两者相加。