Loading

基于Keras的RNN原理及图解

本文翻译自: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)

上式对应的数学方程式:

\[y = \sigma(W_yx+b_y) \]

输入\(x\)和输出\(y\)是一维的,因此权重使得\(W_y∈\R\)\(b_y∈\R\)。 输出层的确是一维的,因为我们在前一个命令行中将unit = 1。 下图可以表示该方程式(对bias项\(b_y\)进行了屏蔽更容易理解):

维度为1的TimeDistributed wrapperTimeDistributed 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))

对应于等式:

\[y_t=\sigma(W_yx_t+b_y) \]

应用于每个\(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 = \begin{bmatrix} 0.76 & 0.68 & 0.66 \\ 0.92 & 0.99 & 0.52 \end{bmatrix}\)\(b_y = \begin{bmatrix} -0.80 \\ -0.79 \\ -0.54 \end{bmatrix}\)\(x_t = \begin{bmatrix} 1 \\ 1 \end{bmatrix}\)。所以\(W_y^\intercal x_t + b_y = \begin{bmatrix} 0.88 \\ 0.88 \\ 0.65 \end{bmatrix}\),通过\(sigmoid\)函数后得到\(y_t = \begin{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\)):

\[\begin{align}h_t =& \sigma(W_x x_t + W_h h_{t-1} + b_h), \\y_t =& \sigma(W_y h_t + b_y).\end{align} \]

和以前一样,训练输入的形状为\((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)\)。这些等式可用下图表示:

Snipaste_2021-04-28_20-12-01

此图显示了网络的一个临时步骤,解释了如何从\(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\)):

\[\begin{align}h_t =& \sigma(W_x x_t + W_h h_{t-1} + b_h), \\h'_t =& \sigma(W'_x h_t + W'_h h'_{t-1} + b'_h), \\y_t =& \sigma(W_y h'_t + b_y).\end{align} \]

并由下图表示(显示了两个时间步骤,以帮助理解所有对象如何相互连接):

Snipaste_2021-04-28_20-44-09

在该示例中,在每个时间\(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\)处隔离的该层表示如下:

Snipaste_2021-04-28_20-51-34

长短期记忆(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')

相应的公式如下:

\[\begin{align}i_t =& \sigma(W_{ix} x_t + W_{ih} h_{t-1} + b_i) \\f_t =& \sigma(W_{fx} x_t + W_{fh} h_{t-1} + b_f) \\\tilde{c}_t =& \tanh(W_{cx} x_t + W_{ch} h_{t-1} + b_c) \\o_t =& \sigma(W_{ox} x_t + W_{oh} h_{t-1} + b_o) \\ \\c_t =& f_t c_{t-1} + i_t \tilde{c}_t \\h_t =& o_t \tanh(c_t)\end{align} \]

(具有用于\(c_{-1}\)\(h_{-1}\)的空向量),并由下图表示:

Snipaste_2021-04-28_21-04-13

在此设置中,\(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')

对应于等式:

\[\begin{align}z_t =& \sigma(W_{zx} x_t + W_{zh} h_{t-1} + b_z) \\r_t =& \sigma(W_{rx} x_t + W_{rh} h_{t-1} + b_r) \\\tilde{h}_t =& \tanh(W_{ox} x_t + W_{oh} r_t h_{t-1} + b_o) \\ \\h_t =& z_t h_{t-1} + (1 - z_t) \tilde{h}_t\end{align} \]

(对于\(h_{-1}\)具有空向量),并由下图表示:

Snipaste_2021-04-28_23-48-40

在这种设置下,\(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

posted @ 2021-04-29 00:04  ZHGQCN  阅读(1052)  评论(0编辑  收藏  举报