在本教程中,讲解 Keras 深度学习库中 LSTM 层的返回序列和返回状态的区别和结果。
完成本教程后,您将了解:
- 该返回序列返回每个输入时间步的隐藏状态输出。
- 该返回状态返回最后一个输入时间步的隐藏状态输出和单元状态。
- 可以同时使用返回序列和返回状态。
本教程分为 4 个部分:
- 长短期记忆
- 返回序列
- 返回状态
- 返回状态和序列
长短期记忆
长短期记忆(LSTM)是一个由内门组成的循环神经网络。与其他循环神经网络不同,该网络的内部门允许模型通过时间反向传播或 BPTT 成功训练,并避免梯度消失问题。
在 Keras 深度学习库中,可以使用LSTM() 类创建 LSTM 层。创建 LSTM 内存单元层允许指定层内的内存单元数。
层内的每个单元或单元都有一个内部单元状态,通常缩写为“ c ”,并输出一个隐藏状态,通常缩写为“ h ”。
Keras API 允许您访问这些数据,这在开发复杂的循环神经网络架构(例如编码器-解码器模型)时可能很有用甚至是必需的。
return_sequences
每个 LSTM 单元将为每个输入输出一个隐藏状态h。
h = LSTM(X)
我们可以在 Keras 中使用一个非常小的模型来证明这一点,该模型具有一个 LSTM 层,该层本身包含一个 LSTM 单元。
在此示例中,我们将有一个具有 3 个时间步长的输入样本,并在每个时间步长观察到一个特征:
t1 = 0.1
t2 = 0.2
t3 = 0.3
下面列出了完整的示例。
from keras.models import Model
from keras.layers import Input
from keras.layers import LSTM
from numpy import array
# define model
inputs1 = Input(shape=(3, 1))
lstm1 = LSTM(1)(inputs1)
model = Model(inputs=inputs1, outputs=lstm1)
# define input data
data = array([0.1, 0.2, 0.3]).reshape((1,3,1))
# make and show prediction
print(model.predict(data))
结果如下:
[[-0.0953151]]
。鉴于 LSTM 权重和单元状态的随机初始化,特定输出值会有所不同。
对比实验
如果想要访问每个输入时间步的隐藏状态输出,在定义 LSTM 层时将return_sequences属性设置为True来完成,如下所示:
LSTM(1, return_sequences=True)
我们可以更新前面的例子如下完整的代码。
from keras.models import Model
from keras.layers import Input
from keras.layers import LSTM
from numpy import array
# define model
inputs1 = Input(shape=(3, 1))
lstm1 = LSTM(1, return_sequences=True)(inputs1)
model = Model(inputs=inputs1, outputs=lstm1)
# define input data
data = array([0.1, 0.2, 0.3]).reshape((1,3,1))
# make and show prediction
print(model.predict(data))
运行该示例会返回一个包含 3 个值的序列,该层中单个 LSTM 单元的每个输入时间步都有一个隐藏状态输出。结果如下
[[[-0.02243521]
[-0.06210149]
[-0.11457888]]]
我们看到,输出了一个array,长度等于timestep,表示网络输出了每个timestep的h(t)。
总结,return_sequences
即表示,LSTM的输出h(t),是输出最后一个timestep的h(t),还是把所有timestep的h(t)都输出出来。在实际应用中,关系到网络的应用场景是many-to-one还是many-to-many,非常重要。
return_states
LSTM 单元或单元层的输出称为隐藏状态。
这很令人困惑,因为每个 LSTM 单元都保留了一个不输出的内部状态,称为单元状态或c。
通常,我们不需要访问单元状态,除非我们正在开发复杂的模型,其中后续层可能需要使用另一层的最终单元状态初始化其单元状态,例如在编码器-解码器模型中。
Keras 向 LSTM 层提供 return_state 参数,该层将提供对隐藏状态输出 ( state_h ) 和单元状态 ( state_c ) 的访问。例如:
lstm1, state_h, state_c = LSTM(1, return_state=True)
这可能看起来令人困惑,因为 lstm1 和state_h都引用相同的隐藏状态输出。我们可以通过下面列出的示例演示对 LSTM 层中单元格的隐藏状态和单元格状态的访问。
from keras.models import Model
from keras.layers import Input
from keras.layers import LSTM
from numpy import array
# define model
inputs1 = Input(shape=(3, 1))
lstm1, state_h, state_c = LSTM(1, return_state=True)(inputs1)
model = Model(inputs=inputs1, outputs=[lstm1, state_h, state_c])
# define input data
data = array([0.1, 0.2, 0.3]).reshape((1,3,1))
# make and show prediction
print(model.predict(data))
运行示例返回 3 个数组,结果如下:
- 最后一个时间步的 LSTM 隐藏状态输出。
- 最后一个时间步的 LSTM 隐藏状态输出(再次)。
- 最后一个时间步的 LSTM 单元状态。
[array([[ 0.10951342]], dtype=float32),
array([[ 0.10951342]], dtype=float32),
array([[ 0.24143776]], dtype=float32)]
注意,输出是一个列表list,分别表示 - 最后一个time step的hidden state - 最后一个time step的hidden state(跟上面一样) - 最后一个time step的cell state(注意就是上文中的c(t))
总结,return_state
就是控制LSTM中的c(t)输出与否。此时隐藏状态和单元状态又可以用于初始化具有相同单元数的另一个 LSTM 层的状态。
同时使用return_sequences和return_states
我们可以同时访问隐藏状态序列和单元状态。这可以通过将 LSTM 层配置为return_sequences和return_state
来完成。
lstm1, state_h, state_c = LSTM(1, return_sequences=True, return_state=True)
下面列出完整的示例。
from keras.models import Model
from keras.layers import Input
from keras.layers import LSTM
from numpy import array
# define model
inputs1 = Input(shape=(3, 1))
lstm1, state_h, state_c = LSTM(1, return_sequences=True, return_state=True)(inputs1)
model = Model(inputs=inputs1, outputs=[lstm1, state_h, state_c])
# define input data
data = array([0.1, 0.2, 0.3]).reshape((1,3,1))
# make and show prediction
print(model.predict(data))
运行这个例子,我们可以看到为什么 LSTM 输出张量和隐藏状态输出张量是分开声明的。
该层返回每个输入时间步的隐藏状态,然后分别返回最后一个时间步的隐藏状态输出和最后一个输入时间步的单元状态。这可以通过查看返回序列(第一个数组)中的最后一个值与隐藏状态(第二个数组)中的值匹配来确认。
[array([[[-0.02145359],
[-0.0540871 ],
[-0.09228823]]], dtype=float32),
array([[-0.09228823]], dtype=float32),
array([[-0.19803026]], dtype=float32)]
输出列表的意义其实跟上面实验三一致,只是第一个hidden state h(t)变成了所有timestep的,因此也是长度等于timestep的array。
概括
在本教程中具体来说,了解到:
- 该返回序列返回每个输入时间步的隐藏状态输出。
- 该返回状态返回最后一个输入时间步的隐藏状态输出和单元状态。
- 可以同时使用返回序列和返回状态。
Time Distributed
最后再讲一讲Keras
中的TimeDistributed
。这个也是在RNN中非常常用但比较难理解的概念,原作者解释说
TimeDistributedDense applies a same Dense (fully-connected) operation to every timestep of a 3D tensor.
其实它的主要用途在于Many-to-Many: 比如输入shape为(1, 5, 1),输出shape为(1, 5, 1)
model = Sequential()
model.add(LSTM(3, input_shape=(length, 1), return_sequences=True))
model.add(TimeDistributed(Dense(1)))
根据上面解读,return_sequences=True
,使得LSTM的输出为每个timestep的hidden state,shape为(1, 5, 3)
现在需要将这个(1 ,5, 3)的3D tensor变换为(1, 5, 1)的结果,需要3个Dense layer,分别作用于每个time step的输出。而使用了TimeDistributed
后,则把一个相同的Dense layer去分别作用,可以使得网络更为紧凑,参数更少的作用。
如果是在many-to-one的情况,return_sequence=False
,则LSTM的输出为最后一个time step的hidden state,shape为(1, 3)。此时加上一个Dense layer, 不用使用TimeDistributed
,就可以将(1, 3)变换为(1, 1)。