吴教授的循环神经网络(RNN)课堂

跳了两个月的票,从Nov begin一直等到新一年的Jan begin,终于在吴教授把他三大计划最后一个计划fund.ai放出的同时,把deeplearning.ai最后一课给更新了。

刚好是自己的研究领域,就必须得写写了。

第一周我的参考答案: https://github.com/andy-yangz/Deeplearning_ai_coursera

序列模型

通过前面的课程,我们已经拥有了全连接网络(Fully-Connected Network)和卷积网络 (Convolutional Neural Network CNN),也能处理很多问题了。但还要补齐深度学习中最后一大块拼图循环神经网络 (Recurrent Neural Network RNN)。

那是因为,虽说理论上全连接网络可以处理所有问题,但对于有些问题全连接网络效率非常差。因此,针对某些特定问题往往都会有更有效的网络架构,比如说对于图片这样的2维信息处理,毫无疑问CNN会更有优势。

同样的,对于有着明显前后关系的序列数据,比如说随着时间变化的数据,当用RNN对其进行处理时,也会有着巨大的优势。

现实生活中这样的数据和应用比比皆是:

  • 说话时的语音处理,可以用序列模型进行语音识别

  • 听的音乐,可以用序列模型进行音乐生成

  • 我们的语言信息,可以用序列模型进行机器翻译、情感分析等等

  • 还有感兴趣的金融市场波动,可以用序列模型进行预测

  • 当然此外还有诸如DNA序列,视频动作识别等等

循环神经网络

先来仔细看看,为什么普通的全连接网络并不适用于序列数据处理。

  • 第一,如果用全连接网络,那么输入输出长度就被固定了,而往往序列数据的输入输出长度是变化的

  • 第二,在学习序列关系时,RNN可以分享学到的关系特征节省参数,而全连接网络不行。这里有点像CNN在不同区域共享参数一样。

那么来看看RNN到底是什么样的吧。

首先假设输入是长度为 t 的序列 x,比如说一个句子 x 有 t 个单词。于是对于RNN单元,我们从左到右每次输入一个时序单位的数据x<t>,也就是一个单词。

那么RNN会对它进行一些计算,获得一个隐藏状态a<t>传往下一个时序,同时再根据a<t>计算出预测输出y^<t>。。

式子中W权重矩阵g是一个非线性激活函数。因此对于每个时序,RNN都是输入当前时序的输入x<t> 和来自之前时序的状态a<t-1>x<t>可以理解为当前的信息,而a<t-1>可以理解为对之前信息的总结。

循环网络之所以称为循环,是因为实际上一个单层的RNN网络只有一个单元,每次输入一个时序的数据,总结信息然后再循环传给自己,进入下一个时序...

一般RNN的表示方式有两种,一种是像上图左端一样,一个单元加一个循环的符号,便于理解RNN的原理;还有一种,把它展开,将不同时序的运算过程都表示出来,便于理解RNN具体的运算过程。

在RNN中前向传播就是这样,一个时序一个时序计算下去,所以可以看做有很多层的“深度”学习。反向传播则是反过来,从最后一个时序向前传播回来,当然也可以分成几个部分来反向传播更新。因为这里的反向传播是跨越了几个时序的,所以也叫作BPTT (Backpropagation Through Time),跨时序反向传播.

RNN的不同架构

为了处理有着不同输入输出组合的各类任务,RNN可以分为以下几种不同的架构。

一对一:其实就是普通的神经网络。

多对一:多个时序的输入,得出一个时序的输出。比如说情感分析里面,看完一句评论,然后输出这句话是正面还是反面。这种结构又叫做 Acceptor (接受器),可以看作接受一段输入,然后输出结论。

多对多:多个时序输入对多个时序输出。最经常的应用就是 Tagging (标记),比如说需要判断一句话中每个词的词性,输入包含多个词的句子,然后输出对应的词性。这种结构又叫做 Transducer (变换器),可以看作将一段输入转换成另一种表示。

一对多:给出一个时序的输入,获得多个时序的输出。比如说音乐生成和语言生成,给第一个音符或字,生成一段旋律或者一句话。这个可以算是 Transducer 里的一种特殊情况。

还有最后一种比较特殊的情况,也是多对多。但不是 transducer,而是被叫做seq2seq (序列到序列) 编码器解码器模型。主要用于像机器翻译这样的任务。

语言模型与序列生成

因为语言天生的序列性,于是理所当然地 RNN 在自然语言处理中得到了广泛的应用。虽然自然语言处理中也有用CNN,但并不多。

先来看看 RNN 怎么解决自然语言处理中,一个很基础同时也是很重要的问题,语言模型。之前在机器翻译介绍里也提到过,语言模型就是判断你说的是不是人话。

科学点的讲就是,语言模型判断这句话出现的概率。一句人话“我吃饭。”的概率,显然比一句乱七八糟的“吃。饭我”的概率要高。

所以语言模型就是求一句话的概率,根据概率论可以把求长度为t的句子的概率,分成对前 t-1 个词求概率,然后用这个概率乘以第 t 个词出现在这样的 t-1个 词后面的概率;而对 t-1 个词求概率,可以分成对前 t-2 个词求概率,乘上... 以此类推。

而用RNN可以很好的模拟这个过程。

我们只需要拿着 RNN 在一般的语言数据上训练,让它记住这些条件概率就好了。

语言模型有很多应用,比方说可以辅助机器翻译。而这里吴教授提到它的一个不能算是应用的应用,序列生成

就是先随便输入一个词,然后 RNN 输出对下一个词的预测,这里的输出是通过 softmax 得到的概率列表,表示每个词的预测概率。之后根据这些概率,采样出一个词,作为下一个时序的输入。然后重复之前的过程。最后就可以获得一个该语言模型生成的句子。

生成的句子可能并没有什么意义,但是因为符合一般语言中的概率分布,所以看起来就会特别像是一句正常的话。比如这个用整本三国演义训练出来的语言模型生成的句子。

输给模型 出战,不胜必斩!”庞

生成: 出战,不胜必斩!”庞德有张,而来。二人与言!”二绍急闻将骑去了了,不可护弟如开策一十,各兵当夺。,入荆州,皆口而奉,金密有奉罪...

就是看起来好像挺通,但是读完发现完全不知云云。

想更深入了解语言模型可以看看这篇文章:浅谈NLP中条件语言模型(Conditioned Language Models)的生成和评估

RNN的问题

随着时序长度变长,RNN运算的深度也就增加,这样也就无法避免深度学习里的两个典型问题,梯度爆炸梯度消失,具体可参考这篇文章《神经网络训练中的梯度消失与梯度爆炸》

梯度爆炸还好解决,只需要每次更新前,检查梯度的大小,太大的话就进行修剪 (clip)。

而梯度消失的问题就不好办了,因此研究人员就提出了更强大的RNN结构来避免梯度消失问题。

升级版RNN:GRU和LSTM

其中最有名的两个结构,一个是97年由四大天王之一的 Jurgen Schmidhuber 提出的老牌大哥LSTM,另一个则是后期之秀的GRU,两者都属于门控结构RNN。而前面提到的RNN单元因为简单,就叫做简单RNN (Simple RNN),如果想在别人面前装装逼,可以叫它埃尔曼 (Elman) RNN。

回到主题,相对来说,GRU因为简单,也更加简单容易理解一些,所以吴教授先解说了GRU。但是根据我的学习经验,我更推崇 Yoav Goldberg 的 Neural Network Methods for Natural Language Processing 书中的讲解步骤。

先讲为什么需要门控机制。首先简单RNN每次更新时是对记忆全体进行擦除和重写,这样导致不能捕捉长期关系。而有的时候,是需要长期保留一些记忆来捕捉长期关系。正如人一样,有长期记忆,同时根据新的信息对局部进行更新。

因此也可以让RNN有这样的结构,在每个时序根据输入,选择哪些记忆保留,哪些记忆忘掉且用新的信息更新。

如上图所示,s是之前时序传递过来的状态,x是当前时序的输入,g则是门(Gate)。门中数值位置是1的部分表示保留记忆的部分,而其他不保留的则直接用当前输入相应位置更新替代。

这就是门结构 RNN 里面的基本模型了。而 GRU 和 LSTM 则是对这里面的门结构进行更细致的设计,因为像这样子的直接全部擦除和更新的方式,也太粗暴了。有时候可能希望,保留部分记忆,更新部分记忆。

吴教授这里讲到的是一个简化版的GRU,公式如下图,和上面讲到的记忆更新公式对比一下就很好理解了。

中间公式Γ (希腊字母G,gate) 就是计算出一个门函数(u表示update更新),负责长期记忆c<t>的更新,而用来更新的信息比起直接输入x,中间增加了一些处理,是利用前面传过来的c<t-1>和当前输入x<t>来计算得到的。

那完整版GRU是怎么样的呢?介个样子。

多了一个r公式,其实就是在获得更新信息时进行了更加复杂的处理。

比起GRU,LSTM则要更加复杂,具体方程是这样,保管看完就晕。

LSTM里面有两个状态,一个是c,另一个是h。c可以理解为长期记忆,h可以理解为短期记忆。c会通过每个时序输入的信息对自身进行更新,同时一直传递下去。而h则是在每个时序里面,需要提取的的短期记忆。这也是为什么LSTM叫做LSTM的原因,Long Short Term Memory (长短期记忆)。

而和GRU一样,对c和h进行操作与更新的就是我们的门结构了。LSTM里面的门有三个,i、f、o分别为输入门、遗忘门、输出门。按照各自的名字,输入门负责决定记住哪些需要输入更新的信息,而遗忘门负责决定忘记哪些长期记忆中的记忆,最后的输出门决定输出最后获得的长期记忆的哪个部分作为短期记忆输出。

费了这么大篇幅,讲了这么多,简直太良心了,但肯定还是一头雾水吧。

这很正常,面对一大堆公式看着就晕。我也是花了好几个月读了很多博客和教程,突然有一天在读前面提到的Yoav Goldberg的书时,懂了,当时特别开心。

从比较直观的角度理解为什么GRU还有LSTM可以避免梯度消失问题,如下图所示。

我们可以清晰地看到,在LSTM的上部长期记忆c从头到尾贯穿,每次只有更新的时候才会对它进行修改。而又因为门函数是sigmoid函数的原因,所以可以对信息进行很好的保留。

关于LSTM和GRU哪个更好的问题,个人经验来说,目前在很多问题上还是用经典款的LSTM要好些。但是因为GRU有结构简单,运算量相对较小等优势的原因,在很多问题上越来越多的人也在开始尝试使用它。

最近LSTM还出了一个挺火的Nested LSTM (嵌套LSTM),有机会写写。

增强技巧:双向与加深

对于上面说的这些RNN系列,除了单纯的一个方向和单层的运行,还可以对它进行扩展。

比如说将单向变成双向,只要将输入x反向输入另一个RNN单元,之后将结果与正向的拼接起来就好了。双向RNN因为可以利用两个方向信息的原因,所以一般能够取得更好的结果。

还有就是增加RNN的纵向深度,多添加几层,增加复杂度。

这就是目前RNN方面一些基本的知识了。

想要更深入的理解,还是要多读多动手。网上更详尽的资料还有很多,输入RNN或者循环神经网络就能搜索到了。

参考:

Coursera DeepLearning.ai Sequence Model

Neural Network Methods for Natural Language Processing by Yoav Goldberg (邮箱留下!)

基于LSTM的语言模型

神经网络训练中的梯度消失与梯度爆炸

推荐阅读更多精彩内容