当前“人工智能”是继“大数据”后又一个即将被毁的词,每家公司都宣称要发力人工智能,就跟4-5年前大数据同样,业界叫的都很是响亮,不由想到以前一个老外说过的话:php
Big Data is like teenage sex: Everyone talks about it, nobody really knows how to do it, everyone thinks everyone else is doing it, so everyone claims.html
如今看来,上面的”Big Data”能够换成”AI”了,在你们还没搞明白大数据的时候,人工智能就开始引领下一个潮流了。本着跟风的态度,我也尝试去窥探个究竟。git
当前不管是学术界仍是工业界,深度学习都受到极大的追捧,尤为是在Google开源深度学习平台TensorFlow以后,更是给深度学习火上浇油。目前在开源社区Github上全部开源项目中,TensorFlow最为活跃,从推出到如今,经历了几个版本的演进,能够说可以灵活高效地解决大量实际问题。本文主要尝试阐述TensorFlow在天然语言处理(NLP)领域的简单应用,让你们伙儿更加感性地认识TensorFlow。github
说到NLP,其实我对它并非很熟悉,以前也不曾有过NLP的相关经验,本文是我最近学习TensorFlow的一些积累,就当抛砖引玉了。当前互联网天天都在产生大量的文本和音频数据,经过挖掘这些数据,咱们能够作一些更加便捷的应用,例如机器翻译、语音识别、词性标注以及信息检索等,这些都属于NLP范畴。而在NLP领域中,语言模型是最基本的一个环节,本文主要围绕语言模型展开,首先介绍其基本原理,进而引出词向量(word2vec)、循环神经网络(RNN)、长短时记忆网络(LSTM)等深度学习相关模型,并详细介绍如何利用 TensorFlow 实现上述模型。算法
语言模型是一种几率模型,它是基于一个语料库建立,获得每一个句子出现的几率,通俗一点讲就是看一句话是否是正常人说出来的,数学上表示为:api
上述公式的意义是:一个句子出现的几率等于给定前面的词状况下,紧接着后面的词出现的几率。它是经过条件几率公式展开获得。其中条件几率 P(w2|w1),P(w3|w1w2),⋯,P(wt|w1w2⋯wt−1)P(w2|w1),P(w3|w1w2),⋯,P(wt|w1w2⋯wt−1) 就是建立语言模型所须要的参数,每一个条件几率的意义解释为:根据前面的词预测下一个词的几率。有了这些条件几率参数,给定一个句子,就能够经过以上公式获得一个句子出现的几率。例若有一句话“php是最好的语言”(我不肯定这是否是天然语言),假设已经分词为“php”、“是”、“最好的”、“语言”,那么它出现的几率为P(“php”,“是”,“最好的”,“语言”)=P(“php”)P(“是”|“php”)P(“最好的”|“php”,“是”)P(“语言”|“php”,“是”,“最好的”),若是这个几率较大,那么判断为正常的一句话。以上这些条件几率经过以下贝叶斯公式获得:网络
根据大数定理上述公式又能够近似为:session
假如语料库里有 NN 个词,一个句子长度为 TT ,那么就有 NTNT 种可能,每一种可能都要计算 TT 个条件几率参数,最后要计算 TNTTNT 个参数并保存,不只计算量大,对于内存要求也是惊人。那么如何避免这个问题呢,以前穷举的方法行不通,那么换个思路,采用一种偷懒的处理方法,就是将上述公式中条件几率作个以下近似:架构
这意思就是说一个词出现的几率只与它前面 n−1n−1 个词有关,而不是与它前面全部的词有关,这样极大的减小了统计的可能性,提升了计算效率,这种处理方法称之为 n-gram 模型,一般 nn 取2~3就能获得不错的效果。总结起来,n-gram 模型就是统计语料库中词串出现的次数,一次性计算获得词串的几率并将其保存起来,在预测一个句子时,直接经过前面所述的条件几率公式获得句子出现的几率。app
近年也流行起神经网络语言模型,从机器学习的角度来看,一开始不所有计算这些词串的几率值,而是经过一个模型对词串的几率进行建模,而后构造一个目标函数,不断优化这个目标,获得一组优化的参数,当须要哪一个词串几率时,利用这组优化的参数直接计算获得对应的词串几率。将词串几率 P(w|context(w))P(w|context(w)) 看作是 ww 和 context(w)context(w) 的函数,其中 context(w)context(w) 表示此 ww 的上下文,即至关于前面所述的 n-gram 模型的前 n−1n−1 个词,那么就有以下数学表示。
目标函数采用对数似然函数,表示以下(其中 NN 表明语料库中词典的大小):
经过优化算法不断最小化目标函数获得一组优化的参数 ΘΘ ,在神经网络中参数 ΘΘ 则为网络层与层间的权值与偏置。那么在用神经网络学习语言模型[1]时,如何表示一个词呢?一般,在机器学习领域,是将一个样本对象抽象为一个向量,因此相似地,神经网络语言模型中是将词(或短语)表示为向量,一般叫作word2vec。那么神经网络语言模型就能够表示以下示意图。
上述神经网络包括输入层、投影层、隐藏层以及输出层,其中投影层只是对输入层作了一个预处理,将输入的全部词进行一个链接操做,假如一个词表示为 mm 维向量,那么由 n−1n−1 个词链接后则为 (n−1)m(n−1)m 维向量,将链接后的向量做为神经网络的输入,通过隐藏层再到输出层,其中 WW 、UU 分别为投影层到隐藏层、隐藏层到输出层的权值参数,pp 、qq 分别为投影层到隐藏层、隐藏层到输出层的偏置参数,整个过程数学表达以下:
其中 σσ 为sigmoid函数,做为隐藏层的激活函数,输出层的输出向量为 NN 维,对应于语料库中词典的大小。通常须要再通过softmax归一化为几率形式,获得预测语料库中每一个词的几率。以上神经网络语言模型看似很简单,可是词向量怎么来呢,如何将一个词转化为向量的形式呢?下面做详细阐述。
词向量要作的事就是将语言数学化表示,以往的作法是采用 One-hot Representation 表示一个词,即语料库词典中有 NN 个词,那么向量的维度则为 NN ,给每一个词编号,对于第 ii 个词,其向量表示除了第 ii 个单元为1,其余单元都为0的 NN 维向量,这种词向量的缺点显而易见,通常来讲语料库的词典规模都特别大,那么词向量的维数就很是大,而且词与词之间没有关联性,并不能真实地刻画语言自己的性质,例如“腾讯”、“小马哥”这两个词经过One-hot向量表示,没有任何关联。为了克服One-hot Representation 的缺点,Mikolov大神提出了一种 Distributed Representation[2],说个题外话,在你们都在如火如荼的用CNN作图像识别的时候,这哥们却在研究如何用神经网络处理NLP问题,最后发了大量关于神经网络NLP的高水平论文,成为这一领域的灵魂人物之一。顾名思义,Distributed Representation 就是把词的信息分布到向量不一样的份量上,而不是像 One-hot Representation 那样全部信息集中在一个份量上,它的作法是将词映射到 mm 维空间,表示为 mm 维向量,也称之为 Word Embedding,这样一方面能够减少词向量的维度,另外一方面,能够将有关联的词映射为空间中相邻的点,词与词之间的关联性经过空间距离来刻画,以下图所示。
词被映射到3维空间,每一个词表示为一个3维向量,相近的词离的较近,能够看到两组差很少关系的词,他们之间的词向量距离也差很少。
要想获得词向量,须要借助语言模型训练获得,本质上来讲,词向量是在训练语言模型过程当中获得的副产品。解决word2vec问题有两种模型,即 CBOW 和 Skip-Gram 模型[3],以下图所示:
CBOW 模型是根据词的上下文预测当前词,这里的上下文是由待预测词的先后 cc 个词组成。而 Skip-Gram 模型则相反,是经过当前词去预测上下文。给定一个语料库做为训练集,就能够经过以上模型训练出每一个词的向量表示。从实验结果来看,CBOW 模型会平滑掉一些分布信息,由于它将词的上下文做为单个样本,而 Skip-Gram 模型将词上下文拆分为多个样本,训练获得的结果更为精确,为此,TensorFlow 中 word2vec 采用的是 Skip-Gram 模型,对应于文[2]中所提出的一种更为优化的 Skip-Gram 模型,下面着重介绍其原理,更多关于 CBOW 和 Skip-Gram 模型细节能够参阅文[3]。
前面也提到, Skip-Gram 模型是根据当前词去预测上下文,例若有以下语句:
“php 是 世界上 最好的 语言”
假定上下文是由待预测词的先后2个词组成,那么由以上句子能够获得以下正样本:
(世界上, 是), (世界上, php), (世界上, 最好的), (世界上, 语言), (最好的, 世界上), …
训练目标为最大化如下对数似然函数:
其中 cc 为上下文的距离限定,即仅取词 wtwt 的先后 cc 个词进行预测。cc 越大,训练结果更精确,可是计算复杂度加大,训练成本相应也更大,通常取 cc 为2~3就能训练出不错的结果。基本的 Skip-Gram 模型采用softmax方法将以上目标函数中几率 p(wi+j|wi)p(wi+j|wi) 定义为:
其中 vwvw 表示输入词 ww 的向量,θwθw 表示预测结果为 ww 的权值参数,两者都是待训练的参数。不难发现,经过以上公式,计算每一个词的损失函数都要用到词典中的全部词,而通常词典的量级都很是大,因此这种方式是不切实际的。对于一个样本,例如(“世界上”, “php”),无非是根据词“世界上”去预测词“php”,那么就能够当作一个二分类问题,对于输入词“世界上”,预测“php”为正,预测其余则为负,其余词多是除“php”之外的全部词,为了简化计算,能够经过采样的方式,每次随机从全部除“php”之外的词中取 kk 个词做为负样本对象,那么训练目标则能够转化为相似于逻辑回归目标函数:
以上表达式称之为 NCE(Noise-contrastive estimation)[4]目标函数,其中等号右边第二项表示经过一个服从 Pn(w)Pn(w)分布的采样算法取得 kk 个负样本的指望损失。文[2]中采用了一个简单的一元分布采样,简化了计算,称之为负采样(Negative Sampling),下面详细介绍负采样算法。
词典中的每一个词在语料库中出现的频次有高有低,理论上来讲,对于那些高频词,被选为负样本的几率较大,对于那些低频词,被选为负样本的几率较小。基于这个基本事实,能够经过带权采样方法来实现,假设每一个词的词频表示为单位线段上的一小分段,对于词典大小为 NN 的语料库,能够将词典中全部的词表示为单位线段上的一点,再在单位线段上等距离划分 MM 个等分, M>>NM>>N , 具体采样过程就是随机获得一个数 i<Mi<M,经过映射找到其对应的词,以下如所示。
文[2]中在实际负采样计算词频时,作了一点修正,不是简单的统计词的出现次数,而是对词的出现次数作了 αα 次幂处理,最后词频公式为:
在一个大语料库中,不少常见的词大量出现,如“的”、“是”等。这些词虽然词频较高,可是能提供的有用信息却不多。通常来讲,这些高频词的词向量在训练几百万样本后基本不会有太大的变化,为了提升训练速度,平衡低频词和高频词,文[2]中提出一种针对高频词二次采样的技巧,对于每一个词,按以下几率丢弃而不作训练。
其中f(wi)f(wi)表示词频,从上述公式中不难发现,二次采样仅针对那些知足 f(wi)>tf(wi)>t 所谓的高频词有效,参数 tt 根据语料库的大小而设置,通常设置为 10−510−5 左右。
根据以上实现原理,下面结合代码阐述利用TensorFlow实现一个简易的word2vec模型[5],借助TensorFlow丰富的api以及强大的计算引擎,咱们能够很是方便地表达模型。给定语料库做为训练数据,首先扫描语料库创建字典,为每一个词编号,同时将那些词频低于min_count的词过滤掉,即不对那些陌生词生成词向量。对于一个样本(“世界上”, “php”),利用负采样获得若干负实例,分别计算输入词为“世界上”到“php”以及若干负样本的logit值,最后经过交叉熵公式获得目标函数(3-3)。
首先定义词向量矩阵,也称为 embedding matrix,这个是咱们须要经过训练获得的词向量,其中vocabulary_size
表示词典大小,embedding_size
表示词向量的维度,那么词向量矩阵为 vocabulary_size ×× embedding_size,利用均匀分布对它进行随机初始化:
1 |
embeddings = tf.Variable( |
定义权值矩阵和偏置向量(对应于3-3式中的 θθ),并初始化为0:
1 |
weights = tf.Variable( |
给定一个batch的输入,从词向量矩阵中找到对应的向量表示,以及从权值矩阵和偏置向量中找到对应正确输出的参数,其中examples
是输入词,labels
为对应的正确输出,一维向量表示,每一个元素为词在字典中编号:
1 |
# Embeddings for examples: [batch_size, embedding_size] |
负采样获得若干非正确的输出,其中labels_matrix
为正确的输出词,采样的时候会跳过这些词,num_sampled
为采样个数,distortion
即为公式(3-4)中的幂指数:
1 |
labels_matrix = tf.reshape( |
找到采样样本对应的权值和偏置参数:
1 |
# Weights for sampled ids: [num_sampled, embedding_size] |
分别计算正确输出和非正确输出的logit值,即计算 WX+bWX+b,并经过交叉熵获得目标函数(3-3):
1 |
# True logits: [batch_size, 1] |
计算流图构建完毕后,咱们须要去优化目标函数。采用梯度降低逐步更新参数,首先须要肯定学习步长,随着迭代进行,逐步减小学习步长,其中trained_words
为已训练的词数量,words_to_train
为全部待训练的词数量:
1 |
lr = init_learning_rate * tf.maximum( |
定义优化算子,使用梯度降低训练模型:
1 |
optimizer = tf.train.GradientDescentOptimizer(lr) |
通过以上步骤后,便可获得词向量矩阵,即上述代码中的变量embeddings
,那么如何验证获得的词向量矩阵的好坏呢,Mikolov等人发现[2],若是一对关系差很少的词,其词向量在空间中的连线近乎平行,以下图所示。
为此,给定基准测试集,其每行包含4个词组成一个四元组 (w1,w2,w3,w4)(w1,w2,w3,w4) ,对于一个较好的词向量结果,每一个四元组大体会有以下关系:
人类不是从脑子一片空白开始思考,当你读一篇文章的时候,你会根据前文去理解下文,而不是每次看到一个词后就忘掉它,理解下一个词的时候又从头开始。传统的神经网络模型是从输入层到隐藏层再到输出层,每层之间的节点是无链接的,这种普通的神经网络不具有记忆功能,而循环神经网络(Recurrent Neural Network,RNN)就是来解决这类问题,它具有记忆性,一般用于处理时间序列问题,在众多NLP问题中,RNN取得了巨大成功以及普遍应用。
在RNN网络中,一个序列当前的输出除了与当前输入有关之外,还与前面的输出也有关,下图为RNN中一个单元的结构示意图,图片来源于文[7]。
上图理解起来可能还不是很形象,根据时间序列将上图平铺展开获得以下图,其链式的特征揭示了 RNN 本质上是与序列相关的,因此 RNN 对于这类数据来讲是最天然的神经网络架构。
然而 RNN 有一个缺点,虽然它能够将以前的信息链接到当前的输入上,可是若是当前输入与以前的信息时间跨度很大,因为梯度衰减等缘由,RNN 学习如此远的信息的能力会降低,这个问题称之为长时间依赖(Long-Term Dependencies)问题。例如预测一句话“飞机在天上”下一个词,可能不须要太多的上下文就能够预测到下一个词为“飞”,这种状况下,相关信息与要预测的词之间的时间跨度很小,RNN 能够很容易学到以前的信息。再好比预测“他来自法国,…,他会讲”的下一个词,从当前的信息来看,下一个词多是一种语言,可是要想准确预测哪一种语言,就须要再去前文找信息了,因为前文的“法国”离当前位置的时间跨度较大,RNN很难学到如此远的信息。更多长时间依赖细节参考文[8]。幸运的是,有一种 RNN 变种,叫作长短时记忆网络(Long Short Term Memory networks, LSTM),能够解决这个问题。
LSTM 是一种带有选择性记忆功能的 RNN,它能够有效的解决长时间依赖问题,并能学习到以前的关键信息。以下图所示为 LSTM 展开后的示意图。
相对于 RNN , LSTM 只是在每一个单元结构上作了改进,在 RNN 中,每一个单元结构只有单个激活函数,而 LSTM 中每一个单元结构更为复杂,它增长了一条状态线(图中最上面的水平线),以记住从以前的输入学到的信息,另外增长三个门(gate)来控制其该状态,分别为忘记门、输入门和输出门。忘记门的做用是选择性地将以前不重要的信息丢掉,以便存储新信息;输入门是根据当前输入学习到新信息而后更新当前状态;输出门则是结合当前输入和当前状态获得一个输出,该输出除了做为基本的输出外,还会做为下一个时刻的输入。下面用数学的方式表达每一个门的意思。
忘记门,要丢掉的信息以下:
输入门,要增长的信息以下:
那么根据忘记门和输入门,状态更新以下:
输出门,获得输出信息以下:
LSTM 单元输入都是上一个时刻的输出与当前时刻的输入经过向量concat链接而获得,基于这个输入,利用sigmoid函数做为三个门的筛选器,分别获得 ftft 、itit 、otot,这三个筛选器分别选择部分份量对状态进行选择性忘记、对输入进行选择性输入、对输出进行选择性输出。以上是 LSTM 基本结构原理,在这基础上,根据不一样的实际应用场景,演变出不少 LSTM 的变体,更多关于 LSTM 的详细解释请参考文[7]。下面介绍一种深层 LSTM 网络[9],该结构也是 TensorFlow 中 LSTM 所实现的根据[10]。
深度学习,其特色在于深,前面已经讲述单层 LSTM 网络结构,深层 LSTM 网络其实就是将多层 LSTM 叠加,造成多个隐藏层,以下图所示。
上图中每一个 LSTM 单元内部结构以下图所示,对于 ll 层 tt 时刻来讲,hlt−1ht−1l 为 ll 层 t−1t−1 时刻(即上一个时刻)的输出,hl−1thtl−1 为 l−1l−1 层(即上一层) tt 时刻的输出,这两个输出叠加做为 ll 层 tt 时刻的输入。
根据上面的结构,能够获得 ll 层 LSTM 数学表达, hl−1t,hlt−1,clt−1→hlt,clthtl−1,ht−1l,ct−1l→htl,ctl:
其中 clt−1ct−1l 表示上一时刻的状态,cltctl 表示由当前输入更新后的状态。
然而,实践证实大规模的 LSTM 网络很容易过拟合,实际应用中,须要采起正则化方法来避免过拟合,神经网络中常见的正则化方法是Dropout方法[11],文[12]提出一种简单高效的Dropout方法运用于 RNN/LTSM 网络。以下图所示,Dropout仅应用于虚线方向的输入,即仅针对于上一层的输出作Dropout。
根据上图的Dropout策略,公式(5-5)能够改写成以下形式:
其中 DD 表示Dropout操做符,会随机地将 hl−1thtl−1 的中的份量设置为零。以下图所示,黑色粗实线表示从 t−2t−2 时刻的信息流向 t+2t+2 时刻做为其预测的参考,它经历了 L+1L+1 次的Dropout,其中 LL 表示网络的层数。
根据前面所述的 LSTM 模型原理,实现以前提到的语言模型,即根据前文预测下一个词,例如输入“飞机在天上”预测下一个词“飞”,使用 TensorFlow 来实现 LSTM 很是的方便,由于 TensorFlow 已经提供了基本的 LSTM 单元结构的Operation,其实现原理就是基于文[12]提出的带Dropout的 LSTM 模型。完整代码请参考ptb_word_lm.py
利用TensorFlow提供的Operation,实现 LSTM 网络很简单,首先定义一个基本的 LSTM 单元,其中size
为 LSTM 单元的输出维度,再对其添加Dropout,根据 LSTM 的层数num_layers
获得多层的 RNN 结构单元。
1 |
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0) |
每次给定一个batch的输入,将 LSTM 网络的状态初始化为0。词的输入由词向量表示,因此先定义一个embedding矩阵,这里能够不要关心它一开始有没有,它会在训练过程当中的慢慢获得的,仅做为训练的副产品。假设LSTM网络展开num_steps
步,每一步给定一个batch的词做为输入,通过 LSTM 单元处理后,状态更新并获得输出,并经过softmax归一化后计算损失函数。
1 |
initial_state = cell.zero_state(batch_size, tf.float32) |
简单采用梯度降低优化上述损失函数,逐步迭代,直至最大迭代次数,获得final_state
,即为LSTM所要学习的参数。
|
optimizer = tf.train.GradientDescentOptimizer(lr) |
模型训练完毕后,咱们已经获得LSTM网络的状态,给定输入,通过LSTM网络后便可获得输出了。
1 |
(cell_output, _) = cell(inputs, state) |
在使用TensorFlow处理深度学习相关问题时,咱们不须要太关注其内部实现细节,只需把精力放到模型的构建上,利用TensorFlow已经提供的抽象单元结构就能够构建灵活的模型。也偏偏正是由于TensorFlow的高度抽象化,有时让人理解起来颇费劲。因此在咱们使用TensorFlow的过程当中,不要把问题细化的太深,一切数据当作Tensor便可,利用Tensor的操做符对其进行运算,不要在脑海里想如何如何的运算细节等等,否则就会身陷囹圄。
[1]. Bengio Y, Schwenk H, Senécal J S, et al. Neural probabilistic language models[M]//Innovations in Machine Learning. Springer Berlin Heidelberg, 2006: 137-186.MLA.
[2]. Mikolov T, Sutskever I, Chen K, et al. Distributed representations of words and phrases and their compositionality[C]//Advances in neural information processing systems. 2013: 3111-3119.
[3]. Mikolov T, Le Q V, Sutskever I. Exploiting similarities among languages for machine translation[J]. arXiv preprint arXiv:1309.4168, 2013.
[4]. Gutmann M U, Hyvärinen A. Noise-contrastive estimation of unnormalized statistical models, with applications to natural image statistics[J]. The Journal of Machine Learning Research, 2012, 13(1): 307-361.
[5]. Vector Representations of Words. https://www.tensorflow.org/versions/r0.8/tutorials/word2vec/index.html#vector-representations-of-words
[6]. word2vec 中的数学原理详解. http://www.cnblogs.com/peghoty/p/3857839.html
[7]. Understanding LSTM Networks. http://colah.github.io/posts/2015-08-Understanding-LSTMs/
[8]. Bengio Y, Simard P, Frasconi P. Learning long-term dependencies with gradient descent is difficult[J]. Neural Networks, IEEE Transactions on, 1994, 5(2): 157-166.
[9]. Graves A. Generating sequences with recurrent neural networks[J]. arXiv preprint arXiv:1308.0850, 2013.
[10]. Recurrent Neural Networks. https://www.tensorflow.org/versions/r0.8/tutorials/recurrent/index.html#recurrent-neural-networks
[11]. Srivastava N. Improving neural networks with dropout[D]. University of Toronto, 2013.
[12]. Zaremba W, Sutskever I, Vinyals O. Recurrent neural network regularization[J]. arXiv preprint arXiv:1409.2329, 2014.
转载请注明出处,本文永久连接:http://sharkdtu.com/posts/nn-nlp.html