本文翻译自:RNN with Keras: Understanding computations
经过查找,觉得这篇博客将得比较清楚,所以进行了搬运。
Author:
Gqq
本教程通过跟踪和理解每个模型执行的计算,重点介绍了常见RNN算法的结构。 它适用于任何已经了解一般深度学习工作流程,但又无需事先了解RNN的人。 如果您真的从未听说过RNN,可以先阅读Christopher Olah
的这篇文章。
本篇文章着重于逐步理解每个模型中的计算,而无需注意训练有用的东西。 它用Keras
代码说明,分为五个部分:
TimeDistributed component
Simple RNN
Simple RNN with two hidden layers
LSTM
GRU
这篇文章的配套源代码在这里。
前馈神经网络上RNN的核心思想是按顺序读取输入。在RNN中,输入x用t索引并顺序处理。索引t可以代表时间序列的时间,也可以代表NLP任务的句子位置。使用隐藏变量可以随时间存储,更新和传输信息。
简单的RNN是一种随时间保持和更新信息的简单方法。在A,B和C部分中对此进行了逐步描述。这种模型很有效,但对于长期依赖的序列很难训练。主要问题是由梯度消失问题引起的。深度学习书的10.7节中详细介绍了此问题。
引入了门控RNN来规避梯度消失问题。在D部分(LSTM)和E部分(GRU)中描述了两种流行的门控RNN。主要思想是通过引入门来控制信息流。您可以在这里找到其运行原因的见解(如果您有更好的参考,请分享)。
请注意,即使是门控RNN,也具有计算和可解释性问题,并且不是显式的内存。解决这些问题的一种有前途的方法是注意力机制。此处提供概述(另请参阅本篇和本篇短篇文章)。
Part A:TimeDistributed component
解释
一个非常简单的网络。让我们从一维输入和输出开始。在Keras
中,命令行:
Dense(activation='sigmoid', units=1)
上式对应的数学方程式:
输入(x)和输出(y)是一维的,因此权重使得(W_y∈R)和(b_y∈R)。 输出层的确是一维的,因为我们在前一个命令行中将unit = 1。 下图可以表示该方程式(对bias项(b_y)进行了屏蔽更容易理解):
维度为1的TimeDistributed wrapper
。TimeDistributed wrapper
在每个时间步均应用相同的层。 例如,对于T = 6
的一维输入和输出,输入用((x_0,...,x_5)∈R^6)表示,输出用((y_0,...,y_5)∈R^6)表示。 然后,该模型为:
TimeDistributed(Dense(activation='sigmoid', units=1), input_shape=(None, 1))
对应于等式:
应用于每个(t, t∈{0,…5})。注意,每个(t)使用的(W_y∈R)和(b_y∈R)都相同(同一组参数)。在上一个命令行中,input_shape =(None,1)
表示输入层是形状为T×1
的数组,而unit = 1
表示每个t时刻输出层为1个单元。 该模型可以用下图表示:
在实践中的输入和输出形状。 输入通常具有((N,T,m))的形状,其中(N)是样本大小,(T)是时间大小,(m)是每个输入向量的维数。 输出的形状为((N,T,m')),其中(m')是每个输出向量的维数。 在前面的示例中,我们选择了(T = 6),(m = 1)和(m'= 1)。
新输入的预测。 给定一个接受形状((N,T,m))输入训练的模型,我们可以为模型输入形状((k,l,m))的新输入。
在前面的示例中,我们可以选择例如:
new_input = np.array([[[1],[0.8],[0.6],[0.2],[1],[0],[1],[1]]])
new_input.shape # (1, 8, 1)
print(model.predict(new_input))
具有更高尺寸的TimeDistributed
的完整示例。 令(N=256,T=6,m=2,m′=3)。 训练输入的形状为((256,6,2)),训练输出的形状为((256,6,3))。
该模型的构建和训练如下:
dim_in = 2
dim_out = 3
model=Sequential()
model.add(TimeDistributed(Dense(activation='sigmoid', units=dim_out), # target is dim_out-dimensional
input_shape=(None, dim_in))) # input is dim_in-dimensional
model.compile(loss = 'mse', optimizer = 'rmsprop')
model.fit(x_train, y_train, epochs = 100, batch_size = 32)
形状((k,l,m))的新输入的输出可以预测如下:
new_input = model.predict(np.array([[[1,1]]]))
new_input.shape # (1, 1, 2), which is a valid shape for this model
print(model.predict(new_input))
# [[[ 0.70669621 0.70633912 0.65635538]]]
# output is (1, 1, 3) as expected.
# Note that each column has been trained differently
通过获取(x_t)二维矢量,计算(W_yx_t + b_y),然后将(Sigmoid)应用于每个分量,可以详细了解此计算。
W_y = model.get_weights()[0] # this is a (2,3) matrix
b_y = model.get_weights()[1] # this is a (3,1) vector
# At each time, we have a dense neural network
# (without hidden layer) from 2+1 inputs to 3 outputs.
# On the whole, there are 9 parameters
# (the same parameters are used at each time).
[[sigmoid(y)
for y in np.dot(x,W_y) + b_y] # like doing X * beta
for x in [[1,1]]]
# We obtain the same results as with 'model.predict'
利用(W_y = egin{bmatrix} 0.76 & 0.68 & 0.66 \ 0.92 & 0.99 & 0.52 end{bmatrix}),(b_y = egin{bmatrix} -0.80 \ -0.79 \ -0.54 end{bmatrix}),(x_t = egin{bmatrix} 1 \ 1 end{bmatrix})。所以(W_y^intercal x_t + b_y = egin{bmatrix} 0.88 \ 0.88 \ 0.65 end{bmatrix}),通过(sigmoid)函数后得到(y_t = egin{bmatrix} 0.71 \ 0.71 \ 0.66 end{bmatrix})。
Part B:simple RNN 解释
简单RNN是神经网络保持信息随时间变化的最简单方法。 信息存储在隐藏变量(h)中,并根据新的输入每次更新。 可以将简单RNN连接到时间分布组件(time distributed component ),以形成1990年推出的Elman网络。时间分布组件允许计算隐藏变量的输出。 我们将在这一部分中描述这个完整的网络。
网络说明。在Keras
中,命令行:
dim_in=3; dim_out=2; nb_units=5;
model=Sequential()
model.add(SimpleRNN(input_shape=(None, dim_in),
return_sequences=True,
units=nb_units))
model.add(TimeDistributed(Dense(activation='sigmoid',
units=dim_out)))
对应于数学方程式(对于所有时间(t)):
和以前一样,训练输入的形状为((N,T,m)),训练输出的形状为((N,T,m'))。 在该示例中,我们取(m = 3)和(m'= 2),则(x_t)是二维矢量,而(y_t)是三维矢量。 我们选择的(units= 5),所以(h_t)是一个五维向量。 详细来说,SimpleRNN
代码行从((x_0,...,x_T))(和初始(h_{-1}))计算完整序列((h_0,...,h_T)), TimeDistributed
代码行从((h_0,...,h_T))计算序列((y_0,...,y_T))。这些等式可用下图表示:
此图显示了网络的一个临时步骤,解释了如何从(x_t)和(h_{t-1})计算(h_t)和(y_t)。
仍然需要选择隐藏变量的初始值(h_{-1}),我们采用空向量:(h_{-1} =(0,0,0,0,0)^⊺)。
简单RNN的完整示例。 令(N=256,T=6,m=2,m′=3)。 训练输入的形状为((256,6,2)),训练输出的形状为((256,6,3))。
该模型的构建和训练如下:
dim_in = 2; dim_out = 3; nb_units = 5
model=Sequential()
model.add(SimpleRNN(input_shape=(None, dim_in),
return_sequences=True,
units=nb_units))
model.add(TimeDistributed(Dense(activation='sigmoid', units=dim_out)))
model.compile(loss = 'mse', optimizer = 'rmsprop')
model.fit(x_train, y_train, epochs = 100, batch_size = 32)
训练有素的网络的权重为:
W_x = model.get_weights()[0] # W_x a (3,5) matrix
W_h = model.get_weights()[1] # W_h a (5,5) matrix
b_h = model.get_weights()[2] # b_h a (5,1) vector
W_y = model.get_weights()[3] # W_y a (5,2) matrix
b_y = model.get_weights()[4] # b_y a (2,1) vector
要预测形状为((k, l, m))的新输入的输出。我们采取形状((1, 3, 3))并让:(x0 =(4,2,1)^T),(x1 =(1,1,1 )^T),(x2 =(1,1,1)^T)。该模型预测此系列的输出:
new_input = [[4,2,1], [1,1,1], [1,1,1]]
print(model.predict(np.array([new_input])))
# [[[ 0.79032147 0.42571515]
# [ 0.59781438 0.55316663]
# [ 0.87601596 0.86248338]]]
# (1, 3, 2)
通过从(x_0)和(h_{-1})计算(h_0)可以手动检索此结果。 然后从(h_0)到(y_0); 然后从(x_1)和(h_0)得到(h_1); 然后从(h_1)得到(y_1); 然后从(x_2)和(h_1)中得到(h_2); 然后从(h_2)得出(y_2)。 随附代码的B部分对此进行了详细说明。
Part C:两层隐藏层Simple RNN解释
在Part B中,我们连接了SimpleRNN
层和TimeDistributed
层,以形成一个具有一个隐藏层的Elman网络。堆叠另一个SimpleRNN
层很容易。 此Keras
代码:
dim_in = 3; dim_out = 2
model=Sequential()
model.add(SimpleRNN(input_shape=(None, dim_in),
return_sequences=True,
units=5))
model.add(SimpleRNN(input_shape=(None,4),
return_sequences=True,
units=7))
model.add(TimeDistributed(Dense(activation='sigmoid',
units=dim_out)))
对应于数学方程式(对于所有时间(t)):
并由下图表示(显示了两个时间步骤,以帮助理解所有对象如何相互连接):
在该示例中,在每个时间(t,x_t,h_t,h'_t,y_t)分别是大小为2、5、7、3的向量。
权重矩阵的形状和手动计算在伴随代码的C部分中进行了详细说明。
Part D:LSTM 解释
从SimpleRNN
层到LSTM
层。在Part B中,我们使用SimpleRNN
层更新了隐藏变量(h),即根据((x_t,h_{t-1}))计算(h_t)。在时间(t)处隔离的该层表示如下:
长短期记忆(LSTM
)网络用LSTM层代替了SimpleRNN
层。 LSTM
层接受3个输入((x_t, h_{t-1}, c_{t-1})),并在每个步骤 (t) 输出一对((h_t,c_t))。 (h)是隐藏变量,(c)称为细胞变量。 这种网络已于1997年引入。在Keras
中,命令行:
LSTM(input_shape=(None, dim_in),
return_sequences=True,
units=nb_units,
recurrent_activation='sigmoid',
activation='tanh')
相应的公式如下:
(具有用于(c_{-1})和(h_{-1})的空向量),并由下图表示:
在此设置中,(f_t)代表遗忘变量(控制(c_{t-1})保留多少信息),(widetilde{c}_t)代表要保存的新信息,并由输入变量(i_t)加权(控制(widetilde{c}_t)保留多少信息)。 这些变量的组合形成细胞单元变量(c_t)。最后,(o_t)表示输出变量(控制(tanh c_t)信息保留多少到(h_t)中)。
矩阵的解释。了解所有矩阵的组织方式可能会造成混淆。让我们假设(dim_{in} = 7)且(nb_{units }=13)。输入向量(x_t)的长度为7,隐藏向量(h_t)和细胞向量(c_t)的长度均为13。
- 矩阵(W_{ix},W_{fx},W_{cx},W_{ox})都为(7×13)的形状,因为它们都将乘以(x_t)
- 矩阵(W_{ih},W_{fh},W_{cx},W_{oh})都为(13×13)的形状,因为它们都乘以(h_t)
- 偏差(b_i,b_j,b_c,b_o)具有13的长度
因此,向量(i_t,f_t,widetilde{c}_t,o_t)都具有13的长度
在LSTM的Keras
实现中,(W_x)和(W_h)定义如下:
- (W_x)是(W_{ix}),(W_{fx}),(W_{cx}),(W_{ox})的串联,形成(7×52)矩阵
- (W_h)是(W_{ih}),(W_{fh}),(W_{ch}),(W_{oh})的串联,得出(13×52)的矩阵
- (b_h)是(b_i),(b_f),(b_c),(b_o)的连接,得出长度为(52)的向量
利用这些符号,我们可以首先计算原始向量(W_xx_t+W_hh_{t-1}+b_h),长度为(52),然后对其进行切割并应用激活函数来获取(i_t,f_t,widetilde{c}_t,o_t)。
请注意,在Christopher Olah
的报告中,(W_i),(W_f),(W_c)和(W_o)的定义如下:
- (W_i)是(W_{ih})和(W_{ix})的拼接
- (W_f)是(W_{fh})和(W_{f x})的拼接
- (W_c)是(W_{ch})和(W_{c x})的拼接
- (W_o)是(W_{oh})和(W_{o x})的拼接
将LSTM层与后续层连接。网络的其余部分都像以前一样工作。在Keras
,我们让:
model=Sequential()
model.add(LSTM(input_shape=(None, dim_in),
return_sequences=True,
units=nb_units,
recurrent_activation='sigmoid',
activation='tanh'))
model.add(TimeDistributed(Dense(activation='sigmoid',
units=dim_out)))
详细而言,LSTM代码行从((x_0,...,x_T)(初始化h_{-1}和c_{-1}))计算完整序列((h_0,...,h_T))和((c_0,...,c_T)); TimeDistributed
代码行从((h_0,...,h_T))计算序列((y_0,...,y_T))和(请注意,细胞变量(c)在LSTM内部使用,但在随后的层中不使用,与隐藏变量(h)相反)。
权重矩阵的形状和手动计算在随附代码的Part D中进行了详细说明。
Part E:GRU 解释
门控循环单元(GRU)是2014年推出的LSTM的一种流行替代方法。显然,它们具有与LSTM类似的结果,但需要训练的参数较少(GRU的权重为3组,而LSTM的权重为4组)
GRU层在每个步骤(t)取输入((x_t,h_{t-1}))并输出(h_t)。在Keras
中,命令行:
GRU(input_shape=(None, dim_in),
return_sequences=True,
units=nb_units,
recurrent_activation='sigmoid',
activation='tanh')
对应于等式:
(对于(h_{-1})具有空向量),并由下图表示:
在这种设置下,(z_t)的作用类似于遗忘变量(控制保留(h_{t-1})和(widetilde{h}_t)的信息量),(r_t)是递归变量(控制(h_{t- 1})的加权方式),(widetilde{h}_t)表示要保存的新信息(随后由$z_ t $加权)。
矩阵的解释。和以前一样,我们假设(dim_{in} = 7)且(nb_{units }= 13),所以(x_t)的长度为7,(h_t)的长度为13。
- (W_{zx},W_{rx},W_{ox})形状为(7×13),它们都与(x_t)相乘
- (W_{zh},W_{rh},W_{oh})形状为(13×13),它们都与(h_t)相乘
- 偏差(b_z,b_r,b_o)都有相同的长度为13
因此,向量(z_t,r_t,widetilde{h}_t)长度都为13
在LSTM的Keras
实现中,(W_x)和(W_h)定义如下:
- (Wx)是(W_{zx},W_{rx},W_{ox})的联接,形成(7×39)的矩阵
- (W_h)是(W_{zh},W_{rh},W_{oh})的联接,形成(13×39)的矩阵
- (b_h)是(b_z,b_r,b_o)的联接,计算得到长度为39的向量
用户手册对Part E进行了详细介绍,与LSTM相比更不直接。
References
- Companion code for this post
- Understanding LSTM Networks by
Christopher Olah
- Keras documentation for TimeDistributed
- Keras documentation for RNN
- Wikipedia page on RNN describing the Elman networks
- Thanks to J. Leon for this Tikz figure, on which I made figures (full sources are here)