2.5.神经网络深入
最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
我们如何从一个词序列中得到语义和情感——隐性语义信息。
词序,如:
词的邻近度(Proximity),如:
关系的模式可以从两个方面理解:空间和时间。
空间:在文字的位置上寻找关系。
时间:词和字母变成了时间序列数据。
全连接神经网络:
卷积神经网络(Convolutional Neural Net, CNN)得名于在数据样本上用滑动窗口(或卷积)的概念。
卷积神经网络最早用于图像处理、图像识别,它能捕捉每个样本中数据点之间的空间关系。卷积网络(Convolutional Net),也称为convnet,它不像传统的前馈网络对每个元素分配权重,取而代之,它定义了一组在图像上移动的过滤器(filter,也称卷积核、滤波器、特征检测器)——这就是卷积。
滑动:可以把它看做一系列快照,数据通过这个窗口时,会做一些处理,窗口向下滑动一点,就再做一次处理。这个滑动/快照使得CNN具有高度并行性,对给定数据样本的每个快照都可独立于其他数据样本而计算,相互之间不等待。
每个卷积“走”的距离称为步长,通常设置为1——移动一个像素,步长过大会导致卷积核之间无重叠,失去像素和相邻像素之间的数据而导致“模糊”效果。
卷积核包含两部分:
一组权重
一个激活函数
卷积核神经元和普通的隐藏层神经元十分相似,但在扫描输入样本的整个过程中,每个卷积核的权重是固定的,整个图像中所有卷积核的权重都一样。卷积神经网络中每个卷积核都是独一无二的,但在图像快照中每个卷积核的元素都是固定的。
参考下边两图的变化:
方法一:忽略输出维度变小的问题,Keras中设置padding = 'valid'
——这种方式的缺点是原始输入的边缘数据会被欠采样,如果输入图像过小,则结果会不理想。
方法二:填充(padding),往外部边缘添加足够多的数据——这种方式缺点是添加了可能不相关的内容,导致输出结果产生偏离。
卷积流水线(略)
卷积核权值的梯度之和:
这个概念和常规前馈网络基本相同——计算每个特定权重对系统综合误差的贡献,再来决定如何更好调整权重,使得在后边的训练样本中尽量减小误差。
卷积网络在NLP应用中主要是体现在“水平”方向上,当然这种概念也适用于从上到下然后从右往左阅读的语言(日语),此时处理的是单方面的“垂直”关系。如下图一堆卷积核:
一堆卷积核:词本身的向量表示是以“向下”扩展的方式,卷积核在移动过程中会覆盖词向量这个维度的整体长度,通常说一堆卷积,表示的是短语的宽度,在二维卷积中——图像,通常会从左到右从上到下扫描输入数据。
Keras实现(略)
我们添加的第一部分是卷积层。
输出层维度小于输入层维度:使用前文中的丢弃。
输出层维度等于输入层维度:可使用前文中的填充(最常用:Zero-Padding,零填充)。
池化是卷积神经网络中的一种降维方法——每个定义的卷积核都会创建一个新“版本”的数据样本——即经过卷积过滤的数据样本,所以性能就显得很重要了。池化的关键思想就是把每个卷积核的输出均匀分成多个子部分,对每个字部分,挑选或计算一个具有代表性的值,然后将这些值的集合作为下一层输入。
池化的两种方式:
平均池化:通过求子集的平均值理论上可保留更多的数据信息。
最大池化:可以看到这个子集区域中最突出的特征信息。
特殊收获:位置不变性。
dropout是一种特殊的技术,用于防止神经网络中的过拟合,NLP中并非特有,但效果不错。——在训练过程中,按照一定比例随机“关掉”部分进入下一层的数据,模型不会学到训练集的特点,导致“过拟合”,而是学到更多数据中略有差别的表现模式。
Keras在Dropout层下面会做一些特殊操作,每次向前传递训练数据时,它会随机关闭一定比例的输入数据,而对实际应用的推理或预测时,则不会做Dropout,所以在非训练的推理阶段,Dropout层后面的层接收到的信号会显著增强。为了缓解该问题,Keras在训练阶段会按比例增强所有的未关闭输入,使得聚合信号和推理阶段的强度一致。
关于损失函数loss
多分类:categorical_crossentropy
二分类:binary_crossentropy
优化参数optimizer
:可设置在训练阶段的一系列优化策略,包括:随机梯度下降、Adam、RSMProp等——根据当前训练状态动态改变训练参数(特别是学习率),某些方法会使用动量。
拟合:compile
完成模型构建,fit
完成模型训练。
在NLP中选择使用CNN主要好处是高效率,虽然池化层和卷积核大小造成的限制,会导致丢弃大量信息,但并不意味着它们不是有用的模型。利用CNN能够有效地对相对较大的数据集进行检测和预测情感,即使依赖Word2Vec词嵌入,CNN也可以在不映射整个语言的条件下,通过较少的词嵌入表示来运行。
神经网络的记忆功能。
如果有一种方式“记忆”之前发生的事情(尤其在t + 1
时刻时,知道t
时刻发生的事情),就能捕获当序列中某些词条出现时,其他词条相对应会出现的模式。循环神经网络(recurrent neural net,RNN)就完成了这样一个事情。
这种网络中,最核心的概念是时刻/时间步(Time Step),它和单独的数据样本不是一回事,它可以将数据样本分解成更小的可以表示时间变化的块。自始至终,当前时刻为t
,下一时刻为t+1
。如上图:整个循环是由一个或多个神经元组成的前馈网络层,隐藏层和输出和普通输出一样,但它本身会和下一个时刻的正常输入数据一起作为输入回传到网络。展开过后效果如下:
t = 0
时刻的循环神经网络:
t = 1
时刻的循环神经网络:
序列中的第一个输入没有“过去”,因此t=0
时的隐藏状态从t-1
时刻接收输入0,“填充”初始状态值的另外一种方式是:将相关但分开的样本一个一个传递到网络中,然后每个样本的最终输出用于下一个样本t=0
时的输入。
CNN和RNN在处理反向传播算法的原理区别如下:
CNN:
RNN:
开始会在最后一个时刻查看网络输出,并将该输出和标签比较,这就是对于误差(error)的定义,而误差是网络最终想要尽量减小的目标,但此处要介绍的处理输出方式和前边章节有所不同。
只有最后的输出会影响结果:
此处反向传播不是传播到上一层,而是过去层,所以最终结果图如下:
最终步骤如:
将每个数据样本切分成词条。
将每个词条输入前馈网络中。
将每个时刻的输出以及下一个时刻的输入作为同一层的输入。
获取最后一个时刻的输出并将其与标签进行比较。
在整个计算图中反向传播误差,一直回到第一个时刻t=0
的输入。
更新过程中比较棘手的部分是正在更新的权重而不是神经网络的不同分支,每个分支代表着不同时刻的相同网络,各个时刻的权重是相同的。
所有的示例中,向前传播传入单个训练样本,然后反向传播误差,和所有神经网络一样,这种向前传播可以依据每个训练样本进行,有可以分批进行。
至关重要的前期输出
所有的输出都很重要:
该过程类似于在n
个时刻执行普通的随时间反向传播,由于权重的校正值是累积的,整个过程中从最后时刻一直反向传播到初始时刻,并且对每个权重计算更新的总数,然后对于在导数第二个时刻计算出的误差进行递归处理,并将反向进行处理直到t=0
的所有校正值加起来。如下图:
所以,较早更新权重会“污染”反向传播中的梯度计算,所以前期输出很重要。
尽管RNN需要学习的权重相对较少,但训练一个RNN的代价高昂,特别是对于长序列(如10个词条),拥有词条越多,每个时刻误差必须反向传播的时间越长,都有更多的求导操作,但RNN的运算量会很大。
如果RNN变深(网络时刻),更多的麻烦也会出现。梯度消失问题(Vanishing Gradient Problem)有一个推论:梯度爆炸问题(Exploding Gradient Problem),随着网络变深,误差信号会随着梯度的每一次计算消散或增长。
RNN也面临同样的问题,从数学原理上,时刻的每一次后退相当于将一个误差反向传播到前一层——所以大多数前馈网络只有几层深。尽管梯度可能会在计算最后一次权重集的过程中消失或爆炸,实际上只更新了一次权重集,且每个时刻的权重集相同。
提示:一个好的经验法则是尽量使模型不要比训练的数据更复杂。说起来容易做起来难,但是这个想法为我们在数据集上做实验时的调参提供了一个基本法则。较复杂的模型对训练数据过拟合,泛化效果不佳;过于简单的模型对训练数据欠拟合,而且对于新数据也没有太多有意义的内容。我们会看到这个讨论被称为偏差与方差的权衡。对数据过拟合的模型具有高方差和低偏差,而欠拟合的模型恰恰相反:低方差和高偏差;它会用一致的方式给出答案,结果把一切都搞错了。
将循环神经网络层生成的“思想向量”传递到前馈网络中,将不再保留我们努力试图想要包含的输入顺序的关系。
maxlen
参数是最大的问题,训练集在样本长度上变化很大,通常做法:
长度不超过100词条的样本加长到400词条
1000个词条的样本截断到400个词条
上述两种方法都引入了大量的噪声数据
embedding_dims
是由选择的Word2vec
模型决定的,但它应该是可以充分表示数据集的值。
batch_size
可加速训练,它减少了需要反向传播的次数。
epochs
易于测试和调优,必须从头开始尝试每个新的epochs
参数,在停止的地方将模型保存;还有一种调优方法是增加EarlyStopping
的回调。
num_neurons
是重要参数,设置隐藏层的神经元个数。
提示:经常实验,并记录模型对我们操作的反应。
某些场景,系统需要记住从一个输入样本到下一个输入样本信息,不仅仅是单个样本的一个输入词条到下一个输入词条的一次时刻。Keras在基本RNN层提供了一个关键参数stateful
(默认False),若添加SimpleRNN
时则为True,这样最后一个样本的最后一个输出将在下一个时刻和第一个词条输入一起传递给它。
这个参数在对一个大型已经被分割成段落或句子进行处理的文档建模时比较有用,stateful=True
。
对于打乱的文本,由于样本顺序不能帮助模型找到合适匹配关系,所以stateful=False
。
人类阅读句子是单向的,当接收到新信息时,大脑可以迅速回到文本前面的内容,可处理那些没有按照最佳顺序呈现的信息。所以需要一种方式允许模型在输入之间来回切换——这就是双向循环神经网络。
基本思路是将两个RNN并排在一起,将输入像普通单向RNN的输入一样传递到其中一个RNN中,并将同样的输入从反向传递到另一个RNN中。然后,在每个时刻将这两个网络的输出拼接到一起作为另一个网络中对应(相同输入词条)时刻的输入。我们获取输入最后一个时刻的输出后,将其与在反向网络的第一个时刻的由相同输入词条生成的输出拼接起来。
在稠密层(denselayer)的前面有一个向量,它的形状(神经元数量×1)来自给定输入序列的循环层的最后一个时刻。这个向量与前一章卷积神经网络中的思想向量是同等的概念。它是词条序列的编码表示。
循环神经网络的缺陷:当传递了两个词条后,前面词条几乎完全失去了它的作用(记忆太短)——有些词条在句子中相距很远,但依旧可能是深度关联的。所以某些场景需要能够在整个输入序列中记住过去的方法——长短期记忆(Long short-term memory,LSTM)则正式所需要的一类方法。
长短期记忆网络的现代版本通常使用一种特殊的神经网络单元,门控循环单元(Gated Recurrent Unit,GRU),它可以有效地保持长、短期记忆,使LSTM能够精确地处理长句子或文档。实际上,LSTM在涉及时间序列、离散序列、NLP领域的应用中都取代了循环神经网络。
LSTM对于循环网络的每一层都引入了状态(state)的概念,状态作为网络的记忆(Memory)——您可以把上述流程看做在面向对象中添加类属性,每个训练样本都会更新记忆状态的属性。LSTM中,管理存储在状态(记忆)中信息的规则就是经过训练的神经网络本身——这就是神奇之处;有了LSTM之后,模型可以开始学习人类习以为常和在潜意识层面上处理语言的模式。
LSTM工作原理如下:
如果展开一个标准的循环神经网络,并添加记忆单元:
每次迭代过程都可以访问记忆单元,这个记忆单元的添加以及与其交互的机制,时它和传统的神经网络有所区别——实际上,可以将LSTM理解成特例化的循环神经网络。
部分文献中,上图的记忆状态又称为LSTM元胞(Cell),而不是LSTM神经元,因为它包含两个额外的神经元或门控单元,就像一个硅计算机记忆细胞一样。当一个LSTM元胞和一个sigmoid激活函数结合,向下一个LSTM元胞输出一个值时,这个包含多个相互作用元素的结构被称为LSTM单元,多个LSTM单元组合形成一个LSTM层。上图中穿过展开循环神经元的水平线表示保存的记忆或状态,当词条序列被传递到一个多单元LSTM层时,它将编程一个具有没有LSTM元胞维度的向量。
这个网络有三个门:遗忘门、输入/候选门、输出门
模拟第一个输入,通过元胞不是一条单一的路径,它拥有很多分支,我们将跟随每个分支一段时间,然后后退、前进、进入另一分支,最后再回到一起,得出元胞输出的最终结果。
在第一个分叉的地方,我们将拼接起来的输入向量的副本传递到似乎会预示厄运的遗忘门(第一站),它的目标是:根据给定的输入,学习要遗忘元胞的多少记忆:
遗忘门的作用:LSTM不仅必须对序列中的长期依赖关系建模,而且同样重要的是,还必须随着新依赖关系的出现而忘记长期依赖关系,这就是遗忘门的作用,在记忆元胞中为相关的记忆腾出空间。遗忘门本身是一个前馈网络,它由n
个神经元组成,每个神经元权重个数为m + n + 1
,激活函数是sigmoid函数。
遗忘门的输出向量是某种掩码(多孔掩码),它会遗忘记忆向量的某些元素
当它输出值接近于1时,对于该时间,关联元素中更多的记忆知识会被保留。
当它接近于0时,遗忘的记忆知识就越多。
候选门是遗忘门的下一个分支,它有两个独立的神经元,需要做两件事:
决定哪些输入向量元素值的记住(遗忘门中的掩码)。
将记住的输入元素按规定路线放置到正确记忆槽中。
遗忘门:
第一部分是具有sigmoid
激活函数的神经元,目标是学习要更新记忆向量的哪些输入值。
第二部分决定使用多大的值来更新记忆,使用一个tanh
激活函数(强制输出-1 ~ 1
之间)。
在穿越元胞的过程中,我们只向元胞的记忆写入了内容,输出时需要利用整个结构,输出门接受输入,并将其传递到输出门。在前边步骤中构建的记忆结构现在已经准备好了,它将对我们应该输出什么进行权衡,这是通过记忆创建最后一个掩码来判断(实际也是一个门,但区别于本章提到的三种门),它对记忆状态的每个元素使用tanh
函数,提供了一个在-1
和1
之间的n维浮点数向量。
LSTM元胞的输出类似于简单循环神经网络的输出,它作为层的输出(在t时刻)传递到元胞外,并作为
t+1
时刻输入的一部分回传到元胞本身。
这个网络如何学习?和其他神经网络一样——通过反向传播算法。
基本的RNN(Vanilla RNN)容易受到梯度消失的影响,在任意给定的时刻,导数都是权重的一个决定因素,因此结合不同学习率,往之前时刻传播时,几次迭代后,权重(学习率)可能会将梯度缩小为0;所以反向的结果权重更新要么很小要么为0。
LSTM通过记忆状态避免了这个问题,它对神经元的更新只依赖当前时刻和前一个时刻的记忆状态,这样,整个函数误差在每个时刻“更接近”神经元,这就是误差传播(error carousel)。
使用统计学的方式来思考,想想模型看到了什么样的“文档”。
从卷积网络开始,我们一直在使用相同的数据,以完全相同的方式进行处理,这样虽然看到了模型在数据上的表现,但也做出了一些损害数据完整性的选择(弄脏了数据)。类似的,循环神经网络的实现,包括简单RNN和LSTM,都在努力构造一个固定长度的思想向量。
一个对象的固定长度向量表示——思想向量,通常被称为嵌入(embedding)。
“未知”的词条基本上就是在预训练的Word2vec模型中找不到的词,其列表可能非常大。
第一种方法(反直觉):对于没有由向量建模的每个词条,从现有词嵌入模型中随机选择一个向量并使用它。
第二种方法(常见的):在重构输入时,用一个特定的词条替换词向量库中没有的所有词条,该特定词条通常称为“UNK”(未知词条)。
语言建模时,很多含义隐藏在字符里面,语音语调、头韵、韵律——如果把它分解到字符集,可以对所有这些建模——人类不需要分解得到如此细致就可以做语言建模,因为定义十分复杂,所以这种逻辑不太容易传递给机器,于是需要讨论字符级建模。
除了上述UNK以外,引入另一个用于填充的字符——PAD。
如果以特定的“风格”或“看法”生成新文本,就得到了一个又去的聊天机器人。它像一个马尔科夫链(Markov Chain),根据出现在1-gram, 2-gram, n-gram
后的词,预测序列将要给出的下一个词,LSTM模型也可以基于刚刚看到的词预测下一个词出现的概率,这就是记忆的优点。
如果只预测下一个字符,就得到如下图:
字符级建模是通向更复杂模型的必经之路——这些模型不仅可以获取拼写等细节,还可以获取语法和标点符号;接下来就可以考虑生成一些新的文本。RMSProp
的工作原理是通过使用“该权重最近梯度大小的平均值”,来调整学习率以更新各个权重。
最后一个主要变化是没有dropout层,由于需要对某个数据集特定建模,而没有兴趣将其推广到其他问题,所以这种场景中是允许过拟合的(还是理想的)。
文本生成的增强方法:
增加语料库的数量并提高质量
增加模型的复杂度(神经元的数量)
实现一个更精细的字符一致性算法
句子分段
根据需要在语法、拼写和语气上添加过滤器
生成比实际展示给用户更多的一些结果案例
使用在会话上下文中选择的种子文本引导聊天机器人转向有用的主题
在每一轮对话中使用多个不同的种子文本来探索聊天机器人擅长谈论什么领域的话题以及用户认为哪些内容会有帮助
思考:如何让机器人以某种风格说出一些具有实际意义的东西。
使用具有窥视孔(peephole)连接到LSTM:标准LSTM元胞中每个门都可以直接访问当前记忆状态,并将其作为输入的一部分,所有门包含与记忆状态相同维度的额外权重;然后,每个门的输入是该时刻元胞的输入、前一个时刻元胞的输出和记忆状态本身的拼接。
将以及单元看做是对名词/动词对或句子与句子之间动词时态引用的特定表示进行编码十分方便,但这病不是实际发生的事。堆叠LSTM层:
使用编码-解码架构(Encoder-Decoder Architecture)构建序列到序列的模型。
我们需要一对而不是一个LSTM,于是可以构建一个模块化的架构:编码-解码架构。
前半部分是序列编码器,将网络序列(自然语言文本)转换为较低维的标识形式。
后半部分是序列解码器,序列解码器设计成将向量重新转换回人类可读的问题。
序列到序列网络的架构,有时候缩写成seq2seq
,通过创建一个思想向量形式的输入表示来解除这个限制。然后,序列到序列模型使用该思想向量(上下文向量)作为第二个网络起点,第二个网络接收不同的输入集来生成输出序列。
思想向量:词向量是将词的意义压缩成一个固定长度的向量,在这个词义向量空间中,具有相似意义的词是相互接近的。思想向量的思路和它类似,神经网络可以将任何自然语言语句中的信息(不止是单个词)压缩成一个固定长度的向量来表示输入文本的内容,思想向量就是这个向量。
第一个网络,就是编码器,将输入文本转换成思想向量,思想向量分两部分:
编码器隐藏层的输出(经过了激活函数),state_h
。
输入样本LSTM元胞的记忆状态,state_c
。
然后思想向量称为第二个网络即解码器网络的输入——生成的状态(思想向量)作为解码器网络的初始状态(initial state),然后第二个网络使用初始状态和一种特殊输入:初始词条(start token);这种特殊结构中,训练阶段和推理阶段处理不同。
LSTM在文档之间共享词条模式识别器,因为遗忘门和更新门具有在读取多个文档后训练的权重,操作流程如:
如CNN和RNN中看到那样,我们需要将输入数据填充成固定长度,通常,我们需要使用填充词条扩展输入序列,使其与最长的输入序列匹配,在序列到序列网络的情况下,还需要准备目标数据并填充它以匹配最长的目标序列(输入和目标不用相等)。
除了所需填充,还应该使用初始词条和终止词条对目标序列注释,告诉解码器任务何时启动以及何时完成。
于是得到两个训练版本:
一个以初始词条开始(解码器输入)
一个不以初始词条开始(损失函数对目标序列的精确性评分)
序列到序列的模型每个训练样本对一个三元组:初始输入,预期输出(以初始词条为前置项),预期输出(没有初始词条),它包含两个网络:
编码器:生成思想向量。
解码器:思想向量做输入。
编码器的唯一目的是创建思想向量,然后将其作为解码器网络的初始状态,下图又称思想编码:
编解码器是独立模块,它们之间可以互换。内部LSTM的状态生成如下:
和编码器网络设置类似,解码器的设置非常简单,其主要区别在于:希望在每个时刻上获得网络的输出:
这是解码器,也是一般序列到序列模型的关键概念。大家训练一个网络来输出次要问题空间(另一种语言或另一种存在体对给定问题的回复),同时对所说的(输入)和回复(输出)形成一个“思想”。这个思想通过一个有一个词条决定了答案,最终只需要一个思想(由编码器生成)和一个通用的初始词条就可以开始工作了,这已经足以触发产生正确的输出序列。
序列到序列的训练是计算密集型的,非常耗时,如果大家的训练序列很长,或者想用一个大型的语料库来训练,我们强烈建议在GPU上训练这些网络,这样可以提高训练速度(30倍)左右。LSTM本质上不像卷积神经网络那样是可并行的,所以为了充分利用GPU,我们应该使用CuDMMLSTM
替换LSTM
层,这是为了在CUDA
支持的GPU上进行训练而优化的网络层。
生成聊天机器人步骤如:
为训练准备语料库
建立字符字典
生成独热编码训练集
训练序列到序列聊天机器人
组装序列生成模型
预测输出序列
生成回复
与聊天机器人交谈
有两种增强训练序列到序列模型的方法。
过多的填充会使计算成本高昂,特别是当大多数序列都很短,只有少数序列接近最大词条长度时,此时需要使用桶装法减少计算量。
和LSA潜在语义分析一样,较长的输入序列(文档)倾向于产生不精确表示这些文档的思想向量,思想向量受LSTM层(神经元数量)维度的限制,对于短输入/输出序列,一个思想向量足够了。注意力机制——这个想法告诉解码器应该注意输入序列中的哪些部分,这种“预演”是通过允许解码器除查看思想向量外,还允许查看编码器网络的所有状态来实现的。