Word2Vec 与词嵌入

词向量的产生思路

一个“单词” (word) 在自然语言处理中,通常被表示为一个离散的符号。然而,计算机无法直接理解这些符号的含义。为了让计算机能够处理和理解文本数据,我们需要将单词转换为一种数值形式,这就是词向量 (word vector) 的概念。

一种自然的方法是使用独热编码 (one-hot encoding)。在这种方法中,我们为每个单词创建一个与词汇表大小相同的向量,向量中只有一个位置为1,其余位置均为0。这样,每个单词都被唯一地表示为一个高维稀疏向量。其缺陷是显然的:

  1. 向量维度与词汇表大小线性,导致高维稀疏表示,计算与存储效率低下。

  2. 独热编码无法捕捉单词之间的语义关系。向量之间两两正交,若想表达单词的语义关系需要额外引入复杂的模型。

因此,我们需要的是一种能够捕捉单词语义关系的低维密集表示。这就是词嵌入 (word embedding) 的目标。词嵌入将单词映射到一个连续的向量空间中,使得语义相似的单词在向量空间中距离较近。

如何衡量一个模型?Skipgram方法

由于词数实在太多,且自然语言的语义关系复杂, 模糊性 (Vagueness), 多义性 (Polysemy), 歧义性 (Ambiguity) 我们无法直接手动设计一个好的词嵌入模型。相反,我们需要通过定义什么样的词嵌入是“好的”,从而在大量文本数据上进行自监督学习。

Word2Vec11 Original Paper 提出了两种经典的方法来训练词嵌入模型:Skip-gram 和 Continuous Bag of Words (CBOW)。这里我们重点介绍 Skip-gram 方法。

一个关键的假设是“语境决定意义”22 You shall know a word by the company it keeps. – J.R. Firth。 也就是说,一个单词的意义可以通过它周围的单词来理解,也可以仅仅通过它出现的上下文来推断。因此,很自然地我们可以引入一个类似“滑动窗口”的概念,在文本中选取一个中心单词及其周围的上下文单词。

考虑一个单词表 𝒱︀,大小为 |𝒱︀|。我们在文本中选取一个中心单词 𝑐, 并根据 𝑐 的位置选取其前后的一个滑动窗口中的一个周边单词 𝑜。Skip-gram 方法的目标是最大化在给定中心单词 𝑐 的情况下,预测周边单词 𝑜 的概率 𝑃(𝑜|𝑐)

为了实现这一目标,我们引入两个嵌入矩阵:针对中心单词的嵌入矩阵 𝑉𝑅𝒱︀|×𝑑 和针对周边单词的嵌入矩阵 𝑈𝑅|𝒱︀|×𝑑。这里,𝑑 是词向量的维度,通常远小于词汇表的大小 |𝒱︀|。这时,我们可以将中心单词 𝑐 和周边单词 𝑜 分别表示为它们在嵌入矩阵中的向量表示 𝑣𝑐𝑢𝑜。则在给定中心单词 𝑐 的情况下,预测周边单词 𝑜 的概率可以通过以下公式计算:

𝑃(𝑜|𝑐)=exp(𝑢𝑜𝑇𝑣𝑐)𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)

这可以看成一个典型的 softmax 函数,这和 Transformer 中的自注意力机制(Self-Attention)有些类似.
Attention(𝑄,𝐾,𝑉)=Softmax(𝑄𝐾𝑇𝑑𝑘)𝑉
其中也是根据Q和K进行归一化并计算权重.
进行归一化的是两个词嵌入向量的点积 (dot product)。需要注意的是,我们如此建立的模型实际上可以视作一种对于真实的单词关联矩阵 𝑀𝑅|𝒱︀|×|𝒱︀| 的低秩近似 (low-rank approximation)。它将一个极其高维的信息压缩到了低维空间中,这个过程中不可避免地会引入信息的丢失。这也就是所谓的嵌入误差 (embedding error),在表观上就体现为内积的限制性:例如词向量的内积只能表示线性关系,而无法捕捉更复杂的非线性关系;一个词 𝑎 可以与词 𝑏 具有很强的语义关联,但与词 𝑐 的关联却很弱,这种不对称关系在内积中难以体现。通常此类误差在机器学习的任务中是具有正向作用的Ilya:压缩即智能。,因为它大大提高了模型的泛化能力 (generalization),避免了过拟合 (overfitting)。

我们的最终目标是最大化在整个文本语料库中所有中心单词和周边单词对 (𝑐,𝑜) 上的对数似然函数,也就是最小化交叉熵损失 33 如果你不清楚为什么需要对数似然函数,一个状态的概率可以通过多个独立事件的概率乘积来计算,而对数将乘积转换为和,便于计算和优化。习惯上我们喜欢最小化损失函数,因此通常会取负对数似然. (cross-entropy loss):

min𝑈,𝑉𝔼𝑜,𝑐[log𝑝𝑈,𝑉(𝑜|𝑐)]

其中的期望是基于真实数据分布 𝑃data(𝑜,𝑐) 计算的。我们考虑以上的滑动窗口情景:考虑整体数据集 𝒟︀ 由一系列单词序列组成,每个序列 𝑑𝒟︀ 包含 𝑚𝑑 个单词 𝑤1,𝑤2,,𝑤𝑚𝑑。对于每个单词 𝑤𝑡,我们将其作为中心单词 𝑐,并选取其前后 𝑘+ 个单词作为周边单词 𝑜。这样,我们可以将 (2) 中的期望展开为:

𝐿(𝑈,𝑉)=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0log𝑃(𝑜=𝑤𝑖+𝑗|𝑐=𝑤𝑖)

随后便是标准的利用随机梯度下降 (SGD) 或其变种(如 Adam)来优化该损失函数,从而学习嵌入矩阵 𝑈𝑉 的过程。具体地,我们可以分析一下某些参数的梯度,例如针对任一中心单词嵌入向量 𝑣𝑐 的梯度:

展开求和梯度项细节如下:
𝛁𝑣𝑐log(𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐))=1𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝛁𝑣𝑐𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)=1𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝛁𝑣𝑐(𝑢𝑤𝑇𝑣𝑐)=1𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝑢𝑤=𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)𝑢𝑤=𝑤𝒱︀𝑃(𝑤|𝑐)𝑢𝑤=𝔼𝑤|𝑐[𝑢𝑤]

𝛁𝑣𝑐𝐿(𝑈,𝑉)=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0𝛁𝑣𝑐log𝑃(𝑜=𝑤𝑖+𝑗|𝑐=𝑤𝑖)=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0(𝛁𝑣𝑐log𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐)exp(𝑢𝑜𝑇𝑣𝑐))=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0(𝛁𝑣𝑐log(𝑤𝒱︀exp(𝑢𝑤𝑇𝑣𝑐))𝛁𝑣𝑐(𝑢𝑜𝑇𝑣𝑐))=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0(𝑤𝒱︀𝑃(𝑤|𝑐)𝑢𝑤𝑢𝑜)=𝑑𝒟︀𝑖=1𝑚𝑑𝑘𝑗𝑘𝑗0(𝔼𝑤|𝑐[𝑢𝑤]𝑢𝑜)

也就是说在梯度下降至最终收敛时,需要使得中心词 𝑐 周边的词向量的期望接近于实际观测到的周边词向量 𝑢𝑜。这也就是我们所想达成的:通过中心词统计性的预测其周边词。

负采样 (Negative Sampling)

我们成功引入了一个模型来学习词嵌入,并很好地地定义了损失函数并证明了其满足我们的目标。然而,在实际应用中,每次梯度下降时会调整整个词汇表的嵌入向量;而查看我们损失函数中的核心部分 (1),可以发现计算 𝑃(𝑜|𝑐) 时未归一化的分子是容易的:仅仅涉及到中心词 𝑐 和周边词 𝑜 的嵌入向量的点积计算;但归一化的分母却涉及到整个词汇表 𝒱︀ 中所有单词的嵌入向量的点积计算,这在词汇表较大时会非常耗时。分母的归一化项(也称配分函数 (partition function))使得模型的训练变得非常低效。

在 Word2Vec 的原始文献中,作者使用了负采样的思路来近似地优化该损失函数(SGNS, Skip-gram with Negative Sampling)。负采样的核心思想是:对于每一个正样本对 (𝑐,𝑜)(即中心词 𝑐 和其真实的周边词 𝑜),我们随机采样若干个负样本对 (𝑐,𝑜),其中 𝑜 是从词汇表中随机选择的单词,这些负样本被视为与中心词 𝑐 不相关的词。通过这种方式,我们将原始的多分类问题转化为多个二分类问题,从而大大简化了计算。

具体地,给定一个正样本对 (𝑐,𝑜),我们希望 𝑢𝑜𝑣𝑐 的点积尽可能大,也就是

𝑃(𝐷=1|𝑐,𝑜)=𝜎(𝑢𝑜𝑇𝑣𝑐)

这里的 𝜎(𝑥)=11+𝑒𝑥 是标准的 sigmoid 函数

给定一个负样本对 (𝑐,𝑜),我们希望 𝑢𝑜𝑣𝑐 的点积尽可能小,也就是

𝑃(𝐷=0|𝑐,𝑜)=1𝜎(𝑢𝑜𝑇𝑣𝑐)=𝜎(𝑢𝑜𝑇𝑣𝑐)

二者结合,我们可以定义负采样的损失函数为:

𝐿NS(𝑈,𝑉)=log𝜎(𝑢𝑜𝑇𝑣𝑐)𝑖=1𝑘log𝜎(𝑢𝑜𝑖𝑇𝑣𝑐)

但问题是我们并未良好地定义如何采样负样本 𝑜。如果完全随机选,那么模型将会以相当高的概率采样到一些非常常见的词(如“the”,“is”,“and”等),这些词在大多数上下文中都可能出现,导致模型难以学习到有意义的语义关系。为了解决这个问题,Word2Vec 提出了使用一个调整过的采样分布 𝑃𝑛(𝑤) 来选择负样本:

𝑃𝑛(𝑤)=𝑓(𝑤)34𝑤𝒱︀𝑓(𝑤)34

在这个分布中,𝑓(𝑤) 是单词 𝑤 在语料库中的频率。通过将频率提升到 34 次方,我们降低了高频词的采样概率,同时增加了低频词的采样概率。依据此概率分布进行负样本采样即可。

在 SGNS(Skip-gram with Negative Sampling)中,若语料、窗口和优化器都不变,只把负样本数 𝑘 从 5 提到 40。下面哪项判断最准确?

GloVe(Global Vectors for Word Representation)

除了 Word2Vec 之外,另一种流行的词嵌入方法是 GloVe44 Original Paper。GloVe 的核心思想是利用全局的词共现矩阵来学习词嵌入。 与 Word2Vec 主要关注局部上下文不同,GloVe 通过统计整个语料库中单词对的共现频率来捕捉词语之间的全局语义关系。

GloVe 方法首先构建一个词共现矩阵 𝑋,其中 𝑋𝑖,𝑗 表示单词 𝑗 在单词 𝑖 的上下文中出现的次数。那么我们定义 𝑋𝑖=𝑘𝑋𝑖,𝑘 为任意单词 𝑘 出现在单词 𝑖 上下文中的总次数。进一步,我们定义条件概率 𝑃𝑖,𝑗=𝑃(𝑗|𝑖)=𝑋𝑖,𝑗𝑋𝑖,表示在给定单词 𝑖 的情况下,单词 𝑗 出现在其上下文中的概率。根据先前对于词嵌入的分析,词嵌入对应的单词 𝑗 出现在单词 𝑖 上下文中的概率应与它们的向量表示的内积相关。具体的即

𝑄𝑖,𝑗=softmax(𝑢𝑗𝑇𝑣𝑖)=exp(𝑢𝑗𝑇𝑣𝑖)𝑘exp(𝑢𝑘𝑇𝑣𝑖)

而该分布对应的交叉熵损失函数为

𝐿=𝑖corpus𝑗context(𝑖)log𝑄𝑖,𝑗

由于这个求和式遍历了整个语料库中的所有单词对,我们可以利用词共现矩阵 𝑋 来重写该损失函数:每对单词 (𝑖,𝑗) 出现 𝑋𝑖,𝑗 次,因此我们可以将损失函数重写为

𝐿=𝑖𝑗𝑋𝑖,𝑗log𝑄𝑖,𝑗

在GloVe中,遇到的一个现实问题是矩阵 𝑄 必须是高度归一化的,而这在大规模语料库中计算非常昂贵。为了解决这个问题,我们转而采用最小二乘损失函数,定义为

𝐿=𝑖𝑗𝑋𝑖,𝑗(log𝑋𝑖,𝑗𝑢𝑗𝑇𝑣𝑖)2

这是一个标准的回归问题,我们的目标是最小化预测的内积 𝑢𝑗𝑇𝑣𝑖 与实际的对数共现频率 log𝑋𝑖,𝑗 之间的平方误差。另一个现实问题是某些单词的出现频率很高,如果简单地采用线性的统计方式会导致这些高频词对损失函数的贡献过大,从而影响模型的训练效果。为了解决这个问题,GloVe 引入了一个加权函数 𝑓(𝑋𝑖,𝑗) 来调整每对单词对的权重。加权函数包含非线性压平(类似 Word2Vec 中的负采样的分布构造),或者者直接截断(忽略那些出现频率过高的单词对)。最终的损失函数变为

𝐿=𝑖𝑗𝑓(𝑋𝑖,𝑗)(𝑢𝑗𝑇𝑣𝑖log𝑋𝑖,𝑗)2

词嵌入的评估 (Evaluation)

广义来说任何机器学习模型的性能评估都可以分为两类:内在评估 (intrinsic evaluation) 和外在评估 (extrinsic evaluation)。

我们将介绍常见的几种词嵌入的内在评估方法。

类比 (Analogy)

一个经典的内在评估方法是类比任务 (analogy task)。该任务基于这样一个假设:词嵌入向量之间的线性关系可以反映词语之间的语义关系。例如,“king” 与 “queen” 之间的关系类似于 “man” 与 “woman” 之间的关系。也就是

king:queen::man:woman

在词嵌入空间中,我们希望词向量之间满足类比之间的线性关系。具体地,给定三个词 𝑎,𝑏,𝑐,找寻类比 𝑎:𝑏::𝑐:𝑑 中的词 𝑑 时应当满足

𝑣𝑏𝑣𝑎+𝑣𝑐𝑣𝑑

那么我们寻找的词 𝑑 应当是使得上述等式成立的词。我们可以通过计算所有词向量与 𝑣𝑏𝑣𝑎+𝑣𝑐 之间的余弦相似度 (cosine similarity) 来找到最接近的词向量,从而确定词 𝑑55 这里默认词向量已经过归一化处理,因此计算余弦相似度时无需除以向量的模长。

𝑑=argmax𝑖(𝑣𝑏𝑣𝑎+𝑣𝑐)𝑇𝑣𝑖𝑣𝑏𝑣𝑎+𝑣𝑐

词相似性 (Correlation)

另一种常见的内在评估方法是词相似性任务 (word similarity task)。该任务基于这样一个假设:语义上相似的词在词嵌入空间中应该距离较近。我们可以使用一些标准的词相似性数据集,这些数据集包含了一系列词对及其人工标注的相似度评分。通过计算词嵌入向量之间的余弦相似度,并与人工评分进行比较,我们可以评估词嵌入模型的质量。具体地,给定一个词对 (𝑤𝑖,𝑤𝑗),我们计算其词向量之间的余弦相似度:

cos_sim(𝑤𝑖,𝑤𝑗)=𝑣𝑤𝑖𝑇𝑣𝑤𝑗𝑣𝑤𝑖𝑣𝑤𝑗

然后,我们可以计算所有词对的余弦相似度与人工评分之间的相关系数(如皮尔逊相关系数或斯皮尔曼等级相关系数),以评估词嵌入模型的性能。

多义词 (Polysemy)

词嵌入模型在处理多义词 (polysemy) 时面临挑战。多义词是指那些具有多个含义的词,例如 “bank” 可以表示银行或河岸。传统的词嵌入模型通常为每个单词生成一个固定的向量表示,这可能无法充分捕捉多义词的不同含义。为了解决这个问题,一个传统的方法是针对不同语义的同一个词创建多个词向量表示(multi-prototype embeddings)。这种方法通过聚类技术将同一个词在不同上下文中的出现进行分类,从而为每个类别生成一个独立的词向量表示。它的思路可以通过以下步骤实现:

  1. 收集大量文本数据中所有目标词的上下文
  2. 将这些上下文的向量表示进行加权平均(可以使用 idf 等方法,以突出重要的上下文词)
  3. 对这些上下文向量进行聚类分析,确定不同的语义类别
  4. 为每个类别单独训练一个词向量表示

这是一个“伪动态”的方法。我们必须先聚类、先存好这几个固定的含义。如果有一个新含义没聚类出来,还是没法处理。现在的 ELMo 和 BERT/Transformer 彻底解决了这个问题。它们不再需要预先聚类,而是根据上下文实时动态生成向量。在 BERT 里,同一个 “bank”,在每一句话里的向量都是独一无二的。