这个专题的笔记是我在学习斯坦福大学的公开课程CS224N:Deep Learning For Nature Language Processing时所做的一些课程笔记。这是第二部分,主要内容是Word2Vec模型。

Word2vec

基于迭代的模型

基于SVD的方法实际上考虑的都是单词的全局特征,因此计算代价和效果都不太好,因此我们可以尝试建立一个基于迭代的模型并根据单词在上下文中出现的概率来进行编码。

另一种思路是设计一个参数是词向量的模型,然后针对一个目标进行模型的训练,在每一次的迭代中计算错误率或者loss函数并据此来更新模型的参数,也就是词向量,最后就可以得到一系列结果作为单词的嵌入向量。

常见的方法包括bag-of-words(CBOW)和skip-gram,其中CBOW的目标是根据上下文来预测中心的单词,而skip-gram则是根据中心单词来预测上下文中的各种单词出现的概率。这两种方法统称为word2vec,是Google AI团队在《Distributed Representations of Words and Phrases and their Compositionality》 等多篇论文中提出的词向量训练模型。

而这些模型常用的训练方法有:

  • 层级化(hierarchical)的softmax

  • 负采样negative sampling:判断两个单词是不是一对上下文与目标词,如果是一对,则是正样本,如果不是一对,则是负样本,而采样得到一个上下文词和一个目标词,生成一个正样本,用与正样本相同的上下文词,再在字典中随机选择一个单词,这就是负采样

语言模型Language Model

语言模型可以给一系列单词序列指定一个概率来判断它是不是可以作为一个完整的句子输出,一般完整性强的,语义通顺的句子被赋予的概率会更大,而无法组成句子的一系列单词可能就会计算出非常小的概率,语言模型用公式来表示就是要计算下面的概率: 如果我们假设每个单词在每个位置出现的概率是独立的话,这个表达式就会变成: 但这显然是不可能的,因为单词的出现并不是不受约束的,而是和其周围的单词(也就是上下文)有一定的联系,这也是NLP中一个非常重要的观点,那就是

分布式语义(Distributional semantics):一个词语的意思是由附近的单词决定的,一个单词的上下文就是"附近的单词",可以通过一个定长的滑动窗口来捕捉每个单词对应的上下文和语义。

Bi-gram model认为单词出现的概率取决于它的上一个单词,即: 但显然这也是一种非常的的模型,说白了就是一个一阶的马尔科夫链,只是一种非常理想化特殊化的模型,因此我们需要寻找更合适的语言模型。

事实上上面提出的CBOW和Skip-gram也可以看成是一种简单的语言模型,我们通过训练一个语言模型的方式来训练出文档中出现的单词的词向量。

连续词袋模型(CBOW)

连续词袋模型(Continuous Bag of Words Model)是一种根据上下文单词来预测中心单词的模型,对于每一个单词我们需要训练出两个向量u和v分别代表该单词作为中心单词和作为上下文单词的时候的嵌入向量(一种代表输出结果,一种代表输入)

因此可以定义两个矩阵 其中n代表了嵌入空间的维度,矩阵的每一列代表了词汇表中一个单词作为上下文单词时候的嵌入向量,用 表示,而矩阵表示的是预测的结果,每一行代表了一个单词的嵌入向量,用 表示。

这里其实隐含了一个假设,那就是单词作为上下文和中心词的时候具有的特征是不一样的,因此要用不同的词向量来表示。

CBOW模型的工作原理

  1. 首先根据输入的句子生成一系列one-hot向量,假设要预测的位置是c,上下文的宽度各为m,则生成的一系列向量可以表示为:

  2. 计算得到上下文单词的嵌入向量:

  3. 求出这些向量的均值:

  4. 生成一个分数向量,当相似向量的点积较大时,会将相似的词推到一起,以获得较高的分数

  5. 用softmax函数将分数向量转化成概率分布:

  6. 根据概率分布得到最终的结果y(是一个one-hot向量的形式),也就是最有可能出现在中心位置的单词

因此现在的问题就变成了,我们应该如何学习到两个最关键的矩阵,这也是CBOW最关键的一个问题,这里可以采用交叉熵来定义一个目标函数: 因为概率的真实值是一个one-hot向量,只有一个维度是1其他的都是0,因此可以进行这样的化简。

损失函数与优化方式

我们可以定义如下形式的损失函数并进行优化: 事实上这里的loss function就是上面的交叉熵的进一步推导。我们可以采用随机梯度下降SGD的方式来进行模型的求解和参数的更新。

Skip-gram模型

Skip-gram模型是一种给定中心单词来预测上下文中各个位置的不同单词出现概率的模型,模型的工作原理可以用如下几个步骤概括:

  1. 生成中心单词的one-hot向量作为输入

  2. 得到输入单词的嵌入向量

  3. 将分数向量用softmax转化成对应的概率分布,则 是预测出的对应位置中出现的单词的概率分布,每个概率向量的维度都和词汇表的大小一样

  4. 根据概率分布生成对应的one-hot向量,作为最终的预测结果,这里就是将概率最大的那个维度对应的单词作为结果。

参数的求解和模型的优化

与CBOW类似,可以采用类似的方法来优化模型的参数,首先Skip-gram模型是通过中心单词c来预测上下文单词o的出现概率,这一过程可以表示为: 而其损失函数可以表示为: 这样一来损失函数J对于词向量的导数分别可以用如下方式来计算,首先来看中心词向量v的导数: 我们首先要搞清楚真实的结果y是一个one-hot向量,所以上述步骤是可以成立的,而对于向量,则要分情况讨论,当w=o的时候,有 而当w是其他值的时候,有: 考虑到真实结果y是一个one-hot向量,上面两个式子又可以合并为: - 上面的这些公式推导在CS224N的编程作业中可能会用到,当然作业中是用numpy手动实现梯度的计算,一般情况这些东西都用和loss.backward()optim.step()来解决 - 不过初学的时候推一推公式还是很有意思的,当然我现在已经忘记光了

负采样Negative Sampling

上面提到的算法中,时间复杂度基本都是的(比如softmax计算中,会消耗大量的算力),也就是词汇表的大小,而这个规模往往是比较大的,因此我们可以想办法逼近这个值。

在每一次的迭代中,不是遍历整个词汇表而是进行一些负采样操作,我们可以从一个噪声分布中"采样",其概率与词汇表频率的顺序匹配。

负采样可以理解为单词和短语及其构成的分布式表示,比如在skip-gram模型中,假设一对单词和上下文,我们用来表示一对单词和上下文是否属于训练集中出现过的内容,首先可以用sigmoid函数来定义这个概率: 这里的v就是上面提到的嵌入向量,然后我们可以构建一个新的目标函数来试图最大化一个单词和上下文对属于训练文本的概率,也就是采用极大似然法来估计参数,这里我们将需要求的参数记作,则极大似然估计的目标可以表示为: 因此可以损失函数可以等价地采用如下形式,相对的,我们需要求的就是损失函数的最小值:

这里的集合 表示负采样集合,也就是一系列不属于训练文本的中心词和上下文的对构成的一个集合,我们可以通过根据训练文本随机生成的方式来得到负采样集合。这样一来CBOW的损失函数就变成了:

而Skip-gram的新损失函数就变成了:

这里的是随机采样产生的K个不是当前中心词的上下文的单词,在skip-gram模型中,梯度可以表示为:

而对于和负采样向量,其梯度是:

全局词向量GloVe

已有的单词表示方法

到现在为止已经介绍过的单词表示方法主要分为两种,一种是基于次数统计和矩阵分解的传统机器学习方法,这类方法可以获取全局的统计信息,并很好的捕捉单词之间的相似性,但是在单词类比之类的任务重表现较差,比较经典的有潜在语义分析(LSA)等等

另一类是上面提到的基于"浅窗口"(shallow window based)的方法,比如CBOW和Skip-gram,通过局部的上下文来进行上下文或者中心单词的预测,这样的方法可以捕捉到复杂的语言模式和上下文信息,但是对全局的统计信息知之甚少,缺少"大局观",这也是这些模型的缺点。

而GloVe则使用一些全局的统计信息,比如共生矩阵,并使用最小二乘损失函数来预测一个单词出现在一段上下文中的概率,并且在单词类比的任务上取得了SOTA的效果。

GloVe算法

首先用来表示训练集的共生矩阵,而表示单词i的上下文中出现单词j的次数,用来表示矩阵中第i行的和则这样一来:

可以表示单词j在单词i的上下文中出现的概率。我们在Skip-gram中,用Softmax来计算单词j出现在i的上下文中的概率:

而训练的过程中用到的损失函数如下所示,又因为上下文的关系在样本中可能会多次出现,因此按照如下方式进行变形:

交叉熵损失的一个显著缺点是它要求分布Q被适当地标准化,因此可以换一种方式,也就是使用基于最小二乘的目标函数来进行优化求解,就可以不用进行标准化了:

但是这样子的目标函数又带来了一个新的问题,那就是 往往是一个比较大的数字,这会对计算量造成很大的影响,一个有效的方法是取对数:

结论

GloVe模型通过只训练共生矩阵中的非零元素充分利用了训练样本的全局信息,并且提供了一个拥有比较有意义的子结构的向量空间,在单词类比的任务中表现优于传统的Word2Vec

词向量的评估

目前已经学习了一系列将单词用词向量来表示的方法,接下来的这一部分主要探讨如何评估生成的词向量的质量的好坏。

内部评估

内部评价(Intrinsic Evaluation)是对生成的一系列词嵌入向量,用一些子任务来验证嵌入向量的性能优劣,并且一般这样的子任务是比较容易完成的,这样可以快速反馈词向量训练的结果。

外部评估

外部评估是用一些实际中的任务来评估当前训练获得的词向量的性能,但是在优化一个表现不佳的外部评估系统的时候我们无法确定是哪个特定的子系统产生的错误,这就激发了对内在评估的需求。