CS224N(2.19)Contextual Word Embeddings

今天介绍几种新的词向量学习方法,在此之前,建议大家看看我关于word2vec或GloVe等传统词向量的介绍:CS224N(1.8)Introduction and Word Vectors。 传统词向量,比如word2vec,它在训练阶段学习到一个词的向量表示之后,在下游的各种NLP任务中,这个词向量不再变动了。也就是说传统词向量的特点是,对一个词只学习一个词向量,且在具体任务中固定不变。传统词向量有两个主要的不足: 难以表达一词多义。一个词在不同的上下文语境中可能表示不同的含义,比如“苹果”在“苹果真好吃”和“苹果手机很好用”这两个句子中表示不同的含义,但word2vec学习到的“苹果”词向量只有一个,也就是说下游任务对于这两个句子用的是同一个词向量。虽然word2vec的词向量可能同时包含了这两个含义,但它把这两个含义糅合到一个向量中了,导致在“苹果真好吃”中可能引入了“苹果手机”的干扰因素,在“苹果手机很好用”中引入了“吃的苹果”的干扰因素。总之就是,word2vec学习到的词向量粒度较粗,向量固定不变,无法根据具体的上下文语境进行改变。 难以表达不同的语法或语义信息。一个词,即使是同一个意思,在语法或语义上也可能充当不同的角色,比如“活动”这个词,既可以做名词、也可以做动词,既可以做主语、也可以做谓语等。但word2vec对一个词只给出一个词向量,无论这个词在句子中充当什么角色,词向量都是一样的。虽然word2vec训练时可能已经学到了一个词的不同语法或语义特征,但它把这些信息糅合到一个向量中了,也就是粒度较粗的问题。 其实上述两点暴露出来的word2vec的不足,本质上是同样的两个原因:1. 词向量是静态的,无法根据上下文进行调整;2. 词向量表示只有一个向量,糅合了太多信息,粒度较粗。 下图中的word type表示一个相同的词,比如“苹果”;word token表示同一个word type在不同上下文的具体实例,比如“苹果真好吃”和“苹果手机很好用”中的“苹果”就是两个不同的word token。word type和word token有点类似于类和实例的关系。 2018年艾伦人工智能研究所提出了ELMo: Embeddings from Language Models,即从语言模型中学习词向量的方法,它的原文标题为Deep contextualized word representations(https://www.aclweb.org/anthology/N18-1202.pdf),即深度的上下文词向量表示。ELMo很好地解决了上述传统词向量的两个不足,ELMo对一个词的表示由多个向量组成,并且每个向量的权重在具体的上下文中动态更新,由此不但粒度更细,而且能根据上下文动态调整一个词的最终词向量。使用ELMo词向量,作者在很多NLP任务上刷新了SOTA,在当年引起了很大的轰动。 ELMo有三个特点: 学习的是word token的词向量,根据上面的定义,word token与具体的上下文有关,不再是静态的word type的词向量; 使用很长的上下文进行学习,而不像word2vec一样使用较小的滑动窗口,所以ELMo能学到长距离词的依赖关系; 使用双向的语言模型进行学习,并使用网络的所有隐藏层作为这个词的特征表示。 下面介绍ELMo具体的训练过程,以下截图来自台湾大学李宏毅老师的教学视频。 首先,既然word2vec使用滑动窗口只能学习到局部特征,那么ELMo就用RNN来建模长距离的依赖关系。如下图所示,ELMo使用双向语言模型bi-LM学习每个词的特征。我们知道,语言模型是给定一个句子前缀,预测下一个可能出现的词,也就是说常规的语言模型是只知道t时刻前的信息,不知道t时刻之后的信息,即通常都是单向的。而ELMo则使用了bi-LM,比如下图我们要学习“退了”这个词的特征,在前向网络中,模型学到了在给定上文的情况下,“退了”的隐藏层特征;在反向网络中,模型学到了在给定下文的情况下,“退了”的隐藏层特征。最后,ELMo把两个方向的隐藏层特征拼起来,作为bi-LM学到的“退了”这个词的特征表示。 为了学到更多的特征,ELMo对双向RNN进行堆叠,每增加一层就能多学习到2个特征表示(一正一反,下图把这两个向量拼接起来作为一个整体向量h)。 ELMo文中只使用了两层的双向LSTM抽取特征,所以对一个词能抽取到4个特征表示,即下图中的h1和h2(每个h包含一正一反特征向量组合)。 在使用ELMo词向量时,每个词的最终词向量是所有隐藏层特征向量h的加权求和,系数是α。这个系数是根据词在不同的上下文中学习得来的。ELMo文章分析发现,不同的NLP任务学到的系数不尽相同,比如在Coref和SQuAD任务中,第一层的系数更大。有可能ELMo在第一层学到的是词的句法特征,第二次学到的是更高级的语义特征。有点类似于CNN中在浅层学到点、线、转角,在高层学到轮廓等高级特征。 ELMo的形式化表示如下图所示。假设堆叠的bi-LSTM有L层,则每一层都能学到前向特征\(\underset{h_{k,j}^{LM}}{\rightarrow}\)和反向特征\(\underset{h_{k,j}^{LM}}{\leftarrow}\),这两个组合起来就是\(h_{k,j}^{LM}\)(也就是上图的\(h_i\))。下图的ELMo还有个\(x_k^{LM}\),它是bi-LM的词向量输入,这相当于词最原始的输入向量,ELMo使用char-CNN抽取一个词的原始输入向量,和上一课提到的Subword model类似,这里不展开。下图j=0时的特征就是\(x_k^{LM}\) 。 所以,对于词\(k\),ELMo得到的完整词向量是\(R_k\),它其实是这个词的一系列特征向量的组合,它还不是这个词最终的词向量,因为最终的词向量要在具体的NLP任务中根据上下文来定。词\(k\)在某个任务task中的词向量\(ELMo_k^{task}\)如下图所示,它等于对所有层的特征向量的加权求和,权重\(s_j^{task}\)(即上图的α)根据当前任务动态学习得到。最终还会有一个参数\(\gamma^{task}\),控制ELMo词向量对任务的贡献程度。 不同层抽取的特征不一样,ELMo只使用了两层bi-LSTM,如果堆叠更多层的话,估计能学到更多有意思的特征。 ELMo词向量的使用方法。在下游的NLP任务中,既可以把ELMo词向量作为输入层特征,也可以将其拼接到隐藏层,反正都需要学习参数\(s_j^{task}\)和\(\gamma^{task}\)。 性能方面,ELMo刷新了很多NLP任务的SOTA,大概能提高3个百分点。 个人理解word2vec和ELMo的本质区别。其实两者在训练阶段都考虑了上下文,只不过word2vec使用的是滑动窗口,只能学习局部特征,而ELMo使用RNN,能学到长距离特征。word2vec学到embedding之后固定成一个向量了,在下游NLP任务中不再变动;而ELMo训练完之后得到这个词的多个特征向量(词的隐含特征表示,粒度更细),且在下游NLP任务中会根据上下文动态组合这些特征向量,得到在这一上下文中的词向量表示(针对性更强)。 其实,仔细想想的话,这个本质区别也没有那么大,虽然word2vec训练到的词向量只有一个,且固定了,但词向量的维度如果够大的话,可以用不同维度来表示一词多义呀。而且用到下游NLP任务时,词向量不同维度的权重也可以不一样啊,不就相当于ELMo的不同\(s_j^{task}\)吗?虽然如此,可能还是不如ELMo的多个隐藏层向量,一方面是经过隐藏层的特征抽取,另一方面是毕竟ELMo出来的特征向量有2L+1个,仅维度上就比word2vec多很多,效果好是理所应当的。 ELMo的作者在2017年发过TagLM的工作,和ELMo类似,被称为Pre-ELMo,感兴趣的可以阅读原文(https://arxiv.org/abs/1705.00108)。ELMo是把语言模型的隐藏层抽取出来作为词的特征向量,2017年的CoVe词向量是把机器翻译过程中的隐藏层提取出来作为词的特征向量,但效果没ELMo好,感兴趣的可以阅读原文(https://arxiv.org/abs/1708.00107)。 同样在2018年,OpenAI和GoogleAI借助Transformer模型,提出了更加强大的上下文词向量学习方法GPT和BERT。Transformer是为了解决RNN无法并行化的问题,提出Attention is all you need(https://arxiv.org/abs/1706.03762)。以前我们提到Attention用来提升RNN的性能,Transformer更加激进,直接不要RNN,只留下Attention,然后堆叠网络深度,使得其可以和CNN一样并行化,使用GPU之后可以大大加速训练过程。下节课将详细介绍Transformer的内容,这里我们只需要知道Transformer是一个强大的seq2seq(Encoder-Decoder)模型。 BERT就是借助Transformer学习上下文词向量的方法,它的全称是BERT (Bidirectional Encoder Representations from Transformers): Pre-training of Deep Bidirectional Transformers for Language Understanding。 ...

February 24, 2020 · 1 min

CS224N(2.14)Subword Models

今天介绍一下subword(子词)模型。之前介绍的NLP模型都是基于word的,对于英文来说是一个个单词,对于中文来说是一个个词语(需要分词)。不过,最近几年,subword模型多起来了,这就是我们今天要介绍的内容。 对于英文来说,文字的粒度从细到粗依次是character, subword, word,character和word都很好理解,subword相当于英文中的词根、前缀、后缀等,如unfortunately中的un、ly、fortun(e)等就是subword,它们都是有含义的。对于中文来说,只有两层,character和subword是同一层,表示单个的字,而word表示词语。 之前介绍的基于word的模型,存在out of vocabulary(OOV,未登录词)的问题。以英文为例,现存的英文单词数量太多了,随便加个前缀、后缀,变个时态什么的都变成新的单词了,所以英文单词的词典数量特别大,而且有很多低频稀疏词。很多模型在训练时都会去掉低频词,只保留高频词。那么这就存在一个问题,如果预测时遇到未登录词,则模型不认识,出现OOV的问题。 为了解决这个问题,一开始想到的是采用character级别的模型,即对26个字母训练word2vec,每个词由其字母的embedding拼接或者求平均得到。但是character级别的模型效果相比于word级别的模型效果差不多,并没有显著优势。而且如果用RNN来训练character级别的模型也有它的问题,就是训练起来非常慢。特别是对英文来说,原来的一个word,现在变成了七八个character,时间步长增加了很多,训练和预测都更久了,而且梯度消失(爆炸)的问题也会更严重。 后来,人们就想用subword模型作为character和word的折中模型。subword模型主要有两种,它们都能解决未登录词(OOV)的问题,如下图所示。第一种是模型结构和word模型完全一样,只不过把word换成了subword。第二种则是word和character模型的杂交模型。 对于第一种模型,关键问题是怎样得到subword。前面提到character的粒度太细,虽然能解决OOV问题,但效果并不是太好;word模型的word数量太多,存在大量稀疏word,删掉它们又会导致OOV问题,所以打算用subword模型。那么,怎样提取一个单词的subword呢?前面提到,unfortunately中的un、ly、fortun(e)等就是subword,但是对每个词都这样提取subword的话,费时费力不说,也不够智能。 有人就想出了用BPE算法来提取高频subword。BPE,全称是byte pair encoder,是上世纪提出的一种压缩算法,其核心思想是不断用字母表中不存在的char来代替最高频的char pair。举个例子,对于字符串aaabdaaabac,其字符串中最开始只出现了a/b/c/d这四个char;统计所有char pair,最高频的是aa,用不在字母表中的另一个字符Z代替aa,则原字符串变成了ZabdZabac,字母表变成了a/b/c/d/Z;如此不断进行下去,直到字母表大小达到一定的阈值,或者所有连续的char pair的频数都等于1了。关于BPE算法的进一步介绍请看这里:https://zhuanlan.zhihu.com/p/38130825。 第一种模型就是用BPE算法来得到高频subword的。比如下图的例子,语料集D中出现了5个low,2个lower等等。最开始,字母表V中是语料集中出现的所有单个字母的集合{l,o,…,d}。然后,发现e s这个char pair出现次数最多,将其作为一个subword加入到V中,同时将D中的es合并看作一个新的char。接着统计发现es t这个char pair(此时es已经是一个char了)出现次数最多,将其作为一个subword加入到V中,如此进行下去。发现没有,我们自动从D中提取了est这个subword,而est就是最高级的后缀。也就是说BPE算法自动提取到了英文的前缀、后缀等subword信息,完全避免了之前费时费力地从unfortunately中手工提取un、ly、fortun(e)的过程。这种方法也能解决未登录词问题,但是粒度又不至于太细。一方面是因为最开始的时候D中原始出现的单个字母都在V中,另一方面,由V中字母组成的未出现词也能由subword构成,比如下图中虽然less没有出现在D中,但可以由l es s这3个subword组成,而这3个subword是在最终的V中的。 得到高频subword作为V之后,后续在进行NLP任务时,encoder的时候查一下V,把char pair替换为新字符;decoder的时候查一下V,把新字符替换回原来的char pair。 最近比较流行的BERT,字典中既有相对比较常见的词,对于不太常见的词则用subwords/wordpieces来表示。 第二种被称为杂交模型的方法就相对简单了。这种方法是Manning老师提出来的,它就是在D中有这个word时就用word embedding,没有的时候就用char embedding来学习word embedding,非常简单。 fasttext就是skipgram+n-gram,一个词的embedding=组成这个词的n-gram的embedding的加权求和,所以fasttext也能解决OOV问题。

February 17, 2020 · 1 min

CS224N(1.15 & 1.17)Backpropagation

这篇博客把1.15和1.17两次课内容合并到一起,因为两次课的内容都是BP及公式推导,和之前的Neural Networks and Deep Learning(二)BP网络内容基本相同,这里不再赘述。下面主要列一些需要注意的知识点。 使用神经网络进行表示学习,不用输入的x直接预测输出,而是加一个中间层(图中橙色神经元),让中间层对输入层做一定的变换,然后中间层负责预测输出是什么。那么中间层能学到输入层的特征,相当于表示学习,自动学习特征。对于word2vec,中间层就是词向量。 命名实体识别(Named Entity Recognition, NER),任务是把一个句子中的一些实体词识别出来,比如下图中识别出地点LOC、机构ORG和人名PER等。通常采用的方法是把需要判断类型的词及其周围的几个词的词向量拼接起来,输入到神经网络进行分类。但是由于一个句子中真正的实体词较少,而很多其他词Others会很多,导致样本不均衡,此时需要进行采样,具体方法可以搜索怎样处理NER样本不均衡的问题。 我们知道词向量其实是NLP实际任务中的副产品,任何一个NLP任务都可以得到词向量。这就存在一个问题,当我们在实现一个具体的NLP任务时,是使用预训练的词向量,还是根据实际任务现场训练一个词向量呢?建议是,如果有可用的预训练词向量,则最好使用预训练词向量。因为预训练的词向量通常在很大规模的数据集上进行过训练,词向量的质量还不错,而某个具体的NLP任务的样本数可能不太多,导致训练得到的词向量还没人家预训练的好。所以,如果实际任务的数据量较小,则用预训练的词向量;否则,可以尝试一下根据实际任务fine tune词向量。 这节课的核心就是不断使用链式法则对BP算法求导,然后反向传播。在反向传播的过程中,可以利用上游计算好的梯度,增量式的更新下游的梯度,如下图所示,就是公式[downstream gradient] = [upstream gradient] × [local gradient]。这个在之前介绍BP网络的时候也提到过,其实就是那篇博客的(BP2)公式,误差对\(w\)的偏导可以通过误差对\(b\)的偏导乘以神经元输出得到。 当有多个输入的时候,也是一样,只不过local gradients变为了多个分支。 很有意思的是老师总结到:加法相当于把上游的梯度分发给下游;max相当于路由;乘法相当于开关。听老师讲下面的实例会有切身的体会。 当然,现在的流行的神经网络框架都帮我们完成了自动求导,我们只需要把local gradient定义好,框架会自动帮我们进行反向传播。需要定义的就两点,一个是正向经过该神经元,output=forward(intput);另一个是反向经过该神经元时,input_gradient=backward(output_gradient)。下面第二个图是一个很简单的例子,定义了forward和backward两个操作。 最后,简要介绍了6个注意事项: 使用正则化避免过拟合 使用python的向量和矩阵运行,而不是for循环,前者相比于后者有~10x加速 目前流行的非线性激活函数是ReLU,Sigmoid和tanh比较少用了 参数(权重)初始化,初始值最好是随机的很小的值,有一些专门的策略,如Xavier sgd优化器效果还不错,不过目前流行的优化器是Adam 学习率最好是10的倍数,而且可以成10倍的放大或缩小;一些fancy的优化器会对设定的学习率进行逐步缩减(比如Adam),所以对于这些优化器,一开始的学习率可以设大一点,比如0.1 最后是本周作业,包含两部分内容,一部分是手动求导,编辑公式太麻烦了,我就写在了纸上,大家可以参考这位仁兄的解答。另一部分是根据手动推导的梯度公式,补充word2vec算法中的求解梯度的算法以及sgd更新公式,如果是第一次接触这方面内容,可以参考这位仁兄的实现。 但是,根据上一篇博客的介绍,word2vec除了可以根据作业中的极大似然的方法求解,还可以用3层全连接网络来实现,相比于极大似然更简洁也更容易理解,具体可以参考这篇博客以及这篇具体实现。

July 29, 2019 · 1 min

CS224N(1.10)Word Vectors 2 and Word Senses

这一讲是上一讲的补充,内容比较零碎,包括:Word2vec回顾、优化、基于统计的词向量、GloVe、词向量评价、词义等,前两个内容没必要再介绍了,下面逐一介绍后四个内容。 基于统计的词向量 词向量的目的就是希望通过低维稠密向量来表示词的含义,而词的分布式语义表示方法认为词的含义由其上下文语境决定。Word2vec把中心词和临近词抽取出来,通过预测的方式训练得到词向量。在Word2vec之前,传统的方式通过统计词的共现性来得到词向量,即一个词的词向量表示为其临近词出现的频率,如果两个词的含义很相近,则其临近词分布会比较像,得到的词向量也比较像。其具体计算过程在第一次作业中有详细的描述,这里再简单回顾如下。 假设一个语料库中包含三个句子,共有8个特异词(包括点号),对于每个词,统计其前后一个词的词频(临近窗口为1),由此能得到一个8×8的对称矩阵,其每一行(或每一列)表示该词的词向量。比如对于like这个词,在三个句子中,其左右共出现2次I,1次deep和1次NLP,所以like对应的词向量中,I、deep和NLP维的值分别为2,1,1。 这种基于词频统计的方法很简单,但是它有如下不足: 特异的词很多,所以矩阵很大,维度很高,需要的存储空间也很大 特异词的数目是在不断增长的,则词向量的维度也在不断增长 矩阵很稀疏,即词向量很稀疏,会导致很多NLP任务会遇到稀疏计算的问题 所以需要把上述计数矩阵转换为一个低维稠密的矩阵,方法就是SVD分解。上述矩阵原本是一个\(n\times n\)的矩阵,SVD分解后能得到一个\(n\times k\)的矩阵,其中\(k\ll n\)。即原本的词向量是一个\(n\)维的高维稀疏向量,变成了\(k\)维的低维稠密向量,而且还不会损失太多的信息。 2005年的一篇文章对上述简单的计数方法进行了改进,包括去掉停用词、使用倾斜窗口、使用皮尔逊相关系数等,提出了COALS模型,该模型得到的词向量效果也不错,也具有句法特征和语义特征。使用统计的方法和使用预测的方法训练词向量,两者的对比如下。基于统计计数的方法的主要特点是:训练速度快,能充分利用统计信息,主要用来捕获词的相似性。基于预测的方法的主要特点是:对语料库的大小可扩展,没有充分利用统计信息,能捕获除了词的相似性之外的其他复杂特征。 GloVe GloVe的全称是GloVe: Global Vectors for Word Representation,正是这门课的老师Christopher D. Manning的研究成果。有关GloVe论文的详细解读,可以看这篇博客。GloVe希望能综合上述基于统计和基于预测的两种方法的优点。 GloVe的基本思想依然是基于统计的方法,当统计得到共现矩阵X之后,可以计算得到词\(k\)是词\(i\)的临近词的概率: $$P_{i,k}=\dfrac{X_{i,k}}{X_{i}}$$再定义两个\(P\)的比值: $$ratio_{i,j,k}=\dfrac{P_{i,k}}{P_{j,k}}$$如果词\(k\)在两个词\(i\)和\(j\)的临近概率相同,无论是同样大(water)还是同样小(fashion),经过比值计算后,\(ratio_{i,j,k}\)都约等于1了,说明在维度water和fashion上,无法区分ice和steam。而在维度solid和gash上,由于概率\(P\)的差异,导致\(ratio_{i,j,k}\)很大或者很小,这是有意义的,说明在solid和gas维度上,可以区分ice和steam的语义。 基于这样的观察,GloVe首先统计语料库中三元组\(i,j,k\)的\(ratio_{i,j,k}\),然后初始化词向量\(v\),构造函数\(g\),使得利用词向量计算得到的\(g(v_{i},v_{j},v_{k})\)和真实\(ratio_{i,j,k}\)尽量接近。 $$\dfrac{P_{i,k}}{P_{j,k}}=ratio_{i,j,k}=g(v_{i},v_{j},v_{k})$$$$J=\sum_{i,j,k}^N(\dfrac{P_{i,k}}{P_{j,k}}-g(v_{i},v_{j},v_{k}))^2$$但是上述方法的复杂度太高了,对于一个\(N\times N\)的共现矩阵,上述算法需要计算所有的三元组,复杂度是\(N\times N\times N\)。GloVe文章通过各种转换技巧,把复杂度降为了一个\(N\times N\)的问题,具体过程可以看上面提到的博客或者paper原文。 总的来说,基于共现矩阵这种统计的方法,能捕获整个语料库全局的信息;而类似word2vec的预测的方法,则主要捕获局部的滑动窗口内的共现信息,两种方法训练得到的词向量效果都不错。 词向量评价 评价词向量的好坏主要有两个尺度,一是内部任务评价(intrinsic),一是外部任务评价(extrinsic),两者的主要特点如下。大概意思是内部任务是根据词本身具有的性质,比如近义、反义等,评价词向量本身的性能。外部任务是指词向量对NLP下游任务的性能的影响,比如同样是一个文本分类问题,换不同的词向量,对文本分类任务的性能的影响能反映出词向量的性能。 常见的内部任务评价是词的类比推理(Word Vector Analogies),就是类似man:woman :: king:queen这种,word2vec还专门整理出了这样的测试数据:word2vec/trunk/questions-words.txt。另一个内部任务评价是使用训练得到的词向量计算的词相似度,和人类认为的相似度做比较,有团队专门整理出了人类对两个词的相似度打分,具体可以看这里。 对词向量的外部任务评价就很多了,几乎所有的NLP任务都可以用来作为词向量的外部任务评价,比如命名实体识别、文本分类等等,这里不再展开。 词义 一个词往往具有多个含义(word senses),特别是对于常用的词或者存在很久的词。那么一个词向量能同时包含这个词的多个语义吗?有文章把一个词的多个语义通过线性加权的方式叠加到一个词向量中,然后还能通过稀疏编码的方式求解出每个语义的词向量,具体可以看下图中的参考文献。

June 23, 2019 · 1 min

CS224N(1.8)Introduction and Word Vectors

今天开始介绍大名鼎鼎的NLP网课Stanford-CS224N,我学的是Winter 2019的版本,课程网站:https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1194/。第一讲内容为课程简介和词向量。 词向量即用来表示这个词的含义的向量。早期的NLP常用one-hot编码来表示词向量,假如词典中共有10000个词,则这个one-hot向量长度就是10000,该词在词典中所处位置对应的值为1,其他值为0。 one-hot表示方法虽然简单,但其有诸多缺点:1. 词典中的词是不断增多的,比如英语,通过对原有的词增加前缀和后缀,可以变换出很多不同的词,one-hot编码会导致向量维度非常大,且每个向量是稀疏的;2. 不同词的one-hot编码向量是垂直的,在向量空间中无法表示近似关系,即使两个含义相近的词,它们的词向量点积也为0。 既然one-hot编码有这么多缺点,那我们就换一种编码,one-hot是高维稀疏向量,那新的编码就改用低维稠密向量,这样就解决了上述问题,那么怎样得到一个词的低维稠密的词向量呢?这就是word2vec算法。 word2vec采用了分布式语义的方法来表示一个词的含义。本质上,一个词的含义就是这个词所处的上下文语境。回想一下我们高中做英语完形填空时,一篇短文,挖了好多空,让我们根据空缺词的上下文语境选择合适的词。也就是说上下文语境已经能够确定这个词的含义了,如果选词正确,也就意味着我们理解了这个空缺词的含义。 word2vec算法发表于2013年,包括两种训练算法Skip-grams (SG)和Continuous Bag of Words (CBOW),这两种方法很类似,其中CBOW和上述介绍到的英语完形填空几乎是一样的,由上下文词预测中心词;而SG则和CBOW正好相反,由中心词预测上下文词,本文主要介绍SG算法。 给定一个语料库,这个语料库包含了很多文章,每篇文章又包含很多句子,每个句子又包含很多词语。所以一个语料库是一个天然的标注集,因为对于每一个选定的中心词,我们都知道其临近的词是什么。这样一个(中心词,临近词)对就构成了一个标注集。SG算法的中心思想就是对于每个选定的中心词,尽量准确的预测其周围可能出现的词的概率分布。具体来说,SG算法首先随机初始化每个词的词向量;然后预测不同临近词出现的概率,最后最大化实际临近词出现的概率。 形式化来说,就是用极大似然估计的方法,求解每个词的词向量。其目标函数如下,其中\(\theta\)是待求解的参数;\(t\)为选定的中心词位置;对于每个\(t\)(外层\(\prod\)),估计其邻域\(\pm m\)个词出现的概率(内层\(\prod\))。 求解极大似然估计的方法比较成熟,一般先把极大似然转换为最小化-log似然,然后用梯度下降求解。所以核心问题就变成了如何求解\(P(w_{t+j}|w_t;\theta)\)。 对于每个词\(w\),定义其两个词向量:\(v_w\)表示当\(w\)为中心词时的词向量,\(u_w\)表示当\(w\)为其他词的临近词时的词向量。则对于一个中心词\(c\)和其临近词\(o\),有: 上式本质是一个softmax函数,因为给定\(c\),\(o\)相当于是标注结果,所以把它们的点积作为分子,希望分子越大越好;而分母则是所有可能的\(u_w\)和\(v_c\)的点积之和,起到归一化作用。 题外话:讲这张幻灯片时,还提到softmax的一个形象解释。softmax包括max和soft两层含义。假设对于一个数组[1,2,3,4],直接max也就是hard max的结果是保留最大值,其他全变为0,即[0,0,0,4]。但是softmax对他们求\(\frac{exp(x_i)}{\sum_{j=1}^nexp(x_j)}\),变成了[0.03, 0.09, 0.24, 0.64],最大的还是第4个数,但第四个数的优势被放大了,原来4只是1的4倍,现在0.64是0.03的21倍。所以softmax不但保留了max的属性,还变得更soft了,原来小的数不会被抹为0,只不过拉大了差异。 使用梯度下降还需要求解\(P\)对参数\(\theta\)的梯度,在这里\(\theta\)代表了所有词的中心词向量和临近词向量。对于上式,\(u_o\)、\(v_c\)等就是\(\theta\)的一部分。不断利用求导的链式法则,容易得到: $$\begin{eqnarray}\frac{\partial P(o|c)}{\partial v_c}=u_o-\sum_{w\in V}P(w|c)u_w.\tag{1}\end{eqnarray}$$最后算出来的梯度很有意思,\(u_o\)表示观察到的上下文词向量(o表示observed),减号后面的是这个位置的期望的词向量,期望=概率*值。差值就是真实观察词向量减去期望词向量,这就是梯度。当它们的差值很小时,说明给定\(c\)能很好的预测其临近词的概率分布。 OK,当以上内容都准备妥当之后,我们就可以开始求解词向量了。首先随机初始化每个词\(w\)的中心词向量\(v_w\)和临近词向量\(u_w\);然后求解-log损失函数\(J(\theta)\);最后根据梯度下降更新所有参数\(\theta\)。 上述word2vec算法简单,直观,但写代码实现比较复杂。在实际应用场景中,人们往往使用神经网络的方法来求解词向量,具体教程请看这里: http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/ 。 我们把训练词向量的问题转换为端到端的文本分类问题。如下图所示,对于语料库中的一个句子,假设临近窗口为前后两个词,则可以抽取出如下图右边所示的训练样本,每个训练样本是一个(中心词,临近词)的pair。比如对于(the, quick)训练样本,我们希望神经网络在输入the这个中心词时,能以较高的概率预测出quick这个词。 网络的结构如下图所示,也非常简单,是仅含一个隐藏层的全连接网络。比如上图的一组训练数据是(the, quick),表示输入是the的one-hot编码,输出是quick的one-hot编码。假设词典里有10,000个不同的词,则one-hot编码长度为10,000。有一个隐藏层的全连接网络,对应权重构成两个权重矩阵,和输入层连接的矩阵为\(V\),其每一行表示词作为中心词时的词向量。输入行向量乘以\(V\)正好得到输入词的词向量,这对应课上讲的作为中心词的词向量\(v_c\)。 隐层和输出层连接的权重矩阵为\(U\),其每一列表示输出层的词的临近词词向量。隐层的行向量\(v_c\)乘以矩阵\(U\),得到词\(c\)的临近词的概率分布,再经过softmax激活,进行归一化。其实反过来看,从输出往隐层看,相当于输出层的行向量乘以\(U\)的转置,得到隐层词向量。这其实就是另一种训练词向量的方法CBOW,即英语完形填空,用临近词来预测中心词。 对于下图的神经网络,输出用softmax激活,损失函数使用-log损失,训练网络时使用梯度下降,其效果正好是课上讲的使用极大似然估计的方法! 另一方面,上图的这种结构是skip-gram模型,如果把对应的箭头反一下,输入变输出,输出变输入,其实就变成了CBOW模型了。 上述全连接网络虽然能很方便的计算词向量,但存在两个问题:1. 网络过于庞大,参数量太多;2. 训练样本太多,每个样本都更新所有参数,训练速度慢。针对这两个问题,作者分别提出了 subsampling 和 negative sampling 的技巧,具体请看教程: http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/ 。 第一个问题,网络参数量太多。假设有1w个特异的词,词向量长度为300,整个网络就有两个300w的矩阵(上图的V和U)参数需要优化。另一方面,训练语料库往往是很大的,随随便便就是成百上千万的文章,由此拆分得到的训练词组对就更大了,很容易到上亿的级别。几百万的参数,几亿的训练数据, 导致网络太过庞大,训练不动。 subsampling技巧是指,每个词有一个保留概率p,以这个概率p保留其在训练数据中,以1-p删掉这个词。比如上面的例子,删掉了fox,则fox对应的4个训练数据也一并删掉了,所以能减少较多的训练数据。对于词\(w_i\),其概率\(P(w_i)\)公式如下,其中\(z(w_i)\)是词\(w\)的词频。概率p和这个词在语料库中的词频有关,词频越大,保留概率越低,即被删掉的概率越大,所以subsampling之后应该能较大的减少训练数据。 $$\begin{eqnarray}P(w_i) = (\sqrt{\frac{z(w_i)}{0.001}} + 1) \cdot \frac{0.001}{z(w_i)} .\tag{2}\end{eqnarray}$$ 第二个问题,原来的网络在训练时,对于每个输入中心词,都会对两个很大的参数矩阵V和U(和上面假设一样,300w)进行轻微的更新,更新操作太多了。 negative sampling技巧,只更新一小部分参数。比如对于(“fox”, “quick”),期望的输出是quick的one-hot,即只有quick对应位为1,其他为0。但网络的softmax输出肯定不可能是完完全全的quick为1,其他为0;有可能是quick为0.8,其他位有些0.001,0.03之类的非0值,这就导致输出层的所有神经元都有误差。按照传统的方法,输出层所有神经元对应的U矩阵的权重都要更新。negative sampling技巧是,只更新和quick连接的U权重以及随机选5个输出神经元的连接权重进行更新,这样一下把需要更新的U权重个数从300w降到了6*300=1800,只需要更新0.06%的参数,大大减小了参数更新量! 5个随机选中的神经元(输出位置,即对应1w维的某个词)被称为negative sample,被选中的概率和词频成正比,词频越大的词被选中的概率越大,和上面subsampling类似。概率公式如下,其中\(f(w_i)\)应该和(2)中的\(z(w_i)\)一样,都表示词频。 $$\begin{eqnarray}P(w_i) = \frac{ {f(w_i)}^{3/4} }{\sum_{j=0}^{n}\left( {f(w_j)}^{3/4} \right) }.\tag{3}\end{eqnarray}$$ 作业请见GitHub: https://github.com/01joy/stanford-cs224n-winter-2019/blob/master/1.8/assignment/a1/exploring_word_vectors.ipynb ...

June 22, 2019 · 1 min