长短期记忆(LSTM)循环神经网络的一个强大功能是它们能够记住长时间序列间隔内的观测值。
这可以通过设计一个简单的序列回显问题来证明,其中整个输入序列或输入序列的连续块作为输出序列被回显。
开发 LSTM 循环神经网络来解决序列回显问题,既能很好地展示 LSTM 的强大功能,又可以用来展示最先进的循环神经网络架构。
在这篇文章中,您将学习如何使用 Keras 深度学习库在 Python 中开发 LSTM 来解决完整和部分序列回显问题。
完成本教程后,您将了解:
- 如何生成随机整数序列,使用 One-Hot 编码表示它们,并将序列构建为具有输入和输出对的监督学习问题。
- 如何开发一个序列到序列的 LSTM,以将整个输入序列作为输出回显。
- 如何开发一个编码器-解码器 LSTM,以回显长度与输入序列不同的部分序列。
通过我的新书《使用 Python 的长短期记忆网络》启动您的项目,其中包括所有示例的**分步教程**和 **Python 源代码文件**。
让我们开始吧。
- 2020年1月更新:更新了Keras 2.3和TensorFlow 2.0的API。

如何使用编码器-解码器 LSTM 回显随机整数序列
图片由 hammy24601 提供,保留部分权利。
概述
本教程分为3个部分;它们是
- 序列回显问题
- 回显整个序列(序列到序列模型)
- 回显部分序列(编码器-解码器模型)
环境
本教程假定您已安装 Python SciPy 环境。您可以使用 Python 2 或 3。
本教程假定您已安装 Keras v2.0 或更高版本,并使用 TensorFlow 或 Theano 后端。
本教程还假定您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。
如果您需要帮助设置 Python 环境,请参阅此帖子
需要 LSTM 帮助进行序列预测吗?
参加我的免费7天电子邮件课程,了解6种不同的LSTM架构(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
序列回显问题
序列回显问题涉及让 LSTM 逐个观察序列,然后要求网络回显部分或完整的连续观察列表。
这迫使网络记住连续的观测块,是 LSTM 循环神经网络学习能力的一个很好的演示。
第一步是编写一些代码来生成随机整数序列并将其编码以供网络使用。
这包括 3 个步骤
- 生成随机序列
- One-Hot 编码随机序列
- 构建编码序列以供学习
生成随机序列
我们可以使用 randint() 函数在 Python 中生成随机整数,该函数接受两个参数,表示要从中抽取值的整数范围。
在本教程中,我们将问题定义为具有 0 到 99 之间整数值和 100 个唯一值。
1 |
randint(0, 99) |
我们可以将其放入一个名为 generate_sequence() 的函数中,该函数将生成所需长度的随机整数序列,默认长度设置为 25 个元素。
该函数如下所示。
1 2 3 |
# 生成 [0, 99] 中的随机数序列 def generate_sequence(length=25): return [randint(0, 99) for _ in range(length)] |
One-Hot 编码随机序列
一旦我们生成了随机整数序列,我们需要将它们转换为适合训练 LSTM 网络的格式。
一种选择是将整数重新缩放到 [0,1] 范围。这会奏效,并且需要将问题表述为回归。
我感兴趣的是预测正确的数字,而不是一个接近预期值的数字。这意味着我更喜欢将问题表述为分类而不是回归,其中预期输出是一个类别,并且有 100 个可能的类别值。
在这种情况下,我们可以对整数值使用 One-Hot 编码,其中每个值由一个 100 个元素的二进制向量表示,除了整数的索引标记为 1 之外,所有值都为“0”。
下面名为 one_hot_encode() 的函数定义了如何遍历整数序列并为每个整数创建二进制向量表示,并以二维数组的形式返回结果。
1 2 3 4 5 6 7 8 |
# One-Hot 编码序列 def one_hot_encode(sequence, n_unique=100): encoding = list() for value in sequence: vector = [0 for _ in range(n_unique)] vector[value] = 1 encoding.append(vector) return array(encoding) |
我们还需要解码编码值,以便我们可以利用预测,在这种情况下,只需查看它们。
可以通过使用 argmax() NumPy 函数来反转 One-Hot 编码,该函数返回向量中最大值的索引。
下面的函数,名为 one_hot_decode(),将解码编码序列,并可用于稍后解码来自我们网络的预测。
1 2 3 |
# 解码 One-Hot 编码字符串 def one_hot_decode(encoded_seq): return [argmax(vector) for vector in encoded_seq] |
构建编码序列以供学习
序列生成并编码后,必须将其组织成适合学习的框架。
这涉及将线性序列组织成输入 (X) 和输出 (y) 对。
例如,序列 [1, 2, 3, 4, 5] 可以构建为一个序列预测问题,其中有 2 个输入 (t 和 t-1) 和 1 个输出 (t-1),如下所示
1 2 3 4 5 6 7 |
X, y NaN, 1, NaN 1, 2, 1 2, 3, 2 3, 4, 3 4, 5, 4 5, NaN, 5 |
请注意,数据的缺失用 NaN 值标记。这些行可以用特殊字符填充并屏蔽。或者,更简单地,这些行可以从数据集中删除,代价是提供了更少的序列示例以供学习。后一种方法将是本示例中使用的方法。
我们将使用 Pandas shift() 函数来创建编码序列的移动版本。使用 dropna() 函数删除缺失数据的行。
然后我们可以从移位的数据帧中指定输入和输出数据。数据必须是三维的才能与序列预测 LSTM 一起使用。输入和输出都需要维度 [样本数, 时间步长, 特征数],其中样本数是行数,时间步长是从中学习进行预测的滞后观测值数量,特征数是单独值的数量(例如,One-Hot 编码为 100)。
下面的代码定义了 to_supervised() 函数,它实现了此功能。它接受两个参数来指定用作输入和输出的编码整数的数量。输出整数的数量必须小于或等于输入数量,并且从最旧的观测值开始计数。
例如,如果为序列 [1, 2, 3, 4, 5] 指定了 5 和 1 作为输入和输出,则该函数将在 X 中返回一行 [1, 2, 3, 4, 5],并在 y 中返回一行 [1]。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 将编码序列转换为监督学习 def to_supervised(sequence, n_in, n_out): # 创建序列的滞后副本 df = DataFrame(sequence) df = concat([df.shift(n_in-i-1) for i in range(n_in)], axis=1) # 删除缺失值的行 df.dropna(inplace=True) # 指定输入和输出对的列 values = df.values width = sequence.shape[1] X = values.reshape(len(values), n_in, width) y = values[:, 0:(n_out*width)].reshape(len(values), n_out, width) return X, y |
有关将序列转换为监督学习问题的更多信息,请参阅以下帖子
完整示例
我们可以将所有这些结合起来。
下面是生成 25 个随机整数序列并将其中的每个整数编码为二进制向量,然后将其分为 X、y 对进行学习,最后打印解码后的对以供查看的完整代码清单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from random import randint from numpy import array from numpy import argmax from pandas import DataFrame from pandas import concat # 生成 [0, 99] 中的随机数序列 def generate_sequence(length=25): return [randint(0, 99) for _ in range(length)] # One-Hot 编码序列 def one_hot_encode(sequence, n_unique=100): encoding = list() for value in sequence: vector = [0 for _ in range(n_unique)] vector[value] = 1 encoding.append(vector) return array(encoding) # 解码 One-Hot 编码字符串 def one_hot_decode(encoded_seq): return [argmax(vector) for vector in encoded_seq] # 将编码序列转换为监督学习 def to_supervised(sequence, n_in, n_out): # 创建序列的滞后副本 df = DataFrame(sequence) df = concat([df.shift(n_in-i-1) for i in range(n_in)], axis=1) # 删除缺失值的行 df.dropna(inplace=True) # 指定输入和输出对的列 values = df.values width = sequence.shape[1] X = values.reshape(len(values), n_in, width) y = values[:, 0:(n_out*width)].reshape(len(values), n_out, width) 返回 X, y # 生成随机序列 sequence = generate_sequence() 打印(序列) # One-Hot 编码 encoded = one_hot_encode(sequence) # 转换为 X,y 对 X,y = to_supervised(encoded, 5, 3) # 解码所有对 for i in range(len(X)): print(one_hot_decode(X[i]), '=>', one_hot_decode(y[i])) |
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
该序列被拆分为 5 个数字的输入序列和输入序列中 3 个最旧观测值的输出序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[86, 81, 88, 1, 23, 78, 64, 7, 99, 23, 2, 36, 73, 26, 27, 33, 24, 51, 73, 64, 13, 13, 53, 40, 64] [86, 81, 88, 1, 23] => [86, 81, 88] [81, 88, 1, 23, 78] => [81, 88, 1] [88, 1, 23, 78, 64] => [88, 1, 23] [1, 23, 78, 64, 7] => [1, 23, 78] [23, 78, 64, 7, 99] => [23, 78, 64] [78, 64, 7, 99, 23] => [78, 64, 7] [64, 7, 99, 23, 2] => [64, 7, 99] [7, 99, 23, 2, 36] => [7, 99, 23] [99, 23, 2, 36, 73] => [99, 23, 2] [23, 2, 36, 73, 26] => [23, 2, 36] [2, 36, 73, 26, 27] => [2, 36, 73] [36, 73, 26, 27, 33] => [36, 73, 26] [73, 26, 27, 33, 24] => [73, 26, 27] [26, 27, 33, 24, 51] => [26, 27, 33] [27, 33, 24, 51, 73] => [27, 33, 24] [33, 24, 51, 73, 64] => [33, 24, 51] [24, 51, 73, 64, 13] => [24, 51, 73] [51, 73, 64, 13, 13] => [51, 73, 64] [73, 64, 13, 13, 53] => [73, 64, 13] [64, 13, 13, 53, 40] => [64, 13, 13] [13, 13, 53, 40, 64] => [13, 13, 53] |
现在我们知道如何准备和表示随机整数序列,我们可以看看如何使用 LSTM 来学习它们。
回显整个序列
(序列到序列模型)
在本节中,我们将为该问题的一个简单框架开发一个 LSTM,即预测或再现整个输入序列。
也就是说,给定一个固定的输入序列,例如 5 个随机整数,输出相同的序列。这听起来可能不像是一个简单的问题框架,但它确实简单,因为解决它所需的网络架构是直接的。
我们将生成 25 个整数的随机序列,并将其构建为 5 个值的输入-输出对。我们将创建一个名为 get_data() 的便捷函数,用于使用上一节中准备的所有功能创建随机整数的编码 X,y 对。该函数如下所示。
1 2 3 4 5 6 7 8 9 |
# 为 LSTM 准备数据 def get_data(n_in, n_out): # 生成随机序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 转换为 X,y 对 X,y = to_supervised(encoded, n_in, n_out) return X,y |
此函数将使用参数 5 和 5 调用,以创建 21 个样本,其中包含 5 个时间步的 100 个特征作为输入,并以相同的方式作为输出(21 而不是 25,因为由于序列的移动导致某些缺失值的行被删除)。
我们现在可以为这个问题开发一个 LSTM。我们将使用有状态 LSTM,并在每次生成样本的训练结束时显式重置内部状态。在序列中的样本之间维护网络内部状态可能不是必需的,因为学习所需的上下文将作为时间步长提供;尽管如此,这种额外的状态可能有所帮助。
我们首先定义输入数据的预期维度为 5 个时间步的 100 个特征。由于我们使用的是有状态 LSTM,这将通过 batch_input_shape 参数而不是 input_shape 来指定。LSTM 隐藏层将使用 20 个记忆单元,这应该足以解决这个问题。
将使用 7 的批次大小。批次大小必须是训练样本数量(在本例中为 21)的因数,并定义 LSTM 中权重更新的样本数量。这意味着在网络训练的每个随机序列中,权重将更新 3 次。
1 2 |
model = Sequential() model.add(LSTM(20, batch_input_shape=(7, 5, 100), return_sequences=True, stateful=True)) |
我们希望输出层一次输出一个整数,每个观测输入一个整数。
我们将输出层定义为一个全连接层 (Dense),其中包含 100 个神经元,对应 One-Hot 编码中的 100 个可能整数值。由于我们使用的是 One-Hot 编码并将问题框定为多分类问题,我们可以在 Dense 层中使用 softmax 激活函数。
1 |
Dense(100, activation='softmax') |
我们需要将这个输出层封装在一个 TimeDistributed 层中。这是为了确保我们可以使用输出层为输入序列中的每个项目一次预测一个整数。这是关键,以便我们实现真正的多对多模型(例如,序列到序列),而不是多对一模型,其中根据内部状态和输入序列中最后一个观测值的值创建一次性向量输出(例如,输出层一次输出 5*100 个值)。
这要求先前的 LSTM 层通过设置 return_sequences=True 来返回序列(例如,输入序列中的每个观测值一个输出,而不是整个输入序列一个输出)。TimeDistributed 层实现了将 LSTM 层的每个序列切片作为输入应用于封装的 Dense 层的技巧,以便一次预测一个整数。
1 |
model.add(TimeDistributed(Dense(100, activation='softmax'))) |
我们将使用适用于多分类问题(categorical_crossentropy)的对数损失函数,以及带有默认超参数的高效 ADAM 优化算法。
除了报告每个 epoch 的对数损失,我们还将报告分类准确率,以了解我们的模型训练情况。
1 |
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) |
为了确保网络不会记住问题,并且网络学会为所有可能的输入序列泛化解决方案,我们将在每个训练周期生成一个新的随机序列。LSTM 隐藏层的内部状态将在每个周期结束时重置。我们将模型拟合 500 个训练周期。
1 2 3 4 5 6 7 |
# 训练 LSTM for epoch in range(500): # 生成新的随机序列 X,y = get_data(5, 5) # 在此序列上拟合模型一个 epoch model.fit(X, y, epochs=1, batch_size=7, verbose=2, shuffle=False) model.reset_states() |
一旦拟合,我们将通过对一个新的随机整数序列进行预测来评估模型,并将解码后的预期输出序列与预测序列进行比较。
1 2 3 4 5 6 |
# 评估 LSTM X,y = get_data(5, 5) yhat = model.predict(X, batch_size=7, verbose=0) # 解码所有对 for i in range(len(X)): print('Expected:', one_hot_decode(y[i]), 'Predicted', one_hot_decode(yhat[i])) |
将所有这些整合在一起,完整的代码清单如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
from random import randint from numpy import array from numpy import argmax from pandas import DataFrame 从 pandas 导入 concat from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense from keras.layers import TimeDistributed # 生成 [0, 99] 中的随机数序列 def generate_sequence(length=25): return [randint(0, 99) for _ in range(length)] # One-Hot 编码序列 def one_hot_encode(sequence, n_unique=100): encoding = list() for value in sequence: vector = [0 for _ in range(n_unique)] vector[value] = 1 encoding.append(vector) return array(encoding) # 解码 One-Hot 编码字符串 def one_hot_decode(encoded_seq): return [argmax(vector) for vector in encoded_seq] # 将编码序列转换为监督学习 def to_supervised(sequence, n_in, n_out): # 创建序列的滞后副本 df = DataFrame(sequence) df = concat([df.shift(n_in-i-1) for i in range(n_in)], axis=1) # 删除缺失值的行 df.dropna(inplace=True) # 指定输入和输出对的列 values = df.values width = sequence.shape[1] X = values.reshape(len(values), n_in, width) y = values[:, 0:(n_out*width)].reshape(len(values), n_out, width) 返回 X, y # 为 LSTM 准备数据 def get_data(n_in, n_out): # 生成随机序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 转换为 X,y 对 X,y = to_supervised(encoded, n_in, n_out) return X,y # 定义 LSTM n_in = 5 n_out = 5 encoded_length = 100 batch_size = 7 model = Sequential() model.add(LSTM(20, batch_input_shape=(batch_size, n_in, encoded_length), return_sequences=True, stateful=True)) model.add(TimeDistributed(Dense(encoded_length, activation='softmax'))) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 训练 LSTM for epoch in range(500): # 生成新的随机序列 X,y = get_data(n_in, n_out) # 在此序列上拟合模型一个 epoch model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() # 评估 LSTM X,y = get_data(n_in, n_out) yhat = model.predict(X, batch_size=batch_size, verbose=0) # 解码所有对 for i in range(len(X)): print('Expected:', one_hot_decode(y[i]), 'Predicted', one_hot_decode(yhat[i])) |
运行示例将打印每个训练周期的对数损失和准确率。运行结束时,将生成一个新的随机序列,并比较预期序列和预测序列。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
在所选配置下,网络每次运行几乎都会收敛到 100% 的准确率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
... 纪元 1/1 0s - loss: 1.7310 - acc: 1.0000 纪元 1/1 0s - loss: 1.5712 - acc: 1.0000 纪元 1/1 0s - loss: 1.7447 - acc: 1.0000 纪元 1/1 0s - loss: 1.5704 - acc: 1.0000 纪元 1/1 0s - loss: 1.6124 - acc: 1.0000 预期:[98, 30, 98, 11, 49] 预测:[98, 30, 98, 11, 49] 预期:[30, 98, 11, 49, 1] 预测:[30, 98, 11, 49, 1] 预期:[98, 11, 49, 1, 77] 预测:[98, 11, 49, 1, 77] 预期:[11, 49, 1, 77, 80] 预测:[11, 49, 1, 77, 80] 预期:[49, 1, 77, 80, 23] 预测:[49, 1, 77, 80, 23] 预期:[1, 77, 80, 23, 32] 预测:[1, 77, 80, 23, 32] 预期:[77, 80, 23, 32, 27] 预测:[77, 80, 23, 32, 27] 预期:[80, 23, 32, 27, 66] 预测:[80, 23, 32, 27, 66] 预期:[23, 32, 27, 66, 96] 预测:[23, 32, 27, 66, 96] 预期:[32, 27, 66, 96, 76] 预测:[32, 27, 66, 96, 76] 预期:[27, 66, 96, 76, 10] 预测:[27, 66, 96, 76, 10] 预期:[66, 96, 76, 10, 39] 预测:[66, 96, 76, 10, 39] 预期:[96, 76, 10, 39, 44] 预测:[96, 76, 10, 39, 44] 预期:[76, 10, 39, 44, 57] 预测:[76, 10, 39, 44, 57] 预期:[10, 39, 44, 57, 11] 预测:[10, 39, 44, 57, 11] 预期:[39, 44, 57, 11, 48] 预测:[39, 44, 57, 11, 48] 预期:[44, 57, 11, 48, 39] 预测:[44, 57, 11, 48, 39] 预期:[57, 11, 48, 39, 28] 预测:[57, 11, 48, 39, 28] 预期:[11, 48, 39, 28, 15] 预测:[11, 48, 39, 28, 15] 预期:[48, 39, 28, 15, 49] 预测:[48, 39, 28, 15, 49] 预期:[39, 28, 15, 49, 76] 预测:[39, 28, 15, 49, 76] |
回显部分序列
(编码器-解码器模型)
到目前为止一切顺利,但是如果我们希望输出序列的长度与输入序列的长度不同呢?
也就是说,我们希望从 5 个观测值的输入序列中回显前 2 个观测值
1 |
[1, 2, 3, 4, 5] => [1, 2] |
这仍然是一个序列到序列的预测问题,但需要改变网络架构。
一种方法是使输出序列长度相同,并使用填充来将输出序列填充到相同长度。
或者,我们可以使用更优雅的解决方案。我们可以实现一个编码器-解码器网络,它允许可变长度输出,其中编码器学习输入序列的内部表示,解码器读取内部表示并学习如何创建相同或不同长度的输出序列。
这对网络来说是一个更具挑战性的问题,因此需要额外的容量(更多记忆单元)和更长的训练时间(更多周期)。
网络的输入、第一个隐藏 LSTM 层和 TimeDistributed Dense 输出层保持不变,只是我们将记忆单元的数量从 20 增加到 150。我们还将批次大小从 7 增加到 21,以便在随机序列的所有样本结束时执行权重更新。经过一些实验,发现这导致该网络的学习速度更快。
1 2 3 |
model.add(LSTM(150, batch_input_shape=(21, 5, 100), stateful=True)) ... model.add(TimeDistributed(Dense(100, activation='softmax'))) |
第一隐藏层是编码器。
我们必须添加一个额外的隐藏 LSTM 层作为解码器。同样,我们将在该层中使用 150 个记忆单元,并且与上一个示例一样,TimeDistributed 层之前的层将返回序列而不是单个值。
1 |
model.add(LSTM(150, return_sequences=True, stateful=True)) |
这两个层不能很好地衔接。编码器层将输出一个二维数组 (21, 150),而解码器期望一个三维数组作为输入 (21, ?, 150)。
我们通过在编码器和解码器之间添加一个 RepeatVector() 层来解决这个问题,并确保编码器的输出重复适当的次数以匹配输出序列的长度。在这种情况下,对于输出序列中的两个时间步长,重复 2 次。
1 |
model.add(RepeatVector(2)) |
因此,LSTM 网络定义为
1 2 3 4 5 6 |
model = Sequential() model.add(LSTM(150, batch_input_shape=(21, 5, 100), stateful=True)) model.add(RepeatVector(2)) model.add(LSTM(150, return_sequences=True, stateful=True)) model.add(TimeDistributed(Dense(100, activation='softmax'))) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) |
训练周期数从 500 增加到 5,000,以适应网络增加的容量。
示例的其余部分相同。
将这些整合在一起,完整的代码清单如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
from random import randint from numpy import array from numpy import argmax from pandas import DataFrame 从 pandas 导入 concat from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense from keras.layers import TimeDistributed from keras.layers import RepeatVector # 生成 [0, 99] 中的随机数序列 def generate_sequence(length=25): return [randint(0, 99) for _ in range(length)] # One-Hot 编码序列 def one_hot_encode(sequence, n_unique=100): encoding = list() for value in sequence: vector = [0 for _ in range(n_unique)] vector[value] = 1 encoding.append(vector) return array(encoding) # 解码 One-Hot 编码字符串 def one_hot_decode(encoded_seq): return [argmax(vector) for vector in encoded_seq] # 将编码序列转换为监督学习 def to_supervised(sequence, n_in, n_out): # 创建序列的滞后副本 df = DataFrame(sequence) df = concat([df.shift(n_in-i-1) for i in range(n_in)], axis=1) # 删除缺失值的行 df.dropna(inplace=True) # 指定输入和输出对的列 values = df.values width = sequence.shape[1] X = values.reshape(len(values), n_in, width) y = values[:, 0:(n_out*width)].reshape(len(values), n_out, width) 返回 X, y # 为 LSTM 准备数据 def get_data(n_in, n_out): # 生成随机序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 转换为 X,y 对 X,y = to_supervised(encoded, n_in, n_out) return X,y # 定义 LSTM n_in = 5 n_out = 2 encoded_length = 100 batch_size = 21 model = Sequential() model.add(LSTM(150, batch_input_shape=(batch_size, n_in, encoded_length), stateful=True)) model.add(RepeatVector(n_out)) model.add(LSTM(150, return_sequences=True, stateful=True)) model.add(TimeDistributed(Dense(encoded_length, activation='softmax'))) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 训练 LSTM for epoch in range(5000): # 生成新的随机序列 X,y = get_data(n_in, n_out) # 在此序列上拟合模型一个 epoch model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() # 评估 LSTM X,y = get_data(n_in, n_out) yhat = model.predict(X, batch_size=batch_size, verbose=0) # 解码所有对 for i in range(len(X)): print('Expected:', one_hot_decode(y[i]), 'Predicted', one_hot_decode(yhat[i])) |
运行示例将显示每个训练周期中随机生成序列的对数损失和准确率。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
所选配置将意味着模型将收敛到 100% 的分类准确率。
将生成一个最终的随机序列,并比较预期序列和预测序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
... 纪元 1/1 0s - loss: 0.0248 - acc: 1.0000 纪元 1/1 0s - loss: 0.0399 - acc: 1.0000 纪元 1/1 0s - loss: 0.0285 - acc: 1.0000 纪元 1/1 0s - loss: 0.0410 - acc: 0.9762 纪元 1/1 0s - loss: 0.0236 - acc: 1.0000 预期:[6, 52] 预测:[6, 52] 预期:[52, 96] 预测:[52, 96] 预期:[96, 45] 预测:[96, 45] 预期:[45, 69] 预测:[45, 69] 预期:[69, 52] 预测:[69, 52] 预期:[52, 96] 预测:[52, 96] 预期:[96, 11] 预测:[96, 96] 预期:[11, 96] 预测:[11, 96] 预期:[96, 54] 预测:[96, 54] 预期:[54, 27] 预测:[54, 27] 预期:[27, 48] 预测:[27, 48] 预期:[48, 9] 预测:[48, 9] 预期:[9, 32] 预测:[9, 32] 预期:[32, 62] 预测:[32, 62] 预期:[62, 41] 预测:[62, 41] 预期:[41, 54] 预测:[41, 54] 预期:[54, 20] 预测:[54, 20] 预期:[20, 80] 预测:[20, 80] 预期:[80, 63] 预测:[80, 63] 预期:[63, 69] 预测:[63, 69] 预期:[69, 36] 预测:[69, 36] |
扩展
本节列出了一些您可能希望探索的教程的可能扩展。
- 线性表示。使用了分类(One-Hot 编码)的问题框架,这极大地增加了建模此问题所需的权重数量(每个随机整数 100 个)。探索使用线性表示(将整数缩放到 0-1 之间的值)并将问题建模为回归。查看这如何影响系统的技能、所需网络大小(记忆单元)和训练时间(周期)。
- 掩盖缺失值。当序列数据被框定为监督学习问题时,缺失数据的行被删除。探索使用 Masking 层或特殊值(例如 -1)来允许网络忽略或学习忽略这些值。
- 回显更长序列。学习回显的部分子序列只有 2 个项长。探索使用编码器-解码器网络回显更长的序列。请注意,您可能需要更大的隐藏层(更多记忆单元)和更长的训练时间(更多周期)。
- 忽略状态。我们注意仅在每个随机整数序列的所有样本结束后才清除状态,并且不打乱序列中的样本。这可能不是必需的。探索并对比使用批次大小为 1 的无状态 LSTM(在每个序列的每个样本后更新权重并重置状态)的模型性能。我预计几乎没有变化。
- 替代网络拓扑。我们注意为输出层使用 TimeDistributed 包装器,以确保使用多对多网络来建模问题。探索使用一对一配置(时间步长作为特征)或多对一配置(输出作为特征向量)的序列回显问题,并查看这如何影响所需的网络大小(记忆单元)和训练时间(周期)。我预计它需要更大的网络并花费更长的时间。
您是否探索过这些扩展?
在下面的评论中分享您的发现。
总结
在本教程中,您学习了如何开发 LSTM 循环神经网络来回显随机整数列表中的序列和部分序列。
具体来说,你学到了:
- 如何生成随机整数序列,使用 One-Hot 编码表示它们,并将问题构建为监督学习问题。
- 如何开发基于序列到序列的 LSTM 网络以回显整个输入序列。
- 如何开发基于编码器-解码器的 LSTM 网络以回显长度与输入序列长度不同的部分输入序列。
你有什么问题吗?
在评论中提出您的问题,我将尽力回答。
Jason
感谢您再次通过实际示例,以代码的形式出色地讲解了一个很棒的创意。
我记得在 Chainer 框架的这个精彩教程中看到了编码器-解码器架构的惊人能力,特别是当添加注意力机制时。
系统学习反转任意长的字符串!
https://talbaumel.github.io/attention/
我很乐意看到这个应用程序使用 Keras。
我正在寻找您的 enc-dec-with-attn. 教程。
另外,您应该将您的序列处理教程汇编成一本书,我将是第一个购买者 🙂
拉维
谢谢拉维!我想写一个为期 14 天的 LSTM 速成课程之类的。很高兴听到您认为它会很有价值。
那太棒了,Jason。
感谢您为使 DNN 更易于理解所做的努力。我非常关注您的博客。
新发布的 TensorFlow (API r.1.2) 在很大程度上整合了 Keras。但是,TimeDistributed 层消失了。我想知道是否应该用新的语法重写代码,即 model = tf.contrib.keras.models.Sequential() 而不是 model = Sequential()??另一个问题是,在新的 TensorFlow 中,tf.contrib.keras.layers.LSTM 和 tf.contrib.rnn.LSTMCell 之间是否存在兼容性??
它仍然存在于 Keras 中
https://keras.org.cn/layers/wrappers/#timedistributed
抱歉,我不了解 TF 代码库中的 Keras 副本。
是的,现在有了,但是“看起来导入没有进入最新版本,但在主分支中:https://github.com/tensorflow/tensorflow/blob/v1.2.1/tensorflow/contrib/keras/api/keras/layers/__init__.py”。我可以继续使用
from tensorflow.contrib.keras.python.keras.layers.wrappers import TimeDistributed
但我得到了另一个错误消息
文件“C:\Users\natlun\AppData\Local\Continuum\Miniconda3\envs\tensorflow\lib\site-packages\tensorflow\contrib\keras\python\keras\layers\wrappers.py”,第 194 行,在调用中
unroll=False)
TypeError: rnn() 得到了一个意外的关键字参数 'input_length'
然而,您示例中的代码在旧版 Keras + TensorFlow 中运行完美...
考虑使用 Keras 的独立版本而不是 TF contrib 版本。
是的,这是唯一让它工作的方法。我从 Google 团队那里听说,这个问题已在 Master 分支中解决,并将在新发布的 API r1.3 中实现,但没有人知道他们何时发布。
另一个问题。为什么您使用自己的 One-Hot 编码,而 Keras 有自己的版本(实际上,有几种 One-Hot 编码的方式)??
为了让事情更清楚。
我发现预测结果只有第一次尝试是正确的。下面是完整代码列表中从“# evaluate LSTM”开始的几行输出。也添加了输入样本。
第一次尝试
[21, 50, 39, 59, 63] 预期:[21, 50] 预测:[21, 50]
[50, 39, 59, 63, 3] 预期:[50, 39] 预测:[50, 39]
[39, 59, 63, 3, 90] 预期:[39, 59] 预测:[39, 59]
[67, 6, 10, 5, 75] 预期:[67, 6] 预测:[67, 6]
[6, 10, 5, 75, 59] 预期:[6, 10] 预测:[6, 10]
[10, 5, 75, 59, 95] 预期:[10, 5] 预测:[10, 5]
第二次尝试
[28, 35, 38, 30, 64] 预期:[28, 35] 预测:[50, 50]
[35, 38, 30, 64, 44] 预期:[35, 38] 预测:[39, 39]
[38, 30, 64, 44, 91] 预期:[38, 30] 预测:[59, 59]
[34, 42, 43, 70, 89] 预期:[34, 42] 预测:[6, 6]
[42, 43, 70, 89, 36] 预期:[42, 43] 预测:[10, 10]
[43, 70, 89, 36, 57] 预期:[43, 70] 预测:[10, 5]
第三次尝试
[77, 81, 11, 62, 88] 预期:[77, 81] 预测:[50, 50]
[81, 11, 62, 88, 53] 预期:[81, 11] 预测:[39, 39]
[11, 62, 88, 53, 82] 预期:[11, 62] 预测:[59, 59]
[68, 63, 62, 30, 44] 预期:[68, 63] 预测:[6, 6]
[63, 62, 30, 44, 52] 预期:[63, 62] 预测:[10, 10]
[62, 30, 44, 52, 75] 预期:[62, 30] 预测:[5, 90]
我使用 Jupyter 进行测试,并将代码放入单独的单元格
是的,所有“get_data”和“model.predict”的调用都将失败,除了第一次。
有什么办法可以解决这个问题吗?
我也遇到了这个问题。有没有办法获得可重复的结果?
请看这篇文章
https://machinelearning.org.cn/reproducible-results-neural-networks-keras/
看起来预测不能运行两次.. 因此 - 无法获得可重复的代码..
# 评估 LSTM
X,y = get_data(n_in, n_out)
yhat = model.predict(X, batch_size=batch_size, verbose=0)
# 解码所有对
for i in range(len(X))
print(‘Expected:’, one_hot_decode(y[i]), ‘Predicted’, one_hot_decode(yhat[i]))
不完全是,神经网络是随机模型。
在这里了解更多
https://machinelearning.org.cn/randomness-in-machine-learning/
您必须在每次重新评估之前重置模型的状态,如下所示
# 评估 LSTM
model.reset_states() #这需要在每次重新评估之前添加
X,y = get_data(n_in, n_out)
yhat = model.predict(X, batch_size=batch_size, verbose=0)
# 解码所有对
for i in range(len(X))
print(‘Expected:’, one_hot_decode(y[i]), ‘Predicted’, one_hot_decode(yhat[i]))
你好,我如何将文本序列加载到 X_train 和 Y_train 中?
例如
X_train 中的 [“How”, “are”, “you”, “?”]
Y_train 中的 [“I’m”, “fine”]
每个词都需要编码为一个整数。
Keras 有一个函数可以做到这一点,请看这里
https://keras.org.cn/preprocessing/text/
您好,
我是机器学习的新手,在这篇文章中,我很难理解以下事实:-
输入层 3d 张量的形状为 [7,5,100],这意味着它有 7 个批次,每个批次有 5 个词/时间步,每个词可以用 100 维向量表示,到目前为止我理解得对吗?
下一部分是“LSTM(20, rest of the code)”,这意味着隐藏层中将有 20 个 LSTM 单元,对吗?
我的问题是,3d 张量形状 [7,5,100] 到 20 个 LSTM 节点之间的映射是如何发生的?这部分我不明白,我们是在 LSTM 方法内部应用全连接、密集层还是 TimeDistributedDense 层?
这将是 7 个样本,5 个时间步,100 个特征。
每个节点将在每个时间步为每个样本接收 100 个特征,
这个编码器/解码器模型的实现似乎比我见过的其他编码器/解码器模型简单得多。我猜这种简单性之所以可能,是因为这是一种输入大小永远不会改变并且可以在输入形状中指定的情况。
原因在于它基于自编码器,并且似乎同样有效。
你好,快速提问。那么当您训练解码器模型时,是否不需要在解码器输入开头添加一个开始标记?如果是这样,那么您将如何为整数序列做到这一点?
是的。
当我使用 TimeDistributed 时,如何创建自定义损失?
当我使用自定义损失时,我得到错误
InvalidArgumentError: 找到 2 个根错误。
(0) 无效参数:您必须为占位符张量“input_7”提供一个值,其 dtype 为 float,形状为 [?,224,224,1]
[[{{节点 input_7}}]]
[[损失/乘法/_929]]
(1) 无效参数:您必须为占位符张量“input_7”提供一个值,其 dtype 为 float,形状为 [?,224,224,1]
[[{{节点 input_7}}]]
0 次成功操作。
0 个派生错误被忽略。
它们不相关。
嗨,Jason,
感谢您提供另一个使用基于 LSTM 的 AE 的有趣应用程序。我很好奇是否可以使用注意力机制(无翻译)和整数来实现相同的功能。
您能否指导我如何做到这一点或发布另一篇文章?
提前感谢。
我看不出为什么不。
谢谢您的建议,我将来可能会写这方面的内容。
感谢您的快速回复。我目前刚接触 Python,并且尝试使用股票价格数据集实现相同的功能。我一直收到“列表赋值索引超出范围”的错误消息。
您能给我一些指导吗?
谢谢
这有助于理解数组索引
https://machinelearning.org.cn/index-slice-reshape-numpy-arrays-machine-learning-python/
谢谢,但我使用了具有适当索引的数据。当我对大约 100k 大小的数组使用 One-Hot 编码时,出现了错误。这与值有关吗,因为它们大多数是股票价格,例如 57500(实际价格 $ * 10000)?因为我很难理解 One-Hot 编码函数中的 for 循环。
如果这对您来说是个有点幼稚的问题,我深表歉意。
顺便说一句,我现在是全职订阅者 🙂
One-Hot 编码仅适用于分类变量。
听起来您有一个数值变量,在这种情况下,One-Hot 编码是不合适的。
没问题,我已解决上述问题。然而,我被内存需求问题困住了,因为 lstm 输入的输入大小约为 [9,70000]。
这里还有其他编码方式可以使用吗?
谢谢
干得好。
如果您有分类输入变量,可以使用多种编码方式,也许这会有所帮助
https://machinelearning.org.cn/how-to-prepare-categorical-data-for-deep-learning-in-python/
还有这个。
https://machinelearning.org.cn/faq/single-faq/how-do-i-handle-a-large-number-of-categories
嗨,Jason,
我已经避免了 One-Hot 编码,因为我选择了一个用于整数变量的无监督模型。这是否意味着在上面的代码中,我可以编写 X = y = 输入变量(1-D)?
那么模型如何处理减少到 2 维而不是 3 维的输入批次形状,更重要的是我如何改变它?
再次感谢
不太明白。如果是无监督的,也许可以改用自编码器架构
https://machinelearning.org.cn/lstm-autoencoders/
感谢您的建议。我最初是想问关于在 LSTM 自编码器中使用注意力机制和整数变量的问题。但我现在明白了要点,我只需要尝试找到一种将注意力融入其中的方法。
如果可能,如果您能解释一下这样的内容,我将不胜感激。
先生,我实际上正在研究根据标签生成随机引号。那么我如何才能获得变化的序列输出呢?因为我的解码器输出与我应用于它的相同,即 (batch_size, No_seq, 200),它是编码为 One-Hot 向量的标签。
您可以预先定义一个长输出并对训练示例进行零填充。
或者您可以使用动态 RNN 模型。