本文主要来自于斯坦福大学CS224d课程笔记note1,文后给出的范例来自于该课程作业assignment1。在完成该作业的时候参考了如下链接中的代码:
http://blog.csdn.net/han_xiaoyang/article/details/51760923
参考的意思是,在有些无法理解的地方会阅读这个代码,理解之后自己再实现。
这个文档网上流传很广,但其中是有些笔误的,譬如:negative sampling子程序negSamplingCostAndGradient中有两个不同实现方法,重复了不说,其中有一个算法还是有问题的。其它部分好像也还有问题,但印象不深了。
所有代码和CS224d课程笔记note1、作业assignment1都已上传至:
http://download.csdn.net/download/foreseerwang/10246820
有兴趣的请自行下载。如果实在没有C币或积分下载,可联系我QQ:50834。
下面言归正传。注意,我不是对note1的翻译,更多的是介绍个人认为重要的内容,并加以自己的解读。
引入word vector的原因是显而易见的,在此不再赘述。
如果引入word vector,最初级、最直观的方案就是one hot vector。这种方案浪费存储空间还是次要的,更重要的是词与词(向量与向量)之间没有相关性,计算机完全无法进行哪怕一丁点的理解和处理。当然,可以人为再创造一个词与词之间的关系库,这就变成一种基于专家系统的自然语言数字化系统。但可想而知,这个库该有多复杂,实现上该有多困难。
这就引出,要对word进行低维度编码,而词与词之间的关系也可以在编码中得到体现。
首先是基于奇异值分解(SVD)的方法。具体来说,第一步是通过大量已有文档统计形成词空间矩阵X,有两种办法,一种是统计出某篇文档中各个词出现的次数,假设词的数目是W、文档篇数是M,则此时X的维度是W*M;第二种方法是针对某个特定词,统计其前后文中其它词的出现频次,从而形成W*W的X矩阵。第二步是针对X矩阵进行SVD分解,得到特征值,根据需要截取前k个特征值及对应的前k个特征向量,那么前k个特征向量构成的矩阵维度是W*k,这就构成了所有W个词的k维表示向量。
这种方法的主要问题是:
1. 需要维护一个极大的词空间稀疏矩阵X,而且随着新词的出现还会经常发生变化;
2. SVD运算量大,而且每增减一个词或文档之后,都需要重新计算。
其次就是目前常用的基于迭代的方法。也就是构建一个word2vec模型,通过大量文档迭代学习其中的参数及已有词的编码结果,这样每新来一篇文档都不用修改已有模型,只需要再次迭代计算参数和词向量即可(注意:note1中提到Word2vec模型中的参数也需要训练,而在assignment1习题中的模型进行了简化,并没有设置需要训练的参数)。
CBOW和Skip-Gram算法极为相像,在此先介绍一些公共概念:
1. 数据提取:分为目标词和上下文词。目标词可以是整个词空间的任意一个词,上下文词是某篇文章中该目标词前后C窗口内的词。举例来说,针对某篇文章中的这一句:“The cat jumped over the puddle.”,目标词可以是从“The”逐个遍历至最后一个句号“.”的任意一个。假设遍历至“jumped”作为目标词时,窗口尺寸C=2,则上下文词一共4个,为:“The”、“cat”、“over”、“the”。这些单词对应的词向量即是CBOW和Skip-Gram所需要用到的输入和输出;
2. CBOW和Skip-Gram本质上都是一种神经网络,本文及程序中使用的都是三层(包括输入层、一个隐含层和输出层;当然也可以使用多层)。各层的系数可以训练出来,但实际应用时是固定为简单的1;输入输出则是需要训练的词向量,而CBOW和Skip-Gram算法的本质区别就在于输入输出选取的不同(这两种算法完全相反):CBOW是输入上下文、输出目标词;Skip-Gram则相反;
3. 既然本质上是神经网络,那么就存在loss函数的选择。传统来说,CBOW和Skip-Gram在输出层使用的都是softmax函数,用于表征输入词预测输出词的命中概率(整个词空间上的概率值);而该概率与真实输出词的概率(一个one-hot向量)的交叉熵(cross-entropy)即为loss函数;
4. 因为softmax需要计算整个词空间中的命中概率,运算量极大,所以必须使用改进算法,一种是网上可以找到的基于霍夫曼树的Hierarchical Softmax,另一种就是本文后面将要提到的negative sampling;
5. 词向量在输入侧和输出侧的表达式不同,因此,实际上需要学习/训练出两组词向量(输入词向量和输出词向量)。
Continuous Bag of Words Model (CBOW)算法:如上所述,CBOW算法使用上下文窗口内词向量作为输入,将这些向量求和(或取均值)后,求得与输出词空间的相关性分布,进而使用softmax函数得到在整个输出词空间上的命中概率,与目标词one-hot编码的交叉熵即为loss值,通过loss针对输入和输出词向量的梯度,即可使用梯度下降(gradient descent)法得到一次针对输入和输出词向量的迭代调整。
Skip-Gram算法:使用目标词向量作为输入,求得其与输出词空间的相关性分布,进而使用softmax函数得到在整个输出词空间上的命中概率,与one-hot编码的上下文词逐一计算交叉熵,求和后即为loss值,通过loss针对输入和输出词向量的梯度,即可使用梯度下降(gradient descent)法得到一次针对输入和输出词向量的迭代调整。
按照如上算法,经过多轮迭代后即可得到输入和输出两组词向量。
负采样算法(negative sampling)
上面提到,传统算法中需要使用softmax计算输入词向量在整个输出词向量上的命中概率,在词向量空间极大的情况下,计算量极大。因此,Mikolov et al.提出了negative sampling算法。
negative sampling的核心思想是修改loss函数,把loss分为两部分:一部分是输入词和输出词的相关性;另一部分是输入词与一些不相关词的非相关性(这两部分都需要加负号,以匹配loss的要求,优化是最小化loss的过程)。那这些不相关词如何选取?个人觉得非常巧妙,就是从输出词空间中按照某种概率随机选择,这个概率是输出词空间各词向量出现概率的0.75次方。
以上即为对Word2vec(CBOW和Skip-Gram)算法的个人理解,不是很系统,但记录了自己在学习过程中的一些印象深刻的地方。如果想系统了解,建议细读相关论文和实现代码。
按照上述方法,实现了cs224d的作业assignment1,最终词向量空间示意图结果如下:
所有代码和相关文档请从如下链接下载:http://download.csdn.net/download/foreseerwang/10246820