Word Embeddings And Word Sense
最近正在系统地学习CS224N 2019版,并将所学知识整理成详细笔记以便随时复习巩固这些知识。同时希望可以帮助那些无法及时观看课程的人快速了解课程要点(当然, 亲自上课仍然是最佳选择)。我也尽量将所有课程的知识细节都记录下来,并补充一些相关知识点。
近年来,在NLP领域的发展变化已经相当显著,在过去几年里取得了突破性进展。如今语言模型已逐渐普及开来,并在自然语言处理应用中发挥着关键作用。然而从技术层面来看词嵌入技术基本上就是稀疏向量转化为密集向量的过程这一过程虽然简单却蕴含着深刻的理论意义值得注意的是尽管这一转化看似简单但它仍是自然语言处理发展历程中的一个重要节点它标志着人类对词语意义的理解发生了根本性的转变并为后续的技术发展奠定了基础
Discrete Representation
非结构化数据难以直接用于数据分析是一个挑战。One-Hot编码 是处理此类问题的基本方法。假设我们有一个词表(vocabulary),其中包含V个不同的单词。我们可以给每个单词分配一个唯一的索引i(i ∈ 0到V-1),然后将每个单词表示为一个长度为V的向量,在该向量中只有对应索引位置上的元素为1,其余位置均为0值。以下是一个使用TensorFlow官方文档提供的示例图:

图一 One-Hot编码示例
One-Hot编码虽然简单易用,但是缺点也很明显:
- 该模型所采用的维数设置与词汇总量相当,并且所构建的每一维都极其稀疏,在实际应用中当词汇表规模较大时会导致计算开销显著增加。
- 在这种表示方式下,任何两不同词汇之间都存在正交性,这意味着无法通过One-Hot编码推导出任何词汇间的关联性.
- 这种模型假设下,任何两不同词汇之间的距离具有恒定性,因而无法仅凭两者之间的距离来评估其语义关联程度.
顺便提一下,在解决缺点1的过程中, 其实早些时候已经有一些解决方案出现. 其中较为简单的一种方法就是Hash Trick. 这种方法通过哈希函数为每个单词生成对应的哈希值, 然后将这些哈希值用作词汇在词表中的索引位置. 这样的处理过程使得最终生成的词向量可人为设定其具体长度, 大大减少了稀疏性带来的额外计算负担. 不过这种做法也存在一个问题: 即存在所谓的"碰撞"问题(即多个单词可能映射到同一个索引位置). 尽管如此, 这种方法虽然方法较为直接且粗暴, 但经过实践验证确实是一种有效的解决方案. 不过这种技巧仍然无法为词语意义的理解提供实质性的帮助.

图二 Hash Trick
Distributional Representation
说实话, 其实一直对我所说的 "分布" 这个词感到困惑(可能源于某种历史渊源, 或者说相对于仅由单个非零元素构成的离散性词向量而言? 欢迎各位大神指导!)。我的理解是, 分布式表述是指用连续性的密集型向量来表示词语。这种分布式表述的优势在于, 它能够更加精确地捕捉词语的意义, 并且通过密集型向量可以方便地计算出词语之间的关系(不再是严格的正交关系)。此外, 当引入新词语时, 我们无需增加维数, 只需将新词语映射到一个维度远低于整个词汇表长度的空间即可。
顺带提一下,后续的很多方法都是基于这句富含哲学意义的话:
You shall know a word by the company it keeps.
此指导原则被所有人所认知,在分析过程中,研究者们通过考察不同情境下的词语使用情况来推断其潜在意义。
Word2Vec
Word2Vec是由谷歌研究员于2013年推出的一种方法。我认为它是具有开创性意义的研究成果。因为它不仅能够有效地应用于捕捉词义信息,并且还能够帮助解决其他领域的自然语言处理问题。作为后续内容的一部分,我将首先介绍它大致的算法原理,并详细阐述具体的实现细节。(按课程安排)
Word2Vec系统需要大量的语料进行训练,并采用一种被称为自监督学习(self-supervise learning)的方法进行模型构建。这里的"语料"指的是用于训练的文本数据集合,在NLP领域中这一术语具有明确的专业意义;而"自监督学习"则指的是训练过程本身属于监督学习范畴(可参考那句蕴含哲理的名言)。
在计算过程中采用固定的采样窗口尺寸,在模型中使用中间词c(center word)去推断环境词o(context words)的位置(如图三所示),或者反过来通过环境词推断中间词的位置(如图四所示)。在遍历整个语料库的过程中,默认我们采取中间词推断环境词的方式来进行建模(即计算条件概率P(o|c))。

图三 中间词预测上下文(来自https://www.jianshu.com/p/af8f20fe7dd3)
利用中间词进行环境词的预测,在输入中间词时需要尽量使对应环境词的预测概率最大化;其中目标是最大化P(o|c);其中预测函数定义为:
P(o|c) = \dfrac{exp(u_o^{\prime}\cdot{v_c})}{\sum_w{exp(u_w^{\prime}\cdot{v_c})}}
- 模型参数即是词向量表示(注:在上例中每个模型都包含两个向量u和v)。
为什么每个单词都需要有两个词向量呢?课程中提到这是为了便于数学表达以及更易于优化。实际上只需一个向量即可使用。例如,在实际应用中,可以通过取这两个词向量的平均值来获得最终的词向量。
首先介绍两种经典的词向量模型CBOW和Skip-Gram。此外还有一种有效的降噪技巧叫做Negative Sampling。
CBOW
CBOW(Continuous Bag-of-Words)是基于周边词汇来预测中心词的模型;其预测函数表示为P(c|o)。
Skip-Gram
与CBOW相反,Skip-Gram是由中间词预测环境词的模型,预测函数为P(o|c)。

图表四展示了CBOW与Skip-Gram模型的概念框架(参考来源:https://www.jianshu.com/p/d534570272a6)。需要注意的是,在该图表中左侧网络结构中的隐藏层通常采用平均池化(average pooling)进行数据融合。
Negative Sampling
在词嵌入模型中,默认情况下隐藏层到输出层的映射使用的是softmax函数来计算概率。然后根据这一概率值确定最有可能出现的结果(见归纳中的第三点)。然而,在这个过程中,默认情况下每个词都需要单独计算一次softmax的概率值,并且当进行反向传播时,默认情况下所有的词向量都会被更新。为了避免不必要的计算负担,并且为了降低反向传播过程中的计算开销,在此之后提出了Negative Sampling这一技术作为优化策略。对于Skip-Gram模型而言
其本质是将Negative Sampling应用于将"中间词的周围词语最有可能是什么"这一问题转化为"这些词语作为中间词出现的概率有多大"。
这里进行注释说明, 这里等式本身并不成立, 但为了便于理解, 我们希望通过中间词预测得到与上下文环境相关的词的概率应尽可能接近1, 而通过随机抽取负样本的概率计算应尽可能接近0
这样目标函数改成为
J(\theta) = \frac{1}{T}\sum_{t=1}^{T}J_{t}(\theta)
其中
J_{t}(\theta) = log\sigma(u_o^{T}\cdot{v_c}) + \sum_{i=1}^{k}E_{j\sim{p(w)}}{[log{\sigma{(-u_j^{T}\cdot{v_c})}}]}
k表示负样本(随机词)个数,\sigma表示sigmoid函数。
在上式中左侧表达式代表中间词预测环境词的概率该概率值应尽可能大而右侧项也应尽量最大化注意前面有一个负号因此右侧项也应尽量最大化因为前面有一个负号所以上述目标函数是一个最大化的优化目标当然我们通常会将其转换为最小化优化目标只需要将目标函数取反即可做到这一点需要注意的是关于负采样的策略我们应当遵循怎样的原则也就是上式中的P(w)应该如何确定显然如果我们采用全随机抽样方法是不够合理的因为高频词的作用通常更为重要因此我们需要适当重视高频词的重要性然而如果直接按照单词频率进行抽样会导致低频词被选中的概率极低因此需要采取一些策略来实现既能重视高频词又能均衡低频词被选中的概率
这个概率的一般计算方法是
P(w) = \frac{U(w_i)^{\frac{3}{4}}}{\sum_{j=0}^{|V|}{U(w_j)^{\frac{3}{4}}}}
上式中U表示Uni-Gram,即一元模型分布,也就是词频的统计。
除了Negative Sampling之外还有一个trick被称为Hierarchy Softmax;而Hierarchy Softmax通常应用于CBOW模型中Negative Sampling则多用于Skip-Gram模型。
它将多分类问题转化为多个二分类问题
每个词都被编码为一个由0和1组成的字符串
在CBOW输出时根据编码串的长度来决定要做多少次的二分过程
其中每次二分使用的权值都会基于上一次输出的结果进行调整
由于哈夫曼树是一种前缀码结构因此在探索结果时只需深入到叶节点即可完成匹配操作
这种方法使得探索每个词汇所需的计算量平均仅达到log2(V)水平相对于直接处理所有V个词而言这一改进节省了大量计算资源
Count Based Method
在深度学习兴起之前,在NLP领域的许多任务主要依赖于统计方法的运用。在这些方法中,默认的做法是利用可获取的语料构建文档-词向量矩阵或词之间的关联矩阵。例如:

图五 词-词共现矩阵(来自https://www.cnblogs.com/DjangoBlog/p/6421536.html)
通过不同行之间计算推导出词与词之间的关系。然而,这样的矩阵非常稀疏,请问是否可以通过将每个向量压缩成较短的形式来提高运算效率呢?这时我们可以考虑采用奇异值分解的方法对这一贡献矩阵进行分解分析。

图六 SVD分解示例(来自https://www.cnblogs.com/DjangoBlog/p/6421536.html)
为了计算方便,在分解得到的隐语义向量基础上进行计算会更加高效;这些隐语义向量能够有效地过滤掉一些噪声数据(如那些较小的奇异值),从而提升整体效果;这个技术通常被称为LSA(latent semantic analysis),它具有高度的技术含量。
相较于现有的CBOW、Skip-Gram一类的现代方法而言,传统的LSA类算法具有显著优势。
- 训练更快速
- 高效利用统计属性
但也有一些缺点:
- 只适合用于捕获词义
- 构建词-词共现矩阵需要大量的内存
GloVe
网上关于GloVe的详细解析相对较少。然而随着现代自然语言处理技术的发展趋势逐渐明确,在正式开始使用词嵌入模型之前通常会先对词向量进行系统的训练这一过程如今也变得越来越少。我对这篇论文的整体框架和核心观点有所了解课程中对此处并没有深入讲解。因此在下面的内容中主要对论文中的推理过程进行了直接翻译并将其中一些难以理解的地方进行了个人解读此外还参考了一些其他博文中的相关内容
GloVe最基本的观点是共现概率比可以更好的捕捉到词义 。
先介绍一些符号:
X_{ij} 代表的是词汇j在其词汇i周围的出现频率。
X_i = \sum_{j}{X_{ij}} 即表示所有词汇在与词汇i相邻的位置上总共出现的次数。
P_{ij} = P(j|i) = {X_{ij}}/{X_{i}} 即为词汇j在其词汇i周围的概率值。
接下来我们用一个栗子来说明这个问题。举例来说,在本例中我们有两个单词:word i对应于ice(冰),word j对应于steam(蒸汽)。

图七 共现概率比(来自https://zhuanlan.zhihu.com/p/33138329)
基于
常规的方法下学习共现概率比的方式通常是直接由语料库计算得出的:
F(w_i,w_j,\tilde{w}) = \frac{P_{ik}}{P_{jk}}
其中w_i \in R^d表示词向量,\tilde{w} \in R^d表示邻近词向量。 F 代表词向量函数。
鉴于我们的目标是利用共现概率比来捕捉词义,并考虑到词向量具有线性结构的特点,因此我们决定将这一函数F重新定义为F(w_i-w_j,\tilde{w}) = \frac{P_{ik}}{P_{jk}}。
上式中函数F接受两个向量作为输入,并仅返回一个数值结果。然而该函数可借助复杂模型进行模拟(例如神经网络)。这种映射关系可能会对我们的目标产生不利影响(这可能会使我们试图捕捉到的线性关系被混淆),因此建议采用向量点积计算:
F(({w_i}-{w_j})^{T}\cdot{\tilde{w}_k}) = \frac{P_{ik}}{P_{jk}}
在词-词共现矩阵中,在考虑词语\mathbf{w_i}与其上下文词语\tilde{\mathbf{w_k}}时,两者之间的差异并不显著(差异不大)。这表明它们的位置实际上是可互换的(完全是可以调换位置的)。为了保证模型的对称性(即保持这种对称性),我们需要函数F满足同态映射(homomorphism)性质:
F((\mathbf{w_i} - \mathbf{w_j})^\top \cdot \tilde{\mathbf{w_k}}) = \frac{ F(\mathbf{w_i}^\top \tilde{\mathbf,w_k}) }{ F(\mathbf,w_j^\top \tilde{\mathbf,w_k}) }
由上面的式子我们已经有
F(w_{i}^{T}{\tilde{w}_k}) = P_{ik} = \frac{X_{ik}}{X_{i}}
如果F为exp,再取log,那么可以得到
w_{i}^{T}\tilde{w}_{k} = log(P_{ik}) = log(X_{ik}) - log(X_{i})
由于\log(X_{i})与k无关的原因,在构建模型时将它作为权重向量b_i;从而确保模型的对称性时,则需将对应的\tilde{w}_{k}也相应地进行加权处理,并将其加至对应的\tilde{w}_{k}上得到最终结果:
w_{i}^{T}\tilde{w}_{k} + b_i + \tilde{b}_k = \log(X_{ik})
到这里我们就可以建立一个联系:在共现矩阵中词i与k之间的对数共现次数与其对应的内积与其各自对应的偏置之和相等。需要注意的是,在这个推导过程中存在两个关键问题:
- 在本研究中所使用的词-词共现矩阵是一个高度稀疏的模型,在这种情况下,矩阵中的许多位置都是零值元素;为了避免出现\log(0)导致的计算问题,在构建模型时需要特别注意这一细节。
- 该模型的主要缺点在于对所有的共现值进行统一处理而不加区分;为了提高模型的表现效果,在构建过程中我们引入了一个权值函数f(x)来进行调整。
将损失函数构建成最小二乘形式,则该问题转化为一个最小二乘回归模型
其中,f(x)的性质
f(0)=0;
为了确保低频共现没有过大的权重分配, 函数必须是单调不降的。
应减少高频共现的作用强度, 并避免让高频共现拥有过高的权重。
论文中采用了该指数形式作为权值函数,并在x小于x_max的情况下进行非线性缩放;对于其他情况,则直接设为1
图像如下

图八 权值函数f取α=3/4
评估
为了确保系统的有效性和可靠性, 所有模型都需要接受持续性的性能监控. 为了全面衡量教学效果, 本课程采用了两种主要的评价机制: 内部评价机制(intrins)与外部评价指标(extrinsic).
内在评估意思是在训练好的词向量中评估,例如做类比。类比又可以分为语义类比和语法类比,语义类比就例如像king-man::queen-woman,而语法类比就类似于big-bigger::fast-faster。通过类比计算准确率即可以得到词向量的质量如何。
而外在评估意思是通过一些具体的任务去得到它的性能评估,例如使用不同方法训练出来的词向量用作文本分类、命名实体识别之类的具体NLP任务,计算准确率、召回率、f1分数之类的指标来评估词向量的好坏。
参考资料
- 简书网提供的一篇文章链接
- 简书网另一篇文章路径
- cnblogs博客中的《Django技术详解》系列文章
- 花园书苑发布的技术文章内容
- 沃尔夫兰技术出版部的《GloVe向量模型》电子版论文
