百面机器学习 之 循环神经网络

        循环神经网络其实主要的就是(暂且我知道的)RNN,LSTM,GRU三个网络,其中后两个都是RNN的变种,来解决梯度消失的问题。

        RNN的介绍以及特点,还有为什么会梯度消失已经写过了,在这里

        LSTM的介绍以及特点,还有怎么改善梯度消失的方法也写过了,在这里  

        下面就是针对百面这本书来对以上提到的网络和已有的博客做一个知识点的补充

1. 处理文本数据时,循环神经网络和前馈神经网络各自的处理方式是怎样的?

        一般的前馈神经网络,卷积神经网络都是接受一个定长的输入,当遇到变长的输入时,会通过滑动窗口加池化的方法将原来的输入转变成一个固定长度的向量,然后再送进去网络进行分析。但是如此一来,就等于每个单词都是单独送进去网络的,丢失了词语和词语之间的依赖关系以及顺序关系。

        循环神经网络是在模拟人们的阅读,将前面阅读到的有用信息编码到状态变量里面去,如此一来下一次取这个状态变量的时候也会拿到之前网络认为有用的信息,也就是拥有了一定的记忆能力,这样就能更好地模拟出我们阅读时候的习惯(带着前文信息去看后文的)

2. RNN为什么会梯度消失/爆炸,有什么改进方案

        在上面文章链接里面最后对W的更新公式写到:

\frac{\partial L_t}{\partial W_x}=\sum^t_{k=0}\frac{\partial L_t}{\partial O_t}\frac{\partial O_t}{\partial S_t}(\prod^{t}_{j=k-1}\frac{\partial S_j}{\partial S_{j-1}})\frac{\partial S_k}{\partial W_x}

        其中:                         \prod\frac{\partial S_j}{\partial S_{j-1}} = \prod tanh'*W

         也就是多个权重矩阵相乘,因为RNN里面权重矩阵是共享的,所以就是权重矩阵的n次方,当这个权重矩阵的值非常小或者非常大,或者tanh的输出值处于饱和状态,就会导致梯度消失/爆炸

        解决方法:

        梯度爆炸就用梯度剪裁,梯度消失就用改进后的RNN:LSTM/GRU等模型通过加入各种控制门去弥补梯度公式的单一所导致的梯度消失。具体可以看上面LSTM的文章。

3. Relu是否可以应用于RNN上

        答案是肯定可以的,只不过要对W矩阵的初值做好限制,不然就爆炸/消失了。直接引用书上的原话了,因为非常好理解,很通俗易懂,10.9 和 10.10的公式是RNN的前传公式:

        就是因为他是共享的,所以导致了t个权重矩阵相乘 

        所以,当W矩阵不是一个单位矩阵时,梯度就会出现消失or爆炸的现象。

        当W是一个单位矩阵的时候,并且使用Relu函数,效果和学习速度会比LSTM要好。

4. LSTM里各个模块分别使用什么激活函数,为什么?

        在LSTM里面,一共有遗忘门,更新门,输出门。

        遗忘门,更新门,输出门都是用sigmod来做激活函数,在更新门里面,生成候选记忆的时候用了Tanh函数。

        用sigmod的原因是因为sigmod的输出都在0-1之间,符合门控的物理定义,且当输入较大/较小的时候,输出就是0/1,也就是对应得上门开/关了。

        如果使用其他函数,例如Relu,将无法实现门控的效果。

        而生成候选记忆的时候,用tanh,是因为其输出在[-1,1]之间,这与大多数场景下的特征分布是0中心的特点相吻合,而且tanh在0附近的梯度比sigmod更大,使得模型收敛得更快。

5. seq2seq模型

        Seq2Seq是一种编码器-解码器的网络结构, 而这里的编码器和解码器就可以是RNN或者是它的变体。这模型的核心思想就是通过深度神经网络将一个可变长的输入序列映射为输出序列。

        具体的模型结构如下:

        分为解码器和编码器两部分

        首先来看编码器:

        可以从上图(蓝色部分)可以看出,隐状态的计算是包含了当前时间t的输入以及上一时间t-1的隐状态的。

        蓝色方框就是隐状态:他的表达式:

        h_t = f(x_t,h_{t-1})

        然后再看中间的红色C:他就是书上所说的背景变量了。他的表达式:

C= q(h_1,...,h_t)

        通常C的计算都是所有h隐状态的累加

        所以编码器的作用就是把一些不定长的输入序列变换何曾一个定长的上下文序列(变量C)

        

        再来看解码器:

        我觉得解码器看这个图比较好:

         其中右边的h' 其实应该叫 S(解码器的隐状态)

        也可以从图上就看出,其实每一个解码器的隐状态都是由背景变量C,上一时刻的隐状态St-1以及上一个时刻的的输出组成的。

        那么给定的训练样本中的输出序列是y1,y2,y3,...,yn。对每一个时间t,解码器输出某一个单词的概率都是基于之前已经输出的序列和背景变量C:(这里每一层的隐藏层后可以接一个softmax来求每一个输出的概率)

P(y_t'|y_1,y_2,...,y'_{t-1},C)

        其中隐藏层的表达式:其中g函数是隐藏层的变换:

s_t = g(y'_{t-1},C,s'_{t-1})

        根据最大似然估计, 我们可以最大化序列基于输入序列的条件概率:

P(y_1,y_2,...,y_T|x_1,x_2,...,x_T) = \prod^T_{i=1}P(y_t|y_1,...,y_{t-1},s_t,c)

        然后两边都取对数和负号,这个就是我们的loss function了,要最小化它

-logP(y_1,...,y_T|x_1,...,x_T) = -\sum^T_{t=1}logP(y_t|y_1,...,y_t,s_t,c)

5.1 seq2seq的解码方法

        最基础的方法就是贪心法,选取一个衡量标准后,每一次都输出当前时间节点(状态)下最佳的一个结果,直到整个序列结束。贪心法由于只取当前的最佳结果,所以会陷入一个局部最优的状态,不能达到全局最优。于是就衍生出一种叫 集束搜索的方法。

        集束搜索是一种启发式的算法,会保存前n个最佳的选择,然后解码时根据保存的选择进行下一步的扩展和排序,接着选择前n个进行保存,直到最后一个时间状态选择出一个最优的。用书上的图来作为例子就很好解释了:

         从图上可以看出,n=2,所以扩展排序后,剪枝后的数量就是2

6. Attention机制 

        首先要知道的就是为什么要在sequence上面引入了attention机制,seq2seq当前有什么弊端

        关键问题就出在了这个背景变量C上了,按照seq2seq的思路,是把所有input的hidden units都求和到一块去了,这就导致了当时间序列非常长的情况下,这个模型的性能发生明显的变化。拿翻译做例子,句子前面的信息就会丢失比较严重,也就是我输出一个词,我要考虑之前所有输入单词的情况(这里面就有一些单词其实作为这次翻译是不需要的)

        所以我们引入attention就是为了解决上面这个问题。

        下面来看看attention是怎么工作的。一如既往的用RNN来编码,得到隐状态 h_1,h_2,..,h_t,但是在解码的时候,每一个输出词都会依赖于前一个隐状态以及输入序列每一个对应的隐状态:

s_i = f(s_{i-1},y_{i-1},c_i)

P(y_t|y_1,...,y_{t-1}) = g(y_{i-1},s_i,c_i)        

        其中背景向量c是输入序列全部隐状态h_1,h_2,..,h_t的一个加权和:

c_i = \sum^T_{t=1}\alpha_{ij}h_j

        其中一个attention权重\alpha_{ij}不是一个固定值,而一个网络计算得到的,这里面可以参考之前写的transformer的文章,这里,里面就写到提到了QKV的计算,后面这里我觉得可以联系起来讲一下,a_{ij}的计算公式:

a_{ij} = \frac{exp(e_{ij})}{\sum^T_{k=1}e_{ik}}

e_{ij} = \alpha(s_{i-1},h_j)

        其中 \alpha 就是一个神经网络,将上一层的输出序列隐状态s_{i-1} 以及 输入序列隐状态h_j 作为输入,计算出最后的权重。里面提到的 e_{ij} 就是下面例子说的每个对应隐向量的分数了

        这里我估计整套流程不太好理解,就找了网上其他大佬的例子来解释一下整个流程:

        输入通过编码器的RNN得到隐藏状态信息、 给每一个隐藏状态信息打分、 把分数进行softmax获得每个隐藏状态的权重、 把权重与隐藏状态相乘、 把前面的相乘结果相加得到上下文向量C、把C放入到解码器就可以进行翻译。 下面采用动画的方式看一下这六步:引用的图 以及 图的解释

        初始状态:有四个输入,分别对应四个隐藏层(下方的绿色圆块)一个输出层的隐藏层(对应动画里棕色的圆圈)

        第二步:解码器根据前一状态的隐藏层和当前输入隐藏层 进行一个计算,得到每个输入隐藏状态(层)的分数。 这个分数就代表着当前时刻的这些输入对于当前的翻译提供信息的多少(或者说对于当前时间步的翻译的关键程度), 最简单的打分方式就是每个输入和解码器前一隐藏状态进行内积运算获得。

 

        比如, 我前一个隐藏状态信息是[10, 5, 10], 我的四个输入隐藏状态分别是[0, 1, 1], [5, 0, 1], [1, 1, 0], [0, 5, 1], 那么我分别用隐藏状态的这个向量与后面的四个向量进行内积运算, 就会得到得分分别是: 15(10 * 0+5 * 1+10 * 1)、60, 15, 35. 会发现第二个输入对当前翻译的影响比较大。 当然这里的打分方式有很多, 内积是比较简单的,并且我们知道两个向量的内积运算其实是比较这两个向量的相似性, 相似性越高, 内积越大, 那么这里的第二个输入说明和解码器前一时刻的隐藏状态挺相关, 而翻译的时候上下文很重要。

         第三步:做softmax操作,将第二步得到的分都做一个和为1的操作

         第四步:把第三步权重与隐藏状态相乘

        通过将每个编码器的隐藏状态与其softmax之后的分数(标量)相乘,我们就得到对其向量。

         第五步:把前面的相乘结果相加得到上下文向量C

         第六步:把C放入到解码器就可以进行翻译

        

        还有一点就是为什么需要用双向RNN来计算隐状态:

        生成一个输出词y_j,会用到第i个输入词对应的隐状态 h_i 以及attention权重a_{ij},如果只使用一个方向的RNN,那么当前的隐状态只包含了从0到j的信息,但其实后面j到n也会对预测有很大的帮助,所以就是用了双向的RNN进行建模,从而每一个输入次对应两个隐状态,代表着两个方向。然后计算权重的时候我们就把同一时刻的两个向量拼接起来成为一个独立向量,再去算当前世界下的权重得分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值