循环神经网络(RNN)的工作方式(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hfutdog/article/details/86504812

0 前言

这篇博客主要是吴恩达《深度学习》课程的《序列模型》第一周课《循环序列模型》的笔记整理,中间加入了一些自己的理解,供自己以后能够快速复习,也供一些有需要的朋友查看。

1.1 为什么选择序列模型

pic_1
序列模型有着丰富的应用场景。我们一起来看看上面ppt中的例子。
第一行是语音识别(Speech recognition)的应用,输入是一段音频,输出是一段文字;第二行是音乐生成(Music generation),输入是一个开始音符或者为空,输出是一段音符序列;第三行是情感分析(Sentiment classification),输入是一段评论,输出是情感倾向;第四行是DNA序列分析(DNA sequence analysis),输入是一个DNA序列,输出是匹配的蛋白质;第五行是机器翻译(Machine translation),输入是一种语言的文字,输出是另外一种语言的文字;第六行是视频行为识别(Video activity recognition),输入是视频帧,输出是识别的行为;第七行是命名实体识别(Name entity recognition),输入是一段文字,输出中标记出文字中的命名实体(比如人名)。
需要注意的是上面的输入输出中可以不全是序列,长度也不一定需要相等,后面我们会谈到这个问题。

1.2 数学符号

在继续后面的内容之前,我们先看一下吴恩达在循环神经网络课程中规定的一些符号。
pic_2
如上面ppt所示,我们用x<t>来索引序列,表示序列中第t个元素,无论这个序列是否是时间序列。x(i)表示第i个样本,x(i)<t>表示训练样本i的序列中的第t个元素,比如ppt中那句话中的单词’invented’可以表示为x(0)<5>。Tx来表示输入序列x的长度,Ty来表示输出序列y的长度,Tx(i)表示第i个样本的输入序列长度,Ty(i)表示第i个样本的输出序列长度。pic_3
如上述ppt,接下来我们看一下一个序列中的某个单词的表示方法。为了表示单词,我们首先要制作一个词典(vocabulary或dictionary)存储表示方法中将要用到的单词。一般的商业应用中词典的大小为30, 000到50, 000个词,我们这里以10, 000个词为例。我们可以采用遍历训练集找到前10,000个常用单词或者浏览网络上的词典获取最常用的10, 000个英文单词的方法来构造词典。
为了表示词典里的每个单词,我们使用one-hot表示法。one-hot表示法中每个单词向量的维度就是词典的大小,并且只有一个位置为1(该单词在词典中的序号),其余都是零,比如ppt中单词’Harry’的向量的第4075个位置(这里序号从1开始)为1,其余全为0。
这样我们可以用9个one-hot向量来表示上述句子中的9个词,输出Y表示成如本节第一张ppt中所示在特定位置为1(这里是命名实体识别问题)的向量(比如 [ 1 , 1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 ] [1, 1, 0, 1, 1, 0, 0, 0, 0] ),构建输入X和输出Y之间的映射,使其成为一个监督学习问题。
最后,不在词典内的词采用<unk>表示,即unknown word。

1.3 循环神经网络模型

pic_4
如上述ppt所示,图中是一个传统的神经网络,左侧是输入数据,右侧是输出。现在需要明白的是我们为什么要用循环神经网络处理序列型数据,而不用这种传统神经网络呢?
这有两方面原因:

  1. 不同的样本的输入输出长度可能不同,比如输入的一个句子长度为10,而下一个可能为20。
  2. 传统的神经网络不能够共享从文本不同位置上学习到的特征。比如输入数据位置1学习到’Harry’是个人名,可能换了个位置就不行了。

对于问题1,也许我们可以使用填充(pad)较短长度的句子来解决,但这种方法并不见得是一种比较好的表达方式。对于问题2,我们需要的是像在卷积神经网络里那样,将在图片的一部分学习到的内容快速推广到图片的其他部分。
因此,我们有必要使用循环神经网络来处理这些问题,并且用一个更好的神经网络的实现方式还可以减少模型中的参数数量。下面我们来看一下循环神经网络中处理数据的过程:
pic_5
上述ppt中左侧的笔绘部分是吴恩达采用的循环神经网络的表达方式,这是一个按照时间展开的循环神经网络。而右侧的笔绘图则是其他一些文献中可能会采用的表达方式,它没有按照时间展开。
在吴恩达绘制的循环神经网络中,展开的每个部分表达了循环神经网络的一个timestep,也就是某个时刻整个循环神经网络的处理过程,这些timestep中的参数是共享的。通俗点说,一个timestep相当于for循环中的某次循环,各个timestep是有先后顺序的,我们所看到的展开的结构并不共同组成循环神经网络,相反它的某个timestep就已经是整个网络结构了。因此,循环神经网络处理输入数据时其实可以看做一个从左向右的扫描过程。
从ppt中我们可以看到循环神经网络第二个timestep的预测值 y ^ &lt; 2 &gt; \hat y^{\lt2\gt} 并不只是考虑当前timestep的输入 x &lt; 2 &gt; x^{\lt2\gt} ,还结合了上一个timestep神经网络隐藏层的输出 a &lt; 1 &gt; a^{\lt1\gt} 。还比如说,在ppt中第3个timestep之前的绿线所示, y ^ &lt; 2 &gt; \hat y^{\lt2\gt} 的值不仅仅取决于 x &lt; 3 &gt; x^{\lt3\gt} ,其实还结合了 x &lt; 1 &gt; x^{\lt1\gt} x &lt; 2 &gt; x^{\lt2\gt} 的信息。一般地,在循环神经网络中,每个timestep都会传递一个激活值到下一个timestep用于计算。因此,在零时刻我们需要造一个伪激活值 a &lt; 0 &gt; a^{\lt0\gt} (通常是零向量)作为一开始的输入。
还有一点值得提一下的是,ppt中神经网络的缺点在于,它只使用了序列中之前的信息来做出预测。例如ppt中下面两句话,为了判断Teddy是不是人名,仅仅使用Teddy之前的两个词是不够的,还需要使用Teddy之后的信息。为了解决这个问题,我们可以使用BRNN,这个在后面的小节中会写到。
另外,ppt中用红色书写的文字表示循环神经网络的参数, W a x W_{ax} 负责输入数据和隐藏层, W a a W_{aa} 负责激活值和隐藏层,输出结果由 W y a W_{ya} 决定。
pic_6
上述ppt展示了循环神经网络的前向传播过程和计算公式,我们可以看到公式中参数 W a x W_{ax} 的下标x意味着要乘以某个x类型的量,下标a意味着用来计算某个a类型的量。
计算 a &lt; t &gt; a^{\lt t \gt} 的激活函数在循环神经网络中一般是tanh或者Relu,计算 y ^ &lt; t &gt; \hat y^{\lt t \gt} 的激活函数一般是sigmoid或者softmax。
pic_7
上面的ppt中将左侧的公式化简为:
a &lt; t &gt; = g ( W a [ a t 1 , x &lt; t &gt; ] + b a ) y ^ t = g ( W y a &lt; t &gt; + b y ) \begin{aligned} a^{&lt;t&gt;} &amp;= g(W_a[a^{t-1}, x^{&lt;t&gt;}] + b_a) \\ \hat y^{t} &amp;= g(W_ya^{&lt;t&gt;} + b_y) \end{aligned}
其中 W a W_a 实际上是 W a a W_{aa} W a x W_{ax} 的水平堆叠(stack)。假设 a &lt; t 1 &gt; a^{&lt;t-1&gt;} a &lt; t &gt; a^{&lt;t&gt;} 等是形状为100的向量(只有一个维度), x &lt; t &gt; x^{&lt;t&gt;} 等是形状为10, 000的向量,那么 W a a W_{aa} 的形状是(100, 100), W a x W_{ax} 的形状是(100, 10000), W a a W_{aa} W a x W_{ax} 水平堆叠后形状为(100, 10100),也就是 W a W_{a}
[ a &lt; t 1 &gt; , x &lt; t &gt; ] [a^{&lt;t-1&gt;}, x^{&lt;t&gt;}] 则是 a &lt; t 1 &gt; a^{&lt;t-1&gt;} x t x^{t} 的垂直堆叠,堆叠后形状为10, 100。这样也就实现了ppt右下方绿色笔记的简化结果,整个公式简化后的计算结果和原来相比是不变的。
同理,还可以对 y ^ &lt; t &gt; \hat y^{&lt;t&gt;} 的计算公式进行简化。简化后公式的形式简单了,同时参数的下标也减少了,现在保留的下标表示我们使用这个参数需要去计算出的结果,比如 W y W_y 是和计算 y y 相关的。

1.4 通过时间的反向传播

在这一节中,我们简单了解一下循环神经网络的反向传播(具体如何计算需要参考吴恩达之前《深度学习》课程前面的章节)。
pic_8
在上面的ppt中,蓝色箭头表示了前向传播的过程,红色箭头表示了反向传播的过程,下面我们看一下具体的计算图。
pic_9
上面的ppt乍一看可能有点眼花缭乱,现在让我们一起一步步解析。
首先用蓝色箭头连接的 x &lt; t &gt; x^{&lt;t&gt;} a &lt; t &gt; a^{&lt;t&gt;} y ^ &lt; t &gt; \hat y^{&lt;t&gt;} 表示的是前向传播过程,绿色字体 W a W_a b a b_a W y W_y b y b_y 是上一节中提到的各个参数,绿色箭头表示这些参数在各个timestep之间是共享的,不断随着时间传递。
接下来我们看loss的定义,也就是图中写有 L &lt; t &gt; L^{&lt;t&gt;} 的方块。loss的计算公式在下方写出,先看第一行。在第一行的公式中, y &lt; t &gt; y^{&lt;t&gt;} 对应序列中某个具体的词,是训练样本中这个序列的这个单词的值,也就是说它是已知的实际值。这里还是以命名实体识别为例, y &lt; t &gt; y^{&lt;t&gt;} 是1表示是命名实体,是0表示不是命名实体,那么 y ^ &lt; t &gt; \hat y^{&lt;t&gt;} 就表示网络预测它是不是命名实体的概率,比如是0.1。然后我们采用标准逻辑回归损失函数(standard logistic regression loss)或者叫交叉熵损失函数(cross entropy loss)进行计算,就是公式右边的式子。这样算出来的结果是单个位置上或者说是某个时间步t的某个单词预测值的损失函数。
然后,再下面一行公式就是整个序列的损失函数值,也就是一个样本的损失函数值,它是把每个timestep的loss相加得来的。
需要注意的是,我们这里的循环神经网络的输入和输出序列仍然是等长的,也就是 T x = T y T_x = T_y
然后我们看红色的箭头,由loss开始到预测值再到激活值,再按照timestep从右向左进行计算,这个过程就是反向传播。这里需要利用我们刚才的公式和与导数相关的参数,用梯度下降法进行计算,从而更新参数,减少损失值。
在这个反向传播过程中,最重要的信息传递或者说最重要的递归运算就是画圈的红色箭头部分,也就是激活值之间的反向传播。这也是“通过时间反向传播”(backpropagation through time)名字的由来。因为前向传播时,从左向右计算,timestep不断增加,而反向传播时,从右向左计算,就好像时间倒流一样。

1.5 不同类型的循环神经网络

在前面小节中描述的都是输入序列长度和输出序列长度相等的RNN结构,在这个小节我们一起看一下输入和输出序列不等长的RNN具有怎样的结构。
pic_10
我们要讲的一些例子源于上述的ppt,下面具体看一下。
pic_11
上述ppt中左侧是Many-to-many(多对多)结构,是常规的输入和输出序列等长的结构。右侧用于情感分析的结构对其进行了相应的修改。比如我们现在输入的x是一个text型的电影评论,输出的y可以是0/1表示电影是好是坏,还可以是1-5的电影星级评价。这个时候我们采用Many-to-one(多对一)结构,它不再在每个timestep都有输出,而是让RNN读入整个输入序列,然后在最后一个timestep得到输出。
最后,该ppt最右侧的是one-to-one结构,也就是一个小型的标准神经网络,输入一个元素x得到一个输出元素y。
pic_12
接下来,我们看上面ppt中RNN的两个结构。左侧是音乐生成的例子,输入的x可以是一个表示想要的音乐类型的整数,或者是想要的音乐的第一个音符,或者还可以为空(零向量),输出是一段音符。这里的一个细节是,当生成音乐序列时,把上一个timestep的输出也输入到下个timestep。
然后ppt右侧是不等长的Many-to-many结构,也就是Encoder-Decoder结构。Encoder部分每个timestep没有输出,输入信息和隐层信息按照timestep传递到Decoder,Decoder再将这些输入信息转换为每个timestep的输出。当然,这里只是简单介绍了一下这个结构,还有一些细节问题后面涉及到的时候再详细讲解。Encoder-Decoder结构可以应用在机器翻译上,因为在机器翻译中输入语言的单词个数和输出语言的单词个数一般不相等。
下面一张ppt总结了这一节所讲到的各个RNN结构。pic_13

1.6 语言模型和序列生成

首先我们了解一下什么是语言模型。
pic_14
假设我们随便说了一句话,然后让语音识别系统去识别。语音识别系统可能会得到两个可能的结果,一个是The apple and pair salad.,另外一个是The apple and pear salad.。显然,如果不是故意说错,我们说的这句话应该是第二个句子,而让语音识别系统选择出第二个句子的方法就是去使用一个语言模型。语言模型能够计算出这两句话是哪个语音的可能性(概率),也就是说语言模型所做的是告诉你某个特定句子出现的概率是多少。
简单地说,当你拿起报纸读到一句话或者听到朋友说了一句话或者在电视里听到一句话,语言模型所做的是判断它是某个特定句子的概率。
语言模型是两种系统的基本组成部分,一个是语音识别系统,一个是机器翻译系统,在这两种系统中,都是输入一个句子,语言模型要能正确输出最接近的句子。它做的基本工作就是,输入一个句子,然后估计输出的序列中各个单词出现的可能性。
那么如何来建立一个语言模型呢?我们来看下面这张ppt。
pic_15
为了使用RNN建立一个这样的语言模型,首先需要一个训练集,它是一个很大的英文文本语料库或者其他的想用于构建模型的语言的语料库(很长的或者数量众多的句子组成的文本)。
现在,我们从语料库中拿到一个句子,首先要做的是对其进行标记化(或者叫符号化,tokenize)。在这之前要做的是根据训练集语料建立一个词典,然后将每个单词都转换成对应的one-hot向量,也就是词典中的索引。另外,除了语料库中的单词,我们可能还需要定义句子的结尾。一般的做法就是增加一个额外的标记,叫做EOS,表示End of Sentence,它能够帮助我们搞清楚句子什么时候结束。如果你想要能够准确识别句子结尾的话,EOS标记可以被附加到训练集中每个句子的结尾。对于标点符号,我们可以自行决定是否把它们看做标记(token)。在本例中是忽略了标点符号的。最后,字典之外的词可以用UNK标记表示,也就是说我们只针对UNK建立概率模型(语言模型),而不针对UNK所代表的具体的词。这样,我们就完成了标记化过程,也意味着我们将输入句子的各个单词都映射到了各个标志上。
下一步,我们要使用一个RNN来构建这些序列的概率模型,请看下述ppt。
pic_16
在上述RNN网络中, a &lt; 0 &gt; a^{&lt;0&gt;} x &lt; 1 &gt; x^{&lt;1&gt;} 被初始化为零向量。而 a &lt; 1 &gt; a^{&lt;1&gt;} 要做的是,通过softmax进行一些预测,来计算出第一个词可能会是什么,其结果就是 y ^ &lt; 1 &gt; \hat y^{&lt;1&gt;} 。这一步其实就是通过一个softmax层来预测词典中的任意单词会是第一个词的概率,比如 P ( a ) , . . . , P ( c a t s ) , . . . , P ( u n k ) P(a), ... , P(cats), ... ,P(unk) 等。 y ^ &lt; 1 &gt; \hat y^{&lt;1&gt;} 的输出是softmax的计算结果,它只是预测第一个词的概率,而不去管结果具体是什么。所以softmax层可能输出10, 000种结果(若将EOSUNK算进去是10, 002种),因为你的词典中有10000个词。
然后RNN进入下一个timestep,在下一个timestep中仍然使用激活项 a &lt; 1 &gt; a^{&lt;1&gt;} 的相关参数,得到新的激活项 a &lt; 2 &gt; a^{&lt;2&gt;} (激活项参数在每个timestep迭代改变),在这步要做的是计算出第二个词会是什么。现在我们传给它正确的第一个词(实际值,非预测值)作为输入,也就是告诉模型第一个词是cats,也就是 y &lt; 1 &gt; y^{&lt;1&gt;} ,即 x &lt; 2 &gt; = y &lt; 1 &gt; x^{&lt;2&gt;} = y^{&lt;1&gt;} 。在第二个timestep,输出结果同样经过softmax层进行计算,RNN的职责就是计算这些词的概率而不管它具体是什么词,并且它还会考虑之前得到的词,也就是后面一步的概率计算是基于前面所得词的条件概率。可以看到,timestep 3计算出前面两个词是"cats average"的条件下第三个词是词典中某个词的条件概率。如此按照时间顺序计算下去,在timestep 9的时候, y ^ &lt; 9 &gt; \hat y^{&lt;9&gt;} 是得到前面所有词情况下的EOS的条件概率。
所以RNN中的每一步都会考虑前面得到的单词,比如给出前三个单词,让它给出下个单词 的分布,这就是RNN如何学习从左到右地每次预测一个词的方法。
接下来,为了训练这个网络,我们要定义代价函数。在某个timestep t,如果真正的词是 y &lt; t &gt; y^{&lt;t&gt;} ,而神经网络的softmax层的预测结果是 y ^ &lt; t &gt; \hat y^{&lt;t&gt;} ,那么 L ( y ^ &lt; t &gt; , y &lt; t &gt; ) = i y i &lt; t &gt; log y ^ i &lt; t &gt; L(\hat y^{&lt;t&gt;}, y^{&lt;t&gt;}) = -\sum_{i} y_i^{&lt;t&gt;}\log{\hat y_i^{&lt;t&gt;}} 就是损失函数,而总体损失函数就是把所有单个timestep的损失函数都相加起来,即 L = t L &lt; t &gt; ( y ^ &lt; t &gt; , y &lt; t &gt; ) L = \sum_t L^{&lt;t&gt;}(\hat y^{&lt;t&gt;}, y^{&lt;t&gt;})
现在,这个语言模型就比较完整了,如果用很大的训练集来训练这个RNN模型,你就可以通过开头一系列单词来计算之后的单词的概率。比如,现在有一个只有三个单词的新句子 y &lt; 1 &gt; , y &lt; 2 &gt; , y &lt; 3 &gt; y^{&lt;1&gt;}, y^{&lt;2&gt;}, y^{&lt;3&gt;} ,现在要计算出它是一个通顺句子的概率。方法就是利用第一个softmax层得到 y &lt; 1 &gt; y^{&lt;1&gt;} 的概率 P ( y &lt; 1 &gt; ) P(y^{&lt;1&gt;}) ,然后利用第二个softmax得到 P ( y &lt; 2 &gt; y &lt; 1 &gt; ) P(y^{&lt;2&gt;}|y^{&lt;1&gt;}) ,以此类推,最后把三个概率相乘,就得到含3个词的整个句子的概率了。
到这里,我们对循环神经网络的工作方式进行了一部分介绍,限于篇幅,我们下篇继续。若是文章中有什么问题,也欢迎在评论区指出,我会及时纠正。

猜你喜欢

转载自blog.csdn.net/hfutdog/article/details/86504812
今日推荐