mastering-tf-1x-zh-merge-1

43 阅读1小时+

精通 TensorFlow 1.x(二)

原文:Mastering TensorFlow 1.x

协议:CC BY-NC-SA 4.0

五、TensorFlow 和 Keras 中的神经网络和 MLP

神经网络是一种受大脑结构和功能启发的建模技术。正如大脑包含数百万个被称为神经元的微小互连单元一样,今天的神经网络由数百万个分层排列的微小互连计算单元组成。由于神经网络的计算单元仅存在于数字世界中,与大脑的物理神经元相反,它们也被称为人工神经元。类似地,神经网络(NN)也称为人工神经网络(ANN)。

在本章中,我们将进一步扩展以下主题:

  • 感知机(人工神经元)
  • 前馈神经网络
  • 用于图像分类的多层感知机MLP
    • 基于 TensorFlow 的用于 MNIST 图像分类的 MLP
    • 基于 Keras 的用于 MNIST 分类的 MLP
    • 基于 TFLearn 的用于 MNIST 分类的 MLP
  • 用于时间序列回归的 MLP

感知机

让我们了解神经网络的最基本构建块,感知机,也称为人工神经元。感知机的概念起源于 Frank Rosenblatt 于 1962 年的作品。

您可能希望阅读以下工作来探索神经网络的起源:

Frank Rosenblatt,神经动力学原理:感知器和脑机制理论,斯巴达书籍,1962 年

在最简化的视图中,感知机被建模在生物神经元之后,使得它接收一个或多个输入并将它们组合以产生输出。

如下图所示,感知机采用三个输入并将它们相加以生成输出y

这种感知机太简单了,不具备任何实际用途。因此,通过添加权重,偏差和激活函数的概念来增强它。将权重添加到每个输入以获得加权和。如果加权和Σw[i]x[i]小于阈值,则输出为 0,否则输出为 1:

阈值称为偏差。让我们将偏差移到等式的左边,用b表示它,Σw·x代表wx的向量点积。感知机的等式现在变为如下:

感知机现在看起来像下图:

 Simple perceptron with weights and bias

到目前为止,神经元是一个线性函数。为了使这个神经元产生非线性决策边界,通过称为激活或传递函数的非线性函数运行求和输出。有许多流行的激活函数可用:

  • ReLU整流线性单元,将值平滑到范围(0, x)

  • sigmoidSigmoid 将值平滑到(0, 1)

  • tanh双曲正切将值平滑到(-1, 1)

使用激活函数,感知机的等式变为:

其中φ(·)是激活函数。

神经元看起来像下图:

多层感知机

当我们将人工神经元连接在一起时,基于明确定义的结构,我们将其称为神经网络。这是一个神经元最简单的神经网络:

Neural network with one neuron

我们连接神经元,使得一层的输出成为下一层的输入,直到最后一层的输出成为最终输出。这种神经网络被称为前馈神经网络(FFNN)。由于这些 FFNN 由连接在一起的神经元层组成,因此它们被称为多层感知机(MLP)深度神经网络(DNN)

作为示例,下图中描绘的 MLP 具有三个特征作为输入:两个隐藏层,每个神经元包含五个神经元,一个输出 y。神经元完全连接到下一层的神经元。这些层也称为致密层或仿射层,并且这种模型也称为顺序模型。

让我们重温一下我们之前探索过的一些示例数据集,并在 TensorFlow 中构建简单的神经网络(MLP 或 DNN)。

您可以按照 Jupyter 笔记本ch-05_MLP中的代码进行操作。

用于图像分类的 MLP

让我们使用不同的库(例如 TensorFlow,Keras 和 TFLearn)构建用于图像分类的 MLP 网络。 我们将在本节中使用示例的 MNIST 数据集。

MNIST 数据集包含从 0 到 9 的手写数字的28x28像素图像,以及它们的标签,训练集为 60K,测试集为 10K。 MNIST 数据集是使用最广泛的数据集,包括 TensorFlow 示例和教程。

MNIST 数据集和相关文档可从此链接获得

让我们从纯 TensorFlow 方法开始。

TensorFlow 中的用于 MNIST 分类的 MLP

首先,加载 MNIST 数据集,并使用以下代码定义训练和测试特征以及目标:

from tensorflow.examples.tutorials.mnist import input_data
mnist_home = os.path.join(datasetslib.datasets_root, 'mnist')
mnist = input_data.read_data_sets(mnist_home, one_hot=True)

X_train = mnist.train.images
X_test = mnist.test.images
Y_train = mnist.train.labels
Y_test = mnist.test.labels

num_outputs = 10 # 0-9 digits
num_inputs = 784 # total pixels

我们创建了三个辅助函数,它们将帮助我们创建一个只有一个隐藏层的简单 MLP,然后是一个更大的 MLP,每层有多个层和多个神经元。

mlp()函数使用以下逻辑构建网络层:

  1. mlp()函数需要五个输入:
    • x是输入特征张量
    • num_inputs是输入特征的数量
    • num_outputs是输出目标的数量
    • num_layers是所需隐藏层数
    • num_neurons是包含每层神经元数量的列表
  2. 将权重和偏差列表设置为空:
 w=[]
 b=[]
  1. 为隐藏层的数量运行循环以创建权重和偏移张量并将它们附加到各自的列表:
    • 张量分别名为w_<layer_num>b_<layer_num>。命名张量有助于调试和查找代码问题。
    • 使用tf.random_normal()以正态分布初始化张量。
    • 权重张量的第一个维度是来自前一层的输入数量。对于第一个隐藏层,第一个维度是num_inputs。权重张量的第二维是当前层中的神经元的数量。
    • 偏差都是一维张量,其中维度等于当前层中的神经元数量。
     for i in range(num_layers):
        # weights
        w.append(tf.Variable(tf.random_normal(
            [num_inputs if i == 0 else num_neurons[i - 1],
             num_neurons[i]]),
            name="w_{0:04d}".format(i)
            ))
        # biases
        b.append(tf.Variable(tf.random_normal(
            [num_neurons[i]]),
            name="b_{0:04d}".format(i)
            ))
  1. 为最后一个隐藏层创建权重和偏差。在这种情况下,权重张量的维度等于最后隐藏层中的神经元数量和输出目标的数量。偏差是一个张量,具有输出特征数量大小的单一维度:
    w.append(tf.Variable(tf.random_normal(
        [num_neurons[num_layers - 1] if num_layers > 0 else num_inputs,
         num_outputs]), name="w_out"))
    b.append(tf.Variable(tf.random_normal([num_outputs]), 
        name="b_out"))
  1. 现在开始定义层。首先,将x视为第一个最明显的输入层:
# x is input layer
layer = x
  1. 在循环中添加隐藏的层。每个隐藏层表示,通过激活函数tf.nn.relu()使线性函数tf.matmul(layer, w[i]) + b[i]非线性化:
# add hidden layers
for i in range(num_layers):
    layer = tf.nn.relu(tf.matmul(layer, w[i]) + b[i])
  1. 添加输出层。输出层和隐藏层之间的一个区别是输出层中没有激活函数:
layer = tf.matmul(layer, w[num_layers]) + b[num_layers]
  1. 返回包含 MLP 网络的layer对象:
return layer

整个 MLP 函数的完整代码如下:

def mlp(x, num_inputs, num_outputs, num_layers, num_neurons):
    w = []
    b = []
    for i in range(num_layers):
        # weights
        w.append(tf.Variable(tf.random_normal(
            [num_inputs if i == 0 else num_neurons[i - 1],
             num_neurons[i]]),
            name="w_{0:04d}".format(i)
        ))
        # biases
        b.append(tf.Variable(tf.random_normal(
            [num_neurons[i]]),
            name="b_{0:04d}".format(i)
        ))
    w.append(tf.Variable(tf.random_normal(
        [num_neurons[num_layers - 1] if num_layers > 0 else num_inputs,
         num_outputs]), name="w_out"))
    b.append(tf.Variable(tf.random_normal([num_outputs]), name="b_out"))

    # x is input layer
    layer = x
    # add hidden layers
    for i in range(num_layers):
        layer = tf.nn.relu(tf.matmul(layer, w[i]) + b[i])
    # add output layer
    layer = tf.matmul(layer, w[num_layers]) + b[num_layers]

    return layer

辅助函数mnist_batch_func()为 MNIST 数据集包装 TensorFlow 的批量函数,以提供下一批图像:

def mnist_batch_func(batch_size=100):
    X_batch, Y_batch = mnist.train.next_batch(batch_size)
    return [X_batch, Y_batch]

此函数不言自明。 TensorFlow 为 MNIST 数据集提供此函数;但是,对于其他数据集,我们可能必须编写自己的批量函数。

辅助函数tensorflow_classification()训练并评估模型。

  1. tensorflow_classification()函数有几个输入:
    • n_epochs是要运行的训练循环的数量
    • n_batches是应该运行每个循环中的训练的随机抽样批次的数量
    • batch_size是每批中的样本数
    • batch_funcbatch_size并返回XY样本批次的函数
    • model是具有神经元的实际神经网络或层
    • optimizer是使用 TensorFlow 定义的优化函数
    • loss是优化器优化参数的成本函数损失
    • accuracy_function是计算准确率分数的函数
    • X_testY_test是测试的数据集
  1. 启动 TensorFlow 会话以运行训练循环:
with tf.Session() as tfs:
    tf.global_variables_initializer().run()
  1. 运行n_epoch循环来训练:
for epoch in range(n_epochs):
  1. 在每个循环中,取样本集的n_batches数量并训练模型,计算每批的损失,计算每个周期的平均损失:
epoch_loss = 0.0
            for batch in range(n_batches):
                X_batch, Y_batch = batch_func(batch_size)
                feed_dict = {x: X_batch, y: Y_batch}
                _, batch_loss = tfs.run([optimizer, loss], feed_dict)
                epoch_loss += batch_loss
            average_loss = epoch_loss / n_batches
            print("epoch: {0:04d} loss = {1:0.6f}".format(
                epoch, average_loss))
  1. 完成所有周期循环后,计算并打印用accuracy_function计算的精度分数:
    feed_dict = {x: X_test, y: Y_test}
    accuracy_score = tfs.run(accuracy_function, 
                        feed_dict=feed_dict)
    print("accuracy={0:.8f}".format(accuracy_score))

tensorflow_classification()函数的完整代码如下:

def tensorflow_classification(n_epochs, n_batches,
                              batch_size, batch_func,
                              model, optimizer, loss, accuracy_function,
                              X_test, Y_test):
    with tf.Session() as tfs:
        tfs.run(tf.global_variables_initializer())
        for epoch in range(n_epochs):
            epoch_loss = 0.0
            for batch in range(n_batches):
                X_batch, Y_batch = batch_func(batch_size)
                feed_dict = {x: X_batch, y: Y_batch}
                _, batch_loss = tfs.run([optimizer, loss], feed_dict)
                epoch_loss += batch_loss
            average_loss = epoch_loss / n_batches
            print("epoch: {0:04d} loss = {1:0.6f}".format(
                epoch, average_loss))
        feed_dict = {x: X_test, y: Y_test}
        accuracy_score = tfs.run(accuracy_function, feed_dict=feed_dict)
        print("accuracy={0:.8f}".format(accuracy_score))

现在让我们定义输入和输出占位符,xy以及其他超参数:

# input images
x = tf.placeholder(dtype=tf.float32, name="x", 
                    shape=[None, num_inputs]) 
# target output
y = tf.placeholder(dtype=tf.float32, name="y", 
                    shape=[None, num_outputs])
num_layers = 0
num_neurons = []
learning_rate = 0.01
n_epochs = 50
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_size)

参数如下所述:

  • num_layers是隐藏层数。我们首先练习没有隐藏层,只有输入和输出层。
  • num_neurons是空列表,因为没有隐藏层。
  • learning_rate是 0.01,随机选择的小数。
  • num_epochs代表 50 次迭代,以学习将输入连接到输出的唯一神经元的参数。
  • batch_size保持在 100,这也是一个选择问题。较大的批量大小不一定提供更高的好处。您可能需要探索不同的批量大小,以找到神经网络的最佳批量大小。
  • n_batches:批次数大致计算为示例数除以批次中的样本数。

现在让我们将所有内容放在一起,使用到目前为止定义的变量定义网络,loss函数,optimizer函数和accuracy函数。

model = mlp(x=x,
            num_inputs=num_inputs,
            num_outputs=num_outputs,
            num_layers=num_layers,
            num_neurons=num_neurons)

loss = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(logits=model, labels=y))
optimizer = tf.train.GradientDescentOptimizer(
    learning_rate=learning_rate).minimize(loss)

predictions_check = tf.equal(tf.argmax(model, 1), tf.argmax(y, 1))
accuracy_function = tf.reduce_mean(tf.cast(predictions_check, tf.float32))

在这段代码中,我们使用一个新的 tensorflow 函数来定义损失函数:

tf.nn.softmax_cross_entropy_with_logits(logits=model, labels=y)

当使用softmax_cross_entropy_with_logits()函数时,请确保输出未缩放且尚未通过softmax激活函数。 此函数在内部使用softmax来缩放输出。

该函数计算模型之间的 softmax 熵(估计值y)和y的实际值。当输出属于一个类而不是一个类时,使用熵函数。在我们的示例中,图像只能属于其中一个数字。

有关此熵函数的更多信息,请参阅此链接

一旦定义了所有内容,运行tensorflow_classification函数来训练和评估模型:

tensorflow_classification(n_epochs=n_epochs, 
   n_batches=n_batches, 
   batch_size=batch_size, 
   batch_func=mnist_batch_func, 
   model = model, 
   optimizer = optimizer, 
   loss = loss, 
   accuracy_function = accuracy_function, 
   X_test = mnist.test.images, 
   Y_test = mnist.test.labels
   )

我们从运行分类得到以下输出:

epoch: 0000   loss = 8.364567
epoch: 0001   loss = 4.347608
epoch: 0002   loss = 3.085622
epoch: 0003   loss = 2.468341
epoch: 0004   loss = 2.099220
epoch: 0005   loss = 1.853206

--- Epoch 06 to 45 output removed for brevity ---

epoch: 0046   loss = 0.684285
epoch: 0047   loss = 0.678972
epoch: 0048   loss = 0.673685
epoch: 0049   loss = 0.668717
accuracy=0.85720009

我们看到单个神经元网络在 50 次迭代中缓慢地将损失从 8.3 降低到 0.66,最终得到几乎 85% 的准确率。对于这个具体的例子,这是非常糟糕的准确率,因为这只是使用 TensorFlow 进行分类使用 MLP 的演示。

我们使用更多层和神经元运行相同的代码,并获得以下准确率:

层数每个隐藏层中的神经元数量准确率
000.857
180.616
22560.936

因此,通过在每层添加两行和 256 个神经元,我们将精度提高到 0.936。我们鼓励您尝试使用不同变量值的代码来观察它如何影响损失和准确率。

Keras 中的用于 MNIST 分类的 MLP

现在让我们与 Keras 建立相同的 MLP 网络,Keras 是 TensorFlow 的高级库。我们保留所有参数与本章中用于 TensorFlow 示例的参数相同,例如,隐藏层的激活函数保留为 ReLU 函数。

  1. 从 Keras 导入所需的模块:
import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
  1. 定义超参数(我们假设数据集已经加载到X_trainY_trainX_testY_test变量):
num_layers = 2
num_neurons = []
for i in range(num_layers):
   num_neurons.append(256)
learning_rate = 0.01
n_epochs = 50
batch_size = 100
  1. 创建顺序模型:
model = Sequential()
  1. 添加第一个隐藏层。只有在第一个隐藏层中,我们必须指定输入张量的形状:
model.add(Dense(units=num_neurons[0], activation='relu', 
    input_shape=(num_inputs,)))
  1. 添加第二层:
model.add(Dense(units=num_neurons[1], activation='relu'))
  1. 使用 softmax 激活函数添加输出层:
model.add(Dense(units=num_outputs, activation='softmax'))
  1. 打印模型详细信息:
model.summary()

我们得到以下输出:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 256)               200960    
_________________________________________________________________
dense_2 (Dense)              (None, 256)               65792     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                2570      
=================================================================
Total params: 269,322
Trainable params: 269,322
Non-trainable params: 0
_________________________________________________________________
  1. 使用 SGD 优化器编译模型:
model.compile(loss='categorical_crossentropy',
   optimizer=SGD(lr=learning_rate),
   metrics=['accuracy'])
  1. 训练模型:
model.fit(X_train, Y_train,
   batch_size=batch_size,
   epochs=n_epochs)

在训练模型时,我们可以观察每次训练迭代的损失和准确率:

Epoch 1/50
55000/55000 [========================] - 4s - loss: 1.1055 - acc: 0.7413     
Epoch 2/50
55000/55000 [========================] - 3s - loss: 0.4396 - acc: 0.8833     
Epoch 3/50
55000/55000 [========================] - 3s - loss: 0.3523 - acc: 0.9010     
Epoch 4/50
55000/55000 [========================] - 3s - loss: 0.3129 - acc: 0.9112     
Epoch 5/50
55000/55000 [========================] - 3s - loss: 0.2871 - acc: 0.9181     

--- Epoch 6 to 45 output removed for brevity ---     

Epoch 46/50
55000/55000 [========================] - 4s - loss: 0.0689 - acc: 0.9814     
Epoch 47/50
55000/55000 [========================] - 4s - loss: 0.0672 - acc: 0.9819     
Epoch 48/50
55000/55000 [========================] - 4s - loss: 0.0658 - acc: 0.9822     
Epoch 49/50
55000/55000 [========================] - 4s - loss: 0.0643 - acc: 0.9829     
Epoch 50/50
55000/55000 [========================] - 4s - loss: 0.0627 - acc: 0.9829 
  1. 评估模型并打印损失和准确率:
score = model.evaluate(X_test, Y_test)
print('\n Test loss:', score[0])
print('Test accuracy:', score[1])

我们得到以下输出:

Test loss: 0.089410082236
Test accuracy: 0.9727

笔记本ch-05_MLP中提供了使用 Keras 进行 MNIST 分类的 MLP 的完整代码。

TFLearn 中的用于 MNIST 分类的 MLP

现在让我们看看如何使用 TFLearn 实现相同的 MLP,TFLearn 是 TensorFlow 的另一个高级库:

  1. 导入 TFLearn 库:
import tflearn
  1. 定义超参数(我们假设数据集已经加载到X_trainY_trainX_testY_test变量):
num_layers = 2
num_neurons = []
for i in range(num_layers):
num_neurons.append(256)

learning_rate = 0.01
n_epochs = 50
batch_size = 100
  1. 构建输入层,两个隐藏层和输出层(与 TensorFlow 和 Keras 部分中的示例相同)
# Build deep neural network
input_layer = tflearn.input_data(shape=[None, num_inputs])
dense1 = tflearn.fully_connected(input_layer, num_neurons[0], 
    activation='relu')
dense2 = tflearn.fully_connected(dense1, num_neurons[1], 
    activation='relu')
softmax = tflearn.fully_connected(dense2, num_outputs, 
    activation='softmax')
  1. 使用最后一步中构建的 DNN(在变量softmax中)定义优化器函数,神经网络和 MLP 模型(在 TFLearn 中称为 DNN):
optimizer = tflearn.SGD(learning_rate=learning_rate)
net = tflearn.regression(softmax, optimizer=optimizer, 
                         metric=tflearn.metrics.Accuracy(), 
                         loss='categorical_crossentropy')
model = tflearn.DNN(net)
  1. 训练模型:
model.fit(X_train, Y_train, n_epoch=n_epochs, 
          batch_size=batch_size, 
          show_metric=True, run_id="dense_model")

训练结束后,我们得到以下输出:

Training Step: 27499  | total loss: 0.11236 | time: 5.853s
| SGD | epoch: 050 | loss: 0.11236 - acc: 0.9687 -- iter: 54900/55000
Training Step: 27500  | total loss: 0.11836 | time: 5.863s
| SGD | epoch: 050 | loss: 0.11836 - acc: 0.9658 -- iter: 55000/55000
--
  1. 评估模型并打印准确率分数:
score = model.evaluate(X_test, Y_test)
print('Test accuracy:', score[0])

我们得到以下输出:

Test accuracy: 0.9637

与使用 TFLearn 相比,我们获得了相当的精确度。

在笔记本ch-05_MLP中提供了使用 TFLearn 进行 MNIST 分类的 MLP 的完整代码。

TensorFlow,Keras 和 TFLearn 中的 MLP 总结

在前面的部分中,我们学习了如何使用 TensorFLow 及其高级库构建简单的 MLP 架构。纯 TensorFlow 的准确率约为 0.93-0.94,Keras 的准确率为 0.96-0.98,TFLearn 的准确率为 0.96-0.97。尽管我们的代码的所有示例都使用下面的 TensorFlow,但相同架构和参数的准确率差异可归因于这样的事实:尽管我们初始化了一些重要的超参数,但高级库和 TensorFlow 却抽象了许多其他超级 - 我们没有从默认值修改的参数。

我们观察到,与 Keras 和 TFLearn 相比,TensorFlow 中的代码非常详细和冗长。高级库使我们更容易构建和训练神经网络模型。

用于时间序列回归的 MLP

我们已经看到了图像数据分类的例子;现在让我们看一下时间序列数据的回归。我们将建立并使用 MLP 作为一个较小的单变量时间序列数据集,称为国际航空公司乘客数据集。该数据集包含多年来的乘客总数。该数据集可从此链接获得:

让我们从准备数据集开始。

  1. 首先,使用以下代码加载数据集:
filename = os.path.join(datasetslib.datasets_root, 
                        'ts-data', 
                        'international-airline-passengers-cleaned.csv')
dataframe = pd.read_csv(filename,usecols=[1],header=0)
dataset = dataframe.values
dataset = dataset.astype('float32')
  1. 利用datasetslib的效用函数,我们将数据集分成测试和训练集。对于时间序列数据集,我们有一个单独的函数,不会改变观察结果,因为对于时间序列回归,我们需要维持观察的顺序。我们使用 67% 的数据进行训练,33% 的数据用于测试。您可能希望尝试使用不同比例的示例。
train,test=dsu.train_test_split(dataset,train_size=0.67)
  1. 对于时间序列回归,我们转换数据集以构建监督数据集。在此示例中,我们使用两个时间步长的滞后。我们将n_x设置为 2,mvts_to_xy()函数返回输入和输出(XY)训练和测试集,使得X是两列具有时间{t-1, t}的值,Y是一列中具有时间{t + 1}的值。我们的学习算法假设通过找到时间{t-1, t, t + 1}的值之间的关系,可以学习时间t + 1的值。
# reshape into X=t-1,t and Y=t+1
n_x=2
n_y=1
X_train, Y_train, X_test, Y_test = tsd.mvts_to_xy(train,
                                    test,n_x=n_x,n_y=n_y)

有关将时间序列数据集转换为监督学习问题的更多信息,请访问此链接

现在我们在我们的训练数据集上构建和训练模型:

  1. 导入所需的 Keras 模块:
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
  1. 设置构建模型所需的超参数:
num_layers = 2
num_neurons = [8,8]
n_epochs = 50
batch_size = 2

请注意,我们使用批量大小为 2,因为数据集非常小。我们使用两层 MLP,每层只有八个神经元,因为我们的示例问题的规模很小。

  1. 构建,编译和训练模型:
model = Sequential()
model.add(Dense(num_neurons[0], activation='relu', 
    input_shape=(n_x,)))
model.add(Dense(num_neurons[1], activation='relu'))
model.add(Dense(units=1))
model.summary()

model.compile(loss='mse', optimizer='adam')

model.fit(X_train, Y_train,
   batch_size=batch_size,
   epochs=n_epochs)

请注意,我们使用 Adam 优化器而不是 SGD。 您可能想要尝试 TensorFlow 和 Keras 中可用的不同优化器。

  1. 评估模型并打印均方误差(MSE)和均方根误差(RMSE):
score = model.evaluate(X_test, Y_test)
print('\nTest mse:', score)
print('Test rmse:', math.sqrt(score))

我们得到以下输出:

Test mse: 5619.24934188
Test rmse: 74.96165247566114
  1. 使用我们的模型预测值并绘制它们,用于测试和训练数据集:
# make predictions
Y_train_pred = model.predict(X_train)
Y_test_pred = model.predict(X_test)

# shift train predictions for plotting
Y_train_pred_plot = np.empty_like(dataset)
Y_train_pred_plot[:, :] = np.nan
Y_train_pred_plot[n_x-1:len(Y_train_pred)+n_x-1, :] = Y_train_pred

# shift test predictions for plotting
Y_test_pred_plot = np.empty_like(dataset)
Y_test_pred_plot[:, :] = np.nan
Y_test_pred_plot[len(Y_train_pred)+(n_x*2)-1:len(dataset)-1, :] = \
    Y_test_pred

# plot baseline and predictions
plt.plot(dataset,label='Original Data')
plt.plot(Y_train_pred_plot,label='Y_train_pred')
plt.plot(Y_test_pred_plot,label='Y_test_pred')
plt.legend()
plt.show()

我们得到以下关于原始和预测时间序列值的图:

如你所见,这是一个非常好的估计。然而,在现实生活中,数据本质上是多变量和复杂的。因此,我们将在后面的章节中看到时间序列数据的循环神经网络架构。

总结

在本章中,我们了解了多层感知机。我们解释了如何为分类和回归问题构建和训练 MLP 模型。我们使用纯 TensorFlow,Keras 和 TFLearn 构建了 MLP 模型。对于分类,我们使用图像数据,对于回归,我们使用时间序列数据。

构建和训练 MLP 网络模型的技术对于任何其他类型的数据(例如数字或文本)是相同的。然而,对于图像数据集,CNN 架构已被证明是最佳架构,对于序列数据集,例如时间序列和文本,RNN 模型已被证明是最佳架构。

虽然我们在本章中仅使用简单的数据集示例来演示 MLP 架构,但在后面的章节中,我们将介绍具有一些大型和高级数据集的 CNN 和 RNN 架构。

六、TensorFlow 和 Keras 中的 RNN

在涉及有序数据序列的问题中,例如时间序列预测自然语言处理,上下文对于预测输出非常有价值。可以通过摄取整个序列而不仅仅是最后一个数据点来确定这些问题的上下文。因此,先前的输出成为当前输入的一部分,并且当重复时,最后的输出结果是所有先前输入的结果以及最后一个输入。 循环神经网络RNN)架构是用于处理涉及序列的机器学习问题的解决方案。

循环神经网络RNN)是一种用于处理顺序数据的专用神经网络架构。顺序数据可以是一段时间内的观察序列,如时间序列数据,或字符序列,单词和句子,如文本数据。

标准神经网络的一个假设是,输入数据的排列方式是一个输入不依赖于另一个输入。然而,对于时间序列数据和文本数据,该假设不成立,因为序列中稍后出现的值通常受到之前出现的值的影响。

为了实现这一目标,RNN 通过以下方式扩展了标准神经网络:

  • 通过在计算图中添加循环或循环,RNN 增加了将一个层的输出用作相同或前一层的输入的特性。
  • RNN 添加存储器单元以存储可在当前计算中使用的先前输入和输出。

在本章中,我们将介绍以下有关 RNN 的主题:

  • 简单的循环神经网络
  • RNN 变种
  • LSTM
  • GRU
  • TensorFlow 中的 RNN
  • Keras 中的 RNN
  • Keras 中用于 MNIST 数据的 RNN

接下来的两章将介绍在 TensorFlow 和 Keras 中为时间序列和文本(NLP)数据构建 RNN 模型的实际示例。

简单循环神经网络

这是一个带循环的简单神经网络:

RNN Network

在该图中,神经网络N接受输入x[t]以产生输出y[t]。由于循环,在下一步t+1,输入y[t]和输入x[t+1]产生输出y[t+1]。在数学上,我们将其表示为以下等式:

当我们展开循环时,RNN 架构在时间步t1看起来如下:

随着时间步长的发展,这个循环在时间步骤 5 展开如下:

在每个时间步骤,使用相同的学习函数φ(·)和相同的参数,wb

输出y并不总是在每个时间步产生。相反,在每个时间步产生输出h,并且对该输出h应用另一个激活函数以产生输出y。 RNN 的等式现在看起来像这样:

其中,

  • w(hx)是连接到隐藏层的x输入的权重向量
  • w(hh)是来自前一时间步的h的值的权重向量
  • w(yh)是连接隐藏层和输出层的层的权重向量
  • 用于h[t]的函数通常是非线性函数,例如 tanh 或 ReLU

在 RNN 中,在每个时间步使用相同的参数w(hx), w(hh), w(yh), b(h), b(y)。这一事实大大减少了我们需要学习的基于序列模型的参数数量。

由此, RNN 在时间步t5如下展开,假设输出y仅在时间步t5产生:

简单的 RNN 由 Elman 在 1990 年引入,因此它也被称为 Elman 网络。然而,简单的 RNN 无法满足我们今天的处理需求,因此我们将在下一节中了解 RNN 的变体。

阅读 Elman 的原始研究论文,了解 RNN 架构的起源:

J. L. Elman, Finding Structure in Time, Cogn. Sci., vol. 14, no. 2, pp. 179211, 1990.

RNN 变种

RNN 架构已经以多种方式扩展,以适应某些问题中的额外需求,并克服简单 RNN 模型的缺点。我们列出了下面的 RNN 架构的一些主要扩展。

  • 双向 RNNBRNN)用于输出依赖于序列的前一个和后一个元素的情况。 BRNN 通过堆叠两个 RNN(称为前向和后向层)来实现,输出是前向和后向层 RNN 的隐藏状态的结果。在前向层中,存储器状态 h 从时间步长t流向时间步长t + 1,并且在后向层中,存储器状态从时间步长t流出。到时间步t-1。两个层在时间步t时采用相同的输入x[t],但它们在时间步t共同产生输出。

  • 深双向 RNNDBRNN)通过添加多个层进一步扩展 BRNN。 BRNN 在时间维度上隐藏了层或单元。但是,通过堆叠 BRNN,我们可以在 DBRNN 中获得分层表示。其中一个显着差异是,在 BRNN 中,我们对同一层中的每个单元使用相同的参数,但在 DBRNN 中,我们对每个堆叠层使用不同的参数。

  • 长短期记忆LSTM)网络通过使用涉及多个非线性函数而不是一个简单非线性函数的架构来扩展 RNN 隐藏状态。 LSTM 由称为单元的黑盒组成,取三个输入:时间t-1的工作记忆(h[t-1]),当前输入(x[t])和时间t-1的长期记忆(c[t-1]),并产生两个输出:更新的工作记忆(h[t])和长期记忆(c[t])。单元使用称为门的函数来决定从记忆中选择性地保存和擦除的内容。我们在下面的部分中详细描述了 LSTM。

阅读以下关于 LSTM 的研究论文,以获得有关 LSTM 起源的更多信息:

S. Hochreiter and J. Schmidhuber, Long Short-Term Memory, Neural Comput., vol. 9, no. 8, pp. 17351780, 1997.http://www.bioinf.jku.at/publications/older/2604.pdf
  • 门控循环单元GRU)网络是 LSTM 的简化变体。 结合遗忘和输入的功能,在更简单的更新门中进行门控。它还将隐藏状态和单元状态组合成一个单一状态。因此,与 LSTM 相比,GRU 在计算上更便宜。 我们在下面的部分中详细描述了 GRU。

阅读以下研究论文以探索 GRU 的更多细节:

K. Cho, B. van Merrienboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, and Y. Bengio, Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014.https://arxiv.org/abs/1406.1078

J. Chung, C. Gulcehre, K. Cho, and Y. Bengio, Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling, pp. 19, 2014. https://arxiv.org/abs/1412.3555
  • seq2seq 模型将编码器 - 解码器架构与 RNN 架构相结合。在 Seq2Seq 架构中,模型训练数据序列,例如文本数据或时间序列数据,然后该模型用于生成输出序列。例如,在英文文本上训练模型,然后从模型生成西班牙文本。 Seq2Seq 模型由编码器和解码器模型组成,它们都使用 RNN 架构构建。可以堆叠 Seq2Seq 模型以构建分层多层模型。

LSTM 网络

当 RNN 在很长的数据序列上进行训练时,梯度往往变得非常大或非常小,它们会消失到几乎为零。 长短期记忆LSTM)网络通过添加用于控制对过去信息的访问的门,来解决消失/爆炸梯度问题。 LSTM 概念最初由 Hochreiter 和 Schmidhuber 在 1997 年引入。

阅读以下关于 LSTM 的研究论文,以获得有关 LSTM 起源的更多信息:

S. Hochreiter and J. Schmidhuber, Long Short-Term Memory, Neural Comput., vol. 9, no. 8, pp. 17351780, 1997. http://www.bioinf.jku.at/publications/older/2604.pdf

在 RNN 中,使用重复使用的学习函数φ的单个神经网络层,而在 LSTM 中,使用由四个主要函数组成的重复模块。构建 LSTM 网络的模块称为单元。 LSTM 单元通过选择性地学习或擦除信息,有助于在长序列通过时更有效地训练模型。组成单元的函数也称为门,因为它们充当传入和传出单元的信息的网守。

LSTM 模型有两种记忆:

  • h(隐藏状态)表示的工作记忆
  • c(单元状态)表示的长期记忆。

单元状态或长期记忆仅在两个线性相互作用下从一个单元流向另一个单元。 LSTM 将信息添加到长期记忆中,或通过门从长期记忆中删除信息。

下图描绘了 LSTM 单元:

The LSTM Cell

通过 LSTM 单元中的门的内部流动如下:

  1. 遗忘门(或记忆门)f()h[t-1]x[t]按照以下等式作为输入流向f()门:

    遗忘门的功能是决定忘记哪些信息以及要记住哪些信息。这里使用sigmoid激活函数,因此输出 1 表示信息被转移到单元内的下一步骤,输出 0 表示信息被选择性地丢弃。

  2. 输入门(或保存门)i()h[t-1]x[t]按照以下等式作为输入流向i()门:

    输入门的功能是决定是保存还是丢弃输入。输入功能还允许单元了解要保留或丢弃的候选存储器的哪个部分。

  3. 候选长期记忆:候选长期记忆由h[t-1]x[t]使用激活函数计算,主要是tanh,按照下式:

  4. 接下来,组合前面的三个计算以得到更新长期记忆,由c[t]表示,如下式所示:

  5. 输出门(或聚焦/关注门)o()h[t-1]x[t]按照以下等式作为输入流向o()门:

    输出门的功能是决定多少信息可用于更新工作记忆。

  6. 接下来,工作记忆h[t]从长期记忆c[t]和焦点/注意力向量更新,如下式所示:

    其中φ(·)是激活函数,通常是tanh

GRU 网络

LSTM 网络的计算成本很高,因此,研究人员发现了一种几乎同样有效的 RNN 配置,称为门控循环单元GRU)架构。

在 GRU 中,不使用工作和长期记忆,只使用一种记忆,用h(隐藏状态)表示。 GRU 单元通过复位更新门,将信息添加到此状态存储器,或从该状态存储器中删除信息。

下图描绘了 GRU 单元(说明如下图):

The GRU Cell

GRU 单元中通过门的内部流量如下:

  1. 更新门u():输入h[t-1]x[t]按照以下公式流向u()门:

  2. 复位门r():输入h[t-1]x[t]按照以下公式流向r()门:

  3. 候选状态记忆:候选长期记忆是根据r()门,h[t-1]x[t]的输出计算出来的,按照下列公式:

  4. 接下来,组合前面的三个计算以得到更新的状态存储器,由h[t],表示,如下式所示:

阅读以下研究论文以探索 GRU 的更多细节:

K. Cho, B. van Merrienboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, and Y. Bengio, Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014. https://arxiv.org/abs/1406.1078

J. Chung, C. Gulcehre, K. Cho, and Y. Bengio, Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling, pp. 19, 2014. https://arxiv.org/abs/1412.3555

TensorFlow RNN

在低级 TensorFlow 库中创建 RNN 模型的基本工作流程与 MLP 几乎相同:

  • 首先创建形状的输入和输出占位符(None, #TimeSteps, #Features)(批量大小, #TimeSteps, #Features)
  • 从输入占位符中,创建一个长度为#TimeSteps的列表,其中包含形状的张量(None, #特征)(批量大小, #特征)
  • tf.rnn.rnn_cell模块创建所需 RNN 类型的单元
  • 使用先前创建的单元和输入张量列表来创建静态或动态 RNN
  • 创建输出权重和偏差变量,并定义损失和优化器函数
  • 对于所需的周期数,使用损失和优化器函数训练模型

这个基本工作流程将在下一章的示例代码中演示。让我们看看可用于支持先前工作流程的各种类。

TensorFlow RNN 单元类

tf.nn.rnn_cell模块包含以下用于在 TensorFlow 中创建不同类型单元的类:

描述
BasicRNNCell提供 RNN 单元的实现
BasicLSTMCell提供 LSTM RNN 单元的实现,基于这个页面
LSTMCell提供 LSTM RNN 单元,基于这个页面这个页面
GRUCell提供 GRU RNN 单元,基于这个页面
MultiRNNCell提供由连续连接的多个简单单元组成的 RNN 单元

tf.contrib.rnn模块提供以下额外的类用于在 TensorFlow 中创建不同类型的单元:

描述
LSTMBlockCell提供块 LSTM RNN 单元,基于这个页面
LSTMBlockFusedCell提供块融合 LSTM RNN 单元,基于这个页面
GLSTMCell提供分组 LSTM 单元,基于这个页面
GridLSTMCell提供网格 LSTM RNN 单元,基于这个页面
GRUBlockCell提供块 GRU RNN 单元,基于这个页面
BidirectionalGridLSTMCell仅在频率上而不是在时间上提供双向网格 LSTM
NASCell提供神经架构搜索 RNN 单元,基于这个页面
UGRNNCell提供更新门 RNN 单元,基于这个页面

TensorFlow RNN 模型构建类

TensorFlow 提供了从 RNN 单元对象创建 RNN 模型的类。静态 RNN 类在编译时为时间步骤添加展开的单元,而动态 RNN 类在运行时添加展开的单元用于时间步长。

  • tf.nn.static_rnn
  • tf.nn.static_state_saving_rnn
  • tf.nn.static_bidirectional_rnn
  • tf.nn.dynamic_rnn
  • tf.nn.bidirectional_dynamic_rnn
  • tf.nn.raw_rnn
  • tf.contrib.rnn.stack_bidirectional_dynamic_rnn

TensorFlow RNN 单元包装器类

TensorFlow 还提供包装其他单元类的类:

  • tf.contrib.rnn.LSTMBlockWrapper
  • tf.contrib.rnn.DropoutWrapper
  • tf.contrib.rnn.EmbeddingWrapper
  • tf.contrib.rnn.InputProjectionWrapper
  • tf.contrib.rnn.OutputProjectionWrapper
  • tf.contrib.rnn.DeviceWrapper
  • tf.contrib.rnn.ResidualWrapper

有关 TensorFlow 中 RNN 的最新文档,请访问此链接

Keras 中的 RNN

与 TensorFlow 相比,在 Keras 中创建 RNN 要容易得多。正如您在第 3 章中学到的,Keras 提供了用于创建循环网络的函数式和顺序 API。要构建 RNN 模型,您必须从kera.layers.recurrent模块添加层。 Keras 在keras.layers.recurrent模块中提供以下类型的循环层:

  • SimpleRNN
  • LSTM
  • GRU

有状态模型

Keras 循环层还支持 RNN 模型,可在批次之间保存状态。您可以通过将stateful参数作为True传递来创建有状态 RNN,LSTM 或 GRU 模型。对于有状态模型,为输入指定的批量大小必须是固定值。在有状态模型中,从训练批次中学到的隐藏状态将重新用于下一批。如果您想在训练期间的某个时刻重置记忆,可以通过调用model.reset_states()layer.reset_states()函数使用额外的代码来完成。

我们将在下一章中看到使用 Keras 构建 RNN 的示例。

有关 Keras 循环层的最新文档可在此链接中找到

RNN 的应用领域

RNN 更频繁使用的一些应用领域如下:

  • 自然语言模型:RNN 模型已用于自然语言处理(NLP),用于自然语言理解和自然语言生成任务。在 NLP 中,RNN 模型被给予一系列单词并且它预测另一个单词序列。因此,训练的模型可用于生成单词序列,称为文本生成的字段。例如,生成故事和剧本。 NLP 的另一个领域是语言翻译,其中给定一种语言的一系列单词,该模型预测另一种语言的单词序列。
  • 语音和语音识别:RNN 模型非常适用于构建模拟音频数据的模型。在语音识别中,RNN 模型被给予音频数据并且它预测一系列语音片段。它可用于训练模型以识别语音命令,甚至用于与基于语音的聊天机器人的对话。
  • 图像/视频描述或字幕生成:RNN 模型可与 CNN 结合使用,以生成图像和视频中找到的元素的描述。这些描述也可用于生成图像和视频的标题。
  • 时间序列数据:最重要的是,RNN 对时间序列数据非常有用。大多数传感器和系统生成时间顺序很重要的数据。 RNN 模型非常适合于查找模式和预测此类数据。

通过此链接了解有关 RNN 的更多信息:

karpathy.github.io/2015/05/21/…

colah.github.io/posts/2015-…

www.wildml.com/2015/09/rec…

r2rt.com/written-mem…

Keras 中的用于 MNIST 数据的 RNN

虽然 RNN 主要用于序列数据,但它也可用于图像数据。我们知道图像具有最小的两个维度 - 高度和宽度。现在将其中一个维度视为时间步长,将其他维度视为特征。对于 MNIST,图像大小为28 x 28像素,因此我们可以将 MNIST 图像视为具有 28 个时间步长,每个时间步长具有 28 个特征。

我们将在下一章中提供时间序列和文本数据的示例,但让我们为 Keras 中的 MNIST 构建和训练 RNN,以快速浏览构建和训练 RNN 模型的过程。

您可以按照 Jupyter 笔记本中的代码ch-06_RNN_MNIST_Keras

导入所需的模块:

import keras
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.layers.recurrent import SimpleRNN
from keras.optimizers import RMSprop
from keras.optimizers import SGD

获取 MNIST 数据并将数据从 1D 中的 784 像素转换为 2D 中的28 x 28像素:

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(os.path.join(datasetslib.datasets_root,
                                               'mnist'), 
                                  one_hot=True)
X_train = mnist.train.images
X_test = mnist.test.images
Y_train = mnist.train.labels
Y_test = mnist.test.labels
n_classes = 10
n_classes = 10
X_train = X_train.reshape(-1,28,28)
X_test = X_test.reshape(-1,28,28)

在 Keras 构建SimpleRNN模型:

# create and fit the SimpleRNN model
model = Sequential()
model.add(SimpleRNN(units=16, activation='relu', input_shape=(28,28)))
model.add(Dense(n_classes))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer=RMSprop(lr=0.01),
              metrics=['accuracy'])
model.summary()

该模型如下:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
simple_rnn_1 (SimpleRNN)     (None, 16)                720       
_________________________________________________________________
dense_1 (Dense)              (None, 10)                170       
_________________________________________________________________
activation_1 (Activation)    (None, 10)                0         
=================================================================
Total params: 890
Trainable params: 890
Non-trainable params: 0
_________________________________________________________________

训练模型并打印测试数据集的准确率:

model.fit(X_train, Y_train,
          batch_size=100, epochs=20)

score = model.evaluate(X_test, Y_test)
print('\nTest loss:', score[0])
print('Test accuracy:', score[1])

我们得到以下结果:

Test loss: 0.520945608187
Test accuracy: 0.8379

总结

在本章中,我们了解了循环神经网络(RNN)。我们了解了 RNN 的各种变体,并详细描述了其中的两个:长短期记忆(LSTM)网络和门控循环单元(GRU)网络。我们还描述了可用于在 TensorFlow 和 Keras 中构建 RNN 单元,模型和层的类。我们构建了一个简单的 RNN 网络,用于对 MNIST 数据集的数字进行分类。

在下一章中,我们将学习如何构建和训练时间序列数据的 RNN 模型。

七、TensorFlow 和 Keras 中的用于时间序列数据的 RNN

时间序列数据是一系列值,以不同的时间间隔记录或测量。作为序列,RNN 架构是从这些数据训练模型的最佳方法。在本章中,我们将使用示例时间序列数据集来展示如何使用 TensorFlow 和 Keras 构建 RNN 模型。

我们将在本章中介绍以下主题:

  • 航空公司乘客(airpass)时间序列数据集:
    • 描述和下载数据集
    • 可视化数据集
  • 在 TensorFlow 中预处理 RNN 的数据集
  • TensorFlow 中用于时间序列数据的 RNN:
    • TensorFlow 中的SimpleRNN
    • TensorFlow 中的 LSTM
    • TensorFlow 中的 GRU
  • 在 Keras 中为 RNN 预处理数据集
  • Keras 中用于时间序列数据的 RNN:
    • Keras 的SimpleRNN
    • Keras 的 LSTM
    • Keras 的 GRU

让我们从了解样本数据集开始。

您可以按照 Jupyter 笔记本中的代码ch-07a_RNN_TimeSeries_TensorFlow

航空公司乘客数据集

为了简洁起见,我们选择了一个名为国际航空公司乘客(航空通票)的非常小的数据集。该数据包含从 1949 年 1 月到 1960 年 12 月的每月总乘客数量。数据集中的数字是指数千的数量。该数据集最初由 Box 和 Jenkins 在 1976 年的工作中使用。它作为 时间序列数据集库TSDL)的一部分与 Rob Hyndman 教授的各种其他时间序列数据集一起收集。在澳大利亚莫纳什大学。后来,TSDL 被转移到 DataMarket

您可以从此链接下载数据集

加载 airpass 数据集

我们将数据集保存为数据集根目录(~/datasets)中ts-data文件夹中的 CSV 文件,并使用以下命令将数据加载到 pandas 数据框中:

filepath = os.path.join(datasetslib.datasets_root,
                        'ts-data',
                        'international-airline-passengers-cleaned.csv'
                       ) 
dataframe = pd.read_csv(filepath,usecols=[1],header=0)
dataset = dataframe.values
dataset = dataset.astype(np.float32)

从 NumPy 数组中的数据框中提取值并转换为np.float32

dataset = dataframe.values
dataset = dataset.astype(np.float32)

可视化 airpass 数据集

让我们看一下数据集的外观:

plt.plot(dataset,label='Original Data')
plt.legend()
plt.show()

airpass数据集的图如下所示:

Airline Passengers Dataset

用于 TensorFlow RNN 模型的数据集预处理

为了使其为学习模型做好准备,通过应用 MinMax 缩放来正则化数据集,该缩放使数据集值介于 0 和 1 之间。您可以尝试根据数据的性质对数据应用不同的缩放方法。

# normalize the dataset
scaler = skpp.MinMaxScaler(feature_range=(0, 1))
normalized_dataset = scaler.fit_transform(dataset)

我们使用自己开发的实用函数将数据集拆分为训练和测试数据集。必须拆分数据而不对数据集进行混洗,因为改组数据集会破坏序列。维护数据序列对于训练时间序列模型非常重要。

train,test=tsu.train_test_split(normalized_dataset,train_size=0.67)

然后我们将训练和测试数据集转换为有监督的机器学习集。让我们试着理解监督学习集的含义。假设我们有一系列数据:1,2,3,4,5。我们想要了解生成数据集的概率分布。为了做到这一点,我们可以假设时间步长t的值是从时间步长t-1tk的值的结果,其中k是窗口大小。为简化起见,假设窗口大小为 1。因此,时间步长t的值(称为输入特征)是时间步长值t-1的结果,被称为目标。让我们重复一遍所有时间步骤,我们得到下表:

输入值或特征输出值或目标
12
23
34
45

我们展示的示例只有一个变量值,它将转换为特征和目标。当目标值取决于一个变量时,它被称为单变量时间序列。同样的逻辑可以应用于多变量时间序列,其中目标取决于多个变量。我们使用x来表示输入特征,使用y来表示输出目标。

考虑到这一背景,为了将airpass数据转换为监督机器学习数据,我们设置了以下超参数:

  1. 设置用于学习或预测下一个时间步的过去时间步数:
n_x=1  
  1. 设置学习或预测的未来时间步长的数量:
n_y=1 
  1. 设置用于学习的x变量的数量;由于当前示例是单变量的,因此设置为 1:
n_x_vars = 1  
  1. 设置要预测的y变量的数量;由于当前示例是单变量的,因此设置为 1:
n_y_vars = 1  
  1. 最后,我们通过应用本节开头所述的逻辑将训练和测试数据集转换为XY集:
X_train, Y_train, X_test, Y_test = tsu.mvts_to_xy(train,
                                  test,n_x=n_x,n_y=n_y)

现在数据已经过预处理并可以输入到我们的模型中,让我们使用 TensorFlow 准备一个SimpleRNN模型。

TensorFlow 中的简单 RNN

在 TensorFlow 中定义和训练简单 RNN 的工作流程如下:

  1. 定义模型的超参数:
state_size = 4
n_epochs = 100
n_timesteps = n_x 
learning_rate = 0.1

这里新的超参数是state_sizestate_size表示 RNN 单元的权重向量的数量。

  1. 为模型定义XY参数的占位符。X占位符的形状为(batch_size, number_of_input_timesteps, number_of_inputs)Y占位符的形状为(batch_size, number_of_output_timesteps, number_of_outputs)。对于batch_size,我们使用None,以便我们以后可以输入任意大小的批次。
X_p = tf.placeholder(tf.float32, [None, n_timesteps, n_x_vars], 
    name='X_p') 
Y_p = tf.placeholder(tf.float32, [None, n_timesteps, n_y_vars], 
    name='Y_p')
  1. 将输入占位符X_p转换为长度等于时间步数的张量列表,在此示例中为n_x或 1:
# make a list of tensors of length n_timesteps
rnn_inputs = tf.unstack(X_p,axis=1)
  1. 使用tf.nn.rnn_cell.BasicRNNCell创建一个简单的 RNN 单元:
cell = tf.nn.rnn_cell.BasicRNNCell(state_size)
  1. TensorFlow 提供static_rnndynamic_rnn便利方法(以及其他方法)分别创建静态和动态 RNN。创建静态 RNN:
rnn_outputs, final_state = tf.nn.static_rnn(cell, 
                                            rnn_inputs,
                                            dtype=tf.float32
                                           )

静态 RNN 在编译时创建单元,即展开循环。动态 RNN 创建单元,即在运行时展开循环 。在本章中,我们仅展示了static_rnn的示例,但是一旦获得静态 RNN 的专业知识,就应该探索dynamic_rnn

static_rnn方法采用以下参数:

  • cell:我们之前定义的基本 RNN 单元对象。它可能是另一种单元,我们将在本章中进一步看到。
  • rnn_inputs:形状(batch_size, number_of_inputs)的张量列表。
  • dtype:初始状态和预期输出的数据类型。
  1. 定义预测层的权重和偏差参数:
W = tf.get_variable('W', [state_size, n_y_vars])
b = tf.get_variable('b', [n_y_vars], 
    initializer=tf.constant_initializer(0.0))
  1. 将预测层定义为密集线性层:
predictions = [tf.matmul(rnn_output, W) + b \
                for rnn_output in rnn_outputs]
  1. 输出 Y 是张量的形状;将其转换为张量列表:
y_as_list = tf.unstack(Y_p, num=n_timesteps, axis=1)
  1. 将损失函数定义为预测标签和实际标签之间的均方误差:
mse = tf.losses.mean_squared_error
losses = [mse(labels=label, predictions=prediction) 
          for prediction, label in zip(predictions, y_as_list)
         ]
  1. 将总损失定义为所有预测时间步长的平均损失:
total_loss = tf.reduce_mean(losses)
  1. 定义优化器以最小化total_loss
optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)
  1. 现在我们已经定义了模型,损耗和优化器函数,让我们训练模型并计算训练损失:
with tf.Session() as tfs:
    tfs.run(tf.global_variables_initializer())
    epoch_loss = 0.0
    for epoch in range(n_epochs):
        feed_dict={X_p: X_train.reshape(-1, n_timesteps, 
                                        n_x_vars), 
                   Y_p: Y_train.reshape(-1, n_timesteps, 
                                        n_x_vars)
                  }
        epoch_loss,y_train_pred,_=tfs.run([total_loss,predictions,
                                optimizer], feed_dict=feed_dict)
    print("train mse = {}".format(epoch_loss))

我们得到以下值:

train mse = 0.0019413739209994674
  1. 让我们在测试数据上测试模型:
feed_dict={X_p: X_test.reshape(-1, n_timesteps,n_x_vars), 
           Y_p: Y_test.reshape(-1, n_timesteps,n_y_vars)
          }
test_loss, y_test_pred = tfs.run([total_loss,predictions], 
                                 feed_dict=feed_dict
                                )
print('test mse = {}'.format(test_loss))
print('test rmse = {}'.format(math.sqrt(test_loss)))

我们在测试数据上得到以下 mse 和 rmse(均方根误差):

test mse = 0.008790395222604275
test rmse = 0.09375710758446143

这非常令人印象深刻。

这是一个非常简单的例子,只用一个变量值预测一个时间步。在现实生活中,输出受到多个特征的影响,并且需要预测不止一个时间步。后一类问题被称为多变量多时间步进预测问题。这些问题是使用循环神经网络进行更好预测的积极研究领域。

现在让我们重新调整预测和原始值并绘制原始值(请在笔记本中查找代码)。

我们得到以下绘图:

令人印象深刻的是,在我们的简单示例中,预测数据几乎与原始数据相匹配。对这种准确预测的一种可能解释是,单个时间步的预测基于来自最后一个时间步的单个变量的预测,因此它们总是在先前值的附近。

尽管如此,前面示例的目的是展示在 TensorFlow 中创建 RNN 的方法。现在让我们使用 RNN 变体重新创建相同的示例。

TensorFlow 中的 LSTM

由于爆炸和消失梯度的问题,简单的 RNN 架构并不总是有效,因此使用了改进的 RNN 架构,例如 LSTM 网络。 TensorFlow 提供 API 来创建 LSTM RNN 架构。

在上一节中展示的示例中,要将简单 RNN 更改为 LSTM 网络,我们所要做的就是更改单元类型,如下所示:

cell = tf.nn.rnn_cell.LSTMCell(state_size)

其余代码保持不变,因为 TensorFlow 会为您在 LSTM 单元内创建门。

笔记本ch-07a_RNN_TimeSeries_TensorFlow中提供了 LSTM 模型的完整代码。

然而,对于 LSTM,我们必须运行 600 个周期的代码才能使结果更接近基本 RNN。原因是 LSTM 需要学习更多参数,因此需要更多的训练迭代。对于我们的简单示例,它似乎有点过分,但对于较大的数据集,与简单的 RNN 相比,LSTM 显示出更好的结果。

具有 LSTM 架构的模型的输出如下:

train mse = 0.0020806745160371065
test mse = 0.01499235536903143
test rmse = 0.12244327408653947

TensorFlow 中的 GRU

要将最后一节中的 LSTM 示例更改为 GRU 网络, 按如下方式更改单元类型,TensorFlow 将为您处理其余部分:

cell = tf.nn.rnn_cell.GRUCell(state_size)

笔记本ch-07a_RNN_TimeSeries_TensorFlow中提供了 GRU 模型的完整代码。

对于小airpass数据集,GRU 在相同数量的周期中表现出更好的表现。在实践中,GRU 和 LSTM 表现出相当的表现。就执行速度而言,与 LSTM 相比,GRU 模型训练和预测更快。

GRU 模型的完整代码在 Jupyter 笔记本中提供。GRU 模型的结果如下:

train mse = 0.0019633215852081776
test mse = 0.014307591132819653
test rmse = 0.11961434334066987

我们鼓励您探索 TensorFlow 中可用的其他选项来创建循环神经网络。现在让我们在 TensorFlow 的一个高级库中尝试相同的示例。

对于下一节,您可以按照 Jupyter 笔记本中的代码ch-07b_RNN_TimeSeries_Keras

用于 Keras RNN 模型的数据集预处理

与使用较低级别 TensorFlow 类和方法构建相比,在 Keras 中构建 RNN 网络要简单得多。对于 Keras,我们预先处理数据,如前面部分所述,以获得受监督的机器学习时间序列数据集:X_train, Y_train, X_test, Y_test

从这里开始,预处理有所不同。对于 Keras,输入必须是(samples, time steps, features)形状。当我们将数据转换为监督机器学习格式时,在重塑数据时,我们可以将时间步长设置为 1,从而将所有输入时间步长作为特征,或者我们可以设置时间步长为实际的时间步数,从而为每个时间步长提供特征集。换句话说,我们之前获得的X_trainX_test数据集可以重新整形为以下方法之一:

方法 1:n时间步长与1特征:

X_train.reshape(X_train.shape[0], X_train.shape[1],1)

方法 2:1时间步长n特征:

X_train.reshape(X_train.shape[0], 1, X_train.shape[1])

在本章中,我们将对特征大小为 1 的数据集进行整形,因为我们只使用一个变量作为输入:

# reshape input to be [samples, time steps, features]
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1],1)
X_test = X_test.reshape(X_test.shape[0], X_train.shape[1], 1)

Keras 中的简单 RNN

通过添加具有内部神经元数量和输入张量形状的SimpleRNN层,可以在 Keras 中轻松构建 RNN 模型,不包括样本维数。以下代码创建,编译和拟合SimpleRNN

# create and fit the SimpleRNN model
model = Sequential()
model.add(SimpleRNN(units=4, input_shape=(X_train.shape[1], 
    X_train.shape[2])))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(X_train, Y_train, epochs=20, batch_size=1)

由于我们的数据集很小,我们使用batch_size为 1 并训练 20 次迭代,但对于较大的数据集,您需要调整这些和其他超参数的值。

该模型的结构如下:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
simple_rnn_1 (SimpleRNN)     (None, 4)                 24        
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 5         
=================================================================
Total params: 29
Trainable params: 29
Non-trainable params: 0

训练的结果如下:

Epoch 1/20
95/95 [==============================] - 0s - loss: 0.0161     
Epoch 2/20
95/95 [==============================] - 0s - loss: 0.0074     
Epoch 3/20
95/95 [==============================] - 0s - loss: 0.0063     
Epoch 4/20
95/95 [==============================] - 0s - loss: 0.0051     

-- epoch 5 to 14 removed for the sake of brevity --

Epoch 14/20
95/95 [==============================] - 0s - loss: 0.0021     
Epoch 15/20
95/95 [==============================] - 0s - loss: 0.0020     
Epoch 16/20
95/95 [==============================] - 0s - loss: 0.0020     
Epoch 17/20
95/95 [==============================] - 0s - loss: 0.0020     
Epoch 18/20
95/95 [==============================] - 0s - loss: 0.0020         
Epoch 19/20
95/95 [==============================] - 0s - loss: 0.0020     
Epoch 20/20
95/95 [==============================] - 0s - loss: 0.0020     

损失从 0.0161 开始,平稳在 0.0020。让我们做出预测并重新调整预测和原件。我们使用 Keras 提供的函数来计算均方根误差:

from keras.losses import mean_squared_error as k_mse
from keras.backend import sqrt as k_sqrt
import keras.backend as K

# make predictions
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

# invert predictions
y_train_pred = scaler.inverse_transform(y_train_pred)
y_test_pred = scaler.inverse_transform(y_test_pred)

#invert originals
y_train_orig = scaler.inverse_transform(Y_train)
y_test_orig = scaler.inverse_transform(Y_test)

# calculate root mean squared error
trainScore = k_sqrt(k_mse(y_train_orig[:,0],
                          y_train_pred[:,0])
                   ).eval(session=K.get_session())
print('Train Score: {0:.2f} RMSE'.format(trainScore))

testScore = k_sqrt(k_mse(y_test_orig[:,0],
                         y_test_pred[:,0])
                  ).eval(session=K.get_session())
print('Test Score: {0:.2f} RMSE'.format(testScore))

我们得到以下结果:

Train Score: 23.27 RMSE
Test Score: 54.13 RMSE

我们可以看到,这不像我们在 TensorFlow 部分得到的那样完美;但是,这种差异是因为超参数值。我们留给您尝试不同的超参数值来调整此 Keras 模型以获得更好的结果。

Keras 中的 LSTM

创建 LSTM 模型只需添加 LSTM 层而不是SimpleRNN层,如下所示:

model.add(LSTM(units=4, input_shape=(X_train.shape[1], X_train.shape[2])))

模型结构如下所示:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 4)                 96        
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 5         
=================================================================
Total params: 101
Trainable params: 101
Non-trainable params: 0
_________________________________________________________________

笔记本ch-07b_RNN_TimeSeries_Keras中提供了 LSTM 模型的完整代码。

由于 LSTM 模型具有更多需要训练的参数,对于相同数量的迭代(20 个周期),我们得到更高的误差分数。我们留给您探索周期和其他超参数的各种值,以获得更好的结果:

Train Score: 32.21 RMSE
Test Score: 84.68 RMSE

Keras 中的 GRU

使用 TensorFlow 和 Keras 的一个优点是它们可以轻松创建模型。与 LSTM 一样,创建 GRU 模型只需添加 GRU 层而不是 LSTM 或SimpleRNN层,如下所示:

model.add(GRU(units=4, input_shape=(X_train.shape[1], X_train.shape[2])))

模型结构如下:

Layer (type)                 Output Shape              Param #   
=================================================================
gru_1 (GRU)                  (None, 4)                 72        
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 5         
=================================================================
Total params: 77
Trainable params: 77
Non-trainable params: 0

笔记本ch-07b_RNN_TimeSeries_Keras中提供了 GRU 模型的完整代码。

正如预期的那样,GRU 模型显示出与 LSTM 几乎相同的表现,我们让您尝试使用不同的超参数值来优化此模型:

Train Score: 31.49 RMSE
Test Score: 92.75 RMSE

总结

时间序列数据是基于序列的数据,因此 RNN 模型是从时间序列数据中学习的相关架构。在本章中,您学习了如何使用 TensorFlow(一个低级库)和 Keras(一个高级库)创建不同类型的 RNN 模型。我们只介绍了SimpleRNN,LSTM 和 GRU,但您应该探索可以使用 TensorFlow 和 Keras 创建的许多其他 RNN 变体。

在下一章中,我们将使用当前章节和前几章中构建的基础为各种自然语言处理NLP)任务创建文本数据的 RNN 模型。

八、TensorFlow 和 Keras 中的用于文本数据的 RNN

文本数据可以被视为一系列字符,单词,句子或段落。 循环神经网络RNN)已被证明是非常有用的序列神经网络结构。为了将神经网络模型应用于自然语言处理NLP)任务,文本被视为单词序列。事实证明,这对于 NLP 任务非常成功,例如:

  • 问题回答
  • 会话智能体或聊天机器人
  • 文本分类
  • 情感分析
  • 图像标题或描述文本生成
  • 命名实体识别
  • 语音识别和标记

NLP 与 TensorFlow 深度学习技术是一个广阔的领域,很难在一章中捕捉到。因此,我们尝试使用 Tensorflow 和 Keras 为您提供该领域中最流行和最重要的示例。一旦掌握了本章的内容,不要忘记探索和试验 NLP 的其他领域。

在本章中,我们将了解以下主题:

  • 词向量表示
  • 为 word2vec 模型准备数据
  • TensorFlow 和 Keras 中的 SkipGram 模型
  • 使用 t-SNE 可视化单词嵌入
  • TensorFlow 和 Keras 中使用 LSTM 模型的文本生成示例

词向量表示

为了从文本数据中学习神经网络模型的参数,首先,我们必须将文本或自然语言数据转换为可由神经网络摄取的格式。神经网络通常以数字向量的形式摄取文本。将原始文本数据转换为数字向量的算法称为字嵌入算法。

一种流行的字嵌入方法是我们在 MNIST 图像分类中看到的单热编码。假设我们的文本数据集由 60,000 个字典单词组成。然后,每个单词可以由具有 60,000 个元素的单热编码向量表示,其中除了表示具有值 1 的该单词的一个元素之外,所有其他元素具有零值。

然而,单热编码方法有其缺点。首先,对于具有大量单词的词汇,单热词向量的维数变得非常大。其次,人们无法找到与单热编码向量的单词相似性。例如,假设猫和小猫的向量分别为[1 0 0 0 0 0][0 0 0 0 0 1]。这些向量没有相似之处。

还有其他基于语料库的方法,用于将基于文本的语料库转换为数字向量,例如:

  • 单词频率 - 反向文档频率(TF-IDF)
  • 潜在语义分析(LSA)
  • 主题建模

最近,用数值向量表示单词的焦点已转移到基于分布假设的方法,这意味着具有相似语义含义的单词倾向于出现在类似的上下文中。

两种最广泛使用的方法称为 word2vec 和 GloVe。我们将在本章中使用 word2vec 进行练习。正如我们在前一段中所了解到的,单热编码给出了语料库字典中单词总数大小的维数。使用 word2vec 创建的单词向量的维度要低得多。

word2vec 系列模型使用两种架构构建:

  • CBOW:训练模型以学习给定上下文词的中心词的概率分布。因此,给定一组上下文单词,模型以您在高中语言课程中所做的填空方式预测中心单词。 CBOW 架构最适用于具有较小词汇表的数据集。
  • SkipGram:训练模型以学习给定中心词的上下文词的概率分布。因此,给定一个中心词,模型以您在高中语言课程中完成的句子方式预测语境词。

例如,让我们考虑一下这句话:

Vets2data.org is a non-profit for educating the US Military Veterans Community on Artificial Intelligence and Data Science.

在 CBOW 架构中,给出单词MilitaryCommunity,模型学习单词Veterans的概率,并在 SkipGram 架构中,给出单词Veterans,模型学习单词MilitaryCommunity的概率。

word2vec 模型以无监督的方式从文本语料库中学习单词向量。文本语料库分为成对的上下文单词和目标单词。虽然这些对是真正的对,但是伪对是用随机配对的上下文词和上下文词生成的,因此在数据中产生噪声。训练分类器以学习用于区分真对和假对的参数。该分类器的参数成为 word2vec 模型或单词向量。

关于 word2vec 理论背后的数学和理论的更多信息可以从以下论文中学到:

Mikolov, T., I. Sutskever, K. Chen, G. Corrado, and J. Dean. Distributed Representations of Words and Phrases and Their Compositionality. _Advances in Neural Information Processing Systems_, 2013, pp. 3111–3119.

Mikolov, T., K. Chen, G. Corrado, and J. Dean. Efficient Estimation of Word Representations in Vector Space. _arXiv_, 2013, pp. 1–12.

Rong, X. word2vec Parameter Learning Explained. _arXiv:1411.2738_, 2014, pp. 1–19.

Baroni, M., G. Dinu, and G. Kruszewski. Don’t Count, Predict! A Systematic Comparison of Context-Counting vs. Context-Predicting Semantic Vectors. 2014.

您应该使用 GloVe 和 word2vec 练习并应用适用于您的文本数据的方法。

有关 GLoVe 算法的更多信息可以从以下文章中学习:

Pennington, J., R. Socher, and C. Manning. GloVe: Global Vectors for Word Representation. 2014.

让我们通过在 TensorFlow 和 Keras 中创建单词向量来理解 word2vec 模型。

您可以按照 Jupyter 笔记本中的下几节的代码ch-08a_Embeddings_in_TensorFlow_and_Keras

用于 word2vec 模型的数据准备

我们将使用流行的 PTB 和 text8 数据集进行演示。

PennTreebankPTB)数据集是在 UPenn 进行的 Penn Treebank 项目的副产品。 PTB 项目团队在华尔街日报三年的故事中提取了大约一百万字,并以 Treebank II 风格对其进行了标注。 PTB 数据集有两种形式: 基本示例,大小约为 35 MB, 高级示例,大小约为 235 MB。我们将使用由 929K 字组成的简单数据集进行训练,73K 字用于验证,82K 字用于测试。建议您浏览高级数据集。有关 PTB 数据集的更多详细信息,请访问此链接

可以从此链接下载 PTB 数据集

text8 数据集是一个较短的清理版本的大型维基百科数据转储,大小约为 1GB。有关如何创建 text8 数据集的过程,请参见此链接

text8 数据集可以从此链接下载

使用我们的自定义库datasetslib中的load_data代码加载数据集:

load_data()函数执行以下操作:

  1. 如果数据集的 URL 在本地不可用,它将从数据集的 URL 下载数据存档。

  2. 由于PTB数据有三个文件,它首先从训练文件中读取文本,而对于text8,它从归档中读取第一个文件。

  3. 它将训练文件中的单词转换为词汇表,并为每个词汇单词分配一个唯一的数字,单词 ID,将其存储在集合word2id中,并准备反向词典,这样我们就可以从 ID 中查找单词,并将其存储在集合id2word中。

  4. 它使用集合word2id将文本文件转换为 ID 序列。

  5. 因此,在load_data的末尾,我们在训练数据集中有一系列数字,在集合id2word中有一个 ID 到字的映射。

让我们看一下从 text8 和 PTB 数据集加载的数据:

加载和准备 PTB 数据集

首先导入模块并加载数据如下::

from datasetslib.ptb import PTBSimple
ptb = PTBSimple()
# downloads data, converts words to ids, converts files to a list of ids
ptb.load_data() 
print('Train :',ptb.part['train'][0:5])
print('Test: ',ptb.part['test'][0:5])
print('Valid: ',ptb.part['valid'][0:5])
print('Vocabulary Length = ',ptb.vocab_len)

每个数据集的前五个元素以及词汇长度打印如下:

Train : [9970, 9971, 9972, 9974, 9975]
Test:  [102, 14, 24, 32, 752]
Valid:  [1132, 93, 358, 5, 329]
Vocabulary Length =  10000

我们将上下文窗口设置为两个单词并获得 CBOW 对:

ptb.skip_window=2
ptb.reset_index_in_epoch()
# in CBOW input is the context word and output is the target word
y_batch, x_batch = ptb.next_batch_cbow() 

print('The CBOW pairs : context,target')
for i in range(5 * ptb.skip_window):
    print('(', [ptb.id2word[x_i] for x_i in x_batch[i]],
          ',', y_batch[i], ptb.id2word[y_batch[i]], ')')

输出是:

The CBOW pairs : context,target
( ['aer', 'banknote', 'calloway', 'centrust'] , 9972 berlitz )
( ['banknote', 'berlitz', 'centrust', 'cluett'] , 9974 calloway )
( ['berlitz', 'calloway', 'cluett', 'fromstein'] , 9975 centrust )
( ['calloway', 'centrust', 'fromstein', 'gitano'] , 9976 cluett )
( ['centrust', 'cluett', 'gitano', 'guterman'] , 9980 fromstein )
( ['cluett', 'fromstein', 'guterman', 'hydro-quebec'] , 9981 gitano )
( ['fromstein', 'gitano', 'hydro-quebec', 'ipo'] , 9982 guterman )
( ['gitano', 'guterman', 'ipo', 'kia'] , 9983 hydro-quebec )
( ['guterman', 'hydro-quebec', 'kia', 'memotec'] , 9984 ipo )
( ['hydro-quebec', 'ipo', 'memotec', 'mlx'] , 9986 kia )

现在让我们看看 SkipGram 对:

ptb.skip_window=2
ptb.reset_index_in_epoch()
# in SkipGram input is the target word and output is the context word
x_batch, y_batch = ptb.next_batch()

print('The SkipGram pairs : target,context')
for i in range(5 * ptb.skip_window):
    print('(',x_batch[i], ptb.id2word[x_batch[i]],
        ',', y_batch[i], ptb.id2word[y_batch[i]],')')

输出为:

The SkipGram pairs : target,context
( 9972 berlitz , 9970 aer )
( 9972 berlitz , 9971 banknote )
( 9972 berlitz , 9974 calloway )
( 9972 berlitz , 9975 centrust )
( 9974 calloway , 9971 banknote )
( 9974 calloway , 9972 berlitz )
( 9974 calloway , 9975 centrust )
( 9974 calloway , 9976 cluett )
( 9975 centrust , 9972 berlitz )
( 9975 centrust , 9974 calloway )

加载和准备 text8 数据集

现在我们使用 text8 数据集执行相同的加载和预处理步骤:

from datasetslib.text8 import Text8
text8 = Text8()
text8.load_data() 
# downloads data, converts words to ids, converts files to a list of ids
print('Train:', text8.part['train'][0:5])
print('Vocabulary Length = ',text8.vocab_len)

我们发现词汇长度大约是 254,000 字:

Train: [5233, 3083, 11, 5, 194]
Vocabulary Length =  253854

一些教程通过查找最常用的单词或将词汇量大小截断为 10,000 个单词来操纵此数据。 但是,我们使用了 text8 数据集的第一个文件中的完整数据集和完整词汇表。

准备 CBOW 对:

text8.skip_window=2
text8.reset_index_in_epoch()
# in CBOW input is the context word and output is the target word
y_batch, x_batch = text8.next_batch_cbow() 

print('The CBOW pairs : context,target')
for i in range(5 * text8.skip_window):
    print('(', [text8.id2word[x_i] for x_i in x_batch[i]],
          ',', y_batch[i], text8.id2word[y_batch[i]], ')')

输出是:

The CBOW pairs : context,target
( ['anarchism', 'originated', 'a', 'term'] , 11 as )
( ['originated', 'as', 'term', 'of'] , 5 a )
( ['as', 'a', 'of', 'abuse'] , 194 term )
( ['a', 'term', 'abuse', 'first'] , 1 of )
( ['term', 'of', 'first', 'used'] , 3133 abuse )
( ['of', 'abuse', 'used', 'against'] , 45 first )
( ['abuse', 'first', 'against', 'early'] , 58 used )
( ['first', 'used', 'early', 'working'] , 155 against )
( ['used', 'against', 'working', 'class'] , 127 early )
( ['against', 'early', 'class', 'radicals'] , 741 working )

准备 SkipGram 对:

text8.skip_window=2
text8.reset_index_in_epoch()
# in SkipGram input is the target word and output is the context word
x_batch, y_batch = text8.next_batch()

print('The SkipGram pairs : target,context')
for i in range(5 * text8.skip_window):
    print('(',x_batch[i], text8.id2word[x_batch[i]],
        ',', y_batch[i], text8.id2word[y_batch[i]],')')

输出为:

The SkipGram pairs : target,context
( 11 as , 5233 anarchism )
( 11 as , 3083 originated )
( 11 as , 5 a )
( 11 as , 194 term )
( 5 a , 3083 originated )
( 5 a , 11 as )
( 5 a , 194 term )
( 5 a , 1 of )
( 194 term , 11 as )
( 194 term , 5 a )

准备小验证集

为了演示该示例,我们创建了一个包含 8 个单词的小型验证集,每个单词是从单词中随机选择的,其中单词 ID 在 0 到10 x 8之间。

valid_size = 8
x_valid = np.random.choice(valid_size * 10, valid_size, replace=False)
print(x_valid)

作为示例,我们将以下内容作为验证集:

valid:  [64 58 59 4 69 53 31 77]

我们将使用此验证集通过打印五个最接近的单词来演示嵌入一词的结果。

TensorFlow 中的 SkipGram 模型

现在我们已经准备好了训练和验证数据,让我们在 TensorFlow 中创建一个 SkipGram 模型。

我们首先定义超参数:

batch_size = 128
embedding_size = 128
skip_window = 2
n_negative_samples = 64
ptb.skip_window=2
learning_rate = 1.0
  • batch_size是要在单个批次中输入算法的目标和上下文单词对的数量
  • embedding_size是每个单词的单词向量或嵌入的维度
  • ptb.skip_window是在两个方向上的目标词的上下文中要考虑的词的数量
  • n_negative_samples是由 NCE 损失函数生成的负样本数,本章将进一步说明

在一些教程中,包括 TensorFlow 文档中的一个教程,还使用了一个参数num_skips。在这样的教程中,作者选择了num_skips(目标,上下文)对。例如,如果skip_window是 2,那么对的总数将是 4,如果num_skips被设置为 2,则只有两对将被随机选择用于训练。但是,我们考虑了所有的对以便保持训练练习简单。

定义训练数据的输入和输出占位符以及验证数据的张量:

inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size])
outputs = tf.placeholder(dtype=tf.int32, shape=[batch_size,1])
inputs_valid = tf.constant(x_valid, dtype=tf.int32)

定义一个嵌入矩阵,其行数等于词汇长度,列等于嵌入维度。该矩阵中的每一行将表示词汇表中一个单词的单词向量。使用在 -1.0 到 1.0 之间均匀采样的值填充此嵌入矩阵。

# define embeddings matrix with vocab_len rows and embedding_size columns
# each row represents vectore representation or embedding of a word
# in the vocbulary

embed_dist = tf.random_uniform(shape=[ptb.vocab_len, embedding_size],
                               minval=-1.0,maxval=1.0)
embed_matrix = tf.Variable(embed_dist,name='embed_matrix')

使用此矩阵,定义使用tf.nn.embedding_lookup()实现的嵌入查找表。tf.nn.embedding_lookup()有两个参数:嵌入矩阵和输入占位符。 查找函数返回inputs占位符中单词的单词向量。

# define the embedding lookup table
# provides the embeddings of the word ids in the input tensor
embed_ltable = tf.nn.embedding_lookup(embed_matrix, inputs)

embed_ltable也可以解释为输入层顶部的嵌入层。接下来,将嵌入层的输出馈送到 softmax 或噪声对比估计(NCE)层。 NCE 基于一个非常简单的想法,即训练基于逻辑回归的二分类器,以便从真实和嘈杂数据的混合中学习参数。

TensorFlow 文档进一步详细描述了 NCE

总之,基于 softmax 损失的模型在计算上是昂贵的,因为在整个词汇表中计算概率分布并对其进行归一化。基于 NCE 损耗的模型将其减少为二分类问题,即从噪声样本中识别真实样本。

NCE 的基本数学细节可以在以下 NIPS 论文中找到:使用噪声对比估计高效学习词嵌入,作者 Andriy Mnih 和 Koray Kavukcuoglu。该论文可从此链接获得

tf.nn.nce_loss()函数在求值计算损耗时自动生成负样本:参数num_sampled设置为等于负样本数(n_negative_samples)。此参数指定要绘制的负样本数。

# define noise-contrastive estimation (NCE) loss layer
nce_dist = tf.truncated_normal(shape=[ptb.vocab_len, embedding_size],
                               stddev=1.0 /
                               tf.sqrt(embedding_size * 1.0)
                               )
nce_w = tf.Variable(nce_dist)
nce_b = tf.Variable(tf.zeros(shape=[ptb.vocab_len]))

loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_w,
                                     biases=nce_b,
                                     inputs=embed_ltable,
                                     labels=outputs,
                                     num_sampled=n_negative_samples,
                                     num_classes=ptb.vocab_len
                                     )
                      )

接下来,计算验证集中的样本与嵌入矩阵之间的余弦相似度:

  1. 为了计算相似性得分,首先,计算嵌入矩阵中每个单词向量的 L2 范数。
# Compute the cosine similarity between validation set samples
# and all embeddings.
norm = tf.sqrt(tf.reduce_sum(tf.square(embed_matrix), 1, 
                             keep_dims=True))
normalized_embeddings = embed_matrix / norm
  1. 查找验证集中的样本的嵌入或单词向量:
embed_valid = tf.nn.embedding_lookup(normalized_embeddings, 
                                     inputs_valid)
  1. 通过将验证集的嵌入与嵌入矩阵相乘来计算相似性得分。
similarity = tf.matmul(
    embed_valid, normalized_embeddings, transpose_b=True)

这给出了具有(valid_sizevocab_len)形状的张量。张量中的每一行指的是验证词和词汇单词之间的相似性得分。

接下来,定义 SGD 优化器,学习率为 0.9,历时 50 个周期。

n_epochs = 10
learning_rate = 0.9
n_batches = ptb.n_batches(batch_size)
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
            .minimize(loss)

对于每个周期:

  1. 逐批在整个数据集上运行优化器。
ptb.reset_index_in_epoch()
for step in range(n_batches):
    x_batch, y_batch = ptb.next_batch()
    y_batch = dsu.to2d(y_batch,unit_axis=1)
    feed_dict = {inputs: x_batch, outputs: y_batch}
    _, batch_loss = tfs.run([optimizer, loss], feed_dict=feed_dict)
    epoch_loss += batch_loss
  1. 计算并打印周期的平均损失。
 epoch_loss = epoch_loss / n_batches 
 print('\n','Average loss after epoch ', epoch, ': ', epoch_loss)
  1. 在周期结束时,计算相似性得分。
similarity_scores = tfs.run(similarity)
  1. 对于验证集中的每个单词,打印具有最高相似性得分的五个单词。
top_k = 5 
for i in range(valid_size):
    similar_words = (-similarity_scores[i,:])
                    .argsort()[1:top_k + 1]
    similar_str = 'Similar to {0:}:'
                    .format(ptb.id2word[x_valid[i]])
    for k in range(top_k):
        similar_str = '{0:} {1:},'.format(similar_str, 
                        ptb.id2word[similar_words[k]])
    print(similar_str)

最后,在完成所有周期之后,计算可在学习过程中进一步利用的嵌入向量:

final_embeddings = tfs.run(normalized_embeddings)

完整的训练代码如下:

n_epochs = 10
learning_rate = 0.9
n_batches = ptb.n_batches_wv()
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)

with tf.Session() as tfs:
    tf.global_variables_initializer().run()
    for epoch in range(n_epochs):
        epoch_loss = 0
        ptb.reset_index()
        for step in range(n_batches):
            x_batch, y_batch = ptb.next_batch_sg()
            y_batch = nputil.to2d(y_batch, unit_axis=1)
            feed_dict = {inputs: x_batch, outputs: y_batch}
            _, batch_loss = tfs.run([optimizer, loss], feed_dict=feed_dict)
            epoch_loss += batch_loss
        epoch_loss = epoch_loss / n_batches
        print('\nAverage loss after epoch ', epoch, ': ', epoch_loss)

        # print closest words to validation set at end of every epoch
        similarity_scores = tfs.run(similarity)
        top_k = 5
        for i in range(valid_size):
            similar_words = (-similarity_scores[i, :]
                             ).argsort()[1:top_k + 1]
            similar_str = 'Similar to {0:}:'.format(
                ptb.id2word[x_valid[i]])
            for k in range(top_k):
                similar_str = '{0:} {1:},'.format(
                    similar_str, ptb.id2word[similar_words[k]])
            print(similar_str)
    final_embeddings = tfs.run(normalized_embeddings)

这是我们分别在第 1 和第 10 周期之后得到的输出:

Average loss after epoch  0 :  115.644006802
Similar to we: types, downturn, internal, by, introduce,
Similar to been: said, funds, mcgraw-hill, street, have,
Similar to also: will, she, next, computer, 's,
Similar to of: was, and, milk, dollars, $,
Similar to last: be, october, acknowledging, requested, computer,
Similar to u.s.: plant, increase, many, down, recent,
Similar to an: commerce, you, some, american, a,
Similar to trading: increased, describes, state, companies, in,

Average loss after epoch  9 :  5.56538496033
Similar to we: types, downturn, introduce, internal, claims,
Similar to been: exxon, said, problem, mcgraw-hill, street,
Similar to also: will, she, ssangyong, audit, screens,
Similar to of: seasonal, dollars, motor, none, deaths,
Similar to last: acknowledging, allow, incorporated, joint, requested,
Similar to u.s.: undersecretary, typically, maxwell, recent, increase,
Similar to an: banking, officials, imbalances, americans, manager,
Similar to trading: describes, increased, owners, committee, else,

最后,我们运行 5000 个周期的模型并获得以下结果:

Average loss after epoch  4999 :  2.74216903135
Similar to we: matter, noted, here, classified, orders,
Similar to been: good, precedent, medium-sized, gradual, useful,
Similar to also: introduce, england, index, able, then,
Similar to of: indicator, cleveland, theory, the, load,
Similar to last: dec., office, chrysler, march, receiving,
Similar to u.s.: label, fannie, pressures, squeezed, reflection,
Similar to an: knowing, outlawed, milestones, doubled, base,
Similar to trading: associates, downturn, money, portfolios, go,

尝试进一步运行,最多 50,000 个周期,以获得更好的结果。

同样,我们在 50 个周期之后使用 text8 模型得到以下结果:

Average loss after epoch  49 :  5.74381046423
Similar to four: five, three, six, seven, eight,
Similar to all: many, both, some, various, these,
Similar to between: with, through, thus, among, within,
Similar to a: another, the, any, each, tpvgames,
Similar to that: which, however, although, but, when,
Similar to zero: five, three, six, eight, four,
Similar to is: was, are, has, being, busan,
Similar to no: any, only, the, another, trinomial,

t-SNE 和单词嵌入可视化

让我们可视化我们在上一节中生成的单词嵌入。 t-SNE 是在二维空间中显示高维数据的最流行的方法。我们将使用 scikit-learn 库中的方法,并重用 TensorFlow 文档中给出的代码,来绘制我们刚学过的词嵌入的图形。

TensorFlow 文档中的原始代码可从此链接获得

以下是我们如何实现该程序:

  1. 创建tsne模型:
tsne = TSNE(perplexity=30, n_components=2,
            init='pca', n_iter=5000, method='exact')
  1. 将要显示的嵌入数限制为 500,否则,图形变得非常难以理解:
n_embeddings = 500
  1. 通过调用tsne模型上的fit_transform()方法并将final_embeddings的第一个n_embeddings作为输入来创建低维表示。
low_dim_embeddings = tsne.fit_transform(
    final_embeddings[:n_embeddings, :])
  1. 找到我们为图表选择的单词向量的文本表示:
labels = [ptb.id2word[i] for i in range(n_embeddings)]
  1. 最后,绘制嵌入图:
plot_with_labels(low_dim_embeddings, labels)

我们得到以下绘图:

t-SNE visualization of embeddings for PTB data set

同样,从 text8 模型中,我们得到以下图:

t-SNE visualization of embeddings for text8 data set

Keras 中的 SkipGram 模型

使用 Keras 的嵌入模型的流程与 TensorFlow 保持一致。

  • 在 Keras 函数式或顺序模型中创建网络架构
  • 将目标和上下文单词的真实性对提供给网络
  • 查找目标和上下文单词的单词向量
  • 执行单词向量的点积来获得相似性得分
  • 将相似性得分通过 sigmoid 层以将输出作为真或假对

现在让我们使用 Keras 函数式 API 实现这些步骤:

  1. 导入所需的库:
from keras.models import Model
from keras.layers.embeddings import Embedding
from keras.preprocessing import sequence
from keras.preprocessing.sequence import skipgrams
from keras.layers import Input, Dense, Reshape, Dot, merge
import keras

重置图,以便清除以前在 Jupyter 笔记本中运行的任何后续效果:

# reset the jupyter buffers
tf.reset_default_graph()
keras.backend.clear_session()
  1. 创建一个验证集,我们将用它来打印我们的模型在训练结束时找到的相似单词:
valid_size = 8
x_valid = np.random.choice(valid_size * 10, valid_size, replace=False)
print('valid: ',x_valid)
  1. 定义所需的超参数:
batch_size = 1024
embedding_size = 512 
n_negative_samples = 64
ptb.skip_window=2
  1. 使用keras.preprocessing.sequence中的make_sampling_table()函数创建一个大小等于词汇长度的样本表。接下来,使用keras.preprocessing.sequence中的函数skipgrams()生成上下文和目标词对以及表示它们是真对还是假对的标签。
sample_table = sequence.make_sampling_table(ptb.vocab_len)
pairs, labels= sequence.skipgrams(ptb.part['train'],
        ptb.vocab_len,window_size=ptb.skip_window,
        sampling_table=sample_table)
  1. 让我们打印一些使用以下代码生成的伪造和真实对:
print('The SkipGram pairs : target,context')
for i in range(5 * ptb.skip_window):
    print(['{} {}'.format(id,ptb.id2word[id]) \ 
        for id in pairs[i]],':',labels[i])

对配对如下:

The SkipGram pairs : target,context
['547 trying', '5 to'] : 1
['4845 bargain', '2 <eos>'] : 1
['1705 election', '198 during'] : 1
['4704 flows', '8117 gun'] : 0
['13 is', '37 company'] : 1
['625 above', '132 three'] : 1
['5768 pessimistic', '1934 immediate'] : 0
['637 china', '2 <eos>'] : 1
['258 five', '1345 pence'] : 1
['1956 chrysler', '8928 exercises'] : 0
  1. 从上面生成的对中拆分目标和上下文单词,以便将它们输入模型。将目标和上下文单词转换为二维数组。
x,y=zip(*pairs)
x=np.array(x,dtype=np.int32)
x=dsu.to2d(x,unit_axis=1)
y=np.array(y,dtype=np.int32)
y=dsu.to2d(y,unit_axis=1)
labels=np.array(labels,dtype=np.int32)
labels=dsu.to2d(labels,unit_axis=1)
  1. 定义网络的架构。正如我们所讨论的,必须将目标和上下文单词输入网络,并且需要从嵌入层中查找它们的向量。因此,首先我们分别为目标和上下文单词定义输入,嵌入和重塑层:
# build the target word model
target_in = Input(shape=(1,),name='target_in')
target = Embedding(ptb.vocab_len,embedding_size,input_length=1,
            name='target_em')(target_in)
target = Reshape((embedding_size,1),name='target_re')(target)

# build the context word model
context_in = Input((1,),name='context_in')
context = Embedding(ptb.vocab_len,embedding_size,input_length=1,
            name='context_em')(context_in)
context = Reshape((embedding_size,1),name='context_re')(context)
  1. 接下来,构建这两个模型的点积,将其输入 sigmoid 层以生成输出标签:
# merge the models with the dot product to check for 
# similarity and add sigmoid layer
output = Dot(axes=1,name='output_dot')([target,context])
output = Reshape((1,),name='output_re')(output)
output = Dense(1, activation='sigmoid',name='output_sig')(output)
  1. 从我们刚刚创建的输入和输出模型构建函数式模型:
# create the functional model for finding word vectors
model = Model(inputs=[target_in,context_in],outputs=output)
model.compile(loss='binary_crossentropy', optimizer='adam')
  1. 此外,在给定输入目标词的情况下,构建一个模型,用于预测与所有单词的相似性:
# merge the models and create model to check for cosine similarity
similarity = Dot(axes=0,normalize=True,
            name='sim_dot')([target,context])
similarity_model = Model(inputs=[target_in,context_in],
            outputs=similarity)

让我们打印模型摘要:

__________________________________________________________________________
Layer (type)               Output Shape          Param #     Connected to                     
==========================================================================
target_in (InputLayer)     (None, 1)          0                                            
__________________________________________________________________________
context_in (InputLayer)    (None, 1)          0                                            
__________________________________________________________________________
target_em (Embedding)      (None, 1, 512)     5120000     target_in[0][0]                  
__________________________________________________________________________
context_em (Embedding)     (None, 1, 512)     5120000     context_in[0][0]                 
__________________________________________________________________________
target_re (Reshape)        (None, 512, 1)     0           target_em[0][0]                  
__________________________________________________________________________
context_re (Reshape)       (None, 512, 1)     0           context_em[0][0]                 
__________________________________________________________________________
output_dot (Dot)           (None, 1, 1)       0           target_re[0][0]                  
                                                          context_re[0][0]                 
__________________________________________________________________________
output_re (Reshape)        (None, 1)          0           output_dot[0][0]                 
__________________________________________________________________________
output_sig (Dense)         (None, 1)          2           output_re[0][0]                  
==========================================================================
Total params: 10,240,002
Trainable params: 10,240,002
Non-trainable params: 0
__________________________________________________________________________
  1. 接下来,训练模型。我们只训练了 5 个周期,但你应该尝试更多的周期,至少 1000 或 10,000 个周期。

请记住,这将需要几个小时,因为这不是最优化的代码。 欢迎您使用本书和其他来源的提示和技巧进一步优化代码。

n_epochs = 5
batch_size = 1024
model.fit([x,y],labels,batch_size=batch_size, epochs=n_epochs)

让我们根据这个模型发现的单词向量打印单词的相似度:

# print closest words to validation set at end of training
top_k = 5
y_val = np.arange(ptb.vocab_len, dtype=np.int32)
y_val = dsu.to2d(y_val,unit_axis=1)
for i in range(valid_size):
    x_val = np.full(shape=(ptb.vocab_len,1),fill_value=x_valid[i], 
            dtype=np.int32)
    similarity_scores = similarity_model.predict([x_val,y_val])
    similarity_scores=similarity_scores.flatten()
    similar_words = (-similarity_scores).argsort()[1:top_k + 1]
    similar_str = 'Similar to {0:}:'.format(ptb.id2word[x_valid[i]])
    for k in range(top_k):
        similar_str = '{0:} {1:},'.format(similar_str, 
                        ptb.id2word[similar_words[k]])
    print(similar_str)

我们得到以下输出:

Similar to we: rake, kia, sim, ssangyong, memotec,
Similar to been: nahb, sim, rake, punts, rubens,
Similar to also: photography, snack-food, rubens, nahb, ssangyong,
Similar to of: isi, rake, memotec, kia, mlx,
Similar to last: rubens, punts, memotec, sim, photography,
Similar to u.s.: mlx, memotec, punts, rubens, kia,
Similar to an: memotec, isi, ssangyong, rake, sim,
Similar to trading: rake, rubens, swapo, mlx, nahb,

到目前为止,我们已经看到了如何使用 TensorFlow 及其高级库 Keras 创建单词向量或嵌入。现在让我们看看如何使用 TensorFlow 和 Keras 来学习模型并将模型应用于一些与 NLP 相关的任务的预测。

TensorFlow 和 Keras 中的 RNN 模型和文本生成

文本生成是 NLP 中 RNN 模型的主要应用之一。针对文本序列训练 RNN 模型,然后通过提供种子文本作为输入来生成文本序列。让我们试试 text8 数据集。

让我们加载 text8 数据集并打印前 100 个单词:

from datasetslib.text8 import Text8
text8 = Text8()
# downloads data, converts words to ids, converts files to a list of ids
text8.load_data() 
print(' '.join([text8.id2word[x_i] for x_i in text8.part['train'][0:100]]))

我们得到以下输出:

anarchism originated as a term of abuse first used against early working class radicals including the diggers of the english revolution and the sans culottes of the french revolution whilst the term is still used in a pejorative way to describe any act that used violent means to destroy the organization of society it has also been taken up as a positive label by self defined anarchists the word anarchism is derived from the greek without archons ruler chief king anarchism as a political philosophy is the belief that rulers are unnecessary and should be abolished although there are differing

在我们的笔记本示例中,我们将数据加载剪切为 5,000 字的文本,因为较大的文本需要高级技术,例如分布式或批量,我们希望保持示例简单。

from datasetslib.text8 import Text8
text8 = Text8()
text8.load_data(clip_at=5000) 
print('Train:', text8.part['train'][0:5])
print('Vocabulary Length = ',text8.vocab_len)

我们看到词汇量现在减少到 1,457 个单词。

Train: [  8 497   7   5 116]
Vocabulary Length =  1457

在我们的示例中,我们构造了一个非常简单的单层 LSTM。为了训练模型,我们使用 5 个单词作为输入来学习第六个单词的参数。输入层是 5 个字,隐藏层是具有 128 个单元的 LSTM 单元,最后一层是完全连接的层,其输出等于词汇量大小。由于我们正在演示这个例子,我们没有使用单词向量,而是使用非常简单的单热编码输出向量。

一旦模型被训练,我们用 2 个不同的字符串作为生成更多字符的种子来测试它:

  • random5:随机选择 5 个单词生成的字符串。
  • first5:从文本的前 5 个单词生成的字符串。
random5 = np.random.choice(n_x * 50, n_x, replace=False)
print('Random 5 words: ',id2string(random5))
first5 = text8.part['train'][0:n_x].copy()
print('First 5 words: ',id2string(first5))

我们看到种子串是:

Random 5 words:  free bolshevik be n another
First 5 words:  anarchism originated as a term

对于您的执行,随机种子字符串可能不同。

现在让我们首先在 TensorFlow 中创建 LSTM 模型。

TensorFlow 中的 LSTM 和文本生成

您可以在 Jupyter 笔记本ch-08b_RNN_Text_TensorFlow中按照本节的代码进行操作。

我们使用以下步骤在 TensorFlow 中实现文本生成 LSTM:

  1. 让我们为xy定义参数和占位符:
batch_size = 128
n_x = 5 # number of input words
n_y = 1 # number of output words
n_x_vars = 1 # in case of our text, there is only 1 variable at each timestep
n_y_vars = text8.vocab_len
state_size = 128
learning_rate = 0.001
x_p = tf.placeholder(tf.float32, [None, n_x, n_x_vars], name='x_p') 
y_p = tf.placeholder(tf.float32, [None, n_y_vars], name='y_p')

对于输入,我们使用单词的整数表示,因此n_x_vars是 1。对于输出,我们使用单热编码值,因此输出的数量等于词汇长度。

  1. 接下来,创建一个长度为n_x的张量列表:
x_in = tf.unstack(x_p,axis=1,name='x_in')
  1. 接下来,从输入和单元创建 LSTM 单元和静态 RNN 网络:
cell = tf.nn.rnn_cell.LSTMCell(state_size)
rnn_outputs, final_states = tf.nn.static_rnn(cell, x_in,dtype=tf.float32)
  1. 接下来,我们定义最终层的权重,偏差和公式。最后一层只需要为第六个单词选择输出,因此我们应用以下公式来仅获取最后一个输出:
# output node parameters
w = tf.get_variable('w', [state_size, n_y_vars], initializer= tf.random_normal_initializer)
b = tf.get_variable('b', [n_y_vars], initializer=tf.constant_initializer(0.0))
y_out = tf.matmul(rnn_outputs[-1], w) + b
  1. 接下来,创建一个损失函数和优化器:
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(
        logits=y_out, labels=y_p))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
            .minimize(loss)
  1. 创建我们可以在会话块中运行的准确率函数,以检查训练模式的准确率:
n_correct_pred = tf.equal(tf.argmax(y_out,1), tf.argmax(y_p,1))
accuracy = tf.reduce_mean(tf.cast(n_correct_pred, tf.float32))
  1. 最后,我们训练模型 1000 个周期,并每 100 个周期打印结果。此外,每 100 个周期,我们从上面描述的种子字符串打印生成的文本。

LSTM 和 RNN 网络需要对大量数据集进行大量周期的训练,以获得更好的结果。 请尝试加载完整的数据集并在计算机上运行 50,000 或 80,000 个周期,并使用其他超参数来改善结果。

n_epochs = 1000
learning_rate = 0.001
text8.reset_index_in_epoch()
n_batches = text8.n_batches_seq(batch_size=batch_size,n_tx=n_x,n_ty=n_y)
n_epochs_display = 100

with tf.Session() as tfs:
    tf.global_variables_initializer().run()

    for epoch in range(n_epochs):
        epoch_loss = 0
        epoch_accuracy = 0
        for step in range(n_batches):
            x_batch, y_batch = text8.next_batch_seq(batch_size=batch_size,
                                n_tx=n_x,n_ty=n_y)
            y_batch = dsu.to2d(y_batch,unit_axis=1)
            y_onehot = np.zeros(shape=[batch_size,text8.vocab_len],
                        dtype=np.float32)
            for i in range(batch_size):
                y_onehot[i,y_batch[i]]=1

            feed_dict = {x_p: x_batch.reshape(-1, n_x, n_x_vars), 
                         y_p: y_onehot}
            _, batch_accuracy, batch_loss = tfs.run([optimizer,accuracy,
                                            loss],feed_dict=feed_dict)
            epoch_loss += batch_loss
            epoch_accuracy += batch_accuracy

        if (epoch+1) % (n_epochs_display) == 0:
            epoch_loss = epoch_loss / n_batches
            epoch_accuracy = epoch_accuracy / n_batches
            print('\nEpoch {0:}, Average loss:{1:}, Average accuracy:{2:}'.
                    format(epoch,epoch_loss,epoch_accuracy ))

            y_pred_r5 = np.empty([10])
            y_pred_f5 = np.empty([10])

            x_test_r5 = random5.copy()
            x_test_f5 = first5.copy()
            # let us generate text of 10 words after feeding 5 words
            for i in range(10):
                for x,y in zip([x_test_r5,x_test_f5],
                               [y_pred_r5,y_pred_f5]):
                    x_input = x.copy()
                    feed_dict = {x_p: x_input.reshape(-1, n_x, n_x_vars)}
                    y_pred = tfs.run(y_out, feed_dict=feed_dict)
                    y_pred_id = int(tf.argmax(y_pred, 1).eval())
                    y[i]=y_pred_id
                    x[:-1] = x[1:]
                    x[-1] = y_pred_id
            print(' Random 5 prediction:',id2string(y_pred_r5))
            print(' First 5 prediction:',id2string(y_pred_f5))

结果如下:

Epoch 99, Average loss:1.3972469369570415, Average accuracy:0.8489583333333334
  Random 5 prediction: labor warren together strongly profits strongly supported supported co without
  First 5 prediction: market own self free together strongly profits strongly supported supported

Epoch 199, Average loss:0.7894854595263799, Average accuracy:0.9186197916666666
  Random 5 prediction: syndicalists spanish class movements also also anarcho anarcho anarchist was
  First 5 prediction: five civil association class movements also anarcho anarcho anarcho anarcho

Epoch 299, Average loss:1.360412875811259, Average accuracy:0.865234375
  Random 5 prediction: anarchistic beginnings influenced true tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy
  First 5 prediction: early civil movement be for was two most most most

Epoch 399, Average loss:1.1692512730757396, Average accuracy:0.8645833333333334
  Random 5 prediction: including war than than revolutionary than than war than than
  First 5 prediction: left including including including other other other other other other

Epoch 499, Average loss:0.5921860883633295, Average accuracy:0.923828125
  Random 5 prediction: ever edited interested interested variety variety variety variety variety variety
  First 5 prediction: english market herbert strongly price interested variety variety variety variety

Epoch 599, Average loss:0.8356450994809469, Average accuracy:0.8958333333333334
  Random 5 prediction: management allow trabajo trabajo national national mag mag ricardo ricardo
  First 5 prediction: spain prior am working n war war war self self

Epoch 699, Average loss:0.7057955612738928, Average accuracy:0.8971354166666666
  Random 5 prediction: teachings can directive tend resist obey christianity author christianity christianity
  First 5 prediction: early early called social called social social social social social

Epoch 799, Average loss:0.772875706354777, Average accuracy:0.90234375
  Random 5 prediction: associated war than revolutionary revolutionary revolutionary than than revolutionary revolutionary
  First 5 prediction: political been hierarchy war than see anti anti anti anti

Epoch 899, Average loss:0.43675946692625683, Average accuracy:0.9375
  Random 5 prediction: individualist which which individualist warren warren tucker benjamin how tucker
  First 5 prediction: four at warren individualist warren published considered considered considered considered

Epoch 999, Average loss:0.23202441136042276, Average accuracy:0.9602864583333334
  Random 5 prediction: allow allow trabajo you you you you you you you
  First 5 prediction: labour spanish they they they movement movement anarcho anarcho two

生成的文本中的重复单词是常见的,并且应该更好地训练模型。虽然模型的准确率提高到 96%,但仍然不足以生成清晰的文本。尝试增加 LSTM 单元/隐藏层的数量,同时在较大的数据集上运行模型以获取大量周期。

现在让我们在 Keras 建立相同的模型:

Keras 中的 LSTM 和文本生成

您可以在 Jupyter 笔记本ch-08b_RNN_Text_Keras中按照本节的代码进行操作。

我们在 Keras 实现文本生成 LSTM,步骤如下:

  1. 首先,我们将所有数据转换为两个张量,张量x有五列,因为我们一次输入五个字,张量y只有一列输出。我们将y或标签张量转换为单热编码表示。

请记住,在大型数据集的实践中,您将使用 word2vec 嵌入而不是单热表示。

# get the data
x_train, y_train = text8.seq_to_xy(seq=text8.part['train'],n_tx=n_x,n_ty=n_y)
# reshape input to be [samples, time steps, features]
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1],1)
y_onehot = np.zeros(shape=[y_train.shape[0],text8.vocab_len],dtype=np.float32)
for i in range(y_train.shape[0]):
    y_onehot[i,y_train[i]]=1
  1. 接下来,仅使用一个隐藏的 LSTM 层定义 LSTM 模型。由于我们的输出不是序列,我们还将return_sequences设置为False
n_epochs = 1000
batch_size=128
state_size=128
n_epochs_display=100

# create and fit the LSTM model
model = Sequential()
model.add(LSTM(units=state_size,
                input_shape=(x_train.shape[1], x_train.shape[2]),
                return_sequences=False
                )
          )
model.add(Dense(text8.vocab_len))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.summary()

该模型如下所示:

Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 128)               66560     
_________________________________________________________________
dense_1 (Dense)              (None, 1457)              187953    
_________________________________________________________________
activation_1 (Activation)    (None, 1457)              0         
=================================================================
Total params: 254,513
Trainable params: 254,513
Non-trainable params: 0
_________________________________________________________________
  1. 对于 Keras,我们运行一个循环来运行 10 次,在每次迭代中训练 100 个周期的模型并打印文本生成的结果。以下是训练模型和生成文本的完整代码:
for j in range(n_epochs // n_epochs_display):
     model.fit(x_train, y_onehot, epochs=n_epochs_display,
                         batch_size=batch_size,verbose=0)
     # generate text
     y_pred_r5 = np.empty([10])
     y_pred_f5 = np.empty([10])
     x_test_r5 = random5.copy()
     x_test_f5 = first5.copy()
     # let us generate text of 10 words after feeding 5 words
     for i in range(10):
         for x,y in zip([x_test_r5,x_test_f5],
                        [y_pred_r5,y_pred_f5]):
             x_input = x.copy()
             x_input = x_input.reshape(-1, n_x, n_x_vars)
             y_pred = model.predict(x_input)[0]
             y_pred_id = np.argmax(y_pred)
             y[i]=y_pred_id
             x[:-1] = x[1:]
             x[-1] = y_pred_id
     print('Epoch: ',((j+1) * n_epochs_display)-1)
     print(' Random5 prediction:',id2string(y_pred_r5))
     print(' First5 prediction:',id2string(y_pred_f5))
  1. 输出并不奇怪,从重复单词开始,模型有所改进,但是可以通过更多 LSTM 层,更多数据,更多训练迭代和其他超参数调整来进一步提高。
Random 5 words: free bolshevik be n another 
First 5 words: anarchism originated as a term

预测的输出如下:

Epoch: 99 
    Random5 prediction: anarchistic anarchistic wrote wrote wrote wrote wrote wrote wrote wrote 
    First5 prediction: right philosophy than than than than than than than than 

Epoch: 199 
    Random5 prediction: anarchistic anarchistic wrote wrote wrote wrote wrote wrote wrote wrote 
    First5 prediction: term i revolutionary than war war french french french french 

Epoch: 299 
    Random5 prediction: anarchistic anarchistic wrote wrote wrote wrote wrote wrote wrote wrote 
    First5 prediction: term i revolutionary revolutionary revolutionary revolutionary revolutionary revolutionary revolutionary revolutionary 

Epoch: 399 
    Random5 prediction: anarchistic anarchistic wrote wrote wrote wrote wrote wrote wrote wrote 
    First5 prediction: term i revolutionary labor had had french french french french 

Epoch: 499 
    Random5 prediction: anarchistic anarchistic amongst wrote wrote wrote wrote wrote wrote wrote 
    First5 prediction: term i revolutionary labor individualist had had french french french 

Epoch: 599 
    Random5 prediction: tolstoy wrote tolstoy wrote wrote wrote wrote wrote wrote wrote     First5 prediction: term i revolutionary labor individualist had had had had had 

Epoch: 699 
    Random5 prediction: tolstoy wrote tolstoy wrote wrote wrote wrote wrote wrote wrote     First5 prediction: term i revolutionary labor individualist had had had had had 

Epoch: 799 
    Random5 prediction: tolstoy wrote tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy 
    First5 prediction: term i revolutionary labor individualist had had had had had 

Epoch: 899 
    Random5 prediction: tolstoy wrote tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy 
    First5 prediction: term i revolutionary labor should warren warren warren warren warren 

Epoch: 999 
    Random5 prediction: tolstoy wrote tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy tolstoy 
    First5 prediction: term i individualist labor should warren warren warren warren warren

如果您注意到我们在 LSTM 模型的输出中有重复的单词用于文本生成。虽然超参数和网络调整可以消除一些重复,但还有其他方法可以解决这个问题。我们得到重复单词的原因是模型总是从单词的概率分布中选择具有最高概率的单词。这可以改变以选择诸如在连续单词之间引入更大可变性的单词。

总结

在本章中,我们学习了单词嵌入的方法,以找到更好的文本数据元素表示。随着神经网络和深度学习摄取大量文本数据,单热表示和其他单词表示方法变得低效。我们还学习了如何使用 t-SNE 图来可视化文字嵌入。我们使用简单的 LSTM 模型在 TensorFlow 和 Keras 中生成文本。类似的概念可以应用于各种其他任务,例如情感分析,问答和神经机器翻译。

在我们深入研究先进的 TensorFlow 功能(如迁移学习,强化学习,生成网络和分布式 TensorFlow)之前,我们将在下一章中看到如何将 TensorFlow 模型投入生产。