参考内容:
0 【案例】利用RNN/LSTM实现对上证指数的预
——————
待补充…
1 简介
Keras RNN API 的设计重点如下:
-
易于使用:您可以使用内置
keras.layers.RNN
、keras.layers.LSTM
和keras.layers.GRU
层快速构建循环模型,而无需进行艰难的配置选择。 -
易于自定义:您还可以通过自定义行为来定义您自己的 RNN 单元层(for 循环的内部),并将其用于通用的
keras.layers.RNN
层(for 循环本身)。这使您能够以最少的代码和灵活的方式快速为不同研究思路设计原型。
2 设置
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
3 内置RNN层:简单示例
Keras 中有三种内置 RNN 层:
keras.layers.SimpleRNN
,一个全连接 RNN,其中前一个时间步骤的输出会被馈送至下一个时间步骤。keras.layers.GRU
keras.layers.LSTM
下面是一个 Sequential 模型的简单示例,该模型可以处理整数序列,将每个整数嵌入 64 维向量中,然后使用 LSTM 层处理向量序列。
model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=100, output_dim=64))
# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))
# Add a Dense layer with 10 units.
model.add(layers.Dense(10))
model.summary()
Model: “sequential”
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, None, 64) 6400
_________________________________________________________________
lstm (LSTM) (None, 128) 98816
_________________________________________________________________
dense (Dense) (None, 10) 1290
=================================================================
Total params: 106,506
Trainable params: 106,506
Non-trainable params: 0
_________________________________________________________________
内置 RNN 支持许多实用功能:
- 通过
dropout
和recurrent_dropout
参数进行循环随机失活 - 能够通过
go_backwards
参数反向处理输入序列 - 通过
unroll
参数进行循环展开(这会大幅提升在 CPU 上处理短序列的速度) - 等等…
4 输出和状态
默认情况下,RNN 层的输出为每个样本包含一个向量。此向量是与最后一个时间步骤相对应的 RNN 单元输出,包含关于整个输入序列的信息。此输出的形状为 (batch_size, units)
,其中 units 对应于传递给层构造函数的 units
参数。
如果设置了 return_sequences=True
,RNN 层还能返回每个样本的整个输出序列(每个样本的每个时间步骤一个向量)。此输出的形状为 (batch_size, timesteps, units)
。
model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))
# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))
model.add(layers.Dense(10))
model.summary()
Model: “sequential”
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, None, 64) 64000
_________________________________________________________________
gru (GRU) (None, None, 256) 247296
_________________________________________________________________
simple_rnn (SimpleRNN) (None, 128) 49280
_________________________________________________________________
dense (Dense) (None, 10) 1290
=================================================================
Total params: 361,866
Trainable params: 361,866
Non-trainable params: 0
_________________________________________________________________
此外,RNN 层还可以返回其最终内部状态。返回的状态可用于稍后恢复 RNN 执行,或初始化另一个 RNN。此设置常用于编码器-解码器序列到序列模型,其中编码器的最终状态被用作解码器的初始状态。
要配置 RNN 层以返回其内部状态,请在创建该层时将 return_state
参数设置为 True
。请注意,LSTM 具有两个状态张量,但 GRU 只有一个。
要配置该层的初始状态,只需额外使用关键字参数 initial_state
调用该层。请注意,状态的形状需要匹配该层的单元大小,如下例所示。
encoder_vocab = 1000
decoder_vocab = 2000
encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
encoder_input
)
# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
encoder_embedded
)
encoder_state = [state_h, state_c]
decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
decoder_input
)
# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)
model = keras.Model([encoder_input, decoder_input], output)
model.summary()
Model: “model”
_______________________________________________________________________________
Layer (type) Output Shape Param # Connected to
============================================================================
input_1 (InputLayer) [(None, None)] 0
_______________________________________________________________________________
input_2 (InputLayer) [(None, None)] 0
_______________________________________________________________________________
embedding (Embedding) (None, None, 64) 64000 input_1[0][0]
_______________________________________________________________________________
embedding_1 (Embedding) (None, None, 64) 128000 input_2[0][0]
_______________________________________________________________________________
encoder (LSTM) [(None, 64), (None, 33024 embedding[0][0]
________________________________________________________________________________
decoder (LSTM) (None, 64) 33024 embedding_1[0][0]
encoder[0][1]
encoder[0][2]
________________________________________________________________________________
dense (Dense) (None, 10) 650 decoder[0][0]
============================================================================
Total params: 258,698
Trainable params: 258,698
Non-trainable params: _______________________________________________________________________________
5 RNN层和RNN单元
除内置 RNN 层外,RNN API 还提供单元级 API。与处理整批输入序列的 RNN 层不同,RNN 单元仅处理单个时间步骤。
单元位于 RNN 层的 for 循环内。将单元封装在 keras.layers.RNN
层内,您会得到一个能够处理序列批次的层,如 RNN(LSTMCell(10))
。
如果使用内置的 GRU
和 LSTM
层,能够使用 CuDNN,并获得更出色的性能。
共有三种内置 RNN 单元,每种单元对应于匹配的 RNN 层。
keras.layers.SimpleRNNCell
对应于SimpleRNN
层。keras.layers.GRUCell
对应于GRU
层。keras.layers.LSTMCell
对应于LSTM
层。
借助单元抽象和通用 keras.layers.RNN
类,可以为研究轻松实现自定义 RNN 架构。
6 跨批次有状态性
在处理非常长的序列(可能无限长)时,您可能需要使用跨批次有状态性模式。
通常情况下,每次看到新批次时,都会重置 RNN 层的内部状态(即,假定该层看到的每个样本都独立于过去)。该层将仅在处理给定样本时保持状态。
但如果序列非常长,一种有效做法是将它们拆分成较短的序列,然后将这些较短序列按顺序馈送给 RNN 层,而无需重置该层的状态。如此一来,该层就可以保留有关整个序列的信息,尽管它一次只能看到一个子序列。可以通过在构造函数中设置 stateful=True
来执行上述操作。
如果您有一个序列 s = [ t 0 , t 1 , . . . t 1546 , t 1547 ] s = [t0, t1, ... t1546, t1547] s=[t0,t1,...t1546,t1547],可以将其拆分成如下式样:
s1 = [t0, t1, … t100]
s2 = [t101, … t201]
…
s16 = [t1501, … t1547]
然后,通过以下方式处理:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
想要清除状态时,您可以使用 layer.reset_states()
完整实例:
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)
# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()
6.1 RNN状态重用
RNN 层的记录状态不包含在 layer.weights()
中。如果您想重用 RNN 层的状态,可以通过 layer.states
找回状态值,并通过 Keras 函数式 API(如 new_layer(inputs, initial_state=layer.states)
)或模型子类化将其用作新层的初始状态。
另请注意,此情况可能不适用于序贯模型,因为它只支持具有单个输入和输出的层,而初始状态具有额外输入,因此无法在此使用。
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
existing_state = lstm_layer.states
new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)
7 双向RNN
Keras 提供了一个简单的 API 来构建此类双向 RNN:keras.layers.Bidirectional
封装容器。
model = keras.Sequential()
model.add(layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10)))
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))
model.summary()
Bidirectional
会在后台复制传入的 RNN 层,并翻转新复制的层的 go_backwards 字段,这样它就能按相反的顺序处理输入了。
8 性能优化和CuDNN内核
内置的 LSTM 和 GRU 层,会在 GPU 可用时默认使用 CuDNN 内核。
由于 CuDNN 内核是基于某些假设构建的,这意味着如果您更改了内置 LSTM 或 GRU 层的默认设置,则该层将无法使用 CuDNN 内核。有关约束的详细列表,请参阅 GRU 和 GRU 层的文档。
8.1 在可用时使用CuDNN内核
batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28
units = 64
output_size = 10 # labels are from 0 to 9
# Build the RNN model
def build_model(allow_cudnn_kernel=True):
# CuDNN is only available at the layer level, and not at the cell level.
# This means `LSTM(units)` will use the CuDNN kernel,
# while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
if allow_cudnn_kernel:
# The LSTM layer with default options uses CuDNN.
lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
else:
# Wrapping a LSTMCell in a RNN layer will not use CuDNN.
lstm_layer = keras.layers.RNN(
keras.layers.LSTMCell(units), input_shape=(None, input_dim)
)
model = keras.models.Sequential(
[
lstm_layer,
keras.layers.BatchNormalization(),
keras.layers.Dense(output_size),
]
)
return model
加载 MNIST 数据集:
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]
选择 sparse_categorical_crossentropy
作为模型的损失函数。模型的输出形状为 [batch_size, 10]
。模型的目标是一个整数向量,每个整数都在 0 到 9 之间。
model = build_model(allow_cudnn_kernel=True)
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 10s 6ms/step - loss: 0.9938 - accuracy: 0.6859 - val_loss: 0.5881 - val_accuracy: 0.8100
与未使用 CuDNN 内核的模型进行对比
noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(noncudnn_model.get_weights())
noncudnn_model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
noncudnn_model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 37s 37ms/step - loss: 0.9431 - accuracy: 0.6969 - val_loss: 0.5370 - val_accuracy: 0.8329
9 支持列表/字典输入或嵌套输入的RNN
实现器可以通过嵌套结构在单个时间步骤内包含更多信息。例如,一个视频帧可以同时包含音频和视频输入。在这种情况下,数据形状可以为:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
在另一个示例中,手写数据可以包括笔的当前位置的 x 和 y 坐标,以及压力信息。因此,数据表示可以为:
[batch, timestep, {"location": [x, y], "pressure": [force]}]