在 TensorFlow 之中使用循环神经网络

在之前的学习之中,我们使用了全连接网络、卷积神经网络以及其他的网络。今天我们要来学习另一种常用的神经・网络 —— 循环神经网络(RNN)。

1. 什么是循环神经网络

循环神经网络( recurrent neural network )简称 RNN 。顾名思义,这种网络之中包含循环的结构,因此才被称作循环神经网络。

在我们之前学习过的网络之中,网络模型只是根据不同的样本的输入来进行处理,前面的输入和后面的输入互不相干,简而言之,我们之前学习过的网络模型不包含记忆性。而循环神经网络( RNN )则可以具有记忆性,它能够 “记住” 之前的输入,从而对后面输入的判别产生影响。

举个简单的例子,当我们在看电影的时候,如果用之前学过的模型来处理,就相当于一帧接着一帧地进行分析;而如果我们使用循环神经网络来处理,就相当于将多个帧联系起来处理。

因此我们可以得出结论,循环神经网络更适合用来处理一些连续输入的数据,比如文本、视频、音频等连续的数据。

既然想要使用循环神经网络,我们便要简单的了解一下循环神经网络的基本结构。

循环神经网络的基本结构如下图所示:

图片描述

我们可以看到,与之前学习过的网络最大的不同就是该网络含有一个循环结构,具体来说,循环神经网络处理数据的基本过程为:

  • 输入数据并处理;
  • 数据经过隐含状态并得到两个结果:
    • 这次输入数据的输出;
    • 隐含状态的更新值。
  • 将数据输出,同时更新隐含的状态。

正是因为 RNN 可以根据不同的输入来不断地更新自己的隐含状态,因此我们才会说 RNN 具有 “记忆性”,也就是说,隐含状态会 “记住之前地输入状态”。

在 RNN 的具体细节之中,涉及到许多的数学推导知识,我们在这里暂时不展开叙述,我们只需要学会如何使用循环神经网络即可。而且在实际的应用之中,我们会采用多个单元的方式进行,从而提升网络的容量。

2. 如何在 TensorFlow 之中使用循环神经网络

在 TensorFlow 之中使用循环神经网络也是一件非常容易的事情。但是值得注意的是,原生的循环神经网络在 TensorFlow 之中的的实现比较复杂,而且通过训练得到的效果也不是很好。因此,我们建议在 TensorFlow 之中使用 RNN 的一些变体,这些变体往往可以通过更少的工作量来获取到更好的工作成果,在这里,我们使用 LSTM (长短期记忆)与 GRU (门循环单元)为例。这两者的改变都涉及到比较底层的知识,大家不需要掌握,只需要知道这些变体能够取得更好的效果并且学会如何使用即可。

最简单的,我们可以通过使用以下 Keras 的 API 来进行长短期记忆(LSTM)的调用。

tf.keras.layers.LSTM(
    units, activation='tanh', recurrent_activation='sigmoid', 
    dropout=0.0, recurrent_dropout=0.0, return_sequences=False,
    return_state=False
)

其中的一些参数的意义如下:

  • units: 正整数,输出的维度;
  • activation: 要采用的激活函数,一般保持默认 “tanh” 即可;
  • recurrent_activation: 在每次循环的时候要采用的激活函数,默认为 “sigmoid”;
  • dropout:用来抑制过拟合的一种方法,被应用于输入的阶段;
  • recurrent_dropout:用来抑制过拟合的一种方法,被应用于循环的阶段;
  • return_sequences:布尔值,是否返回一个完整的输出序列,还是只返回最后一个输出;
  • return_state:布尔值,是否返回中间的隐含状态。

对于 GRU ,我们可以使用以下的 API 实现。

tf.keras.layers.GRU(
    units, activation='tanh', recurrent_activation='sigmoid', dropout=0.0,
    recurrent_dropout=0.0, return_sequences=False, return_state=False
)

可以看到 GRU 的 API 和 LSTM 的 API 的参数非常相似,而实际上他们的含义也相同。

  • units: 正整数,GRU 输出的维度;
  • activation: GRU 要采用的激活函数,一般保持默认 “tanh” 即可;
  • recurrent_activation: GRU 在每次循环的时候要采用的激活函数,默认为 “sigmoid”;
  • dropout:用来抑制过拟合的一种方法,被应用于 GRU 输入的阶段;
  • recurrent_dropout:用来抑制过拟合的一种方法,被应用于 GRU 循环的阶段;
  • return_sequences:布尔值,定义 GRU 是否返回一个完整的输出序列,还是只返回最后一个输出;
  • return_state:布尔值,决定 GRU 是否返回中间的隐含状态。

3. 在 TensorFlow 之中使用循环神经网络的示例

这一小节,我们将会使用一个具体的程序实例来演示如何在 TensorFlow 之中使用循环神经网络,在这里,我们会使用 LSTM 举例,大家也可以常使使用 GRU 等其他循环神经网络。与往常一样,我们仍然采用的是 IMDB 电影评价的数据集来进行演示。

在这里,我们具体的程序为:


import tensorflow as tf
import numpy as np

# 定义基本参数
words_num = 10000
val_num = 12500
EPOCHS = 10
pad_max_length = 256
BATCH_SIZE = 64

# 获取数据
(train_data, train_labels), (test_data, test_labels) = tf.keras.datasets.imdb.load_data(num_words=words_num)
word_index = tf.keras.datasets.imdb.get_word_index()

# 添加特殊字符
word_index = {k:(v+3) for k,v in word_index.items()}
word_index["<pad>"] = 0
word_index["<start>"] = 1
word_index["<unknown>"] = 2
word_index["<unused>"] = 3

# 数据预处理
train_data = tf.keras.preprocessing.sequence.pad_sequences(train_data, value=0, padding='post', maxlen=pad_max_length)
test_data = tf.keras.preprocessing.sequence.pad_sequences(test_data, value=0, padding='post', maxlen=pad_max_length)

# 划分训练集合与验证集合
x_val, x_train = train_data[:val_num], train_data[val_num:]
y_val, y_train = train_labels[:val_num], train_labels[val_num:]

# 模型构建
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(words_num, 32),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

model.summary()

# 编译模型
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# 训练
history = model.fit(x_train, y_train, epochs=EPOCHS,
          batch_size=BATCH_SIZE, validation_data=(x_val, y_val))

# 测试
results = model.evaluate(test_data, test_labels)

print(results)

可以看到,我们的代码与之前的模型几乎完全相同,只是我们的网络模型采用了 LSTM(长短期记忆),大家可以将这次运行的结果与之前运行的结果相比较。

该处的代码中,我们需要注意的一点是,我们将 LSTM 用 tf.keras.layers.Bidirectional () 这一网络层包裹起来了。顾名思义,这一个双向的网络层,通过将 LSTM 使用该网络层包裹,LSTM 不仅仅能够学习到正向的信息,也能学习到反向的信息。举个不恰当的例子,相当于人可以反着读书一样。这可以增强循环神经网络提取信息的能力,童儿提升模型的效果。

最终,通过运行代码,我们可以得到输出:


_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_3 (Embedding)      (None, None, 32)          320000    
_________________________________________________________________
bidirectional_1 (Bidirection (None, 128)               49664     
_________________________________________________________________
dense_6 (Dense)              (None, 64)                8256      
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 65        
=================================================================
Total params: 377,985
Trainable params: 377,985
Non-trainable params: 0
_________________________________________________________________
Epoch 1/10
196/196 [==============================] - 72s 369ms/step - loss: 0.5292 - accuracy: 0.7088 - val_loss: 0.3791 - val_accuracy: 0.8432
Epoch 2/10
196/196 [==============================] - 71s 360ms/step - loss: 0.2714 - accuracy: 0.8993 - val_loss: 0.3445 - val_accuracy: 0.8719
Epoch 3/10
196/196 [==============================] - 71s 363ms/step - loss: 0.1885 - accuracy: 0.9358 - val_loss: 0.3460 - val_accuracy: 0.8706
Epoch 4/10
196/196 [==============================] - 72s 365ms/step - loss: 0.1363 - accuracy: 0.9550 - val_loss: 0.3765 - val_accuracy: 0.8563
Epoch 5/10
196/196 [==============================] - 71s 363ms/step - loss: 0.0915 - accuracy: 0.9713 - val_loss: 0.4558 - val_accuracy: 0.8667
Epoch 6/10
196/196 [==============================] - 71s 362ms/step - loss: 0.0798 - accuracy: 0.9733 - val_loss: 0.7503 - val_accuracy: 0.8242
Epoch 7/10
196/196 [==============================] - 71s 363ms/step - loss: 0.0617 - accuracy: 0.9807 - val_loss: 0.5544 - val_accuracy: 0.8570
Epoch 8/10
196/196 [==============================] - 70s 359ms/step - loss: 0.0636 - accuracy: 0.9782 - val_loss: 0.5350 - val_accuracy: 0.7421
Epoch 9/10
196/196 [==============================] - 70s 357ms/step - loss: 0.0934 - accuracy: 0.9661 - val_loss: 0.6706 - val_accuracy: 0.8348
Epoch 10/10
196/196 [==============================] - 70s 356ms/step - loss: 0.0976 - accuracy: 0.9638 - val_loss: 0.5770 - val_accuracy: 0.8568
782/782 [==============================] - 34s 43ms/step - loss: 0.6340 - accuracy: 0.8440
[0.6339613795280457, 0.843999981880188]

可以看到,我们的网络模型的准确率达到了 84% ,对于之前的网络来说是一个不小的提升。

4. 总结

在这节课之中,我们学习了什么是循环神经网络,并且详细的了解了循环神经网络在 TensorFlow 之中的使用方法。与此同时,我们使用了一个具体的网络示例来展示了如何完整地使用 LSTM ,而这也达到了不错的效果。

图片描述