本文关键词:NLP、词向量、word2vec、ELMo、语言模型
该系列更新的第二篇已在个人微信公众号「AI极客 」发布:
词向量(2)--从ELMo到Bert
后续更新文章,首发于个人公众号,敬请关注!
0. 前言
"词和句子的嵌入已成为所有基于深度学习的自然语言处理(NLP)系统的重要组成部分,它们在固定长度的稠密向量中编码单词和句子,以大幅度提高神经网络处理文本数据的能力。"
大趋势是对通用嵌入的追求:在大型语料库上预训练好的嵌入,可以插入各种下游任务模型(情感分析,分类,翻译…),通过在更大的数据集中学习一些常用的词句表示,从而自动提高它们的性能。
这是一种迁移学习。
作为NLP领域的爱好者,在我之前的多个文章里已经很多次提到词向量技术了(基于TextCNN的文本分类中),它的代表技术word2vec更是“老生常谈”了...
一直想写一篇有关于word2vec的文章,一直没有抽出时间(因为理论数学公式太多了,编辑公示太痛苦了)...
直到一次偶然的面试:被问到一个关于动态词向量的问题(ELMo,当时懵逼了)…事后查阅了一些词向量相关的文献,才发现自己原来还停留在静态词向量的word2vec的世界里,面试官是想考察我对最新技术的洞察力!
词向量为文本数据提供了一种数值化的表示方法,这是文本数据能够被计算机处理的基础,也是机器学习技术能够应用于文本数据处理的重要前提基础。
1. 词向量技术
自然语言是一套用来表达含义的复杂系统。在这套系统中,词是表义的基本单元。顾名思义,词向量是用来表示词的向量,也可被认为是词的特征向量。
这通常需要把维数为词典大小的高维空间嵌入到一个更低维数的连续向量空间。把词映射为实数域上向量的技术也叫词嵌入(word embedding)。近年来,词向量已逐渐成为自然语言处理的基础知识。
那么,我们应该如何使用向量表示词呢?
1.1词向量的获取方式
词向量的获取方式可以大体分为两类:一类是基于统计方法(例如:基于共现矩阵、SVD),另一种是基于语言模型的。
(1).基于共现矩阵的方式
通过统计一个事先指定大小的窗口内的word共现次数,以word周边的共现词的次数做为当前word的vector。具体来说,我们通过从大量的语料文本中构建一个共现矩阵来定义word representation。
例如,有语料如下:
I like deep learning.
I like NLP.
I enjoy flying.
则其共现矩阵X如下:
矩阵定义的词向量在一定程度上缓解了one-hot向量相似度为0的问题,但没有解决数据稀疏性和维度灾难的问题。
(2). SVD(奇异值分解)
既然基于co-occurrence矩阵得到的离散词向量存在着高维和稀疏性的问 题,一个自然而然的解决思路是对原始词向量进行降维,从而得到一个稠密的连续词向量。
首先,统计一个词语的共生矩阵X。X是一个|V|×|V| 大小的矩阵,Xij表示在所有语料中,词汇表V
中第i个词和第j个词同时出现的词数,|V|为词汇表的大小。对X做矩阵分解(如奇异值分解,Singular Value Decomposition [参考资料])得到矩阵正交矩阵U,对U进行归一化得到矩阵,即视为所有词的词向量:
SVD得到了word的稠密(dense)矩阵,该矩阵具有很多良好的性质:语义相近的词在向量空间相近,甚至可以一定程度反映word间的线性关系。
但这样的传统做法有很多问题:
- 由于很多词没有出现,导致矩阵极其稀疏,因此需要对词频做额外处理来达到好的矩阵分解效果;
- 矩阵非常大,维度太高
- 需要手动去掉停用词(如although, a,...),不然这些频繁出现的词也会影响矩阵分解的效果。
(3)语言模型
在介绍词向量模型之前,先简单的介绍一个概念:语言模型。 语言模型旨在为语句的联合概率函数P(w1,...,wT)
建模, 其中wi表示句子中的第i个词。语言模型的目标是,希望模型对有意义的句子赋予大概率,对没意义的句子赋予小概率。 这样的模型可以应用于很多领域,如机器翻译、语音识别、信息检索、词性标注、手写识别等,它们都希望能得到一个连续序列的概率。 以信息检索为例,当你在搜索“how long is a football bame”时(bame是一个医学名词),搜索引擎会提示你是否希望搜索"how long is a football game", 这是因为根据语言模型计算出“how long is a football bame”的概率很低,而与bame近似的,可能引起错误的词中,game会使该句生成的概率最大。
对语言模型的目标概率P(w1,...,wT),如果假设文本中每个词都是相互独立的,则整句话的联合概率可以表示为其中所有词语条件概率的乘积,即:
然而我们知道语句中的每个词出现的概率都与其前面的词紧密相关, 所以实际上通常用条件概率表示语言模型:
上面只是简单的介绍了语言模型,推荐阅读的关于N-gram模型以及神经概率语言模型以便于学习后续的word2vec模型。
2.代表技术之一 word2vec
2013年,Google团队发表了word2vec工具 [1]。word2vec工具主要包含两个模型:跳字模型(skip-gram)和连续词袋模型(continuous bag of words,简称CBOW),以及两种近似训练法:负采样(negative sampling)和层序softmax(hierarchical softmax)。值得一提的是,word2vec的词向量可以较好地表达不同词之间的相似和类比关系。
word2vec自提出后被广泛应用在自然语言处理任务中。它的模型和训练方法也启发了很多后续的词嵌入模型。本节将重点介绍word2vec的模型和训练方法。
Skip-gram模型(跳字模型):
在跳字模型中,我们用一个词来预测它在文本序列周围的词。
举个例子,假设文本序列是:
“I love you very much”
跳字模型所关心的是,给定“you”生成邻近词“I”、“love”、“very”和“much”的条件概率。
在这个例子中,“you”叫中心词,“I”、“love”、“very”和“much”叫背景词。
由于“you”只生成与它距离不超过2的背景词,该时间窗口的大小为2[与N-gram类似]。
我们来描述一下跳字模型[用最大似然估计的思想]:
假设词典索引集V的大小为|V|,且{0,1,…,|V|−1}。给定一个长度为T的文本序列中,文本序列中第t的词为w(t)。当时间窗口大小为m时,跳字模型需要最大化给定任一中心词生成所有背景词的概率:
上式的最大似然估计与最小化以下损失函数等价:
我们可以用v和u分别表示 中心词 和 背景词 的向量。
换言之,对于词典中索引为i的词,它在作为中心词和背景词时的向量表示分别是vi和ui。而词典中所有词的这两种向量正是跳字模型所要学习的模型参数。为了将模型参数植入损失函数,我们需要使用模型参数表达损失函数中的给定中心词生成背景词的条件概率。给定中心词,假设生成各个背景词是相互独立的。设中心词wc在词典中索引为c,背景词wo在词典中索引为o,损失函数中的给定中心词生成背景词的条件概率可以通过softmax函数定义为:
上式:给定任何一个中心词Wc,产生背景词Wo的概率
每一个词,在模型中有两个词向量,一个是作为中心词时的词向量,一个是作为背景词时的词向量
利用随机梯度下降求解:
当序列长度T较大时,我们通常在每次迭代时随机采样一个较短的子序列来计算有关该子序列的损失。然后,根据该损失计算词向量的梯度并迭代词向量。具体算法可以参考“梯度下降和随机梯度下降——从零开始”一节。 作为一个具体的例子,下面我们看看如何计算随机采样的子序列的损失有关中心词向量的梯度。和上面提到的长度为T的文本序列的损失函数类似,随机采样的子序列的损失实际上是对子序列中给定中心词生成背景词的条件概率的对数求平均。通过微分,我们可以得到上式中条件概率的对数有关中心词向量vc的梯度:
该式也可改写作:
上面的迭代更新计算开销太大!!每次都需要遍历整个字典,对应的解决方案在后面(这也是word2vec为啥这么牛逼的原因...厉害的不是这个工具本身,而是一种思想的应用)
随机采样的子序列有关其他词向量的梯度同理可得。训练模型时,每一次迭代实际上是用这些梯度来迭代子序列中出现过的中心词和背景词的向量。训练结束后,对于词典中的任一索引为i的词,我们均得到该词作为中心词和背景词的两组词向量vi和ui。在自然语言处理应用中,我们会使用跳字模型的中心词向量。
CBOW(连续词袋模型)
连续词袋模型与跳字模型类似,与跳字模型最大的不同是:
连续词袋模型用一个中心词在文本序列周围的词来预测该中心词。
举个例子,假设文本序列为:
“I love you very much”
连续词袋模型所关心的是,邻近词“I”、“love”、“very”和“much”一起生成中心词“you”的概率。
假设词典索引集的大小为V,且V={0,1,…,|V|−1}</nobr>。给定一个长度为T的文本序列中,文本序列中第t个词为wu(t)。当时间窗口大小为m时,连续词袋模型需要最大化由背景词生成任一中心词的概率
上式的最大似然估计与最小化以下损失函数等价:
我们可以用v和u分别表示背景词和中心词的向量(注意符号和跳字模型中的不同)。换言之,对于词典中索引为i的词,它在作为背景词和中心词时的向量表示分别是vi和ui。而词典中所有词的这两种向量正是连续词袋模型所要学习的模型参数。为了将模型参数植入损失函数,我们需要使用模型参数表达损失函数中的给定背景词生成中心词的概率。设中心词wc在词典中索引为c,背景词wo1、wo2、...wo2m在词典中索引为o1、o2、....o2m-1、o2m,损失函数中的给定背景词生成中心词的概率可以通过softmax函数定义为
和跳字模型一样,当序列长度T较大时,我们通常在每次迭代时随机采样一个较短的子序列来计算有关该子序列的损失。然后,根据该损失计算词向量的梯度并迭代词向量。 通过微分,我们可以计算出上式中条件概率的对数有关任一背景词向量voi(i=1,2,....2m)的梯度为:
该式也可写作
随机采样的子序列有关其他词向量的梯度同理可得。和跳字模型一样,训练结束后,对于词典中的任一索引为i的词,我们均得到该词作为背景词和中心词的两组词向量vi和ui。
在自然语言处理应用中,我们会使用连续词袋模型的背景词向量。
近似训练法
我们可以看到,无论是skip-gram(跳字模型)还是CBOW(连续词袋模型),每一步梯度计算的开销与词典V的大小相关。
因为计算softmax的时考虑了字典上的所有可能性
当词典较大时,例如几十万到上百万,这种训练方法的计算开销会较大。因此,我们将使用近似的方法来计算这些梯度,从而减小计算开销。常用的近似训练法包括负采样和层序softmax。
(1)负采样(Negative Sample)
我们以跳字模型为例讨论负采样。
实际上,词典V的大小之所以会在损失中出现,是因为给定中心词wc生成背景词wo的条件概率P(w0∣wc)
使用了softmax运算,而softmax运算正是考虑了背景词可能是词典中的任一词(使用了全部词),并体现在分母上。
下面,我们可以使用σ(x)=1/(1+exp(−x))函数来表达中心词wc和背景词wo同时出现在该训练数据窗口的概率。
σ(x)属于[0,1]
那么,给定中心词wc生成背景词wo的条件概率的对数可以近似为:
[上式的含义:中心词wc与背景词wo同时出(D=1)现概率,且中心词wc与噪音词wk不同时出现(D=0)的概率。]
假设噪声词wk在词典中的索引为ik,上式可改写为:
因此,有关给定中心词wc生成背景词wo的损失是:
假设词典V很大,每次迭代的计算开销由O(|V|)变为O(|K|)。当我们把K取较小值时,负采样每次迭代的计算开销将较小。
当然,我们也可以对连续词袋模型进行负采样。有关给定背景词
wt-m、wt-m+1、...、wt+m生成中心词wc的损失:
在负采样中可以近似为:
同样,当我们把K取较小值时,负采样每次迭代的计算开销将较小。
(2)层序softmax[]
层序softmax是另一种常用的近似训练法。它利用了二叉树这一数据结构。树的每个叶子节点代表着词典V中的每个词。
假设L(w)为从二叉树的根节点到词w<的叶子节点的路径(包括根和叶子节点)上的节点数。设n(w,j)为该路径上第j个节点,并设该节点的向量为un(w,j)。以上图为例:L(w3)=4。设词典中的词wi的词向量为vi。那么,跳字模型和连续词袋模型所需要计算的给定词wi生成词w的条件概率为:
其中σ(x)=1/(1+exp(−x)),leftChild(n)是节点n的左孩子节点,如果判断x为真,[x]=1;反之[x]=−1。由于σ(x)+σ(−x)=1,给定词wi生成词典V中任一词的条件概率之和为1这一条件也将满足:
让我们计算给定词wi生成词w3的条件概率。我们需要将wi的词向量vi和根节点到w3路径上的非叶子节点向量一一求内积。由于在二叉树中由根节点到叶子节点w3的路径上需要向左、向右、再向左地遍历,我们得到:
整个遍历的路径已经通过Huffman编码唯一的确定了
在使用softmax的跳字模型和连续词袋模型中,词向量和二叉树中非叶子节点向量是需要学习的模型参数。
假设词典V很大,每次迭代的计算开销由O(|V|)下降至O(log2|V|)。
推荐资料:学习word2vec的经典资料
3.ELMo--动态词向量
ELMo官网:https://allennlp.org/elmo
艾伦研究所开发,并于6月初在NAACL 2018年发布ELMo(深度语境化的单词表示)。
ELMO(Embeddings from Language Models) ,被称为时下最好的通用词和句子嵌入方法,来自于语言模型的词向量表示,也是利用了深度上下文单词表征,该模型的优势:
(1)能够处理单词用法中的复杂特性(比如句法和语义)
(2)这些用法在不同的语言上下文中如何变化(比如为词的多义性建模)
ELMo与word2vec最大的不同:
Contextual: The representation for each word depends on the entire context in which it is used.
(即词向量不是一成不变的,而是根据上下文而随时变化,这与word2vec或者glove具有很大的区别)
举个例子:针对某一词多义的词汇w="苹果"
文本序列1=“我 买了 六斤 苹果。”
文本序列2=“我 买了一个 苹果 7。”
上面两个文本序列中都出现了“苹果”这个词汇,但是在不同的句子中,它们我的含义显示是不同的,一个属于水果领域,一个属于电子产品呢领域,如果针对“苹果”这个词汇同时训练两个词向量来分别刻画不同领域的信息呢?答案就是使用ELMo。
ELMo是双向语言模型biLM的多层表示的组合,基于大量文本,ELMo模型是从深层的双向语言模型(deep bidirectional language model)中的内部状态(internal state)学习而来的,而这些词向量很容易加入到QA、文本对齐、文本分类等模型中,后面会展示一下ELMo词向量在各个任务上的表现。
它首先在大文本语料库上预训练了一个深度双向语言模型(biLM),然后把根据它的内部状态学到的函数作为词向量。实验表明,这些学到的词表征可以轻易地加入到现有的模型中,并在回答问题、文本蕴含、情感分析等 6 个不同的有难度的 NLP 问题中大幅提高最佳表现。实验表明显露出预训练模型的深度内部状态这一做法非常重要,这使得后续的模型可以混合不同种类的半监督信号。
3.1 ELMo的安装与使用
AllenNLP是一个相对成熟的基于深度学习的NLP工具包,它构建于 PyTorch之上,该工具包中集成了ELMo方法。
可以直接使用pip安装:
pip install allennlp
适用于python3.6以上的版本
或者,你也可以尝试一下这个工具包ELMoForManyLangs:
#基于 ELMo 的向量化方法
#详细资料参考 https://github.com/berkay-onder/ELMoForManyLangs
from elmoformanylangs import Embedder
import numpy as np
e = Embedder(r'../auxiliary_data/zhs.model/')
sents = [['今', '天', '天氣', '真', '好', '阿'],
['潮水', '退', '了', '就', '知道', '誰', '沒', '穿', '褲子']]
# the list of lists which store the sentences
# after segment if necessary.
embs = e.sents2elmo(sents)
print(embs)
print(np.mean(embs[0],axis=0))
print (len(np.mean(embs[0],axis=0)))
# will return a list of numpy arrays
# each with the shape=(seq_len, embedding_size)
使用ELMo获得词向量替换Glove的词向量作为多项特定NLP模型的输入,在ELMo的论文实验中表明具有一定的效果提升:
4.结束
词向量技术为文本的额数值化表示提供了解决方案,不同的词向量技术可以提取词汇的不同角度或层级的语义信息,作为NLP各项任务模型的输入,对于提升模型的效果觉有显著的作用。