【深度学习】循环神经网络(RNN)

tech2023-12-05  99

本文为深度学习的学习总结,讲解循环神经网络(RNN)。欢迎在评论区与我交流 😃

序列模型的应用

在语音识别方面,输入 X X X 为语音序列,输出 Y Y Y 为一系列单词。常使用循环神经网络解决这类问题。

音乐生成问题中,只有输出数据 Y Y Y 是序列,输入 X X X 可以是空集、单一整数指代生成音乐的风格或曲子的头几个音符。

感情分类中,输入是序列。

DNA 序列分析中,给出一个 DNA 序列,标记哪部分匹配某种蛋白质。

视频行为识别中,输入一系列的视频帧,要求学习其中的行为。

命名实体识别中,给定一个句子,要求识别句中的人名。

这些所有问题都能使用标签数据 ( X , Y ) (X,Y) (X,Y) 作为训练集的监督学习。上面的问题包含许多类型的序列模型,有些输入和输出数据的类型各有不同,长度也会不一样。

数学符号

命名实体识别问题常用语搜索引擎,例如检索过去 24h 内新闻报道的所有人名,可以查找不同类型文本中的人名、时间、地点等。输入序列 x x x,想让序列模型输出 y y y,表示输入的单词是否是人名的一部分。当然这不是最好的输出形式,不仅能记录是否是人名,还能告诉我们人名在句子中的位置。

输入数据是 9 个单词组成的序列,因此我们最终会有 9 个特征集合表示这 9 个单词,并按序列中的位置进行索引,使用 x < 1 > , … , x < t > , … x < 9 > x^{<1>},…,x^{<t>},…x^{<9>} x<1>,,x<t>,x<9> 表示输入特征集, t t t 为时间序列,但此处无论是否为时间序列都使用 t t t。同样输出用 y < 1 > , … , y < t > , … y < 9 > y^{<1>},…,y^{<t>},…y^{<9>} y<1>,,y<t>,y<9> 表示。用 T x , T y T_x,T_y Tx,Ty 分别表示输入和输出序列长度,这里 T x = T y = 9 T_x=T_y=9 Tx=Ty=9

与以前的文章相同, x ( i ) < t > x^{(i)<t>} x(i)<t> 表示第 i i i 个输入样本的第 t t t 个元素, T x ( i ) T_x^{(i)} Tx(i) 为第 i i i 个训练样本的输出序列长度, y ( i ) y^{(i)} y(i) T y ( i ) T_y^{(i)} Ty(i) 同理。

在 NLP(自然语言处理)中,我们需要知道如何表示句子里的单词。首先要做一张单词表(词典),将可能用到的单词列成一列

通常在实际运用中使用的词典大小远大于 10000。构造词典的一种方法是遍历训练集或网络词典,找到前 10000 个常用词。然后用 one-hot 表示法表示词典中的每一个单词:

之所以称为 one-hot,是因为每个向量只有一个值是 1,其余全为 0。用序列模型在输入 X X X 和目标输出 Y Y Y 之间学习建立一个映射。这是一个监督学习的问题。

如果遇到了不在词典中的单词,需要创建一个新的标记,即叫做 Unknown Word 的伪造单词。用下标 UNK 表示不在单词表中的单词。后面会详细讲解。

RNN 模型

首先我们考虑使用标准的神经网络学习从输入 X X X 到输出 Y Y Y 的映射。将 9 个 one-hot 向量输入到标准神经网络中,经过隐藏层,最终输出 9 个值为 0 或 1 的项,表明输入单词是否为人名的一部分。但这种方法有很大的问题:

输入和输出数据长度是不定的,即不一定有 T x = T y T_x=T_y Tx=Ty,即使将数据填充到最大长度,仍然不是一种很好的表达方式标准的神经网络不共享从文本不同位置上学到的特征。如果神经网络已经学习到了位置 1 出现的 Harry 可能是人名的一部分,但如果 Harry 出现在其它地方 x < t > x^{<t>} x<t>,则无法自动识别为人名的一部分。类似于卷积神经网络中,希望将部分图片里学到的内容快速推广到图像的其他部分模型参数过多。与卷积神经网络类似,用一个更好的表达方式可以减少模型中的参数。

我们从左到右读句子中的单词,首先将 x < 1 > x^{<1>} x<1> 输入到神经网络中,预测输出 y ^ < 1 > \hat{y}^{<1>} y^<1>。当读到句中的第 2 个单词 x < 2 > x^{<2>} x<2> 时,除了 x < 2 > x^{<2>} x<2>,神经网络还会输入一些来自时间步 1 的信息,即时间步 1 的激活值会传递到时间步 2。一直到最后一个时间步,输入 x < T x > x^{<T_x>} x<Tx> 输出 y < T y > y^{<T_y>} y<Ty>,此处 T x = T y T_x=T_y Tx=Ty。如果不相同,模型结构需要做出改变。

每个时间步传递一个激活值到下一个时间步中。在计算时,需要编造一个初始(伪)激活值 a < 0 > a^{<0>} a<0>,通常为零向量,有时也随机用其它方法初始化。

循环神经网络还可以表示为下面的样子,在每个时间步中,输入 x x x 输出 y y y,圆圈表示输回网络层,黑色方块表示延迟一个时间步。其展开后就是上图中的样子。

循环神经网络从左到右读输入数据,同时每个时间步的参数共享, W a x W_{ax} Wax 表示从 x < 1 > x^{<1>} x<1> 到隐藏层的连接的一系列参数,激活值(水平联系)由参数 W a a W_{aa} Waa 决定,输出结果由 W y a W_{ya} Wya 决定,每个时间步使用相同的参数。

对于预测 y ^ < 3 > \hat{y}^{<3>} y^<3>,不仅要使用 x < 3 > x^{<3>} x<3>,还要使用来自 x < 1 > , x < 2 > x^{<1>},x^{<2>} x<1>,x<2> 的信息。 x < 1 > x^{<1>} x<1> 可以通过红色的路径影响 y ^ < 3 > \hat{y}^{<3>} y^<3>

循环神经网络的一个缺点就是只能使用之前的信息做预测。对于下面两个句子,模型无法正确判断 Teddy 是否为人名:

双向循环神经网络(BRNN)可以处理这个问题,RNN 足够我们解释关键概念,之后只需要在此基础稍作修改就能使用前面和后面的信息进行预测了。

下面给出 RNN 的前向传播公式。 a < 0 > = 0 ⃗ a^{<0>}=\vec{0} a<0>=0

a < 1 > = g ( W a a a < 0 > + W a x x < 1 > + b a ) a^{<1>}=g(W_{aa}a^{<0>}+W_{ax}x^{<1>}+b_a) a<1>=g(Waaa<0>+Waxx<1>+ba)

y ^ < 1 > = g ( W y a a < 1 > + b y ) \hat{y}^{<1>}=g(W_{ya}a^{<1>}+b_y) y^<1>=g(Wyaa<1>+by)

这里 2 个函数 g g g 不一定相同。 W a x W_{ax} Wax 中第二个下标 x x x 表示要乘 x x x 类型的量,第一个下标 a a a 表示计算某个 a a a 类型的量。RNN 激活函数常用 tanh,有时为 ReLU,可以用一些其它方法避免梯度消失问题,后文会进行详细讲解。激活函数的选择取决于输出 y y y 的类型,若为二分问题则使用 sigmoid,k 分类使用 softmax。在这个命名实体识别的例子,输出只能为 0 和 1,可以是 sigmoid 激活函数。

更一般地: a < t > = g ( W a a a < t − 1 > + W a x x < t > + b a ) a^{<t>}=g(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a) a<t>=g(Waaa<t1>+Waxx<t>+ba)

y ^ < t > = g ( W y a a < t > + b y ) \hat{y}^{<t>}=g(W_{ya}a^{<t>}+b_y) y^<t>=g(Wyaa<t>+by)

为了表示更复杂的神经网络,对上面的符号进行简化: a < t > = g ( W a [ a < t − 1 > , x < t > ] + b a ) a^{<t>}=g(W_a[a^{<t-1>},x^{<t>}]+b_a) a<t>=g(Wa[a<t1>,x<t>]+ba)

其中 W a = [ W a a W a x ] W_a=\left [ \begin{array}{c:c} \begin{matrix} W_{aa} \end{matrix}& \begin{matrix} W_{ax} \end{matrix} \end{array} \right ] Wa=[WaaWax] [ a < t − 1 > , x < t > ] = [ a < t − 1 > x < t > ] [a^{<t-1>},x^{<t>}]=\left[ \begin{matrix} a^{<t-1>} \\ x^{<t>} \end{matrix} \right] [a<t1>,x<t>]=[a<t1>x<t>]。于是我们将 2 个参数压缩为了 1 个参数。

同样对于第二个式子,简化为: y ^ < t > = g ( W y a < t > + b y ) \hat{y}^{<t>}=g(W_{y}a^{<t>}+b_y) y^<t>=g(Wya<t>+by) 下标从 2 个变为 1 个,表示输出量的类型。

通过时间的反向传播

反向传播的方向与前向传播基本相反。下面是前向传播的示意图:

为了计算反向传播,需要定义一个代价函数,其对应句子中一个特定的词,代价函数输出这个词是名字的概率。将其定义为标准 logistic 回归代价函数(交叉熵代价函数)。 L < t > ( y ^ < t > , y < t > ) = − y < t > log ⁡ y ^ < t > − ( 1 − y < t > log ⁡ ( 1 − y ^ < t > ) ) \mathcal{L}^{<t>}(\hat{y}^{<t>},y^{<t>})=-y^{<t>}\log\hat{y}^{<t>}-(1-y^{<t>}\log(1-\hat{y}^{<t>})) L<t>(y^<t>,y<t>)=y<t>logy^<t>(1y<t>log(1y^<t>)) 上式与【逻辑回归模型】中的二分类的代价函数很像。这是关于单个位置上或某个时间步 t t t 上某个单词的预测值的代价函数。接下来我们定义整个序列的代价函数: L = ∑ t = 1 T x L < t > ( y ^ < t > , y < t > ) \mathcal{L}=\sum\limits_{t=1}^{T_x}\mathcal{L}^{<t>}(\hat{y}^{<t>},y^{<t>}) L=t=1TxL<t>(y^<t>,y<t>) 即将每一个时间步上的代价函数求和。示意图如下:

将前向传播的箭头反向,就可以计算出所有合适的量,通过导数相关参数用梯度下降法更新参数。其中最重要的是递归运算,反向(从右往左)计算激活值 a < t > a^{<t>} a<t>,像是时间倒流(through time)。

不同类型的 RNN

在前面介绍的模型中 T x = T y T_x=T_y Tx=Ty,而实际中并不一定满足这样的条件,我们将介绍不同类型的 RNN 以满足不同的需求。

一对一模型。如果删除左边的激活值 a < 0 > a^{<0>} a<0>,就是一个标准的神经网络。

一对多模型,例如音乐生成问题。

多对一模型。例如情感分类问题。

多对多模型,其中 T x = T y T_x=T_y Tx=Ty。例如命名实体识别问题。

多对多模型,其中 T x ≠ T y T_x\not=T_y Tx=Ty。例如机器翻译问题。

语言模型和序列生成

语言模型

语言模型在 NLP 中是最基础的且重要的,并且能用 RNN 很好地实现。

在语音识别系统中,对于下面两句话,让系统选择第二句话的方式是构建语言模型,计算这两句话各自的可能性,选择概率大的语言。

语言模型可以得到特定句子产生的概率,在语音识别和机器翻译中得到最近似的答案。语言模型输入序列 y < 1 > , … , y < T y ) > y^{<1>},…,y^{<T_y)>} y<1>,,y<Ty)>,估计每个句子中各个单词出现的可能。

RNN 构建语言模型

首先需要一个很大的训练集,例如语料库(数量很多的英文句子组成的文本)。首先对输入的句子进行标记化,即用 one-hot 向量表示。定义句子的结尾,增加额外标记 EOS。也可以对标点符号见进行标记,这里忽略它。

对于语料库中未知的单词,用未知词标志 UNK 表示,我们只针对 UNK 建立概率模型,而不是具体的单词 Mau:

这样,所有输入的单词都映射到了字典中的各个词上,构建 RNN 来构建序列的概率模型。

RNN 模型

在第 0 个时间步计算激活值 a < 1 > a^{<1>} a<1>,以 x < 1 > x^{<1>} x<1> 作为输入函数,设为零向量,同样 a < 0 > a^{<0>} a<0> 也设为零向量。 a < 1 > a^{<1>} a<1> 通过 softmax 预测第一个单词是什么,结果为 y < 1 > y^{<1>} y<1>

下个时间步中仍然使用 a < 1 > a^{<1>} a<1>,用来预测第二个词是什么。这时将第一个正确的词传过来,即 y < 1 > = x < 2 > y^{<1>}=x^{<2>} y<1>=x<2>

RNN 的每一步都会考虑前面得到的单词,例如给出前 3 个词,让模型预测下一个词的分布。

定义每个单词的代价函数: L ( y ^ < t > , y < t > ) = − ∑ i y i < t > log ⁡ y i ^ < t > \mathcal{L}(\hat{y}^{<t>}, y^{<t>})=-\sum\limits_iy_i^{<t>}\log\hat{y_i}^{<t>} L(y^<t>,y<t>)=iyi<t>logyi^<t> 总体代价函数为每个单词代价函数求和: L = ∑ t L < t > ( y ^ < t > , y < t > ) \mathcal{L}=\sum\limits_t\mathcal{L}^{<t>}(\hat{y}^{<t>},y^{<t>}) L=tL<t>(y^<t>,y<t>) 我们根据句子前面的单词,就可以预测后面的单词了。对于一个新句子 y < 1 > , y < 2 > , y < 3 > y^{<1>},y^{<2>},y^{<3>} y<1>,y<2>,y<3>,要计算出句子中各个词的概率 P ( y < 1 > , y < 2 > , y < 3 > ) P(y^{<1>},y^{<2>},y^{<3>}) P(y<1>,y<2>,y<3>)

第一个 softmax 层会预测第一个单词的概率 P ( y < 1 > ) P(y^{<1>}) P(y<1>),第二个 softmax 层考虑在 y < 1 > y^{<1>} y<1> 的条件下 y < 2 > y^{<2>} y<2> 的概率,第三个 softmax 层同理。将 3 个概率相乘得到含 3 个词的整个句子的概率。 P ( y < 1 > , y < 2 > , y < 3 > ) = P ( y < 1 > ) P ( y < 2 > ∣ y < 1 > ) P ( y < 3 > ∣ y < 1 > , y < 2 > ) P(y^{<1>},y^{<2>},y^{<3>})=P(y^{<1>})P(y^{<2>}|y^{<1>})P(y^{<3>}|y^{<1>},y^{<2>}) P(y<1>,y<2>,y<3>)=P(y<1>)P(y<2>y<1>)P(y<3>y<1>,y<2>)

新序列采样

在训练一个序列模型后,想了解新模型学到的东西,可以使用新序列采样这种非正式的方法。

从 RNN 训练的模型中采样序列

一个语言模型模拟了任意特定单词序列的概率,我们对这个概率分布进行采样,生成一个新的单词序列。输入 x < 1 > , a < 0 > x^{<1>},a^{<0>} x<1>,a<0> 为零向量,得到 y ^ < 1 > \hat{y}^{<1>} y^<1> 是所有可能的输出经过 softmax 层的概率,即每个单词出现的概率,根据 softmax 进行随机采样。对输出的向量(one-hot 向量)使用例如 numpy 命令 np.random.choice,根据向量中概率的分布进行采样,得到第一个单词的采样。

将得到的第一个单词的采样输入到下一个时间步中,计算出第二个单词的采样。通过采样到 EOS 或设定采样次数来结束一次采样。采样有时会出现 UNK,如果不想采样 UNK,可以拒绝这次采样,在剩下的单词中重新采样,直到得到不是 UNK 的单词

字符语言模型

还可以构建基于字符的 RNN 结构,字典包含所有的字符。其特点如下:

优点:不会产生 UNK缺点:序列过长,不易捕捉句子的依赖关系(句子前半部分如何影响后半部分)。训练的计算成本较高。

通常我们会使用基于词汇的语言模型,而随着计算机性能的提升,也有越来越多的领域开始使用基于字符的语言模型,例如需要大量处理未知或专业词汇的应用。

RNN 的梯度消失

对于下面的句子。cat 应该与后面的谓语保持单复数主谓一致,但是主谓之间有很多其它的单词,主谓有很长的依赖。

The cat, which already ate apple …, was full.

The cats, which already ate apple …, were full.

前面介绍的的 RNN 不擅长捕捉这种长期依赖效应。在较深的神经网络中,我们讨论过梯度消失的问题,得到的输出 y y y 很难传播回去,从而影响靠前层的权重。

同样 RNN 也会遇到这样的问题,很难让神经网络记住看到的名词是单数还是复数,从而在后面输出依赖的谓语。因此 RNN 模型会有很多局部影响,例如 y < 3 > y^{<3>} y<3> 主要受附近输入的影响。这是 RNN 的一个缺点,我们将在后面主要解决这个问题。

很深的神经网络还会出现梯度爆炸的问题,反向传播时,随着层数的增加,梯度不仅会指数下降,还会指数上升。梯度爆炸很容易检测到,指数极大的梯度会让参数极大,甚至数值溢出,产生 NaN。一种鲁棒的解决方法是使用梯度修剪,给梯度向量设定阈值,大于阈值时缩放梯度向量。

门循环单元 GRU

GRU 改变了 RNN 的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题。

RNN 单元

回顾 RNN 中激活值的计算公式: a < t > = g ( W a a a < t − 1 > + W a x x < t > + b a ) a^{<t>}=g(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a) a<t>=g(Waaa<t1>+Waxx<t>+ba) 其中 g g g 是 tanh 函数。对应作出图像:

GRU(简化)

GRU 有新变量 c c c,代表记忆细胞,例如前面 cat 的句子中,可以记住主语的单复数。 c < t > = a < t > c^{<t>}=a^{<t>} c<t>=a<t>,这里两个值相等,但在 LSTMs 中不同,因此这里使用不同的符号。

在每个时间步中,我们使用一个候选值重写记忆细胞,即 c ^ < t > \hat{c}^{<t>} c^<t> 在 0 到 1 之间: c ^ < t > = tanh ⁡ ( W c [ c < t > , x < t > ] + b c ) \hat{c}^{<t>}=\tanh(W_{c}[c^{<t>},x^{<t>}]+b_c) c^<t>=tanh(Wc[c<t>,x<t>]+bc) GRU 中最重要的思想是门 Γ u \Gamma_u Γu u u u 代表更新(update)门, Γ \Gamma Γ 是一个 0 到 1 之间的值,决定是否更新记忆细胞的值: Γ u = σ ( W u [ c < t − 1 > , x < t > ] + b u ) \Gamma_u=\sigma(W_u[c^{<t-1>},x^{<t>}]+b_u) Γu=σ(Wu[c<t1>,x<t>]+bu) 对于大多数可能的输入, Γ \Gamma Γ 非常接近 0 或 1。

c < t > c^{<t>} c<t> 为 0 或 1,表示句子中的主语是单数还是复数,GRU 会一直记住 c < t > c^{<t>} c<t> 的值,直到读到谓语,于是选择用 was 而不是 were。用式子表示为: c < t > = Γ u ∗ c ^ < t > + ( 1 − Γ u ) ∗ c ^ < t − 1 > c^{<t>}=\Gamma_u*\hat{c}^{<t>}+(1-\Gamma_u)*\hat{c}^{<t-1>} c<t>=Γuc^<t>+(1Γu)c^<t1> 这里 ∗ * 为元素对应相乘。如果 Γ u = 1 \Gamma_u=1 Γu=1 则更新, = 0 =0 =0 不更新。

GRU 的示意图如下:

W u [ c < t − 1 > , x < t > ] + b u W_u[c^{<t-1>},x^{<t>}]+b_u Wu[c<t1>,x<t>]+bu 是一个很小的负数时,门 Γ \Gamma Γ 很容易取到 0,此时有利于维护记忆细胞的值。并且因为 Γ \Gamma Γ 很小接近 0,就不会发生梯度消失的问题了,允许网络运行在长依赖中。

注意这里的 Γ \Gamma Γ 如果是 100 维的向量,100 个比特,告诉 GRU 单元哪个记忆细胞的向量维度在每个时间步要做更新,可以改变一部分的比特。

完整 GRU

需要给记忆细胞的候选值加一个项: c ^ < t > = tanh ⁡ ( W c [ Γ r ∗ c < t > , x < t > ] + b c ) \hat{c}^{<t>}=\tanh(W_{c}[\Gamma_r*c^{<t>},x^{<t>}]+b_c) c^<t>=tanh(Wc[Γrc<t>,x<t>]+bc) r r r 代表相关性, Γ r \Gamma_r Γr 代表候选值和 c < t > c^{<t>} c<t> 有多大的关系。 Γ r \Gamma_r Γr 的计算公式: Γ r = σ ( W r [ c < t − 1 > , x < t > ] + b r ) \Gamma_r=\sigma(W_r[c^{<t-1>},x^{<t>}]+b_r) Γr=σ(Wr[c<t1>,x<t>]+br) 其他公式同简化的 GRU: Γ u = σ ( W u [ c < t − 1 > , x < t > ] + b u ) \Gamma_u=\sigma(W_u[c^{<t-1>},x^{<t>}]+b_u) Γu=σ(Wu[c<t1>,x<t>]+bu)

c < t > = Γ u ∗ c ^ < t > + ( 1 − Γ u ) ∗ c ^ < t − 1 > c^{<t>}=\Gamma_u*\hat{c}^{<t>}+(1-\Gamma_u)*\hat{c}^{<t-1>} c<t>=Γuc^<t>+(1Γu)c^<t1>

通常在论文或文献中,记号会有所不同:

长短期记忆(LSTM)单元

GRU 和 LSTM

在 GRU 中我们有 a < t > = c < t > a^{<t>}=c^{<t>} a<t>=c<t>,一个更新门和一个相关门还有一个候选值。

LSTM 是一个比 GRU 更加强大和通用的版本。LSTM 中不满足 a < t > = c < t > a^{<t>}=c^{<t>} a<t>=c<t>,其主要式子如下。

这里使用 c < t > c^{<t>} c<t>,而不考虑 Γ r \Gamma_r Γr c ^ < t > = tanh ⁡ ( W c [ Γ r ∗ c < t > , x < t > ] + b c ) \hat{c}^{<t>}=\tanh(W_{c}[\Gamma_r*c^{<t>},x^{<t>}]+b_c) c^<t>=tanh(Wc[Γrc<t>,x<t>]+bc) 更新门: Γ u = σ ( W u [ a < t − 1 > , x < t > ] + b u ) \Gamma_u=\sigma(W_u[a^{<t-1>},x^{<t>}]+b_u) Γu=σ(Wu[a<t1>,x<t>]+bu) LSTM 中还有遗忘门,用来代替 1 − Γ u 1-\Gamma_u 1Γu Γ f = σ ( W f [ a < t − 1 > , x < t > ] + b f ) \Gamma_f=\sigma(W_f[a^{<t-1>},x^{<t>}]+b_f) Γf=σ(Wf[a<t1>,x<t>]+bf) Γ o \Gamma_o Γo 为输出: Γ o = σ ( W o [ a < t − 1 > , x < t > ] + b o ) \Gamma_o=\sigma(W_o[a^{<t-1>},x^{<t>}]+b_o) Γo=σ(Wo[a<t1>,x<t>]+bo) 记忆细胞的更新值: c < t > = Γ u ∗ c ^ < t > + Γ f ∗ c ^ < t − 1 > c^{<t>}=\Gamma_u*\hat{c}^{<t>}+\Gamma_f*\hat{c}^{<t-1>} c<t>=Γuc^<t>+Γfc^<t1> 这给记忆细胞选择权去维持旧的 c < t > c^{<t>} c<t>,或加上新值 c ^ < t > \hat{c}^{<t>} c^<t>

最后: a < t > = Γ o ∗ c < t > a^{<t>}=\Gamma_o*c^{<t>} a<t>=Γoc<t>

LSTM 示意图

下面是 LSTM 的示意图:

将所有的 LSTM 单元按时间次序连接起来。下图中上面的线表示,只要正确设置了更新门和遗忘门,LSTM 很容易将 c < 0 > c^{<0>} c<0> 的值一直传递到最后。

因此 GRU 和 LSTM 擅长长时间记住某个值。

最常用的 LSTM 版本是,门值不仅取决于 a < t > , x < t > a^{<t>},x^{<t>} a<t>,x<t>,有时也可以窥透 c < t > c^{<t>} c<t>(上一个记忆细胞的值),这称为窥视孔连接。LSTM 主要的区别在于一个技术上的细节,比如有一个 100 维的隐藏记忆细胞单元,第 50 个 c < t − 1 > c^{<t-1>} c<t1> 的值只会影响第 50 个元素对应的那个门,因此关系是一对一的。

对比 GRU 和 LSTM

GRULSTM优点模型更加简单,因此容易创建一个更大的网络只有 2 个门,运算更快有 3 个门,因此更加强大和灵活

大多数场景下,LSTM 是更优先的选择。

双向 RNN

双向 RNN 可以构建更好的模型,其在序列某点处不仅可以获取之前的信息,还可以获取未来的信息。

获取信息

回顾之前命名实体识别的例子,我们无法从之前的信息中判断 Teddy 是否为人名。不管这些单元是标准的 RNN 块,还是 GRU 或 LSTM 单元,这些构建只有向前的。

双向 RNN(BRNN)

为简单起见,这里输入的句子只有 4 个单词 x < 1 > , … , x < 4 > x^{<1>},…,x^{<4>} x<1>,,x<4>,网络有前向循环单元 a < 1 > , … , a < 4 > a^{<1>},…,a^{<4>} a<1>,,a<4>,用向右的箭头表示。

增加反向循环层,反向连接后网络变为一个无环图(acyclic graph),给定一个输入序列 x < 1 > , … , x < 4 > x^{<1>},…,x^{<4>} x<1>,,x<4>,首先计算前向的 a < 1 > , … , a < 4 > a^{<1>},…,a^{<4>} a<1>,,a<4>(加右箭头),反向序列从 a < 4 > a^{<4>} a<4>(加左箭头)开始向前计算。

我们计算的是网络激活值,属于前向传播,不要和反向传播搞混了。

预测值的公式为: y ^ < t > = g ( W y [ a ⃗ < t > ∣ a ⃗ < t > ] + b y ) \hat{y}^{<t>}=g(W_y[\vec{a}^{<t>}|\vec{a}^{<t>}]+b_y) y^<t>=g(Wy[a <t>a <t>]+by) 这里因公式编辑原因,第二个 a < t > a^{<t>} a<t> 上为左箭头。例如我们想得到 y < 3 > y^{<3>} y<3> 的预测结果, x < 1 > , x < 2 > , x < 3 > x^{<1>},x^{<2>},x^{<3>} x<1>,x<2>,x<3> 的信息会从前面传递过来,而 x < 4 > x^{<4>} x<4> 的信息会反向传递过来。这样预测值 y ^ < t > \hat{y}^{<t>} y^<t> 不仅输入了过去的信息,也输入了未来的信息。

这些单元可以是标准 RNN 单元,也可以是 GRU 或 LSTM 单元。对于大量有 NLP 问题的文本,如果句子是完整的,首先标定句子,然后通常使用 LSTM 单元的 BRNN。

BRNN 的缺点是,需要完整的数据序列才能预测任意位置。例如语音识别系统中,我们要等人把话全部说完才能处理这段语音。实际的语音系统有更加复杂的模块

深层 RNN

在学习十分复杂的函数时,需要将多个 RNN 层堆叠到一起构建更深的模型。

一个标准的神经网络输入 x x x,然后堆叠隐含层,激活值为 a [ 1 ] a^{[1]} a[1],最后得到预测值 y ^ \hat{y} y^

将上面的神经网络按时间展开就可以得到深层 RNN。

下图是我们前面介绍过的标准 RNN, a [ l ] < t > a^{[l]<t>} a[l]<t> 表示第 l l l 层的激活值, t t t 表示第 t t t 个时间点。

再将其它层堆叠在上面,形成 3 个隐藏层的新网络:

其中激活值 a [ 2 ] < 3 > a^{[2]<3>} a[2]<3> 有 2 个输入,一个来自左边一个来自下面: a [ 2 ] < 3 > = g ( W a [ 2 ] [ a [ 2 ] < 2 > ∣ a [ 1 ] < 3 > ] + b a [ 2 ] ) a^{[2]<3>}=g(W_a^{[2]}[a^{[2]<2>}|a^{[1]<3>}]+b_a^{[2]}) a[2]<3>=g(Wa[2][a[2]<2>a[1]<3>]+ba[2]) 参数 W a [ l ] , b a [ l ] W_a^{[l]},b_a^{[l]} Wa[l],ba[l] 在每层中相同。

普通的神经网络层数可能很深,达到 100 层。RNN 达到 3 层已经很多了,因为时间的维度,RNN会变得相当大。一种常见的做法是在最后一层的每一个时间点上面堆叠循环层,没有水平连接。

这些单元也可以是 GRU 或 LSTM 单元,也可以构建深层的 BRNN。尽管深层 RNN 的循环层很少,其训练也需要很多计算资源,不像【卷积神经网络】可以有大量的隐藏层。

有帮助的话点个赞加关注吧 😃

最新回复(0)