长短期记忆 (LSTM) 循环神经网络能够学习长序列数据中的顺序依赖性。
它们是图像字幕和机器翻译等一系列最先进成果中使用的基本技术。
它们也可能难以理解,特别是如何构建问题以最大限度地利用这种网络。
在本教程中,您将学习如何开发一个简单的 LSTM 循环神经网络,以学习如何回显随机整数的特定序列中的数字。尽管这是一个微不足道的问题,但开发此网络将提供在各种序列预测问题中应用 LSTM 所需的技能。
完成本教程后,您将了解:
- 如何为更简单的回显任何给定输入的问题开发 LSTM。
- 如何避免初学者在将 LSTM 应用于回显整数等序列问题时犯的错误。
- 如何开发一个强大的 LSTM 来回显随机整数的特定序列中的最后一个观测值。
使用我的新书 《使用 Python 的长短期记忆网络》 启动您的项目,其中包括分步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2020年1月更新:更新了Keras 2.3和TensorFlow 2.0的API。

如何使用长短期记忆循环神经网络学习回显随机整数
照片由 Franck Michel 拍摄,保留部分权利。
概述
本教程分为4个部分,它们是:
- 生成并编码随机序列
- 回显当前观测值
- 在没有上下文的情况下回显滞后观测值(初学者错误)
- 回显滞后观测值
环境
本教程假定您已安装 Python SciPy 环境。您可以使用 Python 2 或 3。
本教程假设您已安装 Keras v2.0 或更高版本,并使用 TensorFlow 或 Theano 后端。本教程不需要 GPU,所有代码都可以在 CPU 中轻松运行。
本教程还假定您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。
如果您需要帮助设置 Python 环境,请参阅此帖子
需要 LSTM 帮助进行序列预测吗?
参加我的免费7天电子邮件课程,了解6种不同的LSTM架构(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
生成并编码随机序列
第一步是编写一些代码来生成随机整数序列并将其编码以供网络使用。
生成随机序列
我们可以使用 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)] |
独热编码随机序列
一旦我们生成了随机整数序列,我们需要将它们转换为适合训练 LSTM 网络的格式。
一种选择是将整数重新缩放到 [0,1] 范围。这会奏效,并且需要将问题表述为回归。
我感兴趣的是预测正确的数字,而不是接近预期值的数字。这意味着我更喜欢将问题表述为分类而不是回归,其中预期输出是一个类别,并且有 100 个可能的类别值。
在这种情况下,我们可以使用整数值的独热编码,其中每个值都由一个 100 元素二进制向量表示,该向量除整数索引标记为 1 外,所有值都为“0”。
下面名为 one_hot_encode() 的函数定义了如何遍历整数序列并为每个整数创建二进制向量表示,并以 2 维数组形式返回结果。
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_decode() 的函数将解码编码序列,可用于以后解码我们网络的预测。
1 2 3 |
# 解码 One-Hot 编码字符串 def one_hot_decode(encoded_seq): return [argmax(vector) for vector in encoded_seq] |
完整示例
我们可以将所有这些结合起来。
下面是生成 25 个随机整数序列并将每个整数编码为二进制向量的完整代码列表。
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 |
from random import randint from numpy import array from numpy import argmax # 生成 [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] # 生成随机序列 sequence = generate_sequence() 打印(序列) # One-Hot 编码 encoded = one_hot_encode(sequence) print(编码) # 独热解码 decoded = one_hot_decode(encoded) print(decoded) |
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行示例首先打印 25 个随机整数的列表,然后是序列中所有整数的二进制表示的截断视图,每行一个向量,然后是再次解码的序列。
1 2 3 4 5 6 7 8 9 |
[37, 99, 40, 98, 44, 27, 99, 18, 52, 97, 46, 39, 60, 13, 66, 29, 26, 4, 65, 85, 29, 88, 8, 23, 61] [[0 0 0 ..., 0 0 0] [0 0 0 ..., 0 0 1] [0 0 0 ..., 0 0 0] ..., [0 0 0 ..., 0 0 0] [0 0 0 ..., 0 0 0] [0 0 0 ..., 0 0 0]] [37, 99, 40, 98, 44, 27, 99, 18, 52, 97, 46, 39, 60, 13, 66, 29, 26, 4, 65, 85, 29, 88, 8, 23, 61] |
现在我们知道如何准备和表示随机整数序列,我们可以看看如何使用 LSTM 来学习它们。
回显当前观测值
让我们从一个更简单的回声问题开始。
在本节中,我们将开发一个 LSTM 来回显当前观测值。也就是说,给定一个随机整数作为输入,返回相同的整数作为输出。
或更正式地表述为
1 |
yhat(t) = f(X(t)) |
也就是说,模型将当前时间的值 (yhat(t)) 预测为当前时间观测值 (X(t)) 的函数 (f())。
这是一个简单的问题,因为不需要记忆,只需要一个将输入映射到相同输出的函数。
这是一个微不足道的问题,将演示一些有用的东西
- 如何使用上述问题表示机制。
- 如何在 Keras 中使用 LSTM。
- 学习这样一个微不足道的问题所需的 LSTM 容量。
这将为接下来回显滞后观测值奠定基础。
首先,我们将开发一个函数来准备随机序列,以训练或评估 LSTM。此函数必须首先生成一个随机整数序列,使用独热编码,然后将输入数据转换为 3 维数组。
LSTM 需要一个由 [样本、时间步、特征] 维度组成的 3D 输入。我们的问题将由每个序列 25 个示例、1 个时间步和 100 个独热编码特征组成。
此函数如下所示,名为 generate_data()。
1 2 3 4 5 6 7 8 9 |
# 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 转换为 3d 以供输入 X = encoded.reshape(encoded.shape[0], 1, encoded.shape[1]) return X, encoded |
接下来,我们可以定义我们的 LSTM 模型。
模型必须指定输入数据的预期维度。在这种情况下,以时间步长 (1) 和特征 (100) 为单位。我们将使用一个具有 15 个记忆单元的单隐藏层 LSTM。
输出层是一个全连接层 (Dense),具有 100 个神经元,用于 100 个可能的整数输出。输出层使用 softmax 激活函数,以允许网络学习并输出可能输出值的分布。
网络在训练时将使用对多类别分类问题适用的对数损失函数和高效的 ADAM 优化算法。每个训练 epoch 将报告准确度指标,以除了损失之外,还提供模型技能的概览。
1 2 3 4 5 |
# 定义模型 model = Sequential() model.add(LSTM(15, input_shape=(1, 100))) model.add(Dense(100, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) |
我们将通过手动运行每个 epoch 并使用新生成的序列来手动拟合模型。模型将拟合 500 个 epoch,或者换句话说,在 500 个随机生成的序列上进行训练。
这将鼓励网络学习重现实际输入,而不是记忆固定的训练数据集。
1 2 3 4 |
# 拟合模型 for i in range(500): X, y = generate_data() model.fit(X, y, epochs=1, batch_size=1, verbose=2) |
一旦模型拟合完成,我们将对新序列进行预测,并将预测输出与预期输出进行比较。
1 2 3 4 5 |
# 在新数据上评估模型 X, y = generate_data() yhat = model.predict(X) print('Expected: %s' % one_hot_decode(y)) print('Predicted: %s' % one_hot_decode(yhat)) |
完整的示例如下所示。
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 |
from random import randint from numpy import array from numpy import argmax from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense # 生成 [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] # 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 转换为 3d 以供输入 X = encoded.reshape(encoded.shape[0], 1, encoded.shape[1]) return X, encoded # 定义模型 model = Sequential() model.add(LSTM(15, input_shape=(1, 100))) model.add(Dense(100, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 for i in range(500): X, y = generate_data() model.fit(X, y, epochs=1, batch_size=1, verbose=2) # 在新数据上评估模型 X, y = generate_data() yhat = model.predict(X) print('Expected: %s' % one_hot_decode(y)) print('Predicted: %s' % one_hot_decode(yhat)) |
运行示例会打印每个 epoch 的对数损失和准确性。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
该网络有点过度指定,拥有比如此简单的问题所需的更多内存单元和训练 epoch,您可以通过网络快速达到 100% 准确性这一事实看到这一点。
在运行结束时,预测序列与随机生成的序列进行比较,两者看起来相同。
1 2 3 4 5 6 7 8 9 10 11 12 |
... 0s - 损失: 0.0895 - 准确率: 1.0000 纪元 1/1 0s - 损失: 0.0785 - 准确率: 1.0000 纪元 1/1 0s - 损失: 0.0789 - 准确率: 1.0000 纪元 1/1 0s - 损失: 0.0832 - 准确率: 1.0000 纪元 1/1 0s - 损失: 0.0927 - 准确率: 1.0000 预期: [18, 41, 49, 56, 86, 25, 96, 3, 75, 24, 57, 95, 81, 44, 2, 22, 76, 34, 41, 4, 69, 47, 1, 97, 57] 预测: [18, 41, 49, 56, 86, 25, 96, 3, 75, 24, 57, 95, 81, 44, 2, 22, 76, 34, 41, 4, 69, 47, 1, 97, 57] |
现在我们知道如何使用工具创建和表示随机序列,并拟合 LSTM 以学习回显当前序列,接下来让我们看看如何使用 LSTM 来学习回显过去的观测值。
在没有上下文的情况下回显滞后观测值
(初学者错误)
预测滞后观测值的问题可以更正式地定义如下
1 |
yhat(t) = f(X(t-n)) |
其中当前时间步的预期输出 yhat(t) 定义为特定先前观测值 (X(t-n)) 的函数 (f())。
LSTM 的承诺表明,您可以一次向网络显示一个示例,并且网络将使用内部状态来学习并充分记住先前的观测值以解决此问题。
让我们试一试。
首先,我们必须更新 generate_data() 函数并重新定义问题。
我们将使用编码序列的移位版本作为输入,并使用编码序列的截断版本作为输出,而不是将相同的序列用于输入和输出。
需要这些更改才能将一系列数字(例如 [1, 2, 3, 4])转换为具有输入 (X) 和输出 (y) 组件的监督学习问题,例如
1 2 3 4 5 6 |
X y 1, NaN 2, 1 3, 2 4, 3 NaN, 4 |
在此示例中,您可以看到第一行和最后一行不包含网络学习所需的足够数据。这可以标记为“无数据”值并进行掩码,但更简单的解决方案是简单地将其从数据集中删除。
更新后的 generate_data() 函数如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 从 X 中删除第一个值 X = encoded[1:, :] # 转换为 3d 以供输入 X = X.reshape(X.shape[0], 1, X.shape[1]) # 从 y 中删除最后一个值 y = encoded[:-1, :] return X, y |
我们必须测试这个更新的数据表示,以确认它是否符合我们的预期。为此,我们可以生成一个序列并查看序列中解码的 X 和 y 值。
1 2 3 4 |
X, y = generate_data() for i in range(len(X)): a, b = argmax(X[i,0]), argmax(y[i]) print(a, b) |
此健全性检查的完整代码列表如下。
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 |
from random import randint from numpy import array from numpy import argmax from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense # 生成 [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] # 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 从 X 中删除第一个值 X = encoded[1:, :] # 转换为 3d 以供输入 X = X.reshape(X.shape[0], 1, X.shape[1]) # 从 y 中删除最后一个值 y = encoded[:-1, :] 返回 X, y # 测试数据生成器 X, y = generate_data() for i in range(len(X)): a, b = argmax(X[i,0]), argmax(y[i]) print(a, b) |
运行示例会打印问题框架的 X 和 y 组件。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
我们可以看到,考虑到冷启动,第一个模式对于网络来说将很难(不可能)学习。我们可以看到 yhat(t) == X(t-1) 的预期模式贯穿数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
78 65 7 78 16 7 11 16 23 11 99 23 39 99 53 39 82 53 6 82 18 6 17 18 49 17 4 49 34 4 77 34 46 77 22 46 40 22 76 40 85 76 87 85 17 87 75 17 |
网络设计类似,但有一个小改动。
观测值一次一个地显示给网络,并执行权重更新。因为我们期望观测值之间的状态携带学习先前观测值所需的信息,所以我们需要确保此状态在每个批次后不会重置(在这种情况下,一个批次是一个训练观测值)。我们可以通过使 LSTM 层有状态并手动管理何时重置状态来实现此目的。
这涉及将 LSTM 层上的 stateful 参数设置为 True,并使用包含维度 [batchsize, timesteps, features] 的 batch_input_shape 参数定义输入形状。
对于给定的随机序列,有 24 对 X、y 对,因此使用了 6 的批次大小(4 批次,每批次 6 个样本 = 24 个样本)。请记住,一个序列被分解为样本,样本可以以批次形式显示给网络,然后才对网络权重进行更新。使用了 50 个记忆单元的大型网络大小,同样是为了过度指定问题所需的容量。
1 |
model.add(LSTM(50, batch_input_shape=(6, 1, 100), stateful=True)) |
接下来,在每个 epoch(随机生成序列的一次迭代)之后,可以手动重置网络的内部状态。模型拟合 2,000 个训练 epoch,并注意不要在序列中打乱样本。
1 2 3 4 5 |
# 拟合模型 for i in range(2000): X, y = generate_data() model.fit(X, y, epochs=1, batch_size=6, verbose=2, shuffle=False) model.reset_states() |
综上所述,完整的示例代码如下。
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 |
from random import randint from numpy import array from numpy import argmax from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense # 生成 [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] # 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 从 X 中删除第一个值 X = encoded[1:, :] # 转换为 3d 以供输入 X = X.reshape(X.shape[0], 1, X.shape[1]) # 从 y 中删除最后一个值 y = encoded[:-1, :] 返回 X, y # 定义模型 model = Sequential() model.add(LSTM(50, batch_input_shape=(6, 1, 100), stateful=True)) model.add(Dense(100, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 for i in range(2000): X, y = generate_data() model.fit(X, y, epochs=1, batch_size=6, verbose=2, shuffle=False) model.reset_states() # 在新数据上评估模型 X, y = generate_data() yhat = model.predict(X, batch_size=6) print('Expected: %s' % one_hot_decode(y)) print('Predicted: %s' % one_hot_decode(yhat)) |
运行示例给出了一个令人惊讶的结果。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
问题无法学习,训练以模型结束,回显序列中最后一个观测值的准确率为 0%。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... 纪元 1/1 0s - 损失: 4.6042 - 准确率: 0.0417 纪元 1/1 0s - 损失: 4.6215 - 准确率: 0.0000e+00 纪元 1/1 0s - 损失: 4.5802 - 准确率: 0.0000e+00 纪元 1/1 0s - 损失: 4.6023 - 准确率: 0.0000e+00 纪元 1/1 0s - 损失: 4.6071 - 准确率: 0.0000e+00 预期: [71, 44, 6, 11, 91, 23, 55, 37, 53, 4, 42, 15, 81, 6, 57, 97, 49, 69, 56, 86, 70, 12, 61, 48] 预测: [49, 49, 49, 87, 49, 96, 96, 96, 96, 96, 85, 96, 96, 96, 96, 96, 96, 96, 49, 49, 87, 96, 49, 49] |
这怎么可能?
初学者的错误
这是初学者常犯的错误,如果你接触过 RNN 或 LSTM,那么你就会发现上面的这个错误。
具体来说,LSTM 的强大功能确实来自学习到的内部状态,但这种状态只有在作为过去观测值的函数进行训练时才强大。
换句话说,您必须向网络提供预测的上下文(例如,可能包含时间依赖性的观测值)作为输入的时间步。
上述公式训练网络将输出学习为仅当前输入值的函数,如第一个示例所示
1 |
yhat(t) = f(X(t)) |
而不是作为最后 n 个观测值的函数,甚至只是作为先前观测值的函数,正如我们所要求的那样
1 |
yhat(t) = f(X(t-1)) |
LSTM 确实一次只需要一个输入来学习这种未知的时间依赖性,但它必须在序列上执行反向传播才能学习这种依赖性。您必须提供序列的过去观测值作为上下文。
您不是在定义一个窗口(如多层感知器中,每个过去的观测值都是一个加权输入);相反,您正在定义历史观测值的范围,LSTM 将尝试从中学习时间依赖性 (f(X(t-1), … X(t-n)))。
明确地说,这是在 Keras 中使用 LSTM 时初学者常犯的错误,而不是普遍存在的问题。
回显滞后观测值
现在我们已经避开了初学者常犯的陷阱,我们可以开发一个 LSTM 来回显先前的观测值。
第一步是重新制定问题定义。
我们知道网络只需要上一个观测值作为输入即可做出正确预测。但是我们希望网络学习回显哪个过去的观测值才能正确解决这个问题。因此,我们将提供上一个 5 个观测值的子序列作为上下文。
具体来说,如果我们的序列包含:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],则 X,y 对将如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 |
X, y NaN, NaN, NaN, NaN, NaN, NaN NaN, NaN, NaN, NaN, 1, NaN NaN, NaN, NaN, 1, 2, 1 NaN, NaN, 1, 2, 3, 2 NaN, 1, 2, 3, 4, 3 1, 2, 3, 4, 5, 4 2, 3, 4, 5, 6, 5 3, 4, 5, 6, 7, 6 4, 5, 6, 7, 8, 7 5, 6, 7, 8, 9, 8 6, 7, 8, 9, 10, 9 7, 8, 9, 10, NaN, 10 |
在这种情况下,您可以看到前 5 行和最后 1 行不包含足够的数据,因此在这种情况下,我们将删除它们。
我们将使用 Pandas shift() 函数创建序列的移位版本,并使用 Pandas concat() 函数将移位序列重新组合在一起。然后我们将手动排除不可行的行。
更新后的 generate_data() 函数如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 创建滞后输入 df = DataFrame(encoded) df = concat([df.shift(4), df.shift(3), df.shift(2), df.shift(1), df], axis=1) # 删除不可行的行 values = df.values values = values[5:,:] # 转换为 3d 以供输入 X = values.reshape(len(values), 5, 100) # 从 y 中删除最后一个值 y = encoded[4:-1,:] print(X.shape, y.shape) return X, y |
同样,我们可以通过生成一个序列并比较解码的 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 48 49 |
from random import randint from numpy import array from numpy import argmax 从 pandas 导入 concat from pandas import DataFrame from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense # 生成 [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] # 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 创建滞后输入 df = DataFrame(encoded) df = concat([df.shift(4), df.shift(3), df.shift(2), df.shift(1), df], axis=1) # 删除不可行的行 values = df.values values = values[5:,:] # 转换为 3d 以供输入 X = values.reshape(len(values), 5, 100) # 从 y 中删除最后一个值 y = encoded[4:-1,:] 返回 X, y # 测试数据生成器 X, y = generate_data() for i in range(len(X)): a, b, c, d, e, f = argmax(X[i,0]), argmax(X[i,1]), argmax(X[i,2]), argmax(X[i,3]), argmax(X[i,4]), argmax(y[i]) print(a, b, c, d, e, f) |
运行示例显示了最后5个值的上下文作为输入,以及最后一个先前的观测值(X(t-1))作为输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
57 96 99 77 44 77 96 99 77 44 45 44 99 77 44 45 28 45 77 44 45 28 70 28 44 45 28 70 73 70 45 28 70 73 74 73 28 70 73 74 73 74 70 73 74 73 64 73 73 74 73 64 29 64 74 73 64 29 15 29 73 64 29 15 94 15 64 29 15 94 98 94 29 15 94 98 89 98 15 94 98 89 52 89 94 98 89 52 96 52 98 89 52 96 46 96 89 52 96 46 46 46 52 96 46 46 85 46 96 46 46 85 49 85 46 46 85 49 59 49 |
我们现在可以开发一个LSTM来解决这个问题。
对于给定的序列,有20个X、y对;因此,选择了5的批次大小(4批次,每批5个样本 = 20个样本)。
使用了相同的结构,LSTM隐藏层有50个记忆单元,输出层有100个神经元。网络经过2,000个epoch的训练,每个epoch后内部状态都会重置。
完整的代码列表如下。
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 |
from random import randint from numpy import array from numpy import argmax 从 pandas 导入 concat from pandas import DataFrame from keras.models import Sequential 从 keras.layers 导入 LSTM from keras.layers import Dense # 生成 [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] # 为 lstm 生成数据 def generate_data(): # 生成序列 sequence = generate_sequence() # 独热编码 encoded = one_hot_encode(sequence) # 创建滞后输入 df = DataFrame(encoded) df = concat([df.shift(4), df.shift(3), df.shift(2), df.shift(1), df], axis=1) # 删除不可行的行 values = df.values values = values[5:,:] # 转换为 3d 以供输入 X = values.reshape(len(values), 5, 100) # 从 y 中删除最后一个值 y = encoded[4:-1,:] 返回 X, y # 定义模型 model = Sequential() model.add(LSTM(50, batch_input_shape=(5, 5, 100), stateful=True)) model.add(Dense(100, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 for i in range(2000): X, y = generate_data() model.fit(X, y, epochs=1, batch_size=5, verbose=2, shuffle=False) model.reset_states() # 在新数据上评估模型 X, y = generate_data() yhat = model.predict(X, batch_size=5) print('Expected: %s' % one_hot_decode(y)) print('Predicted: %s' % one_hot_decode(yhat)) |
运行示例表明,网络能够解决问题,并正确学习在5个先前观测值的上下文中返回X(t-1)观测值作为预测。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能会有所不同。考虑多次运行示例并比较平均结果。
下面提供了示例输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... 纪元 1/1 0s - loss: 0.1763 - acc: 1.0000 纪元 1/1 0s - loss: 0.2393 - acc: 0.9500 纪元 1/1 0s - loss: 0.1674 - acc: 1.0000 纪元 1/1 0s - loss: 0.1256 - acc: 1.0000 纪元 1/1 0s - loss: 0.1539 - acc: 1.0000 预期:[24, 49, 86, 73, 51, 6, 6, 52, 34, 32, 0, 14, 83, 16, 37, 75, 41, 40, 80, 33] 预测:[24, 49, 86, 73, 51, 6, 6, 52, 34, 32, 0, 14, 83, 16, 37, 75, 41, 40, 80, 33] |
扩展
本节列出了本教程中实验的一些扩展。
- 忽略内部状态。通过在epoch结束时手动重置状态,小心地在序列内的样本之间保留了LSTM的内部状态。我们知道,网络已经通过时间步长获得了每个样本所需的所有上下文和状态。探究额外的跨样本状态是否能为模型的技能带来任何好处。
- 掩盖缺失数据。在数据准备过程中,删除了缺失数据的行。探索使用特殊值(例如-1)标记缺失值,并查看LSTM是否可以从这些示例中学习。还可以探索使用掩码层作为输入,并探索掩盖缺失值。
- 将整个序列作为时间步长。仅提供了最后5个观测值作为上下文,从中学习进行回声。探索将整个随机序列作为每个样本的上下文,随着序列的展开而构建。这可能需要填充甚至掩盖缺失值,以满足LSTM固定大小输入的期望。
- 回声不同滞后值。在回声示例中使用了特定的滞后值(t-1)。探索在回声中使用不同的滞后值,以及这如何影响模型技能、训练时间、LSTM层大小等属性。我期望每个滞后都可以使用相同的模型结构进行学习。
- 回声滞后序列。网络被训练来回声特定的滞后观测值。探索回声滞后序列的变体。这可能需要使用网络输出上的TimeDistributed层来实现序列到序列的预测。
您是否探索过这些扩展?
在下面的评论中分享您的发现。
总结
在本教程中,您学习了如何开发一个LSTM来解决从随机整数序列中回声滞后观测值的问题。
具体来说,你学到了:
- 如何生成和编码该问题的测试数据。
- 如何避免在使用LSTM解决此类问题时初学者常犯的错误。
- 如何开发一个强大的LSTM来回声随机整数的临时序列中的整数。
你有什么问题吗?
在评论中提出您的问题,我将尽力回答。
哇,
这真棒。100%的准确率!!
这也适用于二项式分类问题吗?
这是一个定义明确的小问题的特例。
非常好…谢谢
谢谢。
Jason,您能澄清一下“您不是在定义一个窗口(就像多层感知器中每个过去的观测值都是加权输入的情况一样);相反,您是在定义历史观测值的范围”这句话吗?似乎我们正在做的正是定义一个加权输入的窗口:我们正在输入一系列窗口,而LSTM正在学习从该窗口中挑选一个元素。
不完全是,LSTM一次处理一个时间步,而不是像带有窗口的MLP那样对所有滞后观测值进行加权。
请看这篇文章
https://machinelearning.org.cn/gentle-introduction-backpropagation-time/
嗨 Jason
这篇博文真的很有启发性,特别是涵盖初学者错误的部分。
不过我有一个悬而未决的问题。在最后一节,我们尝试回声滞后观测值,LSTM模型比多层感知器有什么优势呢?
我们可以将时间步长视为MLP的输入特征,然后训练它学习这些输入的正确权重并获得正确的预测输出。
基本上,我想了解使用基于LSTM的RNN会有什么优势,因为MLP模型会失败。回声滞后示例似乎并没有向我展示LSTM模型的优势。
MLP必须一次性接收所有时间步作为特征,而LSTM一次只能看到一个时间步,并随着时间步积累状态以创建输出。
这是关键区别。
谢谢Jason,我理解实现上的差异。但是,既然回声滞后问题也可以通过使用具有所有时间步长作为特征的MLP模型来解决,我想了解LSTM相对于它有什么优势呢?
具体来说,是否存在任何不能通过MLP模型(通过使用时间步长作为输入特征)学习的序列?
每个输入时间步长都是一次性提供的,并且LSTM必须使用内部状态“记住”要回显的内容,这说明了其中的区别。MLP必须一次性将所有时间步长作为输入提供。
也许这篇关于BPTT的帖子能让你更清楚地理解
https://machinelearning.org.cn/gentle-introduction-backpropagation-time/
很棒的文章..谢谢
我很高兴它能帮到你。
非常有用的文章,感谢您的努力。
我对LSTM还比较陌生,但我大概了解了它的工作原理。不过我在理解上有点问题
——我想从“yhat”预测下一个随机输出,所以我做了一个小循环,每次都会预测并附加测试列表(这样它会预测更多的未来输出),换句话说,每次“yhat”列表都会变成“X”列表,我认为这意味着它将预测下一个输出。
但我的问题是它一直停留在最初给出的X列表中
预测数字的列表越来越小,尽管我每次都进行了追加,所以我无法进行未来预测。
您想让我展示那段代码和输出吗?您所做的对我会非常有帮助
再次感谢
干得好!
早上好,我想你没听懂我说的,哈哈,抱歉我解释得不好
每次迭代中,X_list都是前一个yhat预测的列表。意思是
X(n 次迭代) = yhat(n-1),这是执行结果,你可以了解我的问题 ->
(“迭代 = “, 6)
X 文件:[20, 38, 19, 48, 28, 43, 50, 14, 25, 28, 20, 19, 44, 16, 9]
预测值:[48, 28, 43, 50, 14, 25, 28, 20, 19, 44, 16, 9, 25, 13, 15]
(“迭代 = “, 7)
X 文件:[28, 43, 50, 14, 25, 28, 20, 19, 44, 16]
预测值:[14, 25, 28, 20, 19, 44, 16, 9, 25, 13]
(“迭代 = “, 8)
X 文件:[25, 28, 20, 19, 44]
预测值:[19, 44, 16, 9, 25]
(“迭代 = “, 9)
X 文件:[]
预测值:[]
我的想法是每次我们给它一个预测的数字时都继续预测未来的输出,但不幸的是它显示了一个小的恶性循环列表。如果您能为我的教育项目解释一下,我将非常感谢。我让您看看我做的这部分代码
for i in range(1,10): #迭代10次
yhat_list = one_hot_decode(yhat) #将上次预测的列表作为下次预测的输入读取
encoded = one_hot_encode(yhat_list)
# 创建滞后输入
df = DataFrame(encoded)
df = concat([df.shift(4), df.shift(3), df.shift(2), df.shift(1), df], axis=1)
# 删除不可行的行
values = df.values
values = values[5:,:]
# 转换为3D输入
X = values.reshape(len(values), 5, 100)
yhat = model.predict(X, batch_size=5)
print(“迭代 = “, i)
print(‘X 文件: %s’ % one_hot_decode(X))
print(‘预测值: %s’ % one_hot_decode(yhat))
非常感谢您的解释
我不明白。你到底遇到了什么问题?
我想继续预测“yhat”的接下来100个输出,所以我每次都将其作为X列表传入。我以为它
每次都会预测新的输出。
但不幸的是,循环从未进展,并且不断变小并预测已知的数字。(正如我在前面的例子中向您展示的那样)
for i in range(1,100): #迭代10次
yhat_list = one_hot_decode(yhat) #将上次预测的列表作为下次预测的输入读取
encoded = one_hot_encode(yhat_list)
# 创建滞后输入
df = DataFrame(encoded)
df = concat([df.shift(4), df.shift(3), df.shift(2), df.shift(1), df], axis=1)
# 删除不可行的行
values = df.values
values = values[5:,:]
# 转换为3D输入
X = values.reshape(len(values), 5, 100)
yhat = model.predict(X, batch_size=5)
print(“迭代 = “, i)
print(‘X 文件: %s’ % one_hot_decode(X))
print(‘预测值: %s’ % one_hot_decode(yhat))
我有很多关于使用LSTM进行多步预测的帖子,也许可以从这里开始
https://machinelearning.org.cn/start-here/#deep_learning_time_series
好的,谢谢你Jason,我会去看看的,此致
Jason下午好,我想用我自己的数据集替换随机生成器,并迭代它以获得最常见的数字,它在一个CSV文件中,我该如何在代码中实现呢?提前感谢
我无法为您编写自定义示例。您究竟遇到了什么问题?
你的博客很有趣,我读了很多东西。
我认为你提出的这个有趣问题的解决方案与LSTM的长记忆无关。你想要的结果是通过输入轴2提供的。你可以通过代码第47行的“stateful = False”来验证:收敛到acc = 100%甚至更快!
另一方面,你所说的“初学者常犯的陷阱”正是 https://keras.org.cn/examples/lstm_stateful/ 中建议的方式。计算结果 (tsteps = 2 lahead = 1) 表明,尽管问题没有解决,但stateful = True / False 之间存在明显的差异。
在我看来,关键问题是
是否可以训练一个LSTM,使其(在生产模式下)能够仅接收一个随机序列,每次一个数字,就能产生回声?
这个问题将阐明LSTM相对于其他网络的优势,因为没有记忆是无法解决的。
此致
我相信可以,是的。
请看这篇文章
https://machinelearning.org.cn/memory-in-a-long-short-term-memory-network/
Hi Jason - 我可以问一下你为什么使用自定义编码的独热编码,而不是Keras函数to_categorical?
另外,如果n_unique值非常大,会如何实现?
谢谢。
我不记得原因了,抱歉。
有多大?在自然语言处理问题中,将独热编码应用于数万个甚至更多标记是很常见的。
此外,嵌入对于高基数分类变量的效果非常好。
如何使用这个程序预测下一个数字?
调用:model.predict()
也许这会有帮助。
https://machinelearning.org.cn/how-to-make-classification-and-regression-predictions-for-deep-learning-models-in-keras/
谢谢你的精彩代码,先生,但我无法预测下一个数字,这是我从你的上述文章中得到的代码
Xnew = array([[1,36, 0, 36, 21,2]])
ynew = model.predict(Xnew)
print("X=%s, Predicted=%s" % (Xnew[0], ynew[0]))
然后它显示——-
ValueError: 检查输入时出错:预期lstm_26_input有3个维度,但得到形状为(1, 6)的数组
但是,在将其重塑为3D后,它显示错误
Xnew = array([[1,36, 0, 36, 21,2]])
Xnew=Xnew.reshape(Xnew.shape[0],Xnew.shape[1] , 1)
ynew = model.predict(Xnew)
print(“X=%s, Predicted=%s” % (Xnew[0], ynew[0])) 然后它显示
ValueError: 检查输入时出错:预期lstm_26_input的形状为(5, 340),但得到形状为(6, 1)的数组
你能把代码回复一下吗,拜托,因为大多数人都面临这个问题?????
听到这个消息很抱歉,这可能会有帮助
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
先生,您的代码运行完美,但我无法预测下一个数字,如果我想给出我自己选择的X。通过使用model.predict () 假设我想给出34,那么下一个数字是什么?先生,拜托,这只有两行代码,请在评论区写出来
或许可以从现有代码开始,并根据您所需更改进行调整。
但是,我怎么才能预测未来的随机数呢?这对我来说不清楚。因为我们已经把随机数作为输入了,那么如何预测下一个呢?
在本教程中,我们不是预测未来的随机数,我们正在学习如何使用LSTM来回声输入。
对于预测,也许可以从这里开始
https://machinelearning.org.cn/start-here/#deep_learning_time_series
如果我想将生成数字的数量从25限制到5,甚至是6,那么还需要修改哪些其他因素?仅仅更改生成数字中元素的数量会产生错误。顺便说一下:要去买你的电子书,谢谢!
是的,改变问题的定义和编码方式。
您可能需要针对问题难度的变化调整模型和学习超参数。
首先,你的帖子给了我更多知识。但我有一个问题:当我用[a,b,c,d,e]测试你的模型时,我尝试更改e为许多数字,并保持a,b,c,d不变,但你的模型仍能预测出正确的数字d。在我看来,我认为当我们改变一些数字时,预测的d也应该改变?对吗?
也许模型过拟合了。
亲爱的 Jason,
我已经购买了您的两本电子书,学到了很多,两本都与序列和LSTM应用有关。根据您的电子邮件,我发现这个“回声滞后观测”非常棒。我执行了Python代码,真的很有趣,大约500个epoch后准确率达到了1。但我需要您的帮助,因为我尝试预测序列的下一个值,但我真的没有这个技能。程序预测的与X输入完全相同。我需要预测序列中的下一个数字,但我真的不知道如何操作。请问,您能给我预测序列中下一个值的代码吗?
最后几句是
yhat = model.predict(X, batch_size=5)
print(‘预期值: %s’ % one_hot_decode(y))
print(‘预测值: %s’ % one_hot_decode(yhat))
此致,
马科斯
你好 Marcos…你可能想为此目的研究序列到序列的预测。
https://machinelearning.org.cn/develop-encoder-decoder-model-sequence-sequence-prediction-keras/
谢谢你,James。
我想使用这里这份文档中描述的 Echo Random,我唯一不知道如何做的是,给定一个数字序列,如何预测该序列的下一个数字。Jason 展示的这段代码预测的正是算法所呈现的内容。我需要知道的是预测序列下一个数字的代码是什么。我看到的所有示例中,算法都预测出完全相同的序列。我只是需要得到序列的下一个数字。我不知道如何做到。
感谢这篇博客!确实,它对任何长度的随机整数都表现出色。
我想问一下……如果我使用传统方法并应用 LSTM,我会将数据集分成训练集和测试集,预测后,我可以验证训练并预测序列中的下一个值。
在这种随机整数的情况下,如果我向此模式输入某个随机序列,我如何预测序列中的下一个值?
你好 Tariq…使用 LSTM(长短期记忆)模型预测随机整数序列中的下一个值是一项有趣的任务,因为随机序列根据定义没有可预测的模式或依赖关系,而这些是传统时间序列或序列预测模型所利用的。然而,如果序列是伪随机的或包含不立即明显的隐藏模式或依赖关系,LSTM 模型可能能够捕获其中一些特征。
以下是您可能尝试使用 LSTM 模型预测整数序列中下一个值的一般方法:
### 1. **准备数据集**
– **序列创建**:将随机整数序列转换为监督学习问题。这通常涉及创建输入-输出对,其中输入是整数序列,输出是序列中的下一个整数。例如,从序列 \([x_1, x_2, x_3, x_4, …]\) 中,您可以创建输入-输出对,如 \(([x_1, x_2, x_3], x_4)\)。
– **归一化**:根据整数的范围,您可能需要归一化或缩放数据以帮助 LSTM 模型表现更好。
### 2. **模型设计**
– **输入层**:设计您的 LSTM,使其输入层与您的数据维度匹配。对于序列输入,这通常是序列长度和特征数量(例如,对于单特征序列,为 \((sequence\_length, 1)\))。
– **LSTM 层**:添加一个或多个 LSTM 层。模型的复杂性可以根据数据集大小和可用的计算资源进行调整。
– **输出层**:由于您正在预测整数,输出层可以是带有线性激活函数(如果将下一个整数预测为回归问题)的密集层,或者是一个 softmax 激活层(如果分类为类别)。
### 3. **训练模型**
– **损失函数**:回归问题使用 MSE(均方误差),分类问题使用交叉熵。
– **优化器**:常用选择包括 Adam 或 SGD(随机梯度下降)。
– **时期和批次**:根据您的数据集大小和过拟合行为选择适当的值。
### 4. **预测阶段**
– **使用最后已知值**:要预测序列中的下一个整数,请将最后已知值输入模型。例如,如果您的训练期间的最后一个输入序列是 \([x_{n-2}, x_{n-1}, x_n]\),要预测 \(x_{n+1}\),您将 \([x_{n-2}, x_{n-1}, x_n]\) 输入模型。
– **顺序预测**:如果您想预测多个未来步骤,可以将预测结果用作新输入。例如,预测 \(x_{n+1}\),然后使用 \([x_{n-1}, x_n, x_{n+1}]\) 来预测 \(x_{n+2}\),依此类推。
### 5. **评估**
– 使用适当的指标(例如,回归的 RMSE)评估模型的性能。
– 执行诊断以检查模型是仅仅记忆训练数据还是真正捕获了有用的模式。
### 6. **随机数据的挑战**
– **真正随机性**:如果数据是真正随机的,您可能会发现 LSTM 模型在预测未来值方面表现不佳,因为没有可学习的模式。
– **伪随机模式**:如果存在潜在模式或序列是通过确定性伪随机算法生成的,LSTM 可能会捕获其中一些模式。
鉴于随机的性质,使用 LSTM 预测随机整数序列中的未来值更像是一种实验性而非实用的方法。如果您将其作为理论练习或学习目的进行探索,它可以为序列建模和 LSTM 能力提供有价值的见解。