作者:寒小阳 && 龙心尘
时间:2016年6月
出处:http://blog.csdn.net/han_xiaoyang/article/details/51567822
http://blog.csdn.net/longxinchen_ml/article/details/51567960
声明:版权所有,转载请联系作者并注明出处
说明:本文为斯坦福大学CS224d课程的中文版内容笔记,已得到斯坦福大学课程@Richard Socher教授的授权翻译与发表
特别鸣谢:@Fantzy同学对部分内容翻译的帮助
课堂笔记:第一部分
春季2016
关键词:自然语言处理(NLP).词向量(Word Vectors).奇异值分解(Singular Value Decomposition). Skip-gram. 连续词袋(CBOW),负采样样本(Negative Sampling)
这是本课程的第一节,我们会先介绍自然语言处理(NLP)的概念和NLP现在所面对问题;然后开始讨论用数学向量代表自然语言词组的设想。最后我们会讨论现行的词向量构造方法。
在最开始咱们先说说什么是NLP。NLP的目的是设计出算法,让计算机“懂得”人类的自然语言,从而为人类执行任务。这些任务分为几个难度等级,例如
容易的任务:
中等难度的任务:
比较有挑战的任务:
在处理所有NLP任务的时候,我们首先需要解决非常重要的一个问题(可能是最重要的):用什么方式将词组输入到模型中去。简单的NLP问题可能并不需要将词组作为独立个体对待(atomic symbols),但现在的问题绝大多数需要这样一个预处理,来体现词组之间关联/相似性和区别。所以我们引入词向量的概念,如果把词编码成词向量,我们很容易从向量的角度去衡量不同的词之间的关联与差异(常用的距离测度法,包括Jaccard, Cosine, Euclidean等等,注:距离测度法,即用一个可观测度量的量来描述一个不能直接观测度量的量)。
我们拿英文举例。
英语中大约有1300万个词组(token,自定义字符串,译作词组),不过他们全部是独立的吗?并不是哦,比如有一些词组,“Feline猫科动物”和“Cat猫”,“Hotel宾馆“和”Motel汽车旅馆”,其实有一定的关联或者相似性在。因此,我们希望用词向量编码词组,使它代表在词组的N维空间中的一个点(而点与点之间有距离的远近等关系,可以体现深层一点的信息)。每一个词向量的维度都可能会表征一些意义(物理含义),这些意义我们用“声明speech”来定义。例如,语义维度可以用来表明时态(过去与现在与未来),计数(单数与复数),和性别(男性与女性)。
说起来,词向量的编码方式其实挺有讲究的。咱们从最简单的看起,最简单的编码方式叫做one-hot vector:假设我们的词库总共有n个词,那我们开一个1*n的高维向量,而每个词都会在某个索引index下取到1,其余位置全部都取值为0.词向量在这种类型的编码中如下图所示:
究其根本你会发现,是你开了一个极高维度的空间,然后每个词语都会占据一个维度,因此没有办法在空间中关联起来。因此我们可能可以把词向量的维度降低一些,在这样一个子空间中,可能原本没有关联的词就关联起来了。
这是一种构造词嵌入(即词向量)的方法,我们首先会遍历所有的文本数据集,然后统计词出现的次数,接着用一个矩阵 X 来表示所有的次数情况,紧接着对X进行奇异值分解得到一个 USVT 的分解。然后用 U 的行(rows)作为所有词表中词的词向量。对于矩阵 X ,我们有几种选择,咱们一起来比较一下。
最初的想法是,我们猜测相互关联的词组同时出现在相同的文件中的概率很高。例如,“银行”、“债券”、“股票”、“钱”等都可能出现在一起。但是,“银行”、“章鱼”、“香蕉”和“曲棍球”可能不会一直一起出现。基于这个想法,我们建立一个词组文档矩阵 X ,具体是这么做的:遍历海量的文件,每次词组i出现在文件j中时,将 Xij 的值加1。不过大家可想而知,这会是个很大的矩阵 R|V|×M ,而且矩阵大小还和文档个数M有关系。所以咱们最好想办法处理和优化一下。
我们还是用一样的逻辑,不过换一种统计方式,把矩阵 X 记录的词频变成一个相关性矩阵。我们先规定一个固定大小的窗口,然后统计每个词出现在窗口中次数,这个计数是针对整个语料集做的。可能说得有点含糊,咱们一起来看个例子,假定我们有如下的3个句子,同时我们的窗口大小设定为1(把原始的句子分拆成一个一个的词):
1. I enjoy flying.
2. I like NLP.
3. I like deep learning.
由此产生的计数矩阵如下:
然后我们对X做奇异值分解,观察观察奇异值(矩阵的对角元素),并根据我们期待保留的百分比来进行阶段(只保留前k个维度):
然后我们把子矩阵 U1:|V|,1:k 视作我们的词嵌入矩阵。也就是说,对于词表中的每一个词,我们都用一个k维的向量来表达了。
对X采用奇异值分解
通过选择前K个奇异向量来进行降维:
这两种方法都能产生词向量,它们能够充分地编码语义和句法的信息,但同时也带来了其他的问题:
当然,有一些办法可以缓解一下上述提到的问题:
不过缓解终归只是缓解,咱们需要更合理地解决这些问题,这也就是我们马上要提到的基于迭代的方法。
现在我们退后一步,来尝试一种新的方法。在这里我们并不计算和存储全局信息,因为这会包含太多大型数据集和数十亿句子。我们尝试创建一个模型,它能够一步步迭代地进行学习,并最终得出每个单词基于其上下文的条件概率。
词语的上下文:
一个词语的上下文是它周围C个词以内的词。如果C=2,句子"The quick brown fox jumped over the lazy dog"中单词"fox"的上下文为 {"quick", "brown", "jumped", "over"}.
我们想建立一个概率模型,它包含已知和未知参数。每增加一个训练样本,它就能从模型的输入、输出和期望输出(标签),多学到一点点未知参数的信息。
在每次迭代过程中,这个模型都能够评估其误差,并按照一定的更新规则,惩罚那些导致误差的参数。这种想法可以追溯到1986年(Learning representations by back-propagating errors. David E. Rumelhart, Geoffrey E. Hinton, and Ronald J.Williams (1988)),我们称之为误差“反向传播”法。
首先,我们需要建立一个能给“分词序列”分配概率的模型。我们从一个例子开始:
"The cat jumped over the puddle."(猫 跳 过 水坑)
一个好的语言模型会给这句话以很高的概率,因为这是一个在语法和语义上完全有效的句子。同样地,这句”stock boil fish is toy”(股票 煮 鱼 是 玩具)就应该有一个非常低的概率 ,因为它是没有任何意义的。在数学上,我们可以令任意给定的n个有序的分词序列的概率为:
有种模型是以{“The”, “cat”, ’over”, “the’, “puddle”}为上下文,能够预测或产生它们中心的词语”jumped”,叫做连续词袋模型。
上面是最粗粒度的描述,咱们来深入一点点,看点细节。
首先,我们要建立模型的一些已知参数。它们就是将句子表示为一些one-hot向量,作为模型的输入,咱们记为x(c)吧。模型的输出记为y(c)吧。因为连续词袋模型只有一个输出,所以其实我们只需记录它为y。在我们上面举的例子中,y就是我们已经知道的(有标签的)中心词(如本例中的”jumped”)。
好了,已知参数有了,现在我们一起来定义模型中的未知参数。我们建立两矩阵, V∈Rn∗|V| 和 U∈R|V|∗n 。其中的n是可以任意指定的,它用来定义我们“嵌入空间”(embedding space)的维度。V是输入词矩阵。当词语 wi (译注: wi 是只有第i维是1其他维是0的one-hot向量)作为模型的一个输入的时候,V的第i列就是它的n维“嵌入向量”(embedded vector)。我们将V的这一列表示为 vi 。类似的,U是输出矩阵。当 wj 作为模型输出的时候,U的第j行就是它的n维“嵌入向量”。我们将U的这一行表示为 uj 。要注意我们实际上对于每个词语 wi 学习了两个向量。(作为输入词的向量 vi ,和作为输出词的向量 uj )。
连续词袋模型(CBOM)中的各个记号:
那这个模型是如何运作的呢?我们把整个过程拆分成以下几步:
用一幅图来表示就是下面这个样子:
通过上面说的种种步骤,我们知道有了矩阵U、V整个过程是如何运作的,那我们怎样找到U和V呢?——我们需要有一个目标函数。通常来说,当我们试图从已知概率学习一个新的概率时,最常见的是从信息论的角度寻找方法来评估两个概率分布的差距。其中广受好评又广泛应用的一个评估差异/损失的函数是交叉熵:
结合我们当下的例子,y只是一个one-hot向量,于是上面的损失函数就可以简化为:
我们用c表示y这个one-hot向量取值为1的那个维度的下标。所以在我们预测为准确值的情况下 ŷ c=1 。于是损失为 −1 log(1) = 0。所以对于一个理想的预测值,因为预测得到的概率分布和真实概率分布完全一样,因此损失为0。现在让我们看一个相反的情况,也就是我们的预测结果非常不理想,此时 ŷ c=0.01 。计算得到的损失为−1 log(0.01) ≈ 4.605,损失非常大,原本这才是标准结果,可是你给了一个非常低的概率,因此会拿到一个非常大的loss 。可见交叉熵为我们提供了一个很好的衡量两个概率分布的差异的方法。于是我们最终的优化函数为:
很上面提到的模型对应的另一种思路,是以中心的词语”jumped”为输入,能够预测或产生它周围的词语”The”, “cat”, ’over”, “the”, “puddle”等。这里我们叫”jumped”为上下文。我们把它叫做Skip-Gram 模型。
这个模型的建立与连续词袋模型(CBOM)非常相似,但本质上是交换了输入和输出的位置。我们令输入的one-hot向量(中心词)为x(因为它只有一个),输出向量为y(j)。U和V的定义与连续词袋模型一样。
Skip-Gram 模型中的各个记号:
对应到上面部分,我们可以把Skip-Gram 模型的运作方式拆分成以下几步:
用一幅图来表示这个过程如下:
我们再次观察一下目标函数,注意到对整个单词表|V|求和的计算量是非常巨大的,任何一个对目标函数的更新和求值操作都会有O(|V|)的时间复杂度。我们需要一个思路去简化一下,我们想办法去求它的近似。
对于每一步训练,我们不去循环整个单词表,而只是抽象一些负面例子就够了!我们可以从一个噪声分布 (Pn(w)) 中抽样,其概率分布与单词表中的频率相匹配。为了将描述问题的公式与负面抽样相结合,我们只需要更新我们的:
Mikolov ET AL.在他的《Distributed Representations of Words and Phrases and their Compositionality》中提出了负面抽样。虽然负面抽样是基于Skip-Gram 模型,它实际上是对一个不同的目标函数进行最优化。考虑一个“词-上下文”对(w,c),令P(D = 1|w, c)为(w, c)来自于语料库的概率。相应的,P(D = 0|w, c) 则是不来自于语料库的概率。我们首先对P(D = 1|w, c)用sigmoid函数建模:
注意这里的 D˜ 表示“错误的”或者“负面的”语料库,像句子”stock boil fish is toy”就是从这样的语料库来的。不自然的句子应该有比较低的发生概率,我们可以从词库中随机采样来产生这样的“负面的”语料库。我们的新目标函数就变成了:
在这里 {u˜k|k=1,⋯,K} 是从(Pn(w))中抽样取到的。需要多说一句的是,虽然关于怎么样最好地近似有许多讨论和研究,但是工作效果最好的似乎是指数为3/4的一元语言模型。至于为什么是3/4,下面有几个例子来帮助大家感性地理解一下:
你看,经过3/4这样一个指数处理,”Bombastic”(少见)被采样的概率是之前的3倍,而“is”这个词(多见)被采样的概率只是稍微增长了一点点。