【NLP CS224N笔记】Lecture 2 - Word Vector Representations: word2vec

2019-01-02 13:05:51 浏览数 (1)

I. Word meaning

Meaning的定义有很多种,其中有:

  • the idea that is represented by a word,phrase,etc.
  • the idea that a person wants to express by using words, signs, etc.

1.Discrete representation

那么在计算机中是如何获取一个word的meaning的呢?常见的解决办法是使用像WordNet之类的数据集,它包含了同义词(synonym)组和上位词(hypernyms)组。这种表示方法属于Discrete representation

上位词(hypernym),指概念上外延更广的主题词。 例如:”花”是”鲜花”的上位词,”植物”是”花”的上位词,”音乐”是”mp3”的上位词。上位词是相对某主题词的,也有它自己的等同词、上位词、下位词、同类词。

但是类似于WordNet的数据集存在如下缺点:

  • 尽管存储的词条较为丰富,但是词与词之间缺少细微的差别。例如proficient只是good的同义词,但是二者却存在一些差别。
  • 缺少新的词汇,例如Dama(大妈)这种非常fashion的词汇很难及时地更新。
  • 对词的定义较为主观,因为都需要人工提前设定。因此也需要大量的人力去维护这个数据集。
  • 很难计算词之间的相似性。

2.将words表示为离散符号(discrete symbols)

如何将单词量化成计算机能读懂的数据呢?常见的一种方法是one-hot编码。

例如假设我们的数据集一共有6个单词,其中有motel(汽车旅馆)hotel

那么我们可以作如下表示:

  • motel=[0 0 0 0 1 0]
  • hotel=[0 0 0 0 0 1]

但是这有一个很明显的缺点就是词与词之间没有关联性,也就是说我们无法从one-hot编码中得知不同词之间的相似性,因为各个词编码后得到的向量是正交的。

所以我们还需要找到一种能够计算单词相似性,相关性的编码方式。

3. Distributed similarity based representation

一个很自然,很直观的方法就是根据某个单词的上下文对该单词的含义进行编码。该方法的核心思想是:A word’s meaning is given by the words that frequently appear close-by,由J.R.Firth在1957年提出。

下图给出示例,假如我们需要对banking的含义进行编码,那么我们可以根据预先设定的固定大小的窗口对banking前后出现的单词进行统计,banking的含义可以有这些单词表示。

II. Word2vec Indtroduction

1. 学习神经网络word embeddings的基本思路

我们先定义这样一个模型,该模型是在word vectors编码的基础上能够预测出中心词(w_t)的上下文: [p(context|w_t)]

该模型的损失函数为 [J=1-p(w_{-t}|w_t)] (w_{-t})表示出了t以外的其他词,即(w_t)的上下文。

2. word2vec的核心思想

word2vec的核心思想是predict between every word and its context words!

两个算法:

  • Skip-grams (SG):给定目标词汇去预测它的上下文。简单地说就是预测上下文
  • Continuous Bag of Words (CBOW):从bag-ofwords上下文去预测目标词汇

两个稍微高效一点的训练方法:

  • Hierarchical softmax
  • Negative sampling

该课程主要集中讲解Naive softmax,上面的两个算法和训练方法可以参考word2vec原理推导与代码分析。

3. Skip-gram prediction

下图给出了skip-gram示意图。中心词banking位置为(t),用(w_t)表示。窗口大小为(m)

需要注意的是虽然banking的上下文可以分为前面和后面,但是概率分布只有一种,即假设banking左边第三个单词是as,右边第二个是as,这两个as概率分布是要合并计算的,不存在说分前后两种概率分布。

4. Word2vec细节

1)目标函数

介绍完模型后下面需要介绍一下目标函数,函数形式如下: [ begin{align} max,,J'(theta)=prod_{t=1}^T,,,prod_{-m≤j≤m,,,j≠0}p(w_{t j}|w_t;theta) end{align}]

  • (J')表示最优值
  • (t)表示当前中心词所在位置,(T)表示词库的总长度
  • (m)表示窗口的大小,(j)表示从窗口最左边移动到最右边,不等于0是因为不用计算中心词本身
  • (theta)表示word representation模型本身 上式表示尽可能地预测出每个中心词的上下文,即最大化所有概率的乘积。

通常为了方便计算会将上式化为log的形式,即 [ begin{align} min ,,,J(theta)=-frac{1}{T}sum_{t=1}^{T},,,sum_{-m≤j≤m,,,j≠0}log,,p(w_{t j}|w_t;theta) end{align} ]

2)引入softmax

那么如何计算(p(w_{t j}|w_t))呢?为方便说明先做如下定义:

  • (v_w)表示w是一个中心词
  • (u_w)表示w是一个上下文词

所以(p(w_{t j}|w_t))简写成(p(o|c)),且有 [ begin{align} p(o|c)=frac{exp(u_o^Tv_c)}{sum_{w=1}^Vexp(u_w^Tv_c)} end{align} ]

  • (o)表示output或outside之意,即上下文。(u_o)用来表示某个需要计算上下文词汇
  • (c)表示center,(v_c)就表示中心词
  • (V)表示词库的长度,(w)从1开始遍历到(V)

公式(3)中的分子的点积运算,他可以用于计算中心词和上下文词的相似性,值越大表示越相似。

3)流程示意图

上图虽然乍一看很凌乱,但是很清晰地展示了skipgram的计算过程。

从左往右看:

  • 1.首先是维度为(V×1)的向量(w_t),用来表示此时中心词所在的位置,用one-hot形式进行编码,其中(V)表示需要遍历计算的中心词的个数,即总共的词数量。
  • 2.之后是维度为(d×V)的单词矩阵(W),该矩阵存储了所有中心词(center word)的向量表达,(d)表示用于表示词的向量的长度。
  • 3.(w_t)与(W)做矩阵运算后便可得到当前的中心词的representation,即(v_c=W·w_t∈R^{d×1})
  • 4.下一步就是中心词向量(v_c)与上下文矩阵(W')相乘((u_o^Tv_c))求得各个词之间的相似性。
  • 5.接下来根据上一步求出的相似性值,使用softmax将相似性大小转化为概率,选择概率最大的index作为输出。

需要注意的是 (W')并不是(W)的转置 ,他们是两个完全不同的矩阵,只不过维度恰好是对方的转置矩阵维度而已,一般将(W∈R^{d×V})称为input vector,(W'∈R^{V×d})称为output vector。所以每个单词由两个词向量表示,那么那个作为最终的表示呢?有两种策略,一种是将两个词向量加起来,另一种是将两个词向量拼接起来,即得到(R^{2d×1})词向量。

这里有个不明白的地方是为什么单词矩阵(W∈R^{d×V})不止一个?希望明白的朋友能在评论区或者发邮件(marsggbo@foxmail.com)解释一下,谢谢!

下图是官方给的整理后的示意图,意思与上图一样不再赘述。

如果上面的解释还不能让你明白,可以参考Word2Vec介绍:直观理解skip-gram模型。

III. Research Highlight

期间老师让一个中国学生做了一个关于一篇论文的报告,具体内容不作赘述,可参考CS224n研究热点1 一个简单但很难超越的Sentence Embedding基线方法。

IV. Word2vec objective function gradients

目前为止,目标函数和流程图都已经清楚了,那么接下来我们需要计算出模型的参数(theta)了。在上面内容中已经介绍了每个单词由两个维度为(d×1)的向量表示,常见的办法是将二者拼接,这样我们就可以得到一个非常庞大的向量参数,即 [ begin{align} theta&=left[ begin{matrix} v_a \ v_{abandon}\ vdots \ v_{zoo}\ i_a \ u_{abandon}\ vdots \ u_{zoo}\ end{matrix} right] ∈R^{2dV} end{align} ]

那么如何优化这个参数呢?很自然地是梯度下降啦。

首先回顾一下目标函数:

[ begin{align} min ,,,J(theta)&=-frac{1}{T}sum_{t=1}^{T},,,sum_{-m≤j≤m,,,j≠0}log,,p(w_{t j}|w_t;theta) \ &=-frac{1}{T}sum_{t=1}^{T},,,sum_{-m≤j≤m,,,j≠0}log,,p(u_o|v_c;theta) \ &=-frac{1}{T}sum_{t=1}^{T},,,sum_{-m≤j≤m,,,j≠0}log,,frac{exp(u_o^Tv_c)}{sum_{w=1}^Vexp(u_w^Tv_c)} end{align} ]

要想计算(J(theta))最小值,首先需要计算(frac{partial{J(theta)}}{partial{v_c}})。仔细观察公式(7)知道我可以先对右边的log项求微分,具体推导过程如下:

  • 首先将log项中的除法拆分成减法,得到两项:①和②,接着分别对这两项进行偏微分求导
  • ①和②都包含log和指数,所以为了求导方便令(log=ln),所以①很容易就求出来了。需要注意的是(frac{partial{u_o^Tv_c}}{partial{v_c}}=u_o)而不是(u_o^T)。
  • ②的偏微分稍微复杂一点,需要使用链式法则进行求解。
    • 首先我们想log右边的求和项看成一个整体,即为(z=g(v_c)),那么log整体可以看成是(f(z)=f(g(v_c)))注意不能把log和∑换位!!!看视频的时候我就是这么想的。。。
    • 同样为了计算方便令(log=ln),那么(frac{f(g(v_c)}{partial{v_c}}=frac{1}{g(v_c)})
    • 最后只需要再求(frac{partial{g_c}}{partial{v_c}})即可,需要用到指数求导公式,(d{e^x}/dx={e^x}),后面的步骤很简单了,不再赘述。

由上面的步骤可以得到 [ begin{align} frac{partial{J(theta)}}{partial{v_c}}=-frac{1}{T}sum_{t=1}^Tsum_{-m≤j≤m,,,j≠0}(u_o-sum_{x=1}^Vp(u_x|v_c)·u_x) end{align} ]

计算出梯度后,就能够开始优化参数了: [ begin{align} theta^{new} &=theta^{old}-alphafrac{partial{J(theta)}}{partial{theta^{old}}} notag\ &=theta^{old}-alpha nabla_theta J(theta) end{align} ]

上面的公式在实际应用中还存在一些问题,因为通常一个词库是包含非常多单词的,如果对整个词库进行梯度更新的话会非常缓慢。所以可以引入SGD算法,即每次只对窗口大小的数据进行梯度更新。代码流程如下:

代码语言:javascript复制
while True:
    window = sample_window(corpus)
    theta_grad = evaluate_gradient(J, window, theta)
    theta = theta - alpha * theta_grad

0 人点赞