概述
总说
这篇主要是如何一步步说明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,...,xt−1和当前的输入 x t x_t xt进行融合。 由于我们RNN是一个迭代的过程,对于第 t t t次,输入只有 x t x_t xt,那历史的输入怎么办呢?这就要用到“历史信息”,也就是 t − 1 t-1 t−1时刻的隐层状态 h t − 1 h_{t-1} ht−1。这个历史信息只要和历史输入挂钩就行。
比如第一次,我们先设置一个 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} ht−1(就是下左图的中间一行的第一个结点的状态)进行融合的。融合方式很简单,我们只需要对 h t − 1 h_{t-1} ht−1和 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(W1ht−1+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(W1ht−1+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(W1ht−1+W2xt)
h
t
=
h
t
−
1
+
u
t
h_t = h_{t-1}+u_t
ht=ht−1+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(W1ht−1+W2xt)
一个更好的解决梯度消失的办法
其实上面的模型可能还是有点简单,原因在于
h
t
=
h
t
−
1
+
u
t
h_t = h_{t-1}+u_t
ht=ht−1+ut只是简单地相加。是将历史信息
h
t
−
1
h_{t-1}
ht−1完完全全的保留下来。其实我们可能更加希望,“取其精华,去其糟粕”,我们希望保留部分历史信息。所以我们可以定义成
h
t
=
W
′
h
t
−
1
+
W
′
′
u
t
h_t = W'h_{t-1}+W''u_t
ht=W′ht−1+W′′ut的形式貌似不错,我们先来看更加复杂,建模能力更强的一种定义方式,比如我们定义:
f
t
=
σ
(
W
f
x
t
+
U
f
h
t
−
1
)
f_t = sigma(W^fx_t+U^fh_{t-1})
ft=σ(Wfxt+Ufht−1)
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=ft⊙ht−1+(1−ft)⊙ut
其中
⊙
odot
⊙表示向量的点乘。此时我们设置的权重
f
t
f_t
ft是根据输入
x
t
x_t
xt以及上一次迭代的隐层状态
h
t
−
1
h_{t-1}
ht−1共同决定的。显然,当
f
t
f_t
ft较小时,会使得
h
t
−
1
h_{t-1}
ht−1对
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(W1ht−1+W2xt)
为什么设置
f
t
f_t
ft是这样的形式呢,直接设置两个权重让它自己学不好吗?
个人认为效果很可能会降低。我们是不知道针对已有的状态
h
t
−
1
h_{t-1}
ht−1和当前输入
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=W′⊙ht−1+W′′⊙ut,建模能力显然较为简单。如果我们显式地将
f
t
f_t
ft与当前输入
x
t
x_t
xt以及上一次迭代的隐层状态
h
t
−
1
h_{t-1}
ht−1联系起来,显然模型能力会更强。
既然有针对隐层状态的门了,同理可以定义针对“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=ft⊙ht−1+(1−ft)⊙ut,说明当前状态受历史状态
h
t
−
1
h_{t-1}
ht−1的,我们通过
f
t
f_t
ft来表示从历史状态的一个取舍。
其实我们想想,当前状态
h
t
h_t
ht应该还要受当前输入
x
t
x_t
xt的影响! 所以我们将
(
1
−
f
t
)
(1-f_t)
(1−ft)进行替换,替换成一个和当前输入有关的表达式就行。我们用类似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+Uiht−1)
h
t
=
f
t
⊙
h
t
−
1
+
i
t
⊙
u
t
h_t = f_todot h_{t-1}+i_todot u_t
ht=ft⊙ht−1+it⊙ut
我们看到了,
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}
ht−1有关,从而得到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=ft⊙ht−1+it⊙ut
比如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+Uoht−1)
然后我们需要把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
ft⊙ht−1+it⊙ut了,因为还没有经过输出门作用啊,起码经过输出门作用后,才能称之为
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+Uoht−1)
c
t
=
f
t
⊙
h
t
−
1
+
i
t
⊙
u
t
c_t= f_todot h_{t-1}+i_todot u_t
ct=ft⊙ht−1+it⊙ut
我们将“经过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=ot⊙tanh(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}
ht−1的取舍程度,对输入
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所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复