剑指机器学习--循环神经网络

机器学习方法复习笔记-7

Posted by Leon Ling on November 9, 2020

循环神经网络(Recurrent Neural Network,RNN)是用来建模序列化数据的一 种主流深度学习模型。

传统的前馈神经网络一般的输入都是一个定长的向量,无法处理变长的序列信息,即使通过一些方法把序列处理成定长的向量,模型也很难捕捉序列中的长距离依赖关系。RNN则通过将神经元串行起来处理序列化的数据。由于每个神经元能用它的内部变量保存之前输入的序列信息,因此整个序列被浓缩成抽象的表示,并可以据此进行分类或生成新的序列。近年来,得益于计算能力的大幅提升和模型的改进,RNN在很多领域取得了突破性的进展——机器翻译、序列标注、图像描述、推荐系统、智能聊天机器人、自动作词作曲等。

[toc]

1. 循环神经网络和卷积神经网络

1.1 处理文本数据时,循环神经网络与前馈神经网络相比有什么特点?

传统文本处理任务的方法中一般将TF-IDF向量作为特征输入。显而易见,这样的表示实际上丢失了输入的文本序列中每个单词的顺序。在神经网络的建模过程中,一般的前馈神经网络,如卷积神经网络,通常接受一个定长的 向量作为输入。卷积神经网络对文本数据建模时,输入变长的字符串或者单词串,然后通过滑动窗口加池化的方式将原先的输入转换成一个固定长度的向量表示,这样做可以捕捉到原文本中的一些局部特征,但是两个单词之间的长距离依赖关系还是很难被学习到。

循环神经网络却能很好地处理文本数据变长并且有序的输入序列。它模拟了人阅读一篇文章的顺序,从前到后阅读文章中的每一个单词,将前面阅读到的有用信息编码到状态变量中去,从而拥有了一定的记忆能力,可以更好地理解之后的文本。下图展示了一个典型的循环神经网络结构。

由图可见,一个长度为$T$的序列用循环神经网络建模,展开之后可以看作是一个$T$层的前馈神经网络。其中,第$t$层的隐含状态$h_t$编码了序列中前t个输入的信息,可以通过当前的输入$x_t$和上一层神经网络的状态$h_{t−1}$计算得到;最后一层的状态$h_T$编码了整个序列的信息,因此可以作为整篇文档的压缩表示,以此为基础的结构可以应用于多种具体任务。例如,在$h_T$后面直接接一个Softmax层,输出文本所属类别的预测概率$y$,就可以实现文本分类。$h_t$和$y$ 的计算公式为 $\begin{array}net_t&=Yx_t+Wh_{t-1}\\ h_t&=f(net)\\ y&=g(Vh_T) \end{array}$,

其中 $f$ 和 $g$ 为激活函数,$U$ 为输入层到隐含层的权重矩阵,$W$ 为隐含层从上一时刻到下一时刻状态转移的权重矩阵。在文本分类任务中,$f$ 可以选取Tanh函数或者ReLU函数,$g$ 可以采用Softmax函数。

通过最小化损失误差(即输出的y与真实类别之间的距离),我们可以不断训练网络,使得得到的循环神经网络可以准确地预测文本所属的类别,达到分类目的。相比于卷积神经网络等前馈神经网络,循环神经网络由于具备对序列顺序信息的刻画能力,往往能得到更准确的结果。

2. RNN的梯度消失问题

2.1 RNN为什么会出现梯度消失或梯度爆炸?如何改进?

循环神经网络模型的求解可以采用BPTT(Back Propagation Through Time,基于时间的反向传播)算法实现,BPTT实际上是反向传播算法的简单变种。如果将循环神经网络按时间展开成T层的前馈神经网络来理解,就和普通的反向传播算法没有什么区别了。循环神经网络的设计初衷之一就是能够捕获长距离输入之间的依赖。从结构上来看,循环神经网络也理应能够做到这一点。然而实践发现,使用BPTT算法学习的循环神经网络并不能成功捕捉到长距离的依赖关系,这一现象主要源于深度神经网络中的梯度消失。传统的循环神经网络梯度可以表示成连乘的形式 $\frac{\partial net_t}{\partial net_1}=\frac{\partial net_t}{\partial net_{t-1}}\cdot\frac{\partial net_{t-1}}{\partial net_{t-2}}\cdot\cdot\cdot\frac{\partial net_2}{\partial net_1}$,其中

$\begin{array} net_t&=Ux_t+Wh_{t-1},\\ h_t&=f(net_t),\\ y&=g(Vh_t), \\ \frac{\partial net_t}{\partial net_{t_1}}&=\frac{\partial net_t}{\partial h_{t-1}}\frac{\partial h_{t-1}}{\partial net_{t_1}}=W\cdot diag[f’(net_{t-1})]\\ &=\begin{pmatrix} w_{11}f’(net_{t-1}^1) &\dots &w_{1n}f’(net_{t-1}^n)\\ \vdots & \ddots & \vdots \\ w_{n1}f’(net_{t-1}^1) &\dots &w_{nn}f’(net_{t-1}^n) \end{pmatrix} \end{array}$

其中$n$为隐含层$h_{t−1}$的维度(即隐含单元的个数), $\frac{\partial net_t}{\partial net_{t-1}}$对应的$n×n$维矩阵,又被称为雅可比矩阵。

由于预测的误差是沿着神经网络的每一层反向传播的,因此当雅克比矩阵的最大特征值大于1时,随着离输出越来越远,每层的梯度大小会呈指数增长,导致梯度爆炸;反之,若雅克比矩阵的最大特征值小于1,梯度的大小会呈指数缩小,产生梯度消失。对于普通的前馈网络来说,梯度消失意味着无法通过加深网络层次来改善神经网络的预测效果,因为无论如何加深网络,只有靠近输出的若干层才真正起到学习的作用。这使得循环神经网络模型很难学习到输入序列中的长距离依赖关系。

梯度爆炸的问题可以通过梯度裁剪来缓解,即当梯度的范式大于某个给定值时,对梯度进行等比收缩。而梯度消失问题相对比较棘手,需要对模型本身进行改进。深度残差网络是对前馈神经网络的改进,通过残差学习的方式缓解了梯度消失的现象,从而使得我们能够学习到更深层的网络表示;而对于循环神经网络来说,长短时记忆模型及其变种门控循环单元(Gated recurrent unit,GRU)等模型通过加入门控机制,很大程度上弥补了梯度消失所带来的损失。

3. 循环神经网络中的激活函数

3.1 在循环神经网络中能否使用ReLU作为激活函数?

答案是肯定的,但是需要对矩阵的初值做一定限制,否则十分容易引发数值问题。为了解释这个问题,先回顾一下循环神经网络的前向传播公式:$net_t=Ux_t+Wh_{h-1}$,$h_t=f(net_t)$,根据前向传播公式向前传递一层,可以得到 $net_t=Ux_t+Wh_{h-1}=Ux_t+Wf(Ux_{t-1}+Wh_{h-2})$

如果采用ReLU替代公式中的激活函数$f$,并且假设ReLU函数一直处于激活区域(即输入大于0),则有$f(x)=x$,$net_t=Ux_t+Wf(Ux_{t-1}+Wh_{h-2})$ 。继续将其展开,$net_t$的表达式中最终包含$t$个$W$连乘。如果$W$不是单位矩阵(对角线上的元素为1,其余元素为0的矩阵),最终的结果将会趋于0或者无穷,引发严重的数值问题。那么为什么在卷积神经网络中不会出现这样的现象呢?这是因为在卷积神经网络中每一层的权重矩阵W是不同的,并且在初始化时它们是独立同分布的,因此可以相互抵消,在多层之后一般不会出现严重的数值问题。

再回到循环神经网络的梯度计算公式

$\begin{array}\frac{\partial net_t}{\partial net_{t_1}}&=\frac{\partial net_t}{\partial h_{t-1}}\frac{\partial h_{t-1}}{\partial net_{t_1}}=W\cdot diag[f’(net_{t-1})]\\ &=\begin{pmatrix} w_{11}f’(net_{t-1}^1) &\dots &w_{1n}f’(net_{t-1}^n)\\ \vdots & \ddots & \vdots \\ w_{n1}f’(net_{t-1}^1) &\dots &w_{nn}f’(net_{t-1}^n) \end{pmatrix} \end{array}$

假设采用ReLU 激活函数,且一开始所有的神经元都处于激活中(即输入大于0),则$diag[f’(net_{t-1})]$为单位矩阵,有$\frac{\partial net_t}{\partial net_{t-1}}=W$。在梯度传递经历了$n$层之后,$\frac{\partial net_t}{\partial net_1}=W^n$。可以看到,即使采用了ReLU激活函数,只要W不是单位矩阵,梯度还是会出现消失或者爆炸的现象。

综上所述,当采用ReLU作为循环神经网络中隐含层的激活函数时,只有当$W$的取值在单位矩阵附近时才能取得比较好的效果,因此需要将$W$初始化为单位矩阵。实验证明,初始化$W$为单位矩阵并使用ReLU激活函数在一些应用中取得了与长短期记忆模型相似的结果,并且学习速度比长短期记忆模型更快,是一个值得尝试的小技巧。

4. 长短期记忆网络

4.1 LSTM是如何实现长短期记忆功能的?

与传统的循环神经网络相比,LSTM仍然是基于$x_t$和$h_{t−1}$来计算$h_t$,只不过对内部的结构进行了更加精心的设计,加入了输入门$i_t$、遗忘门$f_t$以及输出门$o_t$三个门和一个内部记忆单元$c_t$。输入门控制当前计算的新状态以多大程度更新到记忆单元中;遗忘门控制前一步记忆单元中的信息有多大程度被遗忘掉;输出门控制当前的输出有多大程度上取决于当前的记忆单元。

经典的LSTM中,第 $t$ 步的更新计算公式为 \(\begin{array} f_t&=\sigma(W_fx_t+U_fh_{t-1}+b_f),\\ o_t&=\sigma(W_ox_t+U_oh_{t-1}+b_o),\\ \tilde{c}_t&=Tanh(W_cx_t+U_ch_{t-1}),\\ c_t&=f_t\odot c_{t-1}+i_t\odot\tilde{c}_t,\\ h_t&=o_t\odot Tanh(c_t). \end{array}\)

其中$i_t$是通过输入$x_t$和上一步的隐含层输出$h_{t−1}$进行线性变换,再经过激活函数$σ$得到的。输入门$i_t$的结果是向量,其中每个元素是0到1之间的实数,用于控制各维度流过阀门的信息量;$W_i$、$U_i$两个矩阵和向量$b_i$为输入门的参数,是在训练过程中需要学习得到的。遗忘门$f_t$和输出门$o_t$的计算方式与输入门类似,它们有各自的参数$W$、$U$和$b$。与传统的循环神经网络不同的是,从上一个记忆单元的状态$c_{t−1}$到当前的状态$c_t$的转移不一定完全取决于激活函数计算得到的状态,还由输入门和遗忘门来共同控制。

在一个训练好的网络中,当输入的序列中没有重要信息时,LSTM的遗忘门的值接近于1,输入门的值接近于0,此时过去的记忆会被保存,从而实现了长期记忆功能;当输入的序列中出现了重要的信息时,LSTM应当把其存入记忆中,此时其输入门的值会接近于1;当输入的序列中出现了重要信息,且该信息意味着之前的记忆不再重要时,输入门的值接近1,而遗忘门的值接近于0,这样旧的记忆被遗忘,新的重要信息被记忆。经过这样的设计,整个网络更容易学习到序列之间的长期依赖。

4.2 LSTM里各模块分别使用什么激活函数,可以使用别的激活函数吗?

关于激活函数的选取,在LSTM中,遗忘门、输入门和输出门使用Sigmoid函数作为激活函数;在生成候选记忆时,使用双曲正切函数Tanh作为激活函数。值得注意的是,这两个激活函数都是饱和的,也就是说在输入达到一定值的情况下,输出就不会发生明显变化了。如果是用非饱和的激活函数,例如ReLU,那么将难以实现门控的效果。 Sigmoid函数的输出在$0~1$之间,符合门控的物理定义。且当输入较大或较小时,其输出会非常接近1或0,从而保证该门开或关。在生成候选记忆时,使用Tanh函数,是因为其输出在$−1~1$之间,这与大多数场景下特征 分布是0中心的吻合。此外,Tanh函数在输入为0附近相比Sigmoid函数有更大的梯度,通常使模型收敛更快。

激活函数的选择也不是一成不变的。例如在原始的LSTM中,使用的激活函数是Sigmoid函数的变种,$h(x)=2sigmoid(x)−1,g(x)=4sigmoid(x)−2$,这两个函数的范围分别是$[−1,1]$和$[−2,2]$。并且在原始的LSTM中,只有输入门和输出门,没有遗忘门,其中输入经过输入门后是直接与记忆相加的,所以输入门控$g(x)$的值是0中心的。后来经过大量的研究和实验,人们发现增加遗忘门对LSTM的性能有很大的提升,并且$h(x)$使用Tanh比$2*sigmoid(x)−1$要好,所以现代的LSTM采用Sigmoid和Tanh作为激活函数。事实上在门控中,使用Sigmoid函数是几乎所有现代神经网络模块的共同选择。例如在门控循环单元和注意力机制中,也广泛使用Sigmoid函数作为门控的激活函数。

此外,在一些对计算能力有限制的设备,诸如可穿戴设备中,由于Sigmoid函数求指数需要一定的计算量,此时会使用0/1门(hard gate)让门控输出为0或1的离散值,即当输入小于阈值时,门控输出为0;当输入大于阈值时,输出为1。从而在性能下降不显著的情况下,减小计算量。经典的LSTM在计算各门控时,通常使用输入$x_t$和隐层输出$h_{t−1}$参与门控计算,例如对于输入门的更新:$i_t=\sigma(W_ix_t+U_ih_{t-1}+b_i)$。其最常见的变种是加入了窥孔机制(peephole),让记忆$c_{t−1}$也参与到了门控的计算中,此时输入门的更新方式变为 $i_t=\sigma(W_ix_t+U_ih_{t-1}+V_ic_t-1+b_i)$。

总而言之,LSTM经历了20年的发展,其核心思想一脉相承,但各个组件都发生了很多演化。了解其发展历程和常见变种,在实际工作和研究中,结合问题选择最佳的LSTM模块,灵活地思考,并知其所以然,而不死背各种网络的结构和公式。

5. Seq2Seq模型

5.1 什么是Seq2Seq模型?Seq2Seq模型有哪些优点?

Seq2Seq模型的核心思想是,通过深度神经网络将一个作为输入的序列映射为一个作为输出的序列,这一过程由编码输入与解码输出两个环节构成。在经典的实现中,编码器和解码器各由一个循环神经网络构成,既可以选择传统循环神经网络结构,也可以使用长短期记忆模型、门控循环单元等。在Seq2Seq模型中,两个循环神经网络是共同训练的。

假想一个复习和考试的场景,如图所示。我们将学到的历史信息经过了一系列加工整理,形成了所谓的知识体系,这便是编码过程。然后在考试的时候,将高度抽象的知识应用到系列问题中进行求解,这便是解码过程。譬如对于学霸,他们的网络很强大,可以对很长的信息进行抽象理解,加工内化成编码向量,再在考试的时候从容应答一系列问题。而对于大多数普通人,很难记忆长距离、长时间的信息。在考前只好临时抱佛脚,编码很短期的序列信号,考试时也是听天由命,能答多少写多少,解码出很短时效的信息。

对应于机器翻译过程,如图10.4所示。输入的序列是一个源语言的句子,有三个单词A、B、C,编码器依次读入A、B、C和结尾符。 在解码的第一步,解码器读入编码器的最终状态,生成第一个目标语言的词W;第二步读入第一步的输出W,生成第二个词X;如此循环,直至输出结尾符。输出的序列W、X、Y、Z就是翻译后目标语言的句子。

在文本摘要任务中,输入的序列是长句子或段落,输出的序列是摘要短句。在图像描述文本生成任务中,输入是图像经过视觉网络的特征,输出的序列是图像的描述短句。进行语音识别时,输入的序列是音频信号,输出的序列是识别出的文本。不同场景中,编码器和解码器有不同的设计,但对应Seq2Seq的底层结构却如出一辙。

5.2 Seq2Seq模型在解码时,有哪些常用的办法?

Seq2Seq模型最核心的部分是其解码部分,大量的改进也是在解码环节衍生的。Seq2Seq模型最基础的解码方法是贪心法,即选取一种度量标准后,每次都在当前状态下选择最佳的一个结果,直到结束。贪心法的计算代价低,适合作为基准结果与其他方法相比较。很显然,贪心法获得的是一个局部最优解,由于实际问题的复杂性,该方法往往并不能取得最好的效果。

集束搜索是常见的改进算法,它是一种启发式算法。该方法会保存beam size(后面简写为b)个当前的较佳选择,然后解码时每一步根据保存的选择进行下一步扩展和排序,接着选择前b个进行保存,循环迭代,直到结束时选择最佳的一个作为解码的结果。下图是b为2时的集束搜索示例。

由图可见,当前已经有解码得到的第一个词的两个候选:I和My。然后,将I和My输入到解码器,得到一系列候选的序列,诸如I decided、My decision、I thought等。最后,从后续序列中选择最优的两个,作为前两个词的两个候选序列。很显然,如果b取1,那么会退化为前述的贪心法。随着b的增大,其搜索的空间增大,最终效果会有所提升,但需要的计算量也相应增大。在实际的应用(如机器翻译、文本摘要)中,b往往会选择一个适中的范围,以8~12为佳 。

解码时使用堆叠的RNN、增加Dropout机制、与编码器之间建立残差连接等,均是常见的改进措施。在实际研究工作中,可以依据不同使用场景,有针对地进行选择和实践。

6. 注意力机制

6.1 Seq2Seq模型引入注意力机制是为了解决什么问题?为什么选用了双向的RNN模型?

在实际任务(例如机器翻译)中,使用Seq2Seq模型,通常会先使用一个循环神经网络作为编码器,将输入序列(源语言句子的词向量序列)编码成为一个向量表示;然后再使用一个循环神经网络模型作为解码器,从编码器得到的向量表示里解码得到输出序列(目标语言句子的词序列)。

在Seq2Seq模型中,当前隐状态以及上一个输出词决定了当前输出词,即 $s_i=f(s_{i-1},y_{i-1},c_i)$, $p(y_i\vert y_1, y_2,…,y_{i-1})=g(y_{i-1,s_i,c_i})$,其中语境向量$c_i$是输入序列全部隐状态$h_1,h_2…h_T$的一个加权和 $c_i=\Sigma^T_{j=1}\alpha_{ij}h_j$, 其中注意力权重参数$\alpha_{ij}$并不是一个固定权重,而是由另一个神经网络计算得到 $\alpha_{ij}=\frac{exp(e_{ij})}{\sum_{k=1}^Texp(e_{ik})}$,$e_{ij}=a(s_{i-1}, h_j)$。

神经网络a将上一个输出序列隐状态$s_{i−1}$和输入序列隐状态$h_j$作为输入,计算出一个$x_j$,$y_i$对齐的值$e_{ij}$,再归一化得到权重$\alpha_{ij}$。

我们可以对此给出一个直观的理解:在生成一个输出词时,会考虑每一个输入词和当前输出词的对齐关系,对齐越好的词,会有越大的权重,对生成当前输出词的影响也就越大。下图展示了翻译时注意力机制的权重分布,在互为翻译的词对上会有最大的权重。

在机器翻译这样一个典型的Seq2Seq模型里,生成一个输出词$y_j$,会用到第i个输入词对应的隐状态$h_i$以及对应的注意力权重$α_{ij}$。如果只使用一个方向的循环神经网络来计算隐状态,那么$h_i$只包含了$x_0$到$x_i$的信息,相当于在$α_{ij}$这里丢失了$x_i$后面的词的信息。而使用双向循环神经网络进行建模,第i个输入词对应的隐状态包含了和,前者编码$x_0$到$x_i$的信息,后者编码$x_i$及之后所有词的信息,防止了前后文信息的丢失,如图所示。

注意力机制是一种思想,可以有多种不同的实现方式,在Seq2Seq模型以外的场景也有不少应用。