我是靠谱客的博主 淡淡发带,最近开发中收集的这篇文章主要介绍简易理解RNN与LSTM总说RNNBPTT如何解决RNN的梯度消失问题LSTMTODOpytorch中的lstm,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

总说

这篇主要是如何一步步说明RNN和LSTM的形式的构造,方便对模型有一个更直观的理解。写的比较随意。

RNN

我们知道,卷积是一个输入,得到一个输出。但有时候我们想输出一串,然后得到一串输出呢?并且是这一串都是相互有关联的,比如句子翻译。我们就需要一种能针对历史信息进行融合的单元,比如RNN。其实想想,只要以某种形式,将历史信息与当前输入进行有效融合的方式,应该都可以处理类似的问题。

和CNN的区别是,RNN有一个隐层状态 h t h_t ht,这个状态必须将历史的输入 x 1 , x 2 , . . . , x t − 1 x_1,x_2,...,x_{t-1} x1,x2,...,xt1和当前的输入 x t x_t xt进行融合。 由于我们RNN是一个迭代的过程,对于第 t t t次,输入只有 x t x_t xt,那历史的输入怎么办呢?这就要用到“历史信息”,也就是 t − 1 t-1 t1时刻的隐层状态 h t − 1 h_{t-1} ht1。这个历史信息只要和历史输入挂钩就行。

比如第一次,我们先设置一个 h 0 h_0 h0,那么 h 1 h_1 h1应该是 x 1 x_1 x1 h 0 h_0 h0的融合。嗯,没错。这样一来, h 2 h_2 h2应该是 x 2 x_2 x2 h 1 h_1 h1的融合。此时 h 2 h_2 h2的得到不仅融合了历史输入 x 1 x_1 x1还结合了当前输入 x 2 x_2 x2

我们通过增加了一个隐层状态,从而使得RNN能够将当前输入与历史输入进行有效的融合。隐层状态是历史信息的载体。

对于每次新的输入 x t x_t xt必须要和已有的隐层状态 h t − 1 h_{t-1} ht1(就是下左图的中间一行的第一个结点的状态)进行融合的。融合方式很简单,我们只需要对 h t − 1 h_{t-1} ht1 x t x_t xt分别进行一个变换,好让其输入的维度等于 h t h_t ht的维度就行。所以就有 W 1 W_1 W1 W 2 W_2 W2,分别表示对当前的输入 x t x_t xt以及历史输入的一个“取舍程度”。

RNN还要有输出,既然是迭代的,显然对于第 t t t次迭代,就会有 y ^ t hat{y}_t y^t输出。我们不能直接把 h t h_t ht输出吧,为了增加复杂性,乘以一个权重 W 3 W_3 W3吧,用于表示对当前隐层状态 h t h_t ht的一个“取舍”。
所以自然就有下面:
h t = t a n h ( W 1 h t − 1 + W 2 x t ) h_t=tanh(W^1h_{t-1}+W^2x_t) ht=tanh(W1ht1+W2xt)
y ˉ = W 3 h t bar{y}=W^3h_t yˉ=W3ht
这里写图片描述

值得注意的是,这幅图左边是展开形式。那么要定义给一个RNN,我们当然要定义这个 t t t最大是多少。比如我们希望 t t t最多迭代3次。那么我们就有 h 1 h_1 h1, h 2 h_2 h2 h 3 h_3 h3, 就相当于有3个隐层神经元。因此RNN最多迭代次数就是我们所说的time step的最大值,也是recurrent layer的数目。

看看pytorch的对应函数,emmm,没啥问题。默认的隐层激活函数是tanh, 也可以选择 relu.
这里写图片描述

num_layers是什么?
是RNN有多少层,前面看到的都是一层的RNN。比如很经典的预测下一个字母:
输入是one-hot形式的4*1向量,红色层是输入层。隐层浅绿色,状态是3*1。因此 W x h W_{xh} Wxh应该是3*4的矩阵。输出是浅蓝色部分,大小是4*1的。所以 W h y W_{hy} Why是4*3的矩阵。隐层time step的迭代显然是3*3的方阵 W h h W_{hh} Whh

这里写图片描述

前面的例子都是,输入经过经过一次线性变换,成为隐层状态,再经过一次线性变换,直接变成输出了。为了增加复杂性,可以让隐层状态经过多次线性变换,再到输出。这就是多层RNN
下面是3层的(绿色代表深度为3的隐层,红色是输入层,蓝色是输出层)
这里写图片描述

BPTT

反向传播的梯度推导如下,看看就行。

这里写图片描述

这里写图片描述

这里写图片描述

显然容易出现梯度爆炸或者梯度消失的现象。对于梯度爆炸,直接梯度裁剪就行。但是梯度消失,就不好弄了,你不可能直接乘以一个数吧~~。

如何解决RNN的梯度消失问题

看看原来咋弄的:
h t = t a n h ( W 1 h t − 1 + W 2 x t ) h_t=tanh(W^1h_{t-1}+W^2x_t) ht=tanh(W1ht1+W2xt)
原来的当前隐层状态的得到,是直接将当前输入和上一次迭代的隐层状态,进行简单融合。那么求导时,自然就会有连乘形式,那就容易爆炸或是消失啊!要不转换成“连加”吧

u t = t a n h ( W 1 h t − 1 + W 2 x t ) u_t = tanh(W^1h_{t-1}+W^2x_t) ut=tanh(W1ht1+W2xt)
h t = h t − 1 + u t h_t = h_{t-1}+u_t ht=ht1+ut

现在是,上一次迭代的隐层状态和当前的输入,融合后的 u t u_t ut,只是作为“增量部分”。
这里写图片描述

注意:后面 u t u_t ut都是指
u t = t a n h ( W 1 h t − 1 + W 2 x t ) u_t = tanh(W^1h_{t-1}+W^2x_t) ut=tanh(W1ht1+W2xt)

一个更好的解决梯度消失的办法

其实上面的模型可能还是有点简单,原因在于 h t = h t − 1 + u t h_t = h_{t-1}+u_t ht=ht1+ut只是简单地相加。是将历史信息 h t − 1 h_{t-1} ht1完完全全的保留下来。其实我们可能更加希望,“取其精华,去其糟粕”,我们希望保留部分历史信息。所以我们可以定义成 h t = W ′ h t − 1 + W ′ ′ u t h_t = W'h_{t-1}+W''u_t ht=Wht1+Wut的形式貌似不错,我们先来看更加复杂,建模能力更强的一种定义方式,比如我们定义:
f t = σ ( W f x t + U f h t − 1 ) f_t = sigma(W^fx_t+U^fh_{t-1}) ft=σ(Wfxt+Ufht1)
h t = f t ⊙ h t − 1 + ( 1 − f t ) ⊙ u t h_t=f_todot h_{t-1}+(1-f_t)odot u_t ht=ftht1+(1ft)ut
其中 ⊙ odot 表示向量的点乘。此时我们设置的权重 f t f_t ft根据输入 x t x_t xt以及上一次迭代的隐层状态 h t − 1 h_{t-1} ht1共同决定的。显然,当 f t f_t ft较小时,会使得 h t − 1 h_{t-1} ht1 h t h_t ht的得到作用很小,所以相当于把历史信息给遗忘了,所以称 f t f_t ft为forget门!
值得注意的是: u t = t a n h ( W 1 h t − 1 + W 2 x t ) u_t=tanh(W^1h_{t-1}+W^2x_t) ut=tanh(W1ht1+W2xt)

为什么设置 f t f_t ft是这样的形式呢,直接设置两个权重让它自己学不好吗?
个人认为效果很可能会降低。我们是不知道针对已有的状态 h t − 1 h_{t-1} ht1和当前输入 x t x_t xt该怎样取舍,到底保留多少历史信息。如果单纯的设置 h t = W ′ ⊙ h t − 1 + W ′ ′ ⊙ u t h_t=W'odot h_{t-1}+W''odot u_t ht=Wht1+Wut,建模能力显然较为简单。如果我们显式地将 f t f_t ft与当前输入 x t x_t xt以及上一次迭代的隐层状态 h t − 1 h_{t-1} ht1联系起来,显然模型能力会更强。

既然有针对隐层状态的门了,同理可以定义针对“input”和“output”的门。

再次升级模型v2

我们可以看到 h t = f t ⊙ h t − 1 + ( 1 − f t ) ⊙ u t h_t=f_todot h_{t-1}+(1-f_t)odot u_t ht=ftht1+(1ft)ut,说明当前状态受历史状态 h t − 1 h_{t-1} ht1的,我们通过 f t f_t ft来表示从历史状态的一个取舍。
其实我们想想,当前状态 h t h_t ht应该还要受当前输入 x t x_t xt的影响! 所以我们将 ( 1 − f t ) (1-f_t) (1ft)进行替换,替换成一个和当前输入有关的表达式就行。我们用类似forget 门的方式进行定义input 门。

模型2.0:
i t = σ ( W i x t + U i h t − 1 ) i_t = sigma(W^ix_t+U^ih_{t-1}) it=σ(Wixt+Uiht1)
h t = f t ⊙ h t − 1 + i t ⊙ u t h_t = f_todot h_{t-1}+i_todot u_t ht=ftht1+itut
我们看到了, i t i_t it就是输入门,emmm, i t i_t it作用在增量 u t u_t ut上,使得我们对增量有更加adaptive的一个控制,因此输入门使得状态 h t h_t ht显示的和当前输入有关。

再次升级到v3

既然我们已经让“状态和已有的状态 h t − 1 h_{t-1} ht1有关,从而得到forget 门 f t f_t ft”;而后又让“状态和当前输入有关 x t x_t xt,从而得到input 门 i t i_t it”。是不是觉得还少点什么,为啥不干脆再让“状态和输出 o t o_t ot有关呢”?
其实想到这里,应该挺兴奋了,因为现在的模型已经想的比较复杂了,建模能力应该挺高了。
ummm,想想怎么加啊。之前已经是这样的形式了:
h t = f t ⊙ h t − 1 + i t ⊙ u t h_t = f_todot h_{t-1}+i_todot u_t ht=ftht1+itut
比如RNN时,对 h t h_t ht W 3 W^3 W3作用一下,作为输出 y ^ t hat y_t y^t就可以了。那么现在呢?我们参照上面的做法,弄出一个output门 o t o_t ot,于是:
o t = σ ( W o x t + U o h t − 1 ) o_t=sigma(W^ox_t+U^oh_{t-1}) ot=σ(Woxt+Uoht1)
然后我们需要把output门,作用在“前面的经过input门和forget门得到的东西”。嗯,既然说到这里了,我们就不能用 h t h_t ht表示 f t ⊙ h t − 1 + i t ⊙ u t f_todot h_{t-1}+i_todot u_t ftht1+itut了,因为还没有经过输出门作用啊,起码经过输出门作用后,才能称之为 h t h_t ht吧。
o t = σ ( W o x t + U o h t − 1 ) o_t=sigma(W^ox_t+U^oh_{t-1}) ot=σ(Woxt+Uoht1)
c t = f t ⊙ h t − 1 + i t ⊙ u t c_t= f_todot h_{t-1}+i_todot u_t ct=ftht1+itut
我们将“经过input门和forget门得到的东西,称为 c t c_t ct”,我们这里有一个好听的名字,叫做 cell state,细胞状态。。那么参考之前的设置,隐层状态 h t h_t ht应该是 o t o_t ot c t c_t ct的融合:
h t = o t ⊙ t a n h ( c t ) h_t=o_t odot tanh(c_t) ht=ottanh(ct)
注意这里有一个tanh。
进过遗忘门,输入门以及输出门,这三个门的作用,我们得到了 h t h_t ht,所以输出 y ^ t = U h t hat y_t=Uh_t y^t=Uht和这个没啥好说的,类似RNN得到输出的方式。

LSTM

前面的模型进化到v3时,就已经是LSTM了。

这里写图片描述

再来简单回顾一下:
我们建立了三个门: f t , i t , o t f_t, i_t, o_t ft,it,ot,分别表示对历史信息 h t − 1 h_{t-1} ht1的取舍程度,对输入 x t x_t xt(通过增量 u t u_t ut)的取舍程度,以及对已经初步融合了历史信息和当前输入,从而得到的输出 c t c_t ct(cell state)的取舍程度。我们真正的隐层状态应该是经过这三个门作用后得到的 h t h_t ht
所以上面的公式应该很容易理解了,另外一点是,这个论文的原版设置就是,中间三个门用sigmoid来弄,其他两个用tanh来弄。其实你sigmoid换成tanh也差不多的,只要是原点处是单调平滑递增的就行。不过可能当时提出来的时候,用sigmoid可以时得到的矩阵的值介于0-1之间,类似一种权重分配吧,比较符合直观。

TODO

加入GRU和其他变体,等理解更加深刻之后再完善吧。。又立flag了~哈哈

pytorch中的lstm

具体用法看文档。
一个例子:


x = self.CNN(x)
# CNN是vgg
x = x.view(x.size()[0], 512, -1) # 将spatial size拉成向量
# (batch, input_size, seq_len) -> (batch, seq_len, input_size)
x = x.transpose(1, 2)
# (batch, seq_len, input_size) -> (seq_len, batch, input_size)
x = x.transpose(0, 1).contiguous() #一定要转成这种形式
x, _ = self.LSTM1(x)

其中LSTM可能是:

self.BiLSTM1 = nn.LSTM(input_size=nIn, hidden_size=nHidden, num_layers=1, dropout=0)

conv-lstm

以前lstm的那些权重都是线性变换矩阵,但是有些是用卷积来做的。比如:
Convolutional LSTM Network: A Machine LearningApproach for Precipitation Nowcasting.
代码例子:
https://github.com/Atcold/pytorch-CortexNet/blob/master/model/ConvLSTMCell.py

最后

以上就是淡淡发带为你收集整理的简易理解RNN与LSTM总说RNNBPTT如何解决RNN的梯度消失问题LSTMTODOpytorch中的lstm的全部内容,希望文章能够帮你解决简易理解RNN与LSTM总说RNNBPTT如何解决RNN的梯度消失问题LSTMTODOpytorch中的lstm所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(72)

评论列表共有 0 条评论

立即
投稿
返回
顶部