简易理解RNN与LSTM

版权声明:要转随便转,如果能加上原文的链接就感谢各位了。( ⊙ o ⊙ ) https://blog.csdn.net/Hungryof/article/details/79016506

总说

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

RNN

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

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

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

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

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

RNN还要有输出,既然是迭代的,显然对于第 t 次迭代,就会有 y^t 输出。我们不能直接把 ht 输出吧,为了增加复杂性,乘以一个权重 W3 吧,用于表示对当前隐层状态 ht 的一个“取舍”。
所以自然就有下面:

ht=tanh(W1ht1+W2xt)

y¯=W3ht

这里写图片描述

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

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

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

这里写图片描述

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

BPTT

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

这里写图片描述

这里写图片描述

这里写图片描述

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

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

看看原来咋弄的:

ht=tanh(W1ht1+W2xt)

原来的当前隐层状态的得到,是直接将当前输入和上一次迭代的隐层状态,进行简单融合。那么求导时,自然就会有连乘形式,那就容易爆炸或是消失啊! 要不转换成“连加”吧

ut=tanh(W1ht1+W2xt)
ht=ht1+ut

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

注意:后面 ut 都是指

ut=tanh(W1ht1+W2xt)

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

其实上面的模型可能还是有点简单,原因在于 ht=ht1+ut 只是简单地相加。是将历史信息 ht 完完全全的保留下来。其实我们可能更加希望,“取其精华,去其糟粕”,我们希望保留部分历史信息。所以我们可以定义成 ht=Wht1+W′′ut 的形式貌似不错,我们先来看更加复杂,建模能力更强的一种定义方式,比如我们定义:

ft=σ(Wfxt+Ufht1)

ht=ftht1+(1ft)ut

其中 表示向量的点乘。此时我们设置的权重 ft 根据输入 xt 以及上一次迭代的隐层状态 ht1 共同决定的。显然,当 ft 较小时,会使得 ht1 ht 的得到作用很小,所以相当于把历史信息给遗忘了,所以称 ft 为forget门!
值得注意的是: ut=tanh(W1ht1+W2xt)

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

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

再次升级模型v2

我们可以看到

ht=ftht1+(1ft)ut
,说明当前状态受历史状态 ht1 的,我们通过 ft 来表示从历史状态的一个取舍。
其实我们想想,当前状态 ht 应该还要受当前输入 xt 的影响!所以我们将 (1ft) 进行替换,替换成一个和当前输入有关的表达式就行。我们用类似forget 门的方式进行定义input 门。

模型2.0:

it=σ(Wixt+Uiht1)

ht=ftht1+itut

我们看到了, it 就是输入门,emmm, it 作用在增量 ut 上,使得我们对增量有更加adaptive的一个控制,因此输入门使得状态 ht 显示的和当前输入有关。

再次升级到v3

既然我们已经让“状态和已有的状态 ht1 有关,从而得到forget 门 ft ”;而后又让“状态和当前输入有关 xt ,从而得到input 门 it ”。是不是觉得还少点什么,为啥不干脆再让“状态和输出 ot 有关呢”?
其实想到这里,应该挺兴奋了,因为现在的模型已经想的比较复杂了,建模能力应该挺高了。
ummm,想想怎么加啊。之前已经是这样的形式了:

ht=ftht1+itut

比如RNN时,对 ht W3 作用一下,作为输出 y^t 就可以了。那么现在呢?我们参照上面的做法,弄出一个output门 ot ,于是:
ot=σ(Woxt+Uoht1)

然后我们需要把output门,作用在“前面的经过input门和forget门得到的东西”。嗯,既然说到这里了,我们就不能用 ht 表示 ftht1+itut 了,因为还没有经过输出门作用啊,起码经过输出门作用后,才能称之为 ht 吧。
ot=σ(Woxt+Uoht1)

ct=ftht1+itut

我们将“经过input门和forget门得到的东西,称为 ct ”,我们这里有一个好听的名字,叫做 cell state,细胞状态。。那么参考之前的设置,隐层状态 ht 应该是 ot ct 的融合:
ht=ottanh(ct)

注意这里有一个tanh。
进过遗忘门,输入门以及输出门,这三个门的作用,我们得到了 ht ,所以输出 y^t=Uht 和这个没啥好说的,类似RNN得到输出的方式。

LSTM

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

这里写图片描述

再来简单回顾一下:
我们建立了三个门: ft,it,ot ,分别表示对历史信息 ht1 的取舍程度,对输入 xt (通过增量 ut )的取舍程度,以及对已经初步融合了历史信息和当前输入,从而得到的“输出” ct (cell state) 的取舍程度。我们真正的隐层状态应该是经过这三个门作用后得到的 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

猜你喜欢

转载自blog.csdn.net/Hungryof/article/details/79016506