译者注: 经知友推荐,将 The Unreasonable Effectiveness of Recurrent Neural Networks 一文翻译作为CS231n课程无RNN和LSTM笔记的补充,感谢@堃堃的校对。
目录
- 循环神经网络
- 字母级别的语言模型
- RNN的乐趣
- Paul Graham生成器
- 莎士比亚
- 维基百科
- 几何代数
- Linux源码
- 生成婴儿姓名 译者注:上篇截止处
- 理解训练过程
- 训练时输出文本的进化
- RNN中的预测与神经元激活可视化
- 源代码
- 拓展阅读
- 结论
- 译者反馈
原文如下
循环神经网络(RNN)简直像是魔法一样不可思议。我为 图像标注 项目训练第一个循环网络时的情景到现在都还历历在目。当时才对第一个练手模型训练了十几分钟(超参数还都是随手设置的),它就开始生成一些对于图像的描述,描述内容看起来很不错,几乎让人感到语句是通顺的了。有时候你会遇到模型简单,结果的质量却远高预期的情况,这就是其中一次。当时这个结果让我非常惊讶是因为我本以为RNN是非常难以训练的(随着实践的增多,我的结论基本与之相反了)。让我们快进一年:即使现在我成天都在训练RNN,也常常看到它们的能力和鲁棒性,有时候它们那充满魔性的输出还是能够把我给逗乐。这篇博文就是来和你分享RNN中的一些魔法。
我们将训练RNN,让它们生成一个又一个字母。同时好好思考这个问题:这怎么可能呢?
顺便说一句,和这篇博文一起,我在Github上发布了一个项目。项目基于多层的LSTM,使得你可以训练字母级别的语言模型。你可以输入一大段文本,然后它能学习并按照一次一个字母的方式生成文本。你也可以用它来复现我下面的实验。但是现在我们要超前一点:RNN到底是什么?
循环神经网络
序列。基于知识背景,你可能会思考: 是什么让RNN如此独特呢? 普通神经网络和卷积神经网络的一个显而易见的局限就是他们的API都过于限制:他们接收一个固定尺寸的向量作为输入(比如一张图像),并且产生一个固定尺寸的向量作为输出(比如针对不同分类的概率)。不仅如此,这些模型甚至对于上述映射的演算操作的步骤也是固定的(比如模型中的层数)。RNN之所以如此让人兴奋,其核心原因在于其允许我们对向量的序列进行操作:输入可以是序列,输出也可以是序列,在最一般化的情况下输入输出都可以是序列。下面是一些直观的例子:
————————————————————————————————————————
上图中每个正方形代表一个向量,箭头代表函数(比如矩阵乘法)。输入向量是红色,输出向量是蓝色,绿色向量装的是RNN的状态(马上具体介绍)。从左至右为:
- 非RNN的普通过程,从固定尺寸的输入到固定尺寸的输出(比如图像分类)。
- 输出是序列(例如图像标注:输入是一张图像,输出是单词的序列)。
- 输入是序列(例如情绪分析:输入是一个句子,输出是对句子属于正面还是负面情绪的分类)。
- 输入输出都是序列(比如机器翻译:RNN输入一个英文句子输出一个法文句子)。
- 同步的输入输出序列(比如视频分类中,我们将对视频的每一帧都打标签)。
注意在每个案例中都没有对序列的长度做出预先规定,这是因为循环变换(绿色部分)是固定的,我们想用几次就用几次。
————————————————————————————————————————
如你期望的那样,相较于那些从一开始连计算步骤的都定下的固定网络,序列体制的操作要强大得多。并且对于那些和我们一样希望构建一个更加智能的系统的人来说,这样的网络也更有吸引力。我们后面还会看到,RNN将其输入向量、状态向量和一个固定(可学习的)函数结合起来生成一个新的状态向量。在程序的语境中,这可以理解为运行一个具有某些输入和内部变量的固定程序。从这个角度看,RNN本质上就是在描述程序。实际上RNN是具备 图灵完备性 的,只要有合适的权重,它们可以模拟任意的程序。然而就像神经网络的通用近似理论一样,你不用过于关注其中细节。实际上,我建议你忘了我刚才说过的话。
如果训练普通神经网络是对函数做最优化,那么训练循环网络就是针对程序做最优化。
无序列也能进行序列化处理。你可能会想,将序列作为输入或输出的情况是相对少见的,但是需要认识到的重要一点是:即使输入或输出是固定尺寸的向量,依然可以使用这个强大的形式体系以序列化的方式对它们进行处理。例如,下图来自于 DeepMind 的两篇非常不错的论文。左侧动图显示的是一个算法学习到了一个循环网络的策略,该策略能够引导它对图像进行观察;更具体一些,就是它学会了如何从左往右地阅读建筑的门牌号( Ba et al )。右边动图显示的是一个循环网络通过学习序列化地向画布上添加颜色,生成了写有数字的图片( Gregor et al )。
—————————————————————————————————————————
左边:RNN学会如何阅读建筑物门牌号。右边:RNN学会绘出建筑门牌号。 译者注:知乎专栏不支持动图,建议感兴趣读者前往原文查看。
————————————————————————————————————————
必须理解到的一点就是:即使数据不是序列的形式,仍然可以构建并训练出能够进行序列化处理数据的强大模型。换句话说,你是要让模型学习到一个处理固定尺寸数据的分阶段程序。
RNN的计算。那么RNN到底是如何工作的呢?在其核心,RNN有一个貌似简单的API:它接收输入向量 x ,返回输出向量 y 。然而这个输出向量的内容不仅被输入数据影响,而且会收到整个历史输入的影响。写成一个类的话,RNN的API只包含了一个 step 方法:
rnn = RNN() y = rnn.step(x) # x is an input vector, y is the RNN's output vector
每当 step 方法被调用的时候,RNN的内部状态就被更新。在最简单情况下,该内部装着仅包含一个内部 隐向量 h 。下面是一个普通RNN的step方法的实现:
class RNN: # ... def step(self, x): # update the hidden state self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x)) # compute the output vector y = np.dot(self.W_hy, self.h) return y
上面的代码详细说明了普通RNN的前向传播。该RNN的参数是三个矩阵: W_hh, W_xh, W_hy 。隐藏状态 self.h 被初始化为零向量。 np.tanh 函数是一个非线性函数,将激活数据挤压到[-1,1]之内。注意代码是如何工作的:在tanh内有两个部分。一个是基于前一个隐藏状态,另一个是基于当前的输入。在numpy中, np.dot 是进行矩阵乘法。两个中间变量相加,其结果被tanh处理为一个新的状态向量。如果你更喜欢用数学公式理解,那么公式是这样的: 。其中tanh是逐元素进行操作的。
更深层网络。RNN属于神经网络算法,如果你像叠薄饼一样开始对模型进行重叠来进行深度学习,那么算法的性能会单调上升(如果没出岔子的话)。例如,我们可以像下面代码一样构建一个2层的循环网络: