本课重点:
-
RNN的概念与多种形式
-
语言模型
-
图像标注、视觉问答、注意力模型
-
RNN梯度流
1 RNN的概念与多种形式
1.1 形式
普通的神经网络会有一个固定的输入(比如一张图片、一个向量),经过一系列隐藏层得到一个固定的输出(比如一个分类或多个分类的评分)。但是使用循环神经网络( Recurrent Neural Networks)会灵活的多,可以实现输入和输出的不同类型:
- 1对1:普通神经网络,输入输出都是固定的。
- 1对多:输入固定尺寸,比如一幅图片;输出是可变长度的序列,比如一段文字描述。描述不同,单词数可能就会不同,因此输出值的长度需要是一个变量。(图片描述)
- 多对1:输入的尺寸是变化的,输出是固定的。比如在情感分类任务中,想得到一段文字的情感属性,那么输入的一段文字就是可变的,情感是积极还是消极就相对固定;再比如可以输入时间不等的视频,然后判断视频中的活动就是固定的。
- 多对多:输入输出的尺寸都是可变的,比如机器翻译,英文翻译成中文。输入输出的句子长度都是可变的,并且两者不相等。
- 多对多(一一对应):输入是可变序列,输出是针对输入的每个元素做出判断。比如输入是帧数可变的视频,现在对每一帧进行决策,输出也是可变的。(帧级别视频分类)
即使是输入输出尺寸都是固定的情形也可以使用循环神经网络。比如一张写满数字的固定尺寸的图片,想要获取上面的数字,不是经过一次前向传播获取结果,需要经过一系列的观察,观察图片的不同部分,然后做出决策;再比如生成一张固定尺寸的图片,上面写满了数字,也需要经过一系列的步骤,一次只能生成一部分。
1.2 概念
如下图所示,RNN都有一个这样的小循环核心单元,输入x给RNN,RNN有内部隐藏态,这个内部隐藏态会在每次读取新的输入时进行更新。然后,当模型下一次读取输入时,内部隐藏态会将结果反馈给模型。
通常,我们想让RNN在每一个时间步都给出输出,于是就有了下面的模式——
读取输入 -> 更新隐藏态 -> 得到输出。
由于输入是一系列x向量,在每一个时刻绿色的RNN部分可以使用循环公式来表示:
其中,是某一时刻输入的向量,是该时刻之前的隐藏态,是参数W的函数,是更新后的隐藏态。依次循环。如果想要在每一个时间步骤得到一个输出,那么可以把更新后的隐藏态作为输入,经过全连接网络,得到决策。注意:在每一个时步都是相同的。
下面是一个最简单的例子,"Vanilla RNN":
旧状态和输入x都是与权重矩阵相乘再求和经过非线性函数tanh(),输出也是新状态与权重矩阵相乘。每个隐藏态都只有一个单一的h向量。
1.3 计算图
多对多(一一对应)
如果看公式不好理解RNN,可以画出多步计算图来描述。比如下面是一个多对多的计算图:
我们有一个初始状态 h0,现在在 t=1 时刻有输入 x1,将 h0、x1 带入参数函数 f 得到 h1更新隐藏态作为下一个时刻的状态,这样以此类推得到 xt 时刻的隐藏态 ht。计算 ht 的每一步都是使用的相同的参数 W,参数函数 f 也是完全相同的。这样在反向传播计算梯度时,需要将每一个时刻的梯度累加起来得到最终 W 的梯度。除了更新隐藏态,如果每一个时刻都想得到一个输出,可以直接将 ht 经过某些网络得到 yt,如果对于输入 x 的每一个时刻都对应一个真实标签的话,在计算出 yt 的同时可以得到每一步的损失 Lt,最终的损失也是所有的 Lt 加起来。(计算损失相对于 W 的梯度时,不要忘记将所有梯度相加。)
多对一
类似于情感分析任务,根据最后一个隐藏态得到输出。因为最后一个隐藏态包含了之前所有的情况。
一对多
一对多的情形会接受固定长度的输入项,输出不定长的输出项,这个固定长度的输入项会用来初始化初始隐藏态,然后RNN会对输出的单元逐个处理,最终会得到不定长的输出序列,输出的每个元素都得以展现。
多对多
输入输出都是不定长序列的情形,比如机器翻译,可以看作是多对一与一对多的组合。首先输入一个不定长的x,将这个序列编码成一个单独的向量,然后作为输入,输入到一对多的模型中,得到输出序列,可能是用另一种语言表述的相同意思的句子。然后对这个不定长的输出,每一个时步都会做出预测,比如接下来使用什么词。想象一下整个训练过程和计算图的展开,对输出序列的损失求和,然后像之前一样反向传播。
2 语言模型
在语言模型(Language Model)问题中,我们想要读取一些语句,从而让神经网络在一定程度上学会生成自然语言,这在字符水平上是可行的,我们让模型逐个生成字符。同样,也可以在单词层面上,让模型逐个生成单词。
举个字符层面上的例子,比如网络会读取一串字符序列,然后模型需要预测这个字符流的下一个字符是什么。我们有一个很小的字符表 [h, e, l, o] 包含所有字母,以及有一个训练序列 "hello",使用循环公式:
在语言模型的训练阶段,我们将训练序列作为输入项,每一个时步的输入都是一个字符,首先需要做的是在神经网络中表示这个单词。由于这里只使用了四个字符,所以用长为4的向量来表示每个字符,只有字符对应的位置是1,其他位置为0。比如 ‘h’ 可以用向量 [1 0 0 0] 表示,‘l’ 使用 [0 0 1 0] 表示。现在在第一个时步中,网络会接收输入 ‘h’, 进入第一个RNN单元,然后得到输出 y1,作为对接下来的字符的一个预测,也就是网络认为的接下来应该输入的字符。由于第一个输入的是 ‘h’,所以接下来输入的应该是 ‘e’,但是模型只是在做预测,如下图所示,模型可能认为接下来要输入的是 ‘o’,因为预测的字符向量第四个值最大。在这种错误预测下,可以使用softmax函数来度量我们对预测结果的不满意程度。在下一个时步,我们会输入 ‘e’,利用这个输入和之前的隐藏态计算出新的隐藏态,然后利用新的隐藏态对接下来的字符进行预测,我们希望下一个字符是 ‘l’,但这里模型不这么认为,然后就有了损失。这样经过不断的训练,模型就会学会如何根据当前的输入预测接下来的输入。
在语言模型测试阶段,我们想用训练好的模型测试样本或者生成新的文本(类似于训练时使用的文本)。方法是输入文本的前缀来测试模型,下面的例子中前缀是 ‘h’,现在在RNN的第一步输入 ‘h’,它会产生基于词库所有字母得分的一个softmax概率分布,然后使用这个概率分布预测接下来的输出,如果我们足够幸运得到了字符 ‘e’,然后把这个得到的 ‘e’ 重新写成 01 向量的形式反馈给模型,作为下一个时步的输入,以此类推。
截断反向传播(Truncated Backpropagation )
在前行传播中需要遍历整个序列来计算损失,在反向传播中也需要遍历整个序列来计算梯度。如果是像训练维基百科中所有文本这样的例子,那么时间花费以及内存占用都是巨大的,如下图所示。
实际应用中,通常使用沿时间的截断反向传播(Truncated Backpropagation ),这样输入的序列可以接近无穷。前向传播时不再使用整个序列计算损失,而是使用序列的一个块,比如100个时步,计算出损失值,然后反向传播计算梯度。然后通过第二批数据,此时使用第一批数据更新的隐藏态,计算损失,然后反向传播时只能使用第二批输入序列。这样一直循环下去,大大节省开销。
这个过程的完整实现代码:点击这里
应用
- 使用莎士比亚的文集对语言模型进行训练,然后生成新的文本。结果可以生成莎翁风格的文章,虽然可能有些地方有错误;
- 使用拓扑学教材,可以自动生成一些定理、公式甚至可以画图,虽然都没有意义,但可以学会这些结构。
- 使用linux源码进行训练。可以生成C代码,有缩进有变量声明甚至会写注释等等,看起来非常像C代码,只是错误很多无法编译。
解释
在RNN中有隐藏向量,每一步都会更新,我们在这些隐藏向量中寻找可以解释的单元。比如在语义训练中观察隐藏向量中的某一个元素值,元素值会随着每一个时步进行改变。大多数的元素值变化都是杂乱无章的,似乎在进行一些低级的语言建模。但是有一些元素却有特殊的表现:(蓝数值低,红数值高)
- 比如某些元素遇到引号后,元素值会变得很低,然后一直保持很低直到下一个引号处被激活,元素值变大,然后保持到下一个引号再变低。所以有可能是检测引号的神经元。
- 还有某些神经元在统计回车符前的字符数量,即字符有多少时会自动换行。某一行开始处元素的值很低,然后慢慢增大,达到一定值后自动变成0,然后文本换行。
- 在训练代码的例子中,有些神经元似乎在判断是否在
if
语句,是否在注释内,以及表示不同的缩进层级。
以上的例子可以看到,虽然我们训练的是文本,但是最终RNN学到一些文本的结构。当然这些已经不是计算机视觉的内容了。
3 图像标注、视觉问答、注意力模型
之前提过很多次图像标注(Image Captioning),即训练一个模型,输入一张图片,然后得到对这张图片的自然语言语义描述。这里输出的语义可能是不同长度的序列,不同的描述单词字符数不同,所以天生适合RNN模型。
以李飞飞实验室的模型为例,图像标注模型训练阶段一般都先通过卷积神经网络处理图像生成图像向量,然后输入到循环神经网络语言模型的第一个时步中,RNN第一个时步会输入开始标志,要对语言模型的输出做一个softmax分值判定得到损失,之后语言模型在每一个时步生成一个组成描述文本的单词,如图所示。
测试阶段和之前字符级的语言模型很相似。我们把测试图像输入到卷积神经网络,但是不是用来得到不同分类的softmax得分,而是得到模型最后一个用于分类的全连接层之前的一个图像向量,用来概述整张图像的内容。之后会给语言模型输入一个开始标志,告诉模型开始生成以这个图像为条件的文本。不同于以往的隐藏态公式,现在需要把图像信息输入到每个时步用于更新隐藏态:
现在就可以根据图像的内容生成一个词汇表(有很多词汇)中所有单词的一个softmax得分概率分布,取样之后作为下一个时步的输入。最后一步会采样到结束符号,这样文本就生成完成,正如我们在训练的时候每次都是期望以结束符作为结束。
这些训练后的模型在测试时对和训练集类似的片会表现的很好,对和训练集差距大的图片可能会有一些误解,比如对一些没见过的物体进行误判,以及分不清扔球还是接球等。
Image Captioning with Attention(注意力模型)
这个模型的RNN在生成单词时,会将注意力放在图像不同的部分。现在的CNN在处理图像的时候,不再返回一个单独的向量,而是得到图像不同位置的特征向量,比如 L个位置,每个位置的特征有D维,最终返回的处理后的图片数据是一个 LxD 的特征图。这样在RNN的每一个时步,除了得到词汇表的采样,还会得到基于图片位置的分布,即RNN想要观察图像的哪个部分。这种位置分布,就是RNN模型应该观察图像哪个位置的注意力。
在第一个隐藏态会计算基于图片位置的分布 a1,这个分布会返回到图像特征图 LxD,给得出一个单一的具有统计性质的 D 维特征向量,即把注意力集中在图像的一部分。这个特征向量会作为下一个时步的额外输入,另一个输入是单词。然后会得到两个输出,一个是基于词汇的分布,一个是基于位置的分布。这个过程会一直持续下去,每个步骤都有两个输入两个输出。如下图所示:
这样模型会自己学习每一个时步应该看向图片的什么位置得到什么词汇,最终生成的文本就会很帅!
这个模型也可以用于其他任务,比如视觉问答(Visual Question Answering)。这里的模型会有两个输入,一个是图像,一个是关于图像的用自然语言描述的问题。然后模型从一些答案中选择一个正确的。这是一个多对一模型,我们需要将问题作为序列输入,针对序列的每一个元素建立RNN,可以将问题概括成一个向量。然后把图像也概括成一个向量,现在将两个向量结合通过RNN编程预测答案。结合方式可以是直接连接起来也可以是进行复杂的运算。
4 RNN梯度流
多层RNN(Multilayer RNNs)
之前的隐藏态只有一层,现在隐藏态有三层。一次运行得到RNN第一个隐藏态的序列,然后作为第二个隐藏态的输入。
4.1 普通RNN梯度流
前向传播计算:
反向传播梯度流从到需要乘.
但是在整个RNN训练过程中,如下图所示,从最后一个隐藏态传到第一个隐藏态,中间要乘很多次权重。如果权重的最大奇异值大于1,就会梯度爆炸;最大奇异值小于1,梯度就会慢慢趋近0。
对于梯度爆炸,可以给梯度设置一个阈值,如果梯度的L2范式超过这个阈值就要减小梯度,代码如下:
grad_num = np.sum(grad * grad)
if grad_num > threshold:
grad *= (threshold / grad_num)
对于梯度急速缩小的情形,可以换一个复杂的RNN网络。
4.2 LSTM(Long Short Term Memory )
LSTM(长短期记忆)网络就是用来解决梯度爆炸和梯度缩减的问题的,与其在输出上限制梯度,LSTM的网络结构更加复杂。
LSTM在每一个时步都会维持两个隐藏态:ht 和普通RNN中的一致,是RNN网络的隐藏态;还有一个单元状态向量 ct,是保留在LSTM内部的隐藏态,不会完全暴露到外部去。计算公式可以看出,LSTM会使用输入和之前的隐藏态来更新四个组成 ct 的门,然后使用 ct 来更新 ht.
与普通RNN不同的是,普通RNN直接将权重矩阵 乘 和 拼接成的向量再经过一个 tanh() 函数就得到了隐藏态 。而LSTM中的权重矩阵比普通RNN中权重矩阵大得多,相乘的结果是得到4个与隐藏态 h 大小相同的向量,然后分别通过不同的非线性函数就得到了单元状态 的四个门:
- 是输入门(Input gate),表示有多少内容被写到单元状态;
- 是遗忘门(Forget gate),表示对之前的单元状态的遗忘程度;
- 是输出门(Output gate),表示单元状态输出多少给隐藏态;
- 是门值门(Gate gate ),控制写入到单元状态的信息。
从的计算公式来看,之所采用不同的非线性函数,可以这么理解:f 是对之前的单元状态的遗忘,如果是0全部遗忘,如果是1就全部保留,那个圆圈加点的符号表示逐元素相乘;i 和 g 共同控制写入到单元状态向量的信息,i 在0-1之间,g 在-1到1之间,这样每个时步,单元状态向量的每个元素最大自增1或最小自减1。这样可以看成是对[-1 1]的按时步计数。然后通过tanh()将这个计数压缩到[0 1]范围,然后 o 来控制将多少单元状态的信息输出给隐藏态 。
LSTM梯度流
灰色的线表示LSTM前向传播,完全按照公式来的,圆圈加点运算符表示两个向量逐元素相乘,不是矩阵的乘法。这样从到的反向传播过程,只会与 f 进行逐元素相乘,与乘W相比要简单很多。由于不同的时步 f 的值都不同,不像普通RNN每次都乘相同的W,这样就一定程度避免梯度爆炸或锐减。而且 f 的值也是在[0, 1]性质非常好。
当多个单元连起来的时候,就会为梯度在单元状态间流动提供了一条高速公路。这种形式与残差网络很像。
4.3 GRU(Gated Recurrent Unit)
门循环单元(GRU)也是应用了这种逐元素相乘与加法结合的形式,这样梯度也能高速流动。