前面我们学习了全连接神经网络、卷积神经网络,这些网络的传递都是单向,元素之间相互独立,输入与输出也是毫无关系,这样虽然让神经网络容易学习,但是一定程度上却弱化了神经网络的能力。而我们这一章要介绍的循环神经网络,就是在一些方面对前馈神经网络的一种补充。
在实际生活中,很多元素都是相互联系的,比如一个人说了:我喜欢《黎明前的黑暗》,请帮我放一下___ ,大家都知道是这里应该补充《黎明前的黑暗》。因为我们自己很容易根据上下文的内容判断,但机器并不知道啊,想做到这一步就非常难了。因此,就有了循环神经网络,他的本质是:像人一样拥有记忆的能力。
循环神经网络(
R
N
N
RNN
RNN)是一种具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其它神经元的信息,也可以接受自身的信息,形成具有环路的网络结构,也就是说它不仅依赖当前特征,而且也比较依赖之前的输入特征来推断后面的结果。循环神经网络经常使用在自然语言处理(语言建模、文本生成)、机器翻译、语音识别、生成图像描述、视频标记、推荐等实际情况中。
循环神经网络的基本结构特别简单,就是将网络的输出保存在一个记忆单元中,这个记忆单元和下一次的输入一起进入神经网络中 。下图左边为一个简单的循环神经网络结构,右边为其展开:
![](https://i-blog.csdnimg.cn/blog_migrate/a26ce2266a386fc28e4c345d0cb1616e.png)
但是有时候,比如说语音信号,需要前后的信息来预测,这个时候并不需要创建两个不同方向的单向网络结构,可以直接使用一个双向的循环网络结构来完成任务,网络会从序列的正方向读取数据,再从反方向读取数据,最后将网络输出的两种结果合在一起形成网络的最终输出结果。
但是记忆有很大的遗忘性,对于短时间的信息,
R
N
N
RNN
RNN 可以很好的的预测近期的信息,如果时间比较久,记忆信息跟预测的位置之间比较远,那么网络也无法准确记住这些信息,比如说:在一次谈话中,开始我就说比较喜欢听《黎明前的黑暗》,中间又跟别人聊了很久其他信息,那么最后希望放一下《黎明前的黑暗》,这时网络就没办法很好的预测出来。
针对上面的情况,提出了循环神经网络的变形: L S T M LSTM LSTM, G R U GRU GRU。
LSTM
L
S
T
M
(
L
o
n
g
S
h
o
r
t
T
e
r
m
M
e
m
o
r
y
)
LSTM (Long Short Term Memory)
LSTM(LongShortTermMemory) 又称为长短期记忆网络,是目前使用最多的时间序列算法。它相对于简单的
R
N
N
RNN
RNN 引入了门来控制信息的传递,共引入输入门、遗忘门、输出门三个门,输入门控制着网络的输入,遗忘门控制着记忆单元,输出门控制着网络的输出 ,其中遗忘门的作用就是决定之前的哪些记忆被保留,哪些记忆被抹掉,由于遗忘门,使得
L
S
T
M
LSTM
LSTM 具有长时记忆的功能,对于给定的任务,遗忘门能够自己学习保留多少以前的记忆,这使得不再需要人为干扰,网络就能够自主学习。
所有
R
N
N
RNN
RNN 都具有一种重复神经网络模块的链式的形式。在标准的
R
N
N
RNN
RNN 中,这个重复的模块只有一个非常简单的结构,可以为
t
a
n
h
tanh
tanh,也可以为
R
e
L
U
ReLU
ReLU,结构如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/8c07a6e9fe489692d94c60f9539298fd.png)
L S T M LSTM LSTM 同样是这样的结构,但是隐藏单元的结构更加复杂,它引入一个新的内部状态 c t c_t ct 专门进行线性的循环信息传递,同时输出信息给隐藏层的外部状态 h t h_t ht。结构如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/f0a3aad65e6faa424709bcea249ab7c5.png)
从上图中可以看出,在每个序列索引位置 t t t 时刻向前传播的除了和 R N N RNN RNN 一样的隐藏状态 h t h_t ht ,还多了另一个隐藏状态,如下图中上面的长横线。这个隐藏状态我们一般称为细胞状态,记为 C t C_t Ct。如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/b7600acba0d088a4e4ded6a783cea33d.png)
细胞状态类似于传送带。直接在整个链上运行,只有一些少量的线性交互。信息在上面传输很容易保持不变。
遗忘门
遗忘门的作用是决定丢弃什么信息,其结构与数学表示如下:
![](https://i-blog.csdnimg.cn/blog_migrate/06ca9beae52f9053f6f8a221838232e3.png)
f
t
=
δ
(
W
f
[
h
t
−
1
,
x
t
]
+
b
f
)
{{\text{f}}_t} = \delta ({W_f}[{h_{t - 1}},{x_t}] + {b_f})
ft=δ(Wf[ht−1,xt]+bf)
h
t
−
1
{h_{t - 1}}
ht−1 表示上一时刻信息,
x
t
x_t
xt 表示当前输入的新信息,
f
t
{{\text{f}}_t}
ft 为一个在
0
0
0~
1
1
1 之间的数值,
0
0
0 表示完全舍弃,
1
1
1 表示全部保留。
输入门
输入门的作用是确定什么样的新信息被存放在细胞状态中。
![](https://i-blog.csdnimg.cn/blog_migrate/a78f28e5e28375d6290244e0c6890b91.png)
数学表达式表示为:
i
t
=
δ
(
W
t
[
h
t
−
1
,
x
t
]
+
b
i
)
{i_t} = \delta ({W_t}[{h_{t - 1}},{x_t}] + {b_i})
it=δ(Wt[ht−1,xt]+bi)
C
~
t
=
tanh
(
W
C
[
h
t
−
1
,
x
t
]
+
b
C
)
{\widetilde C_t} = \tanh ({W_C}[{h_{t - 1}},{x_t}] + {b_C})
C
t=tanh(WC[ht−1,xt]+bC)
i
t
{i_t}
it 为当前信息的衰减系数,用来决定更新什么值,
C
~
t
{\widetilde C_t}
C
t 为当前学习到的信息。有了这两个条件,我们就可以更新当前信息:
![](https://i-blog.csdnimg.cn/blog_migrate/251c6a00a65265b272236f574471b916.png)
C
t
=
f
t
∗
C
t
−
1
+
i
t
∗
C
t
~
{C_t} = {f_t}*{C_{t - 1}} + {i_t}*\widetilde {{C_t}}
Ct=ft∗Ct−1+it∗Ct
首先把旧状态
C
t
−
1
{C_{t - 1}}
Ct−1 与 衰减
f
(
t
)
f(t)
f(t) 相乘,丢掉确认需要丢弃的信息,然后加上衰减
i
t
{i_t}
it 乘以当前时候学习到的信息
C
t
~
\widetilde {{C_t}}
Ct
,这样便得到了
t
t
t 时刻下的更新信息
C
t
C_t
Ct。
输出门
当前时刻 t t t 的网络输出 h t h_t ht 取决于当前时刻 t t t 的记忆状态 C t C_t Ct 和 t t t 时刻的输入 x t x_t xt 、 t − 1 t-1 t−1 时刻的输出 h t − 1 h_{t-1} ht−1。
![](https://i-blog.csdnimg.cn/blog_migrate/160194cd0b66243a64230078dc70055f.png)
o t = δ ( W o [ h t − 1 , x t ] + b o ) {o_t} = \delta ({W_o}[{h_{t - 1}},{x_t}] + {b_o}) ot=δ(Wo[ht−1,xt]+bo)
h t = o t ∗ tanh ( C t ) {h_t} = {o_t}*\tanh ({C_t}) ht=ot∗tanh(Ct)
首先使用类似于计算记忆衰减系数的方法计算得到输出门的系数 o t o_t ot, 这个系数决定了输出的信息,最后得到网络输出 h t {h_t} ht。
GRU
G R U GRU GRU 和 L S T M LSTM LSTM 最大的不同在于 G R U GRU GRU 将遗忘门和输入门合成新的更新门,同时网络不再额外给出记忆状态 C t C_t Ct,而是将输出结果作为记忆状态不断向后循环传递,网络的输人和输出都变得特别简单。其网络结构如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/2c67f6324d575aad653890e40d7a324b.png)
z t = o t ( W z [ h t − 1 , x t ] ) {z_t} = {o_t}({W_z}[{h_{t - 1}},{x_t}]) zt=ot(Wz[ht−1,xt])
r t = o t ( W z [ h t − 1 , x t ] ) {r_t} = {o_t}({W_z}[{h_{t - 1}},{x_t}]) rt=ot(Wz[ht−1,xt])
h ~ t = tanh ( W [ r t ∗ h t − 1 , x t ] ) {\widetilde h_t} = \tanh ({W_{}}[{r_t}*{h_{t - 1}},{x_t}]) h t=tanh(W[rt∗ht−1,xt])
h
t
=
(
1
−
z
t
)
∗
h
t
−
1
+
z
t
∗
h
t
~
{h_t} = (1 - {z_t})*{h_{t - 1}} + {z_t}*\widetilde {{h_t}}
ht=(1−zt)∗ht−1+zt∗ht
其中,
r
t
{r_t}
rt 表示重置门,
z
t
{z_t}
zt 表示更新门。重置门决定是否将之前的状态忘记,相当于合并了
L
S
T
M
LSTM
LSTM 中的遗忘门和输入门。当
r
t
{r_t}
rt 趋于
0
0
0 的时候,前一个时刻的状态信息
h
t
−
1
{h_{t - 1}}
ht−1 会被忘掉,隐藏状态
h
~
t
{\widetilde h_t}
h
t 会被重置为当前输入的信息。更新门决定是否要将隐藏状态更新为新的状态
h
~
t
{\widetilde h_t}
h
t。
总结
L
S
T
M
LSTM
LSTM 网络是目前为止最成功的循环神经网络模型,成功应用在很多领域,比如语音识别、机器翻译、语音模型以及文本生成。
L
S
T
M
LSTM
LSTM 网络通过引入线性连接来缓解长距离依赖问题。虽然
L
S
T
M
LSTM
LSTM 网络取得了很大的成功,其结构的合理性一直受到广泛关注。人们不断有尝试对其进行改进来寻找最优结构,比如减少门的数量、提高并行能力等。
G
R
U
GRU
GRU 是
L
S
T
M
LSTM
LSTM 的一种变体,两者的性能在很多任务上基本没有区别,不同的是
G
R
U
GRU
GRU 参数相对较少更容易收敛,但是在数据集较大的情况下,
L
S
T
M
LSTM
LSTM 性能更好。
pytorch API
标准RNN
n
n
.
R
N
N
(
i
n
p
u
t
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
,
n
u
m
_
l
a
y
e
r
s
,
n
o
n
l
i
n
e
a
r
i
t
y
,
b
i
a
s
,
b
a
t
c
h
_
f
i
r
s
t
,
d
r
o
p
o
u
t
,
b
i
d
i
r
e
c
t
i
o
n
a
l
)
nn.RNN(input\_size, hidden\_size, num\_layers, nonlinearity, bias, batch\_first, dropout, bidirectional)
nn.RNN(input_size,hidden_size,num_layers,nonlinearity,bias,batch_first,dropout,bidirectional)
参数解释:
i
n
p
u
t
_
s
i
z
e
input\_size
input_size:输入的特征维度
h
i
d
d
e
n
_
s
i
z
e
hidden\_size
hidden_size:输出的特征维度
n
u
m
_
l
a
y
e
r
s
num\_layers
num_layers:网络的层数
n
o
n
l
i
n
e
a
r
i
t
y
nonlinearity
nonlinearity:非线性激活函数,默认是
t
a
n
h
tanh
tanh
b
i
a
s
bias
bias:是否使用偏置,默认使用
b
a
t
c
h
_
f
i
r
s
t
batch\_first
batch_first:输入数据的形式,默认是
F
a
l
s
e
False
False,即
(
s
e
q
,
b
a
t
c
h
,
f
e
a
t
u
r
e
)
(seq, batch, feature)
(seq,batch,feature),
T
r
u
e
True
True 为
(
b
a
t
c
h
,
s
e
q
,
f
e
a
t
u
r
e
)
(batch, seq, feature)
(batch,seq,feature)
d
r
o
p
o
u
t
dropout
dropout:是否在输出层应用 dropout
b
i
d
i
r
e
c
t
i
o
n
a
l
bidirectional
bidirectional:是否使用双向的
r
n
n
rnn
rnn,默认是
F
a
l
s
e
False
False 为单向
输入
(
i
n
p
u
t
,
h
0
)
(input, h_0)
(input,h0)
i
n
p
u
t
input
input:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
i
n
p
u
t
_
s
i
z
e
]
[seq, batch\_size, input\_size]
[seq,batch_size,input_size]
h
0
h_0
h0:
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction, batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
输出
(
o
u
t
p
u
t
,
h
n
)
(output, h_n)
(output,hn)
h
n
h_n
hn:
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction,batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
o
u
t
p
u
t
output
output:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
∗
d
i
r
e
c
t
i
o
n
]
[seq, batch\_size, hidden\_size*direction]
[seq,batch_size,hidden_size∗direction]
LSTM
n n . L S T M ( i n p u t _ s i z e , h i d d e n _ s i z e , l a y e r _ n u m . . . ) nn.LSTM(input\_size, hidden\_size, layer\_num ...) nn.LSTM(input_size,hidden_size,layer_num...), 后面参数与标准 R N N RNN RNN 相同,这里就不介绍了。
输入
(
i
n
p
u
t
,
(
h
0
,
c
0
)
)
(input, (h_0, c_0))
(input,(h0,c0))
i
n
p
u
t
input
input:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
i
n
p
u
t
_
s
i
z
e
]
[seq, batch\_size, input\_size]
[seq,batch_size,input_size]
(
h
0
,
c
0
)
(h_0, c_0)
(h0,c0):
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction, batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
输出
(
o
u
t
p
u
t
,
(
h
n
,
c
n
)
)
(output, (h_n, c_n))
(output,(hn,cn))
o
u
t
p
u
t
output
output:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
d
i
m
∗
d
i
r
e
c
t
i
o
n
]
[seq, batch\_size, hidden\_dim*direction]
[seq,batch_size,hidden_dim∗direction]
(
h
n
,
c
n
)
(h_n, c_n)
(hn,cn):
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction,batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
GRU
n
n
.
G
R
U
(
i
n
p
u
t
_
d
i
m
,
h
i
d
d
e
n
_
s
i
z
e
,
l
a
y
e
r
_
n
u
m
.
.
.
)
nn.GRU(input\_dim, hidden\_size, layer\_num...)
nn.GRU(input_dim,hidden_size,layer_num...)
参数解释:
i
n
p
u
t
_
s
i
z
e
input\_size
input_size:输入的特征维度
h
i
d
d
e
n
_
s
i
z
e
hidden\_size
hidden_size:输出的特征维度
n
u
m
_
l
a
y
e
r
s
num\_layers
num_layers:网络的层数
输入
(
i
n
p
u
t
,
(
h
0
,
c
0
)
)
(input, (h_0, c_0))
(input,(h0,c0))
i
n
p
u
t
input
input:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
i
n
p
u
t
_
s
i
z
e
]
[seq, batch\_size, input\_size]
[seq,batch_size,input_size]
(
h
0
,
c
0
)
(h_0, c_0)
(h0,c0):
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction, batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
输出
(
o
u
t
p
u
t
,
(
h
n
,
c
n
)
)
(output, (h_n, c_n))
(output,(hn,cn))
o
u
t
p
u
t
output
output:
[
s
e
q
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
∗
d
i
r
e
c
t
i
o
n
]
[seq, batch\_size, hidden\_size*direction]
[seq,batch_size,hidden_size∗direction]
(
h
n
,
c
n
)
(h_n, c_n)
(hn,cn):
[
n
u
m
_
l
a
y
e
r
s
∗
d
i
r
e
c
t
i
o
n
,
b
a
t
c
h
_
s
i
z
e
,
h
i
d
d
e
n
_
s
i
z
e
]
[num\_layers*direction,batch\_size, hidden\_size]
[num_layers∗direction,batch_size,hidden_size]
参考
- RNN, LSTM 理解
- 廖星宇:《深度学习之pytorch》