在许多自然语言处理任务中,许多单词表达是由他们的tf-idf分数决定的。即使这些分数告诉我们一个单词在一个文本中的相对重要性,但是他们并没有告诉我们单词的语义。Word2Vec是一类神经网络模型——在给定无标签的语料库的情况下,为语料库的单词产生一个能表达语义的向量。
word2vec是Google开源的一款用于词向量计算 的工具,可以很好的度量词与词之间的相似性;
word2vec建模是指用CBoW模型或Skip-gram模型来计算不同 词语的向量(word vector) 这里有一个关于word2cev的实战代码
CBoW是给定上下文来预测目标词、Skip-gram给定输入词预测上下文,但最终都会得到下图的“词向量矩阵W”
Statistical Language Model (统计语言模型)
在深入word2vec之前,首先回顾下nlp中的一个基本问题:如何计算一段文本序列在某种语言下出现的概率?(Ngram)统计语言模型给出了这一类问题的一个基本解决框架。对于一段文本序列:$S=w_{1},w_{2},...,w_{T}$它的概率可表示为:$P(S)=P(w_{1},w_{2},...,w_{T})=\prod_{t=1}^{T}p(w_{t}|w_{1},w_{2},...,w_{t-1})$,即将序列的联合概率转化为一系列条件概率的乘积。问题变成了如何去预测这些给定previous words下的条件概率:$p(w_{t}|w_{1},w_{2},...,w_{t-1})$,由于其巨大的参数空间,这样一个原始的模型在实际中并没有什么用。我们更多的是采用其简化版本——Ngram模型:$p(w_{t}|w_{1},w_{2},...,w_{t-1})\approx p(w_{t}|w_{t-n+1},...,w_{t-1})$
常见的如bigram模型(N=2)和tirgram模型(N=3)。事实上,由于模型复杂度和预测精度的限制,我们很少会考虑N>3的模型。我们可以用最大似然法去求解Ngram模型的参数——等价于去统计每个Ngram的条件词频。为了避免统计中出现0概率问题(一段从未在训练集中出现过的Ngram片段会使整个序列的概率为0),人们基于原始的Ngram模型进一步发展出了back-off trigram模型(用低阶的bigram和unigram代替0概率的trigram)和interpolated trigram模型(将条件概率表示为unigram、bigram、trigram三者的线性函数)。
Distributed Representation(分布特征表示语义)
不过Ngram模型仍有局限性。首先无法处理N>3的词序列(实际应用中>3会发生参数空间的爆炸式增长,维度灾难问题),其次它并没有考虑词与词之间的内在联系性(只是概率的词频统计)。例如,考虑"the cat is walking in the bedroom"这句话。如果我们在训练语料中看到了很多类似“the dog is walking in the bedroom”或是“the cat is running in the bedroom”这样的句子,那么,即使我们没有见过这句话,也可以从“cat”和“dog”(“walking”和“running”)之间的相似性,推测出这句话的概率。然而, Ngram模型做不到。
于是,人们就自然而然的想到,能否用一个连续的稠密向量去刻画一个word的特征呢? 这样我们不仅可以去刻画词与词之间的相似度,还可以建立一个从向量到概率的平滑函数模型,使得相似的词向量可以映射到相近的概率空间上。这个稠密连续向量也被称为word的distributed representation(分布式表示)。
事实上,这个概念在信息检索(Information Retrieval)领域早就已经被广泛使用了。只不过在IR领域里,这个概念被称为向量空间模型(Vector Space Model,以下简称VSM)。
VSM是一种Statistical Semantics Hypothesis:语言的统计特征隐藏着语义的信息(Statistical pattern of human word usage can be used to figure out what people mean)。例如,两篇具有相似词分布的文档可以被认为是有着相近的主题。这个Hypothesis有很多衍生版本。其中,比较广为人知的两个版本是Bag of Words Hypothesis和Distributional Hypothesis。前者是说,一篇文档的词频(而不是词序)代表了文档的主题;后者是说,上下文环境相似的两个词有着相近的语义。后面我们会看到word2vec算法也是基于Distributional的假设。
那么,VSM是如何将稀疏离散的one-hot词向量映射为稠密连续的Distributional Representation的呢?简单来说,基于Bag of Words Hypothesis(词袋假设),我们可以构造一个term-document(关联矩阵)矩阵A:矩阵的行Ai,: 对应着词典里的一个word,矩阵的列A:,j 对应着训练预料里的一篇文档;矩阵里的元素Ai,j代表着word wi 在文档Dj 中出现的次数(或频率)。那么我们就可以提取列向量作为word的语义向量(不过,实际应用中,我们更多的是用列向量做为文档的主题向量,即一篇文档的词频代表了文档的主题)
term-document关联矩阵:
doc 1:new home sales top forecasts doc 2:home sales rise in july
doc 3:increase in home sales in july doc 4: july new home sales rise
类似的,我们可以基于Distributional Hypothesis(分布式特征表达猜想/上下文特征表示)构造一个word-context的矩阵。与term-document相比,矩阵的列变成了context里的word,矩阵的元素也变成了一个context窗口里word的共现次数。word-context矩阵 通过统计一个事先指定大小的窗口内的word共现次数,不仅可以刻画word的语义信息,还在一定程度上反映了word的语法结构信息。
word-context类语义矩阵:
doc1:I like deep learning doc2:I like NLP doc3: I enjoy flying
设置窗口的大小为1, 得到word-context矩阵:
注意,这两类矩阵的行向量所计算的相似度有着细微的差异:term-document矩阵会给经常出现在同一篇document里的两个word赋予更高的相似度;而word-context矩阵会给那些有着相同context的两个word赋予更高的相似度。后者相对于前者是一种更高阶的相似度,因此在传统的信息检索领域中得到了更加广泛的应用。
Neural Network Language Model(神经网络语言模型)
鉴于Ngram等模型的不足,2003年,Bengio等人发表了一篇开创性的文章:A neural probabilistic language model。在这篇文章里,他们总结出了一套用神经网络建立统计语言模型的框架(Neural Network Language Model,以下简称NNLM),并首次提出了word embedding的概念(虽然没有叫这个名字),从而奠定了包括word2vec在内后续研究word representation learning的基础。
NNLM模型的基本思想可以概括如下:
- 假定词表中的每一个word都对应着一个连续的特征向量;
- 假定一个连续平滑的 概率模型,输入一段词向量的序列,可以输出这段序列的 联合概率;
- 同时学习词向量的权重和概率模型里的参数。
值得注意的一点是,这里词向量也是要学习的参数。
在03年的论文中,Bengio等人采用了一个简单的前向反馈神经网络 $f(w_{t-n+1,...,w_{t}})$来拟合一个词序列的条件概率
$p(w_{t}|w_{1},w_{2},...,w_{t-1})$(计算Ngram概率)。整个模型的网络结构见下图:
我们将整个模型拆分成两部分加以理解:
- 首先是一个线性的Embedding层。它将输入的N-1个one-hot词向量,通过一个共享的D×V的矩阵C,映射为N-1个分布式的词向量(distributed vector)。其中,V是词典的大小,D是Embedding向量的纬度(一个先验参数,可以调整)。C矩阵(C矩阵初始值是随机初始化的)里存储了要学习的word vector。
- 其次是一个简单的前向反馈网络g。它由一个tanh(激活函数)隐层和一个softmax(多分类输出占比)输出层组成。通过将Embedding层输出的N-1个词向量映射为一个长度为D的概率分布向量,从而对词典中的word在输入context下的条件概率作出预估:
$p(w_{i}|w_{1},w_{2},...,w_{t-1})\approx f(w_{i},w_{t-1},...,w_{t-n+1})=g(w_{i},C(w_{t-n+1}),...,C(w_{t-1}))$
我们可以通过最小化一个cross-entropy(交叉熵)的正则化损失函数来调整模型的参数θ:$L(\theta)=\frac{1}{T}\sum_{t}log f(w_{t},w_{t-1},...,w_{t-n+1})+R(\theta)$
其中,模型的参数θ包含了Embedding层矩阵C的元素,和前向反馈神经网络g里的权重。这是一个巨大的参数空间。不过,在用SGD(一种梯度下降的方法)学习更新模型的参数时,并不是所有的参数都需要调整(例如未在输入的context中出现的 词对应的词向量)。计算的瓶颈主要在softmax层的归一化函数上(需要对词典中所有的word计算一遍条件概率)。
下面给出softmax函数曲线和计算方程($S_{i}= \frac{e^i}{ \sum_{j} e^j}$)及计算方法:
∞∞
(图片来自网络)
关于softmax详细讲解,请看这个链接
然而,抛弃复杂的参数空间,我们不禁要问,为什么这样一个简单的模型会取得巨大的 成功呢?
仔细观察这个模型你就会发现,它其实在同时解决两个问题:一个是统计语言模型里关注的条件概率 $p(w_{t}|context)$的计算;一个是向量空间模型里关注的词向量的表达。而这两个问题本质上并不独立。通过引入连续的词向量和平滑的概率模型,我们就可以在一个连续空间里对序列概率进行建模,从而从根本上缓解数据稀疏性和纬度灾难的问题。另一方面,以条件概率$p(w_{t}|context)$为学习目标去更新词向量的权重,具有更强的导向性,同时也与VSM里的Distributional Hypothesis不谋而合。
开始正题w2v:
CBoW & Skip-gram Model(词向量模型算法)
一个问题是,同Ngram模型一样,NNLM模型只能处理定长的序列。在03年的论文里,Bengio等人将模型能够一次处理的序列长度N提高到了5,虽然相比bigram和trigram已经是很大的提升,但依然缺少灵活性。因此,Mikolov等人在2010年提出了一种RNNLM模型,用递归神经网络代替原始模型里的前向反馈神经网络,并将Embedding层与RNN里的隐藏层合并,从而解决了变长序列的问题。
另一个问题就比较严重了。NNLM的训练太慢了。即便是在百万量级的数据集上,即便是借助了40个CPU进行训练,NNLM也需要耗时数周才能给出一个稍微靠谱的解来。显然,对于现在动辄上千万甚至上亿的真实语料库,训练一个NNLM模型几乎是一个impossible mission。
这时候,还是那个Mikolov站了出来。他注意到,原始的NNLM模型的训练其实可以拆分成两个步骤:
- 用一个简单模型训练出连续的词向量;
- 基于词向量的表达(不会同时训练词向量了),训练一个连续的Ngram神经网络模型。
而NNLM模型的计算瓶颈主要在第二步。
如果我们只想得到word的连续特征向量,是不是可以对第二步里的神经网络模型进行简化呢?Mikolov是这么想的,也是这么做的。他在2013年一口气推出了两篇paper,并开源了一款计算词向量的工具——至此,word2vec横空出世,主角闪亮登场。
有了前边的基础,理解word2vec就变的很简单了。首先,我们对原始的NNLM模型做如下改造:
- 移除前向反馈神经网络中非线性的hidden layer,直接将中间层的Embedding layer与输出层的softmax layer连接;
- 忽略上下文环境的序列信息:输入的所有词向量均汇总到同一个Embedding layer;
- 将Future words纳入上下文环境
得到的模型称之为CBOW模型(Continuous Bag-of-Words Model),也是word2vec算法的第一个模型。上边可以不看 :P
CBoW模型
简单介绍下图二 CBOW参数的含义:
- 输入层(Input layer):上下文单词的onehot。(假设单词空间向量dim为V,V的值为词典中所有词的个数,上下文单词个数为C)
- 所有onehot分别乘以共享的输入权重矩阵W(V*N的矩阵,N为自己设定的数,权重矩阵W初始值为随机初始化的任意值)
- N-dim隐藏层向量的值为所有的C个onehot与W相乘,再相加取平均,size为1*N
- 再乘以输出权重矩阵W'(矩阵size为N*V)
- 得到向量(1*V),激活函数(softmax)处理得到V-dim概率分布(PS:因为是onehot,每个一维都代表着一个单词),概率最大的index所表示的单词为预测出的中间词( target word )
- 与true label的onehot(上左图中的true label为 w(t))作比较,误差越小越好
所以需要定义loss function(一般为交叉熵代价函数),采用梯度下降算法更新W和W'。训练完毕后,输入层的每个单词与矩阵W相乘得到的向量就是我们想要的词向量(word embedding)这个W矩阵(所有单词的word embedding)也叫做look up table。
详细例子,请看这里
Skip-gram模型
CBoW模型依然是从context对target word的预测中学习到词向量的表达。反过来,我们能否从target word对context的预测中学习到word vector呢?答案是可以的:
关于skip-gram的详细介绍,请看这里
这个模型被称为Skip-gram模型(名称源于该模型在训练时会对上下文环境里的word进行采样)。
如果将Skip-gram模型的前向计算过程写成数学形式(只是一个softmax),我们得到:
$p(w_{o}|w_{i})=\frac{e^{U_{o} \cdot V_{i}}}{\sum_{j}e^{U_{j} \cdot V_{i}}}$
其中,Vi 是Embedding层矩阵里的列向量,也被称为$w_{i}$的input vector。$U_{j}$是softmax层矩阵里的行向量,也被称为$w_{i}$的output vector。
因此,Skip-gram模型的本质是计算输入word的input vector与目标word的output vector之间的余弦相似度,并进行softmax归一化。我们要学习的模型参数正是这两类词向量。
然而,直接对词典里的V个词计算相似度并归一化,显然是一件及其耗时的impossible mission。为此,Mikolov引入了两种优化算法:层次Softmax(Hierarchical Softmax)和负采样(Negative Sampling)。
Hierarchical Softmax(层次Softmax)
层次Softmax的方法最早由Bengio在05年引入到语言模型中。它的基本思想是将复杂的归一化概率分解为一系列条件概率乘积的形式:
$p(v|context)=\prod_{i=1}^{m}p(b_{i}(v)|b_{1}(v),...,b_{i-1}(v),context)$
其中,每一层条件概率对应一个二分类问题,可以通过一个简单的逻辑回归函数去拟合。这样我们对V个词的概率归一化问题,转化成了对$logV$个词的概率拟合问题。我们可以通过构造一颗分类二叉树来直观地理解这个过程。首先,我们将原始字典D划分为两个子集D1、D2,并假设在给定的context下,target word属于子集D1 的概率$p(w_{t} \in D_{1}| context)$服从logistical function的形式:
$p(w_{t}\in D_{1}|context)=\frac{1}{1+e^{-U_{D_{root}}}} \cdot V_{w_{t}}$ (UDroot和Vwt 都是模型的参数)
接下来,我们可以对子集D1和D2进一步划分。重复这一过程,直到集合里只剩下一个word。这样,我们就将原始大小为V的字典D转换成了一棵深度为logV的二叉树。树的叶子节点与原始字典里的word一 一对应;非叶节点则对应着某一类word的集合。显然,从根节点出发到任意一个叶子节点都只有一条唯一路径——这条路径也编码了这个叶子节点所属的类别。
同时,从根节点出发到叶子节点也是一个随机游走的过程。因此,我们可以基于这颗二叉树对叶子节点出现的似然概率进行计算。例如,对于训练样本里的一个target word wt,假设其对应的编码为{1,0,1...,1},则我们构造的似然函数为:
$p(w_{t}|context)=p(D_{1}=1|context)p(D_{2}=0|D_{1}=1)...p(w_{t}|D_{k}=1)$
乘积中的每一项都是一个逻辑回归的函数。
我们可以通过最大化这个似然函数来求解二叉树上的参数——非叶节点上的向量,用来计算游走到某一个子节点的概率。
层次Softmax是一个很巧妙的模型。它通过构造一颗二叉树,将目标概率的计算复杂度从最初的V降低到了logV的量级。不过付出的代价时人为增强了词与词之间的耦合性。例如,一个word出现的条件概率的变化,会影响到其路径上所有非叶子节点的概率变化,间接的对其他word出现的条件概率带来不同程度的影响。因此,构造一颗有意义的二叉树就显得十分重要。实践证明,在实际的应用中,基于Huffman编码的二叉树可以满足大部分应用场景的需求。
Negative Sampling(负采样)
负采样的思想最初来源于一种叫做Noise-Contrastive Estimation的算法,原本时为了解决那些无法归一化的概率模型的参数预估问题。与改造模型输出概率的层次Softmax算法不同,NCE算法改造的时模型的似然函数。
以Skip-gram模型为例,其原始的似然函数对应着一个Multinomial的分布。在用最大似然法求解这个似然函数时,我们得到一个cross-entropy的损失函数 :
$J(\theta)=- \frac {1}{T} \sum_{t=1}^T \sum_{-c \le j \le ,j \ne 0} logp(w_{t+j}|w_{t})$
式中的$p(w_{t+j}|w_{t})$是一个在整个字典上的归一化的概率。
而在NCE的算法中,我们构造了这样一个问题:对于一组训练样本<context, word>,我们想知道,target word的出现,是来自于context的驱动,还是一个事先假定的背景噪声的驱动?显然,我们可以用一个逻辑函数来回答这个问题:
$p(D=1|w,context)= \frac{p(w|context)}{p(w|context)+k_{p_{n}}(w)}=\sigma (logp(w|context)-logkp_{n}(w))$
这个式子给出了一个target word w来自于context驱动的概率。其中,k是一个先验参数,表明噪声的采样频率。p(w | context)是一个非归一化的概率分布,这里采用softmax归一化函数中的分子部分。pn(w)则是背景噪声的词分布。通常采用word的unigram分布。
通过对噪声分布的k采样,我们得到一个新的数据集:<context,word,label>。其中,label标记了数据的来源(真实数据分布还是背景噪声分布?)。在这个新的数据集上,我们就可以用最大化上式中逻辑回归的似然函数来求解模型的参数。
而Mikolov在2013年的论文里提出的负采样算法,是NCE的一个简化版本。在这个算法里,Mikolov抛弃了NCE似然函数中对噪声的依赖,直接用原始softmax函数里的分子定义了逻辑回归函数,进一步简化了计算:
$p(D=1|w_{0},w_{i})=\sigma (U_{o} \cdot V_{i})$
此时,模型相应的目标函数变为:
$J(\theta)=log \sigma (U_{o} \cdot V_{i})+ \sum_{j=1}^{k}E_{w_{j} \sim p_{n}(w)}[log \sigma (-U_{j} \cdot V_{i})]$
除了这里介绍的层次Softmax和负采样的优化算法,Mikolov在13年的论文里还介绍了另一个trick:下采样(subsample)。其基本思想是在训练时依赖概率随机丢弃那些高频的词:
$p_{discard}=1- \sqrt{ \frac {t}{f(x)}}$
其中,t是一个先验参数,一般取为10-5。f(w)是w在语料中出现的频率。实验证明,这种下采样技术可以显著提高低频词的词向量的准确度。
参考文献: