TensorFlow2.0 Guide官方教程 学习笔记6- Recurrent Neural Networks (RNN) with Keras

本笔记参照TensorFlow官方教程,主要是对‘Recurrent Neural Networks (RNN) with Keras’教程内容翻译和内容结构编排,原文链接:Recurrent Neural Networks (RNN) with Keras



递归神经网络(RNN)是一类对时间序列或自然语言等序列数据进行建模的神经网络。从理论上讲,RNN层使用for循环遍历序列的时间步长,同时维护一个内部状态,该状态对迄今为止看到的时间步长信息进行编码。
Keras RNN API的设计重点是:
(1)使用简单:内置层(tf.keras.layers.RNN,tf.keras.layers.LSTM,tf.keras.layers.GRU)可以让你快速地创建一个递归模型,而不用去做太多负责的配置选择。
(2)易于定制:我们也可以定制自己的RNN单元层(for循环内部一部分),并且与泛型一起使用。tf.keras.layers.RNN层(循环本身)能个让你快速地用最小的代码将研究观点的原型做出来。


创建环境(Setup)

from __future__ import absolute_import, division, print_function, unicode_literals

import collections
import matplotlib.pyplot as plt
import numpy as np

import tensorflow as tf

from tensorflow.keras import layers

一、创建一个简单的模型

在Keras里有三个内置的RNN层:
(1)tf.keras.layers.SimpleRNN,一个完全连接的RNN,它将前一个时间步的输出反馈给下一个时间步。
(2)tf.keras.layers.GRU,首次提出了在学习短语表示使用RNN编码器-解码器的统计机器翻译。
(3)tf.keras.layers.LSTM,最早是在长短时记忆中提出的。
2015年初,Keras首次实现了LSTM和GRU的可重用开源Python实现。 下面是一个简单的序列模型示例,它处理整数序列,将每个整数嵌入到一个64维向量中,然后使用LSTM层处理向量序列。

model = tf.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=1000, output_dim=64))

# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))

# Add a Dense layer with 10 units and softmax activation.
model.add(layers.Dense(10, activation='softmax'))

model.summary()

二、输出和状态

默认情况下,RNN层的输出每个样本包含一个向量。这个向量是与最后一个时间步对应的RNN单元输出,包含关于整个输入序列的信息。这个输出的形状是(batch_size, units),其中的units对应于传递给层构造函数的units参数。如果将return_sequences=True,则RNN层还可以返回每个样本的整个输出序列(每个时间步一个向量)。这个输出的形状是(batch_size、timesteps、units)。

model = tf.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, activation='softmax'))

model.summary() 

此外,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, activation='softmax')(decoder_output)

model = tf.keras.Model([encoder_input, decoder_input], output)
model.summary()

三、RNN层和RNN单元(cells)

除了内置的RNN层之外,RNN API还提供单元级API。与处理整批输入序列的RNN层不同,RNN单元只处理单个时间步长。

单元格位于RNN层的for循环的内部。将细胞包裹在tf.keras.layers中。RNN层提供了一个能够处理成批序列的层,例如RNN(LSTMCell(10))。

在数学上,RNN(LSTMCell(10))产生与LSTM(10)相同的结果。事实上,这一层在TF v1.x中的实现只是创建了相应的RNN单元并将其包装在RNN层中。

然而,使用内置的GRU和LSTM层可以使用CuDNN,这样我们可能会看到更好的性能。

Keras里有三中内置的RNN单元,分别对应三种RNN层。
(1)tf.keras.layers.SimpleRNNCell与SimpleRNN层相对应
(2)tf.keras.layers.GRUCell与GRU层相对应
(3)tf.keras.layers.LSTMCell与LSTM层相对应
抽象单元,以及通用的tf.keras.layers.RNN类,使它非常容易实现自定义的RNN架构为我们的研究使用。

四、Cross-batch状态性(Cross-batch statefulness)

当处理非常长的序列(有可能无限长)时,你可能需要使用‘Cross-batch statefulness’模式。
通常,RNN层的内部状态会在每次看到一个新的批处理时重置(也就是说,该层看到的每个样本都被认为是独立于过去的)。该层将只在处理给定样本时维护一个状态。
如果有很长的序列,把它们分成更短的序列是很有用的,然后把这些更短的序列按顺序输入一个RNN层,而不重置层的状态。这样,层可以保留关于整个序列的信息,即使它一次只看到一个子序列。
我们可以通过在构造函数里设置stateful=True。如果你有一个序列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()。

注意:在此设置中,假定给定批中的sample i是前一批中的sample i的延续。这意味着所有批次应该包含相同数量的样品(批次大小)。例如,如果一个批处理包含[sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100],那么下一个批处理应该包含[sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200]。

下面是完整的例子:

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()

五、双向RNN

对于时间序列以外的序列(例如文本),通常情况下,如果RNN模型不仅从头到尾处理序列,而且向后处理序列,那么它的性能会更好。例如,要预测一个句子中的下一个单词,通常需要了解这个单词的上下文,而不仅仅是它前面的单词。
Keras提供一个简单的API来创建双向RNNs:
tf.keras.layers.Bidirectional

model = tf.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, activation='softmax'))

model.summary()

在底层,‘双向’是值复制传入的RNN层,并翻转新复制层的go_backwards字段,以便以相反的顺序处理输入。
默认情况下,双向RNN的输出是前向层输出+后向层输出的和。如果我们需要不同的合并行为,例如:连接,可以在双向包装器构造函数中更改merge_mode参数。

六、TensorFlow2.0中的性能优化和CuDNN内核

在TensorFlow2.0里,内置的LSTM和GRU层已经更新,在GPU可用时,默认情况下利用CuDNN内核。有了这个改变,之前的keras.layers.CuDNNLSTM/CuDNNGRU 层已经被弃用,我们可以创建自己的模型,不用担心需要运行它的硬件。
由于CuDNN内核是基于一定的假设构建的,这意味着如果你改变了内置的LSTM或GRU层的默认值,该层将不能使用CuDNN内核。例如:
(1)将‘activation’函数‘tanh’改成其它
(2)将‘recurrent_activation’函数从‘sigmoid’改成其它
(3)使用‘recurrent_dropout’>0
(4)将unroll设置为True,这将强制LSTM/GRU分解内部tf.while_loop进入一个展开的for循环
(5)将use_bias设置为False
(6)使用掩码,当输入数据不是严格正确填补(如果面具对应严格正确填补数据,CuDNN仍然可以使用。这是最常见的情况)。

6.1 使用CuDNN内核

让我们创建一个简单的LSTM模型来演示性能区别。我们将使用MNIST数字的行序列作为输入序列(将每一行像素作为一个时间步长),并预测数字的标签。

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 = tf.keras.layers.LSTM(units, input_shape=(None, input_dim))
  else:
    # Wrapping a LSTMCell in a RNN layer will not use CuDNN.
    lstm_layer = tf.keras.layers.RNN(
        tf.keras.layers.LSTMCell(units),
        input_shape=(None, input_dim))
  model = tf.keras.models.Sequential([
      lstm_layer,
      tf.keras.layers.BatchNormalization(),
      tf.keras.layers.Dense(output_size, activation='softmax')]
  )
  return model

6.2加载Load MNIST数据集

mnist = tf.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]

6.3创建一个模型实例并且编译它

我们选择用‘sparse_categorical_crossentropy’作为模型的代价函数。模型的输出形状同[batch_size, 10]。模型的目标是一个整型向量,每个整数的范围0~9。

model = build_model(allow_cudnn_kernel=True)

model.compile(loss='sparse_categorical_crossentropy', 
              optimizer='sgd',
              metrics=['accuracy'])
model.fit(x_train, y_train,
          validation_data=(x_test, y_test),
          batch_size=batch_size,
          epochs=5)

6.4创建一个不带CuDNN内核的模型

slow_model = build_model(allow_cudnn_kernel=False)
slow_model.set_weights(model.get_weights())
slow_model.compile(loss='sparse_categorical_crossentropy', 
                   optimizer='sgd', 
                   metrics=['accuracy'])
slow_model.fit(x_train, y_train, 
               validation_data=(x_test, y_test), 
               batch_size=batch_size,
               epochs=1)  # We only train for one epoch because it's slower.

在这里插入图片描述
不难看出,带有CuDNN内核的模型比不带CuDNN内核的模型要快很多。在仅CPU环境里也可以启用同样的CuDNN模型用来推理。下面的tf.device注释只是强制设备放置。如果没有可用的GPU,该模型将默认运行在CPU上。所以我们没必要担心硬件运行环境具体怎么样。

with tf.device('CPU:0'):
  cpu_model = build_model(allow_cudnn_kernel=True)
  cpu_model.set_weights(model.get_weights())
  result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
  print('Predicted result is: %s, target result is: %s' % (result.numpy(), sample_label))
  plt.imshow(sample, cmap=plt.get_cmap('gray'))

在这里插入图片描述

七、带序列/字典输入或嵌套输入的RNNs

嵌套结构可以在单个时间步内执行更多信息。比如,视频框架可以同时进行音频和视频输入,像下面这样:

[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]

另外一个例子,手写数据里有手写笔当前位置的xy坐标,也有压力信息,展现出来像下面这样:

[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]

下面的内容就是展示如何自定义一个支持这种输入结构的RNN单元

7.1定义一个支持嵌套输入/输出的单元

NestedInput = collections.namedtuple('NestedInput', ['feature1', 'feature2'])
NestedState = collections.namedtuple('NestedState', ['state1', 'state2'])

class NestedCell(tf.keras.layers.Layer):

  def __init__(self, unit_1, unit_2, unit_3, **kwargs):
    self.unit_1 = unit_1
    self.unit_2 = unit_2
    self.unit_3 = unit_3
    self.state_size = NestedState(state1=unit_1, 
                                  state2=tf.TensorShape([unit_2, unit_3]))
    self.output_size = (unit_1, tf.TensorShape([unit_2, unit_3]))
    super(NestedCell, self).__init__(**kwargs)

  def build(self, input_shapes):
    # expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
    input_1 = input_shapes.feature1[1]
    input_2, input_3 = input_shapes.feature2[1:]

    self.kernel_1 = self.add_weight(
        shape=(input_1, self.unit_1), initializer='uniform', name='kernel_1')
    self.kernel_2_3 = self.add_weight(
        shape=(input_2, input_3, self.unit_2, self.unit_3),
        initializer='uniform',
        name='kernel_2_3')

  def call(self, inputs, states):
    # inputs should be in [(batch, input_1), (batch, input_2, input_3)]
    # state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
    input_1, input_2 = tf.nest.flatten(inputs)
    s1, s2 = states

    output_1 = tf.matmul(input_1, self.kernel_1)
    output_2_3 = tf.einsum('bij,ijkl->bkl', input_2, self.kernel_2_3)
    state_1 = s1 + output_1
    state_2_3 = s2 + output_2_3

    output = [output_1, output_2_3]
    new_states = NestedState(state1=state_1, state2=state_2_3)

    return output, new_states

7.2 创建一个带有嵌套输入/输出的RNN模型

下面用创建一个Keras模型,模型包括tf.keras.layers.RNN层和刚刚我们自定义的单元(cell)

unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])

7.3使用随机产生的数据训练模型

由于该模型没有好的候选数据集,我们就用随机Numpy数据来演示。

input_1_data = np.random.random((batch_size * num_batch, timestep, input_1))
input_2_data = np.random.random((batch_size * num_batch, timestep, input_2, input_3))
target_1_data = np.random.random((batch_size * num_batch, unit_1))
target_2_data = np.random.random((batch_size * num_batch, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]

model.fit(input_data, target_data, batch_size=batch_size)

在这里插入图片描述
有了tf.keras.layers.RNN层,我们只需要定义序列中各个步骤的数学逻辑,tf.keras.layers.RNN层会为我们处理序列迭代。这是一种非常强大的将新RNNS(例如LSTM变体)快速原型化的方法

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值