Dropout可以理解为bagging。
在RNN中,有两种形式:
RNN中的梯度消失与前馈网络梯度消失类似,但有所区别。
RNN中的梯度消失:并不是说整体的梯度小时了,而是对于与t时刻间隔比较远的位置的梯度消失了。也就是说,参数的更新主要依赖于与当前时刻相邻的状态,长距离的状态对参数没啥影响。
搞清楚反向传播中那部分导致了。
= _−1 + +
可见,一般选用的激活函数是sigmoid,所以里面导数的范围是0-0.25。因此,取决于 U T U^T UT的里面元素的大小。只要距离变长,就造成系统的不稳定。这就是长时间间隔的依赖关系难以建模的原因。
所以,真正的问题是出在
其实说白了就是 h k h_k hk与 h k − 1 h_{k-1} hk−1的导数需要一直算下去。比如你计算 δ t , 1 \delta_{t,1} δt,1,理解为t时刻对于1时刻的误差项,那就要从 h 1 , h 2 , . . . . h t h_1,h_2,....h_t h1,h2,....ht算下来,所以连乘的代价就是消失或者爆炸。
解决办法:LSTM、GRU。但注意是缓解,并不是真正解决。LSTM中的缓解主要体现在cell单元的更新上。
c t c_t ct对于 c t − 1 c_{t-1} ct−1的导数写起来很复杂,但第一部分就是遗忘门 f t f_t ft。其可以决定有多少信息要被遗忘(有多少信息保留)。这个能力是网络自己学习的。所以最终连乘的这部分梯度,在LSTM中不会始终大于1或者小于1,一定程度缓解。但也要注意, f t f_t ft的值也和参数初始化有关,一般而言,会把偏置项设为1、2这样。
一般在深度网络参数学习时,参数初始化的值一般都比较小.但是在训练LSTM 网络时,过小的值会使得遗忘门的值比较小.这意味着前一时刻的信息大部分都丢失了,这样网络很难捕捉到长距离的依赖信息.并且相邻时间间隔的梯度会非常小,这会导致梯度弥散问题.因此遗忘的参数初始值一般都设得比较大,其偏置向量 设为1 或2.
理解梯度消失,就很容易记住LSTM的结构了。
用top-down的写法。
隐藏单元: h t = o t t a n h ( c t ) h_t = o_ttanh(c_t) ht=ottanh(ct)
记忆单元 c t = f t c t − 1 + i t c ~ t c_t = f_tc_{t-1}+i_t\tilde c_t ct=ftct−1+itc~t
候选状态: c ~ t = t a n h ( W c x t + U c h t − 1 + b c ) \tilde c_t = tanh(W_cx_t+U_ch_{t-1}+b_c) c~t=tanh(Wcxt+Ucht−1+bc)
遗忘门: f t = σ ( W f x t + U f h t − 1 + b f ) f_t = \sigma(W_fx_t+U_fh_{t-1}+b_f) ft=σ(Wfxt+Ufht−1+bf)决定上一时刻记忆单元要遗忘多少
输入门: i t = σ ( W i x t + U i h t − 1 + b i ) i_t = \sigma(W_ix_t+U_ih_{t-1}+b_i) it=σ(Wixt+Uiht−1+bi)决定候选状态要保留多少
输出门: o t = σ ( W o x t + U o h t − 1 + b o ) o_t = \sigma(W_ox_t+U_oh_{t-1}+b_o) ot=σ(Woxt+Uoht−1+bo)决定当前记忆单元要输出多少
说明
另一个细节,两类激活函数:
LSTM中遗忘门和输出门的激活函数比较重要。删除任何一个激活函数都会对性能有较大的影响。说明这两个激活函数对于信息的取舍作用很重要。
学会区分 RNN 的 output 和 state
来看看tensorflow中的LSTM【当然我用的是keras里面的】
tf.keras.layers.LSTM(units,return_sequences=True,return_state=True)
参数有很多,不一一列举,看文档就很清晰,最关键是把输入、输出、维度搞清楚。
units:Positive integer, dimensionality of the output space.
【其实就是隐藏层呀,输出的维度】比如embeeding的维度是128,units是64,最后就是64。
return_sequences:返回的是h1,…ht还是最后一个。
看下源代码:
if self.return_sequences:
output = outputs
else:
output = last_output
return_state:Whether to return the last state in addition to the output.
这里的state包含了h与c。
看下源代码:
states = [new_h, new_c]
if self.return_state:
return [output] + list(states)
elif self.return_runtime:
return output, runtime
else:
return output
看个例子:
注意下,如果return_sequences=False,output与hidden_state还是不一样的。
注意,前向后向的输入都是Input Layer,然后进行拼接。
双向的输出应该是[forward,backward],所以如果输出维度每个是128,最后应该是256。
看下如何实现:
bi_output = Bidirectional(LSTM(128,return_sequences=True),backward_layer = LSTM(128,return_sequences=True,go_backwards= True) )(x)
注意,backward_layer的那一层要有参数go_backwards 。就是方向一定要相反,不然会报错。【我试了下不同层,比如LSTM GRU】好像不太行,不过可以手工拼接(如果想尝试的话)。
其实backward_layer可以忽略,这个层是会默认算的。
bi_output = Bidirectional(LSTM(128,return_sequences=True))(x)
这样即可。
最后输出shape=(None, 64, 256)。
重置门: r t r_t rt = σ ( W r x t + U r h t − 1 ) \sigma(W_rx_t + U_rh_{t-1}) σ(Wrxt+Urht−1)
更新门: z t z_t zt = σ ( W z x t + U z h t − 1 ) \sigma(W_zx_t + U_zh_{t-1}) σ(Wzxt+Uzht−1)
状态的更新公式:
h ^ t = t a n h ( W h x t + U h ( r t ⋅ h t − 1 ) ) \hat h_t=tanh(W_hx_t+U_h(r_t\cdot h_{t-1})) h^t=tanh(Whxt+Uh(rt⋅ht−1))
h t = ( 1 − z t ) h t − 1 + z t h ^ t h_t = (1-z_t)h_{t-1}+z_t\hat h_t ht=(1−zt)ht−1+zth^t
因此用一个更新门 z t z_t zt实现了遗忘和更新的功能。