因为在组里分享会要讲word2vec,重新整理了之前凌乱的笔记,结果发现有不少新的收获,真是所谓的温故而知新!
word2vec最初是Tomas Mikolov发表的一篇文章[1],同时开源了相应的代码,作用是将所有词语投影到 K 维的向量空间,每个词语都可以用一个 K 维向量表示。
为什么要将词用向量来表示呢?这样可以给词语一个数学上的表示,使之可以适用于某些算法或数学模型。通常将词语表示成向量有如下两种方法。
假如语料库里一共有 N 个词,one-hot表示即是为每个词分配一个唯一的索引,并且将每个词表示为 N 维的向量,在该词索引对应的维度值为1,其余维度均为0。如一共有三个词:今天、天气、真好,那么三个词的词向量分别可以是 [1,0,0],[0,1,0],[0,0,1] 。这种简单的表示方法已经可以解决相当一部分NLP的问题,不过仍然存在不足,即词向量与词向量之间都是相互独立的,我们无法通过这种词向量得知两个词在语义上是否相似,并且如果 N 非常大,这种高维稀疏的表示也有可能引发维度灾难。为了解决上述问题,就有了词向量的第二种表示方法。
word2vec就是通过这种方法将词表示为向量,即通过训练将词表示为限定维度 K 的实数向量,这种非稀疏表示的向量很容易求它们之间的距离(欧式、余弦等),从而判断词与词语义上的相似性。如 K=3 时,我们得到的实数向量可以是 [0.5,0.22,0.7] 这样。
不过Distributed表示法并不是word2vec诞生才有的,这种方法早在1986年Hinton就提出了[2]。word2vec之所以会产生这么大的影响,是因为它采用了简化的模型,使得训练速度大为提升,让word embedding这项技术(也就是词的distributed表示)变得较为实用。
在详细介绍word2vec的原理之前还需要简单的提一下概率语言模型。概率语言模型就是表示语言的基本单位(通常为句子)的概率分布函数。有了语言模型我们可以判断一句话是自然语言的概率,或者让机器开口说话(根据语言模型生成一句话)。概率语言模型的数学表示如下
其中 s 代表当前的sentence, w1 到 wT 为组成该句子的词。context为上下文,因此 p(wt|context) 也就是在给定上下文时出现词 wt 的概率。
根据context的不同划分方法,有可以分为好几种模型。如context=NULL就是上下文无关模型,也就是说我们假设每一个词出现的概率与它的上下文均无关,这显然是不合理的假设。常用的是n-gram语言模型,也就是假设一个词的出现与它前面的n-1个词有关,比如n=2的时候, context=wt−1 。这里的n不能太大,n太大要扫描语料库统计条件概率就很困难了,通常最多为3。其余的模型还有n-pos模型,最大熵模型等等。
上述都是基于统计的方法,还有一些方法可以直接拟合出概率分布函数,比如NNLM(Neural Network Language Model),log-linear等。
回到word2vec,word2vec本质上也就是可以拟合出语言模型概率分布的一种方法。这种方法基于神经网络,输入为distributed表示的词向量,输出为语言模型的概率分布。只不过我们并不关心得到的语言模型,而是保留每个词的词向量。
先介绍一下word2vec的训练过程,目的是想说明这个算法拆解开步骤并不多,初学的时候听说这是deep learning的应用,就感觉高深莫测,其实并不会。
STEP 1:为了训练出词向量,肯定要先准备好语料,巧妇难为无米之炊嘛。我们先将中文语料分好词,分词的方法有很多,这里就不细说了。再去除一些无意义的词,比如纯数字1523523523,乱码fasdfkalsjfwek等等,这样得到的结果看起来会更干净。如果是用google开源的word2vec实现,那就将所有语料保存在一个文本里,可以所有词用一行表示,也可以有换行符\n
,google的代码里自动将换行符替换为<\s>
了,所以在结果词向量里会看到这么一个词,不要觉得奇怪。
STEP 2:扫描语料库,统计每个词出现的次数,保存在一个hash表里。
STEP 3: 根据各词的词频建立哈夫曼树。哈夫曼树是一棵最优二叉树,哈夫曼树中的每个叶子节点都有一个权值,并且所有叶子节点的权值乘上其到根节点路径的长度的累加和最小。因此权值较大的叶子节点往往比较靠近根节点。每个词汇最终都是哈夫曼树的叶子节点,词频就是相应的权值,所有的非叶子节点代表了某一类的词,如下图中的 Wsyn1 ,每一个 Wsyn1 也是与叶子节点的词一样,为 K 维的向量。哈夫曼树建立好以后,每个词都会有一个二进制的哈夫曼编码,用于表示从根节点到该词汇的路径。比如我们假设往左子节点走编码为0,往右为1,下图中红框内的词的哈夫曼编码就是101
。
STEP 4: 初始化词向量与哈夫曼树非叶子节点的向量。向量的维度是我们给定的参数 K ,google的代码里将词向量的每个维度都随机初始化为 ±0.00X ,将非叶子节点每个维度初始化为0。word2vec最终得到的是词向量,但并不是执行一系列复杂的运算,在最终算出一堆的词向量,词向量在一开始就已经存在了,整个运算过程只是不断的迭代优化,让被随机初始化的词向量在词向量空间里逐渐挪到属于它自己的位置。
STEP 5:训练,也就是迭代最优化。再回到语料库,逐句的读取一系列的词,然后用梯度下降算出梯度,再更新词向量的值、非叶子节点处向量的词。通常神经网络训练过程,会指定max epoch,与early stop的patience,并且独立保存一个验证集,用于每一个epoch后用验证集算一下error,然后用early stop策略来控制是否停止训练。但是word2vec的实现更简单粗暴,如果语料文档读完了,就终止,或者是每个线程遍历过的词数超过了一个值。
word2vec用的是神经网络模型,分为两种,cbow与skip-gram,每个模型的训练方法又分别有两种,hierarchical sofmax与negative sampling。
[1] Tomas Mikolov, Kai Chen, Greg Corrado, and Jeffrey Dean. Efficient Estimation of Word Representations in Vector Space. In Proceedings of Workshop at ICLR, 2013.
[2] A. Mnih and G. Hinton. Three new graphical models for statistical language modelling. Proceedings of the 24th international conference on Machine learning,pages 641–648, 2007