理解 word2vec

2015-01-28 13:10:16

简介

word2vec 是 Google 推出的用来做词表示的开源工具包。
在自然语言处理中,我们把一个句子当成词的集合,那么一篇文章所出现的所有词的集合就称之为词典 (vocabulary).
在词典中表示一个具体的词,可以有两张办法:

用 index 来表示,比如 第138个词

或者

用词向量 (vector) 来表示,假设总共有 k 个词, 这个词是第138个,那么它的表示就是一个 k-dimension vector,在138位为1, 其他为0

这种表示称为 one-hot encoding

但是这种表示除了能让我们索引到词以外,并不能提供任何其他的信息,我们希望一种更好的词表达方式,能够或多或少地表达这个词的意义。

因此 word2vec 就出现了。

让我们首先来思考,如何(在数学上)表达一个词的意义?

通常当你翻开词典寻找一个词的解释的时候,都会有近义词的条目。那么直觉来说,一个词的意义,最容易由近义词来表达。
而近义词在数学上是好定义的,只要这两个词在某种 metric 下距离相近即可。

这种表示称为 distributed representation,word2vec 就是这样一种词表示。在这种表示里,我们需要解决的问题,就是要定义这种 metric
实际上也就是,要定义好词向量。

word2vec 把定义一个词向量转换为两个分类问题,然后用两个 module 来解决它:

Skip-gram:

给定一个词和以这个词为中心的一个窗口, Skip-gram 预测窗口内这个词周围的词

Continuous bag of words (CBOW):

反过来, 给定周围的词和这个窗口,CBOW 预测这个中心词

通过这两个 module, word2vec 就能够获取词向量了。

模型

机器学习中最重要的事情是定义好目标函数。机器学习过程本质就是在目标函数的约束下的优化过程。

对于 CBOW 的目标函数为:

$$ L = \sum_{w\in C} \log p(w|Context(w)) $$

对于 Skip-gram 的目标函数为:

$$ L = \sum_{w\in C} \log p(Context(w)|w) $$

这两个目标函数都是非常直接的。采用对数似然函数是自然语言处理的惯用办法,可以参考我们之前的文章。
现在我们的关键就在于 $p(w|Context(w))$ 和 $p(Context(w)|w)$ 的构造上

基于 Hierachical Softmax 的 CBOW 模型

CBOW 的目标是在训练集中最大化 $p(w|Context(w))$。这里需要用到 Huffman 编码,每个叶子节点对应一个词典中的词。那么对于每个词来说, 在 Huffman 树上都有一条(唯一的)路径。
假设这个路径长为 $l$, 那么就有 $l-1$ 个分支,每个分支可以看作是一个二分类问题,那么把所有分类的概率乘起来,就是这个条件概率
$p(w|Context(w))$。这种思想就是基于 Hierachical Softmax 的 CBOW 模型

具体地

$$ p(w|Context(w)) = \prod_{j=2}^{l^w}p(d_j^{w}|X^w, \theta_{j-1}^w) $$

其中 $l^w$ 表示路径长度(跟词 w 有关),$d_j^{w}$表示路径中第 $j$ 个词的编码,
$\theta_{j-1}^w$ 表示第 $j$ 非叶子节点对应的词向量(这个词向量只是辅助用的),
$p(d_j^{w}|X^w, \theta_{j-1}^w)$ 是分类的概率。

如果我们采用 Logistic Regression 来做分类
并且代入到目标函数里,最终得到

\begin{aligned}
L &= \sum_{w\in C} \log p(w|Context(w)) \\
&= \sum_{w\in C} \log \prod_{j=2}^{l^w}p(d_j^{w}|X^w, \theta_{j-1}^w) \\
&= \sum_{w\in C} \sum_{j=2}^{l^w} { (1 - d_j^{w}) \log(\sigma(X^w\theta_{j-1}^w)) + d_j^{w} \log(1-\sigma(X^w\theta_{j-1}^w)) }
\end{aligned}
其中 $\sigma()$ 是 sigmoid 函数,下文同,不再赘述。

这个目标函数可以用随机梯度法来求解。

Skip-gram 模型

Skip-gram 的目标是在训练集中最大化 $p(Context(w)|w)$。首先我们有

$$p(Context(w)|w) = \prod_{u \in Context(w)} p(u|w) $$

同样采用 Hierachical Softmax 思想

$$ p(u|w) = \prod_{j=2}^{l^u}p(d_j^{u}|V^w, \theta_{j-1}^u) $$

并且仍然利用 Logistic Regression 作为分类器,我们可以得到目标函数:

\begin{aligned}
L &= \sum_{w\in C} \log p(Context(w)|w) \\
&= \sum_{w\in C} \log \prod_{u \in Context(w)} \prod_{j=2}^{l^u}p(d_j^{u}|V^w, \theta_{j-1}^u) \\
&= \sum_{w\in C} \sum_{u\in Context{w}} \sum_{j=2}^{l^u} { (1 - d_j^{u}) \log(\sigma(V^w\theta_{j-1}^u)) + d_j^{u} \log(1-\sigma(V^w\theta_{j-1}^u)) }
\end{aligned}

仍然采用随机梯度法,但 word2vec 是每处理完一个在 $Context(w)$ 中的词 $u$ 就更新 $V^w$,而不是处理完整个 $Context(w)$ 再更新。

优化: Negative Sampling

到这里 word2vec 的主要部分已经介绍完了。接下来就是一些提高性能和效率的办法。 Negative Sampling 就是其中之一。

本质上在其文中的方法类似于 word-context 矩阵分解,每一块为 word 和 context pair 之间的 Pointwise Mutual Information (PMI)

PMI 矩阵在自然语言处理也是一种常用的技术,以后再写文章详谈。