introduction
??在订票场景中leave 北京和arrive 北京,虽然都是通过北京但是一个北京是作为出发地,另一个北京作为目标地,但是对NN来说,输入是相同的,它没有办法区分出“北京”是出发地还是目的地。这个时候我们就希望神经网络是有记忆的,如果NN在看到“北京”的时候,还能记住之前已经看过的“arrive”或是“leave”,就可以根据上下文得到正确的答案。这种有记忆力的神经网络,就叫做Recurrent Neural Network(RNN) ??在RNN中hidden layer每次产生的output
a
1
a_{1}
a1?、
a
2
a_{2}
a2?,都会被存到memory里,下一次有input的时候,这些neuron就不仅会考虑新输入的
x
1
x_{1}
x1?、
x
2
x_{2}
x2?,还会考虑存放在memory中的
a
1
a_{1}
a1?、
a
2
a_{2}
a2? ??注:在input之前,要先给内存里的
a
i
a_{i}
ai?赋初始值,比如0 此时即使输入同样是“Taipei”,我们依旧可以根据前文的“leave”或“arrive”来得到不一样的输出
Elman Network & Jordan Network
RNN有不同的变形:
- Elman Network:将hidden layer的输出保存在memory里
- Jordan Network:将整个neural network的输出保存在memory里
由于hidden layer没有明确的训练目标,而整个NN具有明确的目标,因此Jordan Network的表现会更好一些
Bidirectional RNN
RNN 还可以是双向的,你可以同时训练一对正向和反向的RNN,把它们对应的hidden layer
x
t
x_{t}
xt?拿出来,都接给一个output layer,得到最后的
y
t
y_{t}
yt? 使用Bi-RNN的好处是,NN在产生输出的时候,它能够看到的范围是比较广的,RNN在产生
y
t
+
1
y_{t+1}
yt+1?的时候,它不只看了从句首
x
1
x_{1}
x1?开始到
x
t
+
1
x_{t+1}
xt+1?的输入,还看了从句尾
x
n
x_{n}
xn?一直到
x
t
+
1
x_{t+1}
xt+1?的输入,这就相当于RNN在看了整个句子之后,才决定每个词汇具体要被分配到哪一个槽中,这会比只看句子的前一半要更好
LSTM
前文提到的RNN只是最简单的版本,并没有对memory的管理多加约束,可以随时进行读取,而现在常用的memory管理方式叫做长短期记忆(Long Short-term Memory),简称LSTM
Three-gate
LSTM有三个gate:
- 当某个neuron的输出想要被写进memory cell,它就必须要先经过一道叫做input gate的闸门,如果input gate关闭,则任何内容都无法被写入,而关闭与否、什么时候关闭,都是由神经网络自己学习到的
- output gate决定了外界是否可以从memory cell中读取值,当output gate关闭的时候,memory里面的内容同样无法被读取
- forget gate则决定了什么时候需要把memory cell里存放的内容忘记清空,什么时候依旧保存
如果从表达式的角度看LSTM,它比较像下图中的样子:
z
z
z是想要被存到cell里的输入值
z
i
z_{i}
zi?是操控input gate的信号
z
0
z_{0}
z0?是操控output gate的信号
z
f
z_{f}
zf?是操控forget gate的信号
a
a
a是综合上述4个input得到的output值 把
z
z
z、
z
i
z_{i}
zi?、
z
0
z_{0}
z0?、
z
f
z_{f}
zf?通过activation function,分别得到
g
(
z
)
g(z)
g(z)、
f
(
z
i
)
f(z_{i})
f(zi?)、
f
(
z
0
)
f(z_{0})
f(z0?)、
f
(
z
f
)
f(z_{f})
f(zf?) 其中对
z
i
z_{i}
zi?、
z
0
z_{0}
z0?和
z
f
z_{f}
zf?来说,它们通过的激活函数一般会选sigmoid function,因为它的输出在0~1之间,代表gate被打开的程度
令
g
(
z
)
g(z)
g(z)与
f
(
z
i
)
f(z_{i})
f(zi?)相乘得到
g
(
z
)
?
f
(
z
i
)
g(z)·f(z_{i})
g(z)?f(zi?),然后把原先存放在cell中的c与
f
(
z
f
)
f(z_{f})
f(zf?)相乘得到
c
f
(
z
f
)
cf(z_{f})
cf(zf?),两者相加得到存在memory中的新值
c
′
=
c
f
(
z
f
)
+
g
(
z
)
?
f
(
z
i
)
c'=cf(z_{f})+g(z)·f(z_{i})
c′=cf(zf?)+g(z)?f(zi?)
- 若
f
(
z
i
)
=
0
f(z_{i})=0
f(zi?)=0,则相当于没有输入,若
f
(
z
i
)
=
1
f(z_{i})=1
f(zi?)=1,则相当于直接输入
g
(
z
)
g(z)
g(z)
- 若
f
(
z
f
)
=
1
f(z_{f})=1
f(zf?)=1,则保存原来的c值并加到新的值上,若
f
(
z
f
)
=
0
f(z_{f})=0
f(zf?)=0,则旧的值将被遗忘清除
从中也可以看出,forget gate的逻辑与我们的直觉是相反的,控制信号打开表示记得,关闭表示遗忘
此后,
c
′
c'
c′通过激活函数得到
h
(
c
′
)
h(c')
h(c′),与output gate的
f
(
z
0
)
f(z_{0})
f(z0?)相乘,得到输出
a
=
h
(
c
′
)
f
(
z
0
)
a = h(c')f(z_{0})
a=h(c′)f(z0?)
LSTM Structure
只需要把LSTM整体看做是下面的一个neuron即可 假设目前我们的hidden layer只有两个neuron,则结构如下图所示:
- 输入
x
1
x_{1}
x1?、
x
2
x_{2}
x2?会分别乘上四组不同的weight,作为neuron的输入以及三个状态门的控制信号
- 在原来的neuron里,1个input对应1个output,而在LSTM里,4个input才产生1个output,并且所有的input都是不相同的
- 从中也可以看出LSTM所需要的参数量是一般NN的4倍
LSTM for RNN
??假设我们现在有一整排的LSTM作为neuron,每个LSTM的cell里都存了一个scalar值,把所有的scalar连接起来就组成了一个vector
c
t
?
1
c^{t-1}
ct?1 ??在时间点t,输入了一个vector
x
t
x^{t}
xt ,它会乘上一个matrix,通过转换得到z,而的每个dimension就代表了操控每个LSTM的输入值,同理经过不同的转换得到
z
i
z^{i}
zi、
z
f
z^{f}
zf和
z
0
z^{0}
z0,得到操控每个LSTM的门信号 ??下图是单个LSTM的运算情景,其中LSTM的4个input分别是
z
z
z、
z
i
z^i
zi、
z
f
z^f
zf和
z
0
z^0
z0的其中1维,每个LSTM的cell所得到的input都是各不相同的,但它们却是可以一起共同运算的,整个运算流程如下图左侧所示: ??
f
(
z
f
)
f(z^f)
f(zf)与上一个时间点的cell值
c
t
?
1
c^{t-1}
ct?1相乘,并加到经过input gate的输入
g
(
z
)
?
f
(
z
i
)
g(z)·f(z_{i})
g(z)?f(zi?)上,得到这个时刻cell中的值
c
t
c^{t}
ct,最终再乘上output gate的信号
f
(
z
0
)
f(z^0)
f(z0),得到输出
y
t
y^t
yt
??上述的过程反复进行下去,就得到下图中各个时间点上,LSTM值的变化情况,其中与上面的描述略有不同的是,这里还需要把hidden layer的最终输出以及当前cell的值都连接到下一个时间点的输入上
??因此在下一个时间点操控这些gate值,不只是看输入的,还要看前一个时间点的输出和cell值,你需要把这3个vector并在一起,乘上4个不同的转换矩阵,去得到LSTM的4个输入值,再去对LSTM进行操控 注意:下图是同一个LSTM在两个相邻时间点上的情况 ??上图是单个LSTM作为neuron的情况,事实上LSTM基本上都会叠多层,如下图所示,左边两个LSTM代表了两层叠加,右边两个则是它们在下一个时间点的状态
how to train LSTM
lost function
我们需要把model的输出
y
i
y^i
yi与映射到slot的reference vector求交叉熵,比如“Taipei”对应到的是“dest”这个slot,则reference vector在“dest”位置上值为1,其余维度值为0
RNN的output和reference vector的cross entropy之和就是损失函数,也是要minimize的对象
需要注意的是,word要依次输入model,比如“arrive”必须要在“Taipei”前输入,不能打乱语序
Training
有了损失函数后,训练其实也是用梯度下降法,为了计算方便,这里采取了反向传播(Backpropagation)的进阶版,Backpropagation through time,简称BPTT算法
BPTT算法与BP算法非常类似,只是多了一些时间维度上的信息。
Error Surface
下图中,z轴代表loss,x轴和y轴代表两个参数w1和w2,可以看到loss在某些地方非常平坦,在某些地方又非常的陡峭 如果此时你的训练过程类似下图中从下往上的橙色的点,它先经过一块平坦的区域,又由于参数的细微变化跳上了悬崖,这就会导致loss上下抖动得非常剧烈
如果你的运气特别不好,一脚踩在悬崖上,由于之前一直处于平坦区域,gradient很小,你会把参数更新的步长(learning rate)调的比较大,而踩到悬崖上导致gradient突然变得很大,这会导致参数一下子被更新了一个大步伐,导致整个就飞出去了,这就是学习曲线突然跳到无穷大的原因
想要解决这个问题,就要采用Clipping方法,当gradient即将大于某个threshold的时候,就让它停止增长,比如当gradient大于15的时候就直接让它等于15
为什么RNN会有这种奇特的特性? 在w=1周围gradient几乎是突变的,这让我们很难去调整learning rate。 因此我们可以解释,RNN训练困难,是由于它把同样的操作在不断的时间转换中重复使用。
从memory接到neuron输入的参数w,在不同的时间点被反复使用,w的变化有时候可能对RNN的输出没有影响,而一旦产生影响,经过长时间的不断累积,该影响就会被放得无限大,因此RNN经常会遇到这两个问题:
- 梯度消失(gradient vanishing),一直在梯度平缓的地方停滞不前
- 梯度爆炸(gradient explode),梯度的更新步伐迈得太大导致直接飞出有效区间
解决方法
1、LSTM LSTM就是最广泛使用的技巧,它会把error surface上那些比较平坦的地方拿掉,从而解决梯度消失(gradient vanishing)的问题,但它无法处理梯度崎岖的部分,因而也就无法解决梯度爆炸的问题(gradient explode)。但由于做LSTM的时候,大部分地方的梯度变化都很剧烈,因此训练时可以放心地把learning rate设的小一些。 为什么LSTM能够解决梯度消失的问题? RNN和LSTM对memory的处理其实是不一样的:
- 在RNN中,每个新的时间点,memory里的旧值都会被新值所覆盖
- 在LSTM中,每个新的时间点,memory里的值会乘上
f
(
g
f
)
f(g_{f})
f(gf?)与新值相加
对RNN来说,w对memory的影响每次都会被清除,而对LSTM来说,除非forget gate被打开,否则w对memory的影响就不会被清除,而是一直累加保留,因此它不会有梯度消失的问题。
2、GRU (Gated Recurrent Unit) 它的基本精神是旧的不去,新的不来,GRU会把input gate和forget gate连起来,当forget gate把memory里的值清空时,input gate才会打开,再放入新的值
|