第二讲:简单的词向量表示:word2vec, Glove
How do we represent words?
在处理所有NLP任务的时候,我们首先需要解决非常重要的一个问题(可能是最重要的):用什么方式将词组输入到模型中去。早期的NLP工作并不将词组作为独立个体对待(atomic symbols),但现在的问题绝大多数需要这样一个预处理,来体现词组之间关联/相似性和区别。所以我们引入词向量的概念,如果把词编码成词向量(word vectors),我们很容易从向量的角度去衡量不同的词之间的关联与差异(常用的距离测度法,包括Jaccard, Cosine, Euclidean等等,注:距离测度法,即用一个可观测度量的量来描述一个不能直接观测度量的量)。
Word Vectors
我们拿英文举例。
英语中大约有1300万个词组(token,自定义字符串,译作词组),不过他们全部是独立的吗?并不是哦,比如有一些词组,“Feline猫科动物”和“Cat猫”,“Hotel宾馆“和”Motel汽车旅馆”,其实有一定的关联或者相似性在。因此,我们希望用词向量编码词组,使它代表在词组的N维空间中的一个点(而点与点之间有距离的远近等关系,可以体现深层一点的信息)。每一个词向量的维度都可能会表征一些意义(物理含义),这些意义我们用“声明speech”来定义。例如,语义维度可以用来表明时态(过去与现在与未来),计数(单数与复数),和性别(男性与女性)。
(a) Discrete representation of word
说起来,词向量的编码方式其实挺有讲究的, 最简单的编码方式叫做one-hot vector:假设我们的词库总共有n个词,那我们开一个1*n的高维向量,而每个词都会在某个索引index下取到1,其余位置全部都取值为0.词向量在这种类型的编码中如下图所示:
这种词向量编码方式简单粗暴,我们将每一个词作为一个完全独立的个体来表达。遗憾的是,这种方式下,我们的词向量没办法给我们任何形式的词组相似性度量。例如:
(注:hotel和motel是近义词)
究其根本你会发现,是你开了一个极高维度的空间,然后每个词语都会占据一个维度,因此没有办法在空间中关联起来。因此我们可能可以把词向量的维度降低一些,在这样一个子空间中,可能原本没有关联的词就关联起来了。
(b)Distributional similarity based representations
通过一个词语的上下文可以学到这个词语的很多知识,”You shall know a word by the company it keeps”。这是现代统计NLP很成功的一个观点。其实这也符合人类的思维习惯,人理解一个词并不是单纯的看这个词的拼写来理解它的意思,而是通过上下文来理解其意思。
© Make neighbors represent words
1. Co-occurrence Matrix(共现矩阵)
使用co-occurrence matrix表示单词,CM有两种表示法:
• 词-文档矩阵(Word-Document Matrix)
它的大小是|V|M,|V|是词汇表的大小,M是文档的数目。Word-document的共现矩阵最终会得到泛化的主题(例如体育类词汇会有相似的标记),这就是浅层语义分析(LSA, Latent Semantic Analysis)。
• 基于窗口的共现矩阵(Windows based Co-occurrence Matrix)
它的大小是|V||V|,这种方法容易探测到单词的Syntactic and semantic information,windows的选择一般选5-10之间。一个简单例子:
• 窗口长度是1(一般是5-10)
• 对称(左右内容无关)
• 语料样例:I like deep learning; I like NLP; I enjoy flying
CM存在的问题,
•规模随着语料库词汇的增加而增加
•非常高的维度:需要大量的存储
•过于稀疏,不利于后续分类处理
•模型不够健壮
解决方案:低维向量
• idea: 将最重要的信息存储在固定的,低维度的向量里:密集向量(dense vector)
• 维数通常是25-1000
那如何降维呢?
2. SVD(奇异值分解)
对共现矩阵X进行奇异值分解。
观察奇异值(矩阵的对角元素),并根据我们期待保留的百分比来选择(只保留前k个维度):
然后我们把子矩阵U1:|V|,1:k视作我们的词嵌入矩阵。也就是说,对于词表中的每一个词,我们都用一个k维的向量来表达了。
对X采用奇异值分解
通过选择前K个奇异向量来进行降维:
Python中简单的词向量SVD分解
• 语料:I like deep learning. I like NLP. I enjoy flying
• 打印U矩阵的前两列这也对应了最大的两个奇异值
这两种方法都能产生词向量,它们能够充分地编码语义和句法的信息,但同时也带来了其他的问题:
• 矩阵的维度会经常变化(新的词语经常会增加,语料库的大小也会随时变化)。
• 矩阵是非常稀疏的,因为大多数词并不同时出现。
• 矩阵的维度通常非常高(≈106×106)
• 训练需要O(n2)的复杂度(比如SVD)
• 需要专门对矩阵X进行特殊处理,以应对词组频率的极度不平衡的状况
当然,有一些办法可以缓解一下上述提到的问题:
• 忽视诸如“he”、“the” 、“has”等功能词。
• 使用“倾斜窗口”(ramp window),即:根据文件中词组之间的距离给它们的共现次数增加相应的权重。
• 使用皮尔森的相关性(Pearson correlation),将0记为负数,而不是它原来的数值。
Hacks to CM
之前用SVD处理的方法很好,但是在原始的CM中有很多问题。
•功能词(the,he, has)太多,对语法有很大影响,解决办法是使用或完全忽略功能词。
•之前的CM建立方法不合理,很显然距离单词越近的词和这个词的相关性越高,反之相关性越小,所以我们可以使用一个Ramped windows(斜的窗)来建立CM,类似于用一个weight乘count,距离越远weight越小反之越大(weight是个bell shape函数);
•用皮尔逊相关系数代替计数,并置负数为0
Interesting semantic patterns emerge in the vectors
至此使用SVD处理就大概讲完了,这玩意有啥用呢?这时出现了三个美丽的Patterns。第一个就是用Hierarchical Clustering来聚类行向量可以进行同义词聚类,这个video上讲的比较略,我是通过网上查了半天太查出来这个东东(- -);第二个美丽的pattern就是同义的单词在word-spaces里,同义词靠的近,不同意思的词靠的远;第三个美丽的pattern就是相似的semantic pair word之间的向量距离也类似。
problems with SVD
•计算量极大,人类的语料库有数以trillion的数据,将它保存到一个矩阵中,然后做SVD恐怕目前最先进的计算机都吃不消;对于n*m的矩阵来说计算的时间复杂度是O(mn^2)。
•对于新词或者新的文档很难及时更新;
•相对于其他的DL模型,有着不同的学习框架。
3.基于迭代的方法-word2vec
现在我们退后一步,来尝试一种新的方法。在这里我们并不计算和存储全局信息,因为这会包含太多大型数据集和数十亿句子。我们尝试创建一个模型,它能够一步步迭代地进行学习,并最终得出每个单词基于其上下文的条件概率。
词语的上下文:一个词语的上下文是它周围C个词以内的词。如果C=2,句子"The quick brown fox jumped over the lazy dog"中单词"fox"的上下文为 {“quick”, “brown”, “jumped”, “over”}.
思想是设计一个模型,它的参数就是词向量,然后训练这个模型。在每次迭代过程中,这个模型都能够评估其误差,并按照一定的更新规则,惩罚那些导致误差的参数。这种想法可以追溯到1986年(Learning representations by back-propagating errors. David E. Rumelhart, Geoffrey E. Hinton, and Ronald J.Williams (1988)),我们称之为误差“反向传播”法。
通过向量定义词语的含义
对每个词组都创建一个dense vector,使得它能够预测上下文其他单词(其他单词也是用向量表示)。
补充:词向量目前常用的有2种表示方法,one-hot representation和distributed representation。词向量,顾名思义就是将一个词表示为向量的形式,一个词,怎么可以将其表现为向量呢?最简单的就是One-hot representation,它是以词典V中的词的个数作为向量的维度,按照字典序或某种特定的顺序将V排序后,词w的向量可以表示为: [0,0,1,0,0,…,0],即词w出现的位置为1,其余均为0. 可以看到,这种方法表示的词向量太过于稀疏,维度太高,会引起维度灾难,而且非常不利于计算词之间的相似度。另一种distributed representation可以解决上述问题,它通过训练将一个词映射为相对于One-hot representation来说一个比较短的向量,它的表示形式类似于:[0.1,0.34,0.673,0.983]。
■ 学习神经网络word embeddings的基本思路
定义一个以预测某个单词的上下文的模型:
p(context∣wt)=...p(context|w_t)=...p(context∣wt)=...
损失函数定义如下:
J=1−p(w−t∣wt)J=1-p(w_{-t}|w_t)J=1−p(w−t∣wt)
这里的w−t表示wt的上下文(负号通常表示除了某某之外),如果完美预测,损失函数为零。
然后在一个大型语料库中的不同位置得到训练实例,调整词向量,最小化损失函数。
■直接学习低维度的词向量
这其实并不是多么新潮的主意,很早就有一些研究了:
• Learning representations by back-propagating errors (Rumelhart et al., 1986)
• A neural probabilistic language model (Bengio et al., 2003)
• NLP (almost) from Scratch (Collobert & Weston, 2008)
• A recent, even simpler and faster model: word2vec (Mikolov et al. 2013)
只不过以前一直没有引起重视,直到Bengio展示了它的用处之大。后来研究才开始火热起来,并逐渐出现了更快更工业化的模型。
3.1 语言模型(1-gram,2-gram等等)
首先,我们需要建立一个能给“分词序列”分配概率的模型。我们从一个例子开始:
“The cat jumped over the puddle.”(猫 跳 过 水坑)
一个好的语言模型会给这句话以很高的概率,因为这是一个在语法和语义上完全有效的句子。同样地,这句”stock boil fish is toy”(股票 煮 鱼 是 玩具)就应该有一个非常低的概率 ,因为它是没有任何意义的。在数学上,我们可以令任意给定的n个有序的分词序列的概率为:
p(w1,w2,...wn)p(w_1,w_2,...w_n)p(w1,w2,...wn)
我们可以采用一元语言模型。它假定词语的出现是完全独立的,于是可以将整个概率拆开相乘: p(w1,w2,...wn=∏i=1np(wi))p(w