长短期记忆模型(LSTM)网络是一种强大而流行的循环神经网络。
它被广泛使用,因为其架构克服了困扰所有循环神经网络的梯度消失和梯度爆炸问题,从而可以创建非常大且非常深的网络。
像其他循环神经网络一样,LSTM 网络会保持状态,而 Keras 框架中具体如何实现这一点可能会令人困惑。
在这篇文章中,您将确切了解 Keras 深度学习库如何在 LSTM 网络中保持状态。
阅读本文后,你将了解:
- 如何为序列预测问题开发一个简单的 LSTM 网络
- 如何使用 LSTM 网络通过批量和特征仔细管理状态
- 如何在 LSTM 网络中手动管理状态以进行有状态预测
通过我的新书《使用 Python 进行深度学习》启动您的项目,其中包括逐步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2016 年 7 月:首次发布
- 2017 年 3 月更新:更新了 Keras 2.0.2、TensorFlow 1.0.1 和 Theano 0.9.0 的示例
- 2018 年 8 月更新:更新了 Python 3 的示例,更新了有状态示例以获得 100% 的准确率
- 2019 年 3 月更新:修正了有状态示例中的错字
- 2022 年 7 月更新:更新至 TensorFlow 2.x API

使用 Keras 在 Python 中理解有状态 LSTM 循环神经网络
图片由 Martin Abegglen 拍摄,保留部分权利。
问题描述:学习字母表
在本教程中,您将开发并对比几种不同的 LSTM 循环神经网络模型。
这些比较的背景是一个学习字母表的简单序列预测问题。也就是说,给定一个字母表中的字母,它将预测字母表中的下一个字母。
这是一个简单的序列预测问题,一旦理解,就可以推广到其他序列预测问题,如时间序列预测和序列分类。
让我们用一些您可以重复使用的 Python 代码来准备这个问题。
首先,让我们导入本教程中将使用的所有类和函数。
1 2 3 4 5 6 |
import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical |
接下来,您可以设置随机数生成器种子,以确保每次执行代码时结果都相同。
1 2 |
# 设置随机种子以保证结果可复现 tf.random.set_seed(7) |
现在您可以定义您的数据集,即字母表。为了可读性,您将字母表定义为大写字符。
神经网络对数字进行建模,因此您需要将字母表中的字母映射到整数值。您可以通过创建字母索引到字符的字典(映射)轻松实现这一点。您还可以创建一个反向查找,用于将预测结果转换回字符,以备后用。
1 2 3 4 5 |
# 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) |
现在,您需要创建用于训练神经网络的输入和输出对。您可以通过定义输入序列长度,然后从输入字母表序列中读取序列来完成此操作。
例如,使用输入长度为 1。从原始输入数据开头开始,您可以读取第一个字母“A”作为输入,下一个字母“B”作为预测。您每次移动一个字符并重复此操作,直到预测到“Z”。
1 2 3 4 5 6 7 8 9 10 |
# 准备编码为整数的输入到输出对数据集 seq_length = 1 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) |
此外,打印输入对以进行健全性检查。
运行到此为止的代码将生成以下输出,总结了长度为 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 25 |
A -> B B -> C C -> D D -> E E -> F F -> G G -> H H -> I I -> J J -> K K -> L L -> M M -> N N -> O O -> P P -> Q Q -> R R -> S S -> T T -> U U -> V V -> W W -> X X -> Y Y -> Z |
您需要将 NumPy 数组重塑为 LSTM 网络所期望的格式,具体为 [样本数, 时间步长, 特征数]。
1 2 |
# 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), seq_length, 1)) |
重塑后,您可以将输入整数归一化到 0 到 1 的范围,这是 LSTM 网络使用的 Sigmoid 激活函数的范围。
1 2 |
# 归一化 X = X / 浮点数(长度(alphabet)) |
最后,您可以将此问题视为一个序列分类任务,其中 26 个字母中的每一个都代表一个不同的类别。因此,您可以使用 Keras 内置函数 **to_categorical()** 将输出 (y) 转换为独热编码。
1 2 |
# 对输出变量进行独热编码 y = to_categorical(dataY) |
您现在可以拟合不同的 LSTM 模型了。
Python 深度学习需要帮助吗?
参加我的免费为期两周的电子邮件课程,发现 MLP、CNN 和 LSTM(附代码)。
立即点击注册,还将免费获得本课程的 PDF 电子书版本。
用于学习单字符到单字符映射的朴素 LSTM
让我们从设计一个简单的 LSTM 开始,学习如何在只给定一个字符的上下文的情况下,预测字母表中的下一个字符。
您将把问题框定为单字母输入到单字母输出对的随机集合。正如您将看到的,这对 LSTM 学习来说是一个有问题的问题框定。
我们来定义一个拥有 32 个单元的 LSTM 网络,以及一个带有 Softmax 激活函数用于预测的输出层。由于这是一个多类别分类问题,您可以使用对数损失函数(在 Keras 中称为“**categorical_crossentropy**”),并使用 ADAM 优化函数优化网络。
该模型在 500 个 epoch 中进行拟合,批次大小为 1。
1 2 3 4 5 6 |
# 创建并拟合模型 model = Sequential() model.添加(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.拟合(X, y, epochs=500, batch_size=1, verbose=2) |
拟合模型后,您可以评估并总结整个训练数据集的性能。
1 2 3 |
# 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) |
然后,您可以再次通过网络运行训练数据并生成预测,将输入和输出对都转换回其原始字符格式,以直观地了解网络学习问题的效果。
1 2 3 4 5 6 7 8 9 |
# 演示一些模型预测 对于 模式 在 dataX: x = np.reshape(pattern, (1, len(pattern), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
完整的代码清单如下所示。
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 |
# 朴素 LSTM 学习单字符到单字符映射 import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical # 设置随机种子以保证结果可复现 tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 seq_length = 1 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), seq_length, 1)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 model = Sequential() model.添加(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.拟合(X, y, epochs=500, batch_size=1, verbose=2) # 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 对于 模式 在 dataX: x = np.reshape(pattern, (1, len(pattern), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行此示例将产生以下输出。
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 |
模型准确率:84.00% ['A'] -> B ['B'] -> C ['C'] -> D ['D'] -> E ['E'] -> F ['F'] -> G ['G'] -> H ['H'] -> I ['I'] -> J ['J'] -> K ['K'] -> L ['L'] -> M ['M'] -> N ['N'] -> O ['O'] -> P ['P'] -> Q ['Q'] -> R ['R'] -> S ['S'] -> T ['T'] -> U ['U'] -> W ['V'] -> Y ['W'] -> Z ['X'] -> Z ['Y'] -> Z |
您可以看到,这个问题对于网络来说确实很难学习。
原因是可怜的 LSTM 单元没有任何上下文可供使用。每个输入-输出模式都以随机顺序显示给网络,并且在每个模式(每个批次,其中每个批次包含一个模式)之后,网络的状态都会被重置。
这是一种滥用 LSTM 网络架构的行为,将其视为标准多层感知器。
接下来,让我们尝试以不同的方式构建问题,为网络提供更多序列以供学习。
用于三字符特征窗口到单字符映射的朴素 LSTM
一种为多层感知器的数据添加更多上下文的常用方法是使用窗口法。
这就是将序列中的前几个步骤作为附加输入特征提供给网络。您可以尝试使用相同的技巧为 LSTM 网络提供更多上下文。
在这里,您将序列长度从 1 增加到 3,例如
1 2 |
# 准备编码为整数的输入到输出对数据集 seq_length = 3 |
这会创建如下所示的训练模式:
1 2 3 |
ABC -> D BCD -> E CDE -> F |
序列中的每个元素随后都作为新的输入特征提供给网络。这需要修改数据准备步骤中输入序列的重塑方式。
1 2 |
# 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), 1, seq_length)) |
它还需要修改在演示模型预测时如何重塑样本模式。
1 |
x = np.reshape(pattern, (1, 1, len(pattern))) |
完整的代码清单如下所示。
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 |
# 用于学习三字符窗口到单字符映射的朴素 LSTM import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical # 设置随机种子以保证结果可复现 tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 seq_length = 3 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), 1, seq_length)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 model = Sequential() model.添加(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.拟合(X, y, epochs=500, batch_size=1, verbose=2) # 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 对于 模式 在 dataX: x = np.reshape(pattern, (1, 1, len(pattern))) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行此示例将提供以下输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
模型准确率:86.96% ['A', 'B', 'C'] -> D ['B', 'C', 'D'] -> E ['C', 'D', 'E'] -> F ['D', 'E', 'F'] -> G ['E', 'F', 'G'] -> H ['F', 'G', 'H'] -> I ['G', 'H', 'I'] -> J ['H', 'I', 'J'] -> K ['I', 'J', 'K'] -> L ['J', 'K', 'L'] -> M ['K', 'L', 'M'] -> N ['L', 'M', 'N'] -> O ['M', 'N', 'O'] -> P ['N', 'O', 'P'] -> Q ['O', 'P', 'Q'] -> R ['P', 'Q', 'R'] -> S ['Q', 'R', 'S'] -> T ['R', 'S', 'T'] -> U ['S', 'T', 'U'] -> V ['T', 'U', 'V'] -> Y ['U', 'V', 'W'] -> Z ['V', 'W', 'X'] -> Z ['W', 'X', 'Y'] -> Z |
您可能会看到性能略有提升,这可能是也可能不是真实的。这是一个简单的问题,即使使用窗口方法,您仍然无法用 LSTM 学习。
再次强调,这是对 LSTM 网络的误用,因为问题框架不佳。实际上,字母序列是单个特征的时间步,而不是单独特征的一个时间步。您为网络提供了更多的上下文,但没有提供预期的更多序列。
在下一节中,您将以时间步的形式为网络提供更多上下文。
用于三字符时间步窗口到单字符映射的朴素 LSTM
在 Keras 中,LSTM 的预期用法是以时间步的形式提供上下文,而不是像其他网络类型那样以窗口特征的形式。
您可以采用您的第一个示例,并简单地将序列长度从 1 更改为 3。
1 |
seq_length = 3 |
同样,这会创建如下所示的输入-输出对:
1 2 3 4 |
ABC -> D BCD -> E CDE -> F DEF -> G |
不同之处在于,输入数据的重塑将序列视为一个特征的时间步序列,而不是多个特征的单个时间步。
1 2 |
# 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), seq_length, 1)) |
这是在 Keras 中为 LSTM 提供序列上下文的正确预期用法。完整的代码示例如下所示。
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 |
# 朴素 LSTM 学习三字符时间步到单字符映射 import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical # 设置随机种子以保证结果可复现 tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 seq_length = 3 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), seq_length, 1)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 model = Sequential() model.添加(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.拟合(X, y, epochs=500, batch_size=1, verbose=2) # 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 对于 模式 在 dataX: x = np.reshape(pattern, (1, len(pattern), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行此示例将提供以下输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
模型准确率:100.00% ['A', 'B', 'C'] -> D ['B', 'C', 'D'] -> E ['C', 'D', 'E'] -> F ['D', 'E', 'F'] -> G ['E', 'F', 'G'] -> H ['F', 'G', 'H'] -> I ['G', 'H', 'I'] -> J ['H', 'I', 'J'] -> K ['I', 'J', 'K'] -> L ['J', 'K', 'L'] -> M ['K', 'L', 'M'] -> N ['L', 'M', 'N'] -> O ['M', 'N', 'O'] -> P ['N', 'O', 'P'] -> Q ['O', 'P', 'Q'] -> R ['P', 'Q', 'R'] -> S ['Q', 'R', 'S'] -> T ['R', 'S', 'T'] -> U ['S', 'T', 'U'] -> V ['T', 'U', 'V'] -> W ['U', 'V', 'W'] -> X ['V', 'W', 'X'] -> Y ['W', 'X', 'Y'] -> Z |
您可以看到,模型完美地学习了问题,这在模型评估和示例预测中得到了证明。
但它学习了一个更简单的问题。具体来说,它学会了从字母表中三个字母的序列中预测下一个字母。它可以被赋予字母表中任何随机的三字母序列并预测下一个字母。
它实际上不能枚举字母表。一个足够大的多层感知网络可能能够使用窗口方法学习相同的映射。
LSTM 网络是有状态的。它们应该能够学习整个字母序列,但默认情况下,Keras 的实现会在每个训练批次后重置网络状态。
批次内的 LSTM 状态
Keras 对 LSTM 的实现会在每个批次后重置网络的状态。
这表明,如果您的批次大小足够大以容纳所有输入模式,并且所有输入模式都按顺序排列,则 LSTM 可以利用批次内的序列上下文来更好地学习序列。
您可以通过修改第一个示例来轻松演示这一点,该示例用于学习一对一映射,并将批次大小从 1 增加到训练数据集的大小。
此外,Keras 在每个训练 epoch 之前都会打乱训练数据集。为了确保训练数据模式保持顺序性,您可以禁用此打乱功能。
1 |
model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False) |
网络将使用批次内序列上下文学习字符映射,但这种上下文在进行预测时将不可用于网络。您可以评估网络随机和按顺序进行预测的能力。
完整的代码示例如下所示。
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 |
# 朴素 LSTM 学习单字符到单字符映射,所有数据都在每个批次中 import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical 来自 tensorflow.keras.preprocessing.sequence 导入 pad_sequences # 设置随机种子以保证结果可复现 tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 seq_length = 1 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) # 将列表的列表转换为数组并根据需要填充序列 X = pad_sequences(dataX, maxlen=seq_length, dtype='float32') # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (X.shape[0], seq_length, 1)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 model = Sequential() model.添加(LSTM(16, input_shape=(X.shape[1], X.shape[2]))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False) # 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 对于 模式 在 dataX: x = np.reshape(pattern, (1, len(pattern), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) # 演示预测随机模式 打印("测试随机模式:") 对于 i 在 范围(0,20): pattern_index = np.random.randint(len(dataX)) pattern = dataX[pattern_index] x = np.reshape(pattern, (1, len(pattern), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行该示例将提供以下输出。
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 |
模型准确率:100.00% ['A'] -> B ['B'] -> C ['C'] -> D ['D'] -> E ['E'] -> F ['F'] -> G ['G'] -> H ['H'] -> I ['I'] -> J ['J'] -> K ['K'] -> L ['L'] -> M ['M'] -> N ['N'] -> O ['O'] -> P ['P'] -> Q ['Q'] -> R ['R'] -> S ['S'] -> T ['T'] -> U ['U'] -> V ['V'] -> W ['W'] -> X ['X'] -> Y ['Y'] -> Z 测试随机模式 ['T'] -> U ['V'] -> W ['M'] -> N ['Q'] -> R ['D'] -> E ['V'] -> W ['T'] -> U ['U'] -> V ['J'] -> K ['F'] -> G ['N'] -> O ['B'] -> C ['M'] -> N ['F'] -> G ['F'] -> G ['P'] -> Q ['A'] -> B ['K'] -> L ['W'] -> X ['E'] -> F |
正如预期的那样,网络能够利用序列内的上下文学习字母表,在训练数据上实现了 100% 的准确率。
重要的是,网络能够准确预测随机选择字符的字母表中的下一个字母。这非常令人印象深刻。
有状态 LSTM 用于单字符到单字符映射
您已经看到,您可以将原始数据分解为固定大小的序列,并且 LSTM 可以学习这种表示,但只能学习 3 个字符到 1 个字符的随机映射。
您还看到,您可以改变批次大小以向网络提供更多序列,但仅限于训练期间。
理想情况下,您希望将整个序列暴露给网络,让它学习相互依赖关系,而不是您在问题框架中明确定义这些依赖关系。
您可以在 Keras 中通过使 LSTM 层有状态,并在每个 epoch 结束时(也是训练序列的结束)手动重置网络状态来做到这一点。
这才是 LSTM 网络真正的使用方式。
您首先需要将 LSTM 层定义为有状态。这样做时,您必须在输入形状上明确指定批次大小作为一个维度。这也意味着当您评估网络或进行预测时,您也必须指定并遵守相同的批次大小。现在这不是问题,因为您使用的是批次大小为 1。当批次大小不为 1 时,这可能会在进行预测时引入困难,因为预测需要在批次和序列中进行。
1 2 |
batch_size = 1 model.添加(LSTM(50, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True)) |
训练有状态 LSTM 的一个重要区别是,您手动一次训练一个 epoch,并在每个 epoch 之后重置状态。您可以在 for 循环中执行此操作。同样,不要打乱输入,保持输入训练数据创建时的序列。
1 2 3 |
对于 i 在 范围(300): model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() |
如前所述,您在评估网络在整个训练数据集上的性能时指定批次大小。
1 2 3 4 |
# 总结模型性能 scores = model.evaluate(X, y, batch_size=batch_size, verbose=0) model.reset_states() 打印("模型准确率: %.2f%%" % (scores[1]*100)) |
最后,您可以证明网络确实学习了整个字母表。您可以给它播种第一个字母“A”,请求一个预测,将预测反馈作为输入,然后重复这个过程一直到“Z”。
1 2 3 4 5 6 7 8 9 10 |
# 演示一些模型预测 seed = [char_to_int[alphabet[0]]] 对于 i 在 范围(0, len(alphabet)-1): x = np.reshape(seed, (1, len(seed), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) print(int_to_char[seed[0]], "->", int_to_char[index]) seed = [index] model.reset_states() |
您还可以查看网络是否可以从任意字母开始进行预测。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 演示一个随机的起始点 letter = "K" seed = [char_to_int[letter]] 打印("新起点: ", letter) 对于 i 在 范围(0, 5): x = np.reshape(seed, (1, len(seed), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) print(int_to_char[seed[0]], "->", int_to_char[index]) seed = [index] 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 54 55 56 57 58 59 60 61 62 63 64 65 |
# 有状态 LSTM 学习单字符到单字符映射 import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical # 设置随机种子以保证结果可复现 tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 seq_length = 1 dataX = [] dataY = [] 对于 i 在 范围(0, 长度(alphabet) - seq_length, 1): seq_in = alphabet[i:i + seq_length] seq_out = alphabet[i + seq_length] dataX.添加([char_to_int[char] 对于 char 在 seq_in]) dataY.添加(char_to_int[seq_out]) 打印(seq_in, '->', seq_out) # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(dataX, (len(dataX), seq_length, 1)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 batch_size = 1 model = Sequential() model.添加(LSTM(50, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True)) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) 对于 i 在 范围(300): model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() # 总结模型性能 scores = model.evaluate(X, y, batch_size=batch_size, verbose=0) model.reset_states() 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 seed = [char_to_int[alphabet[0]]] 对于 i 在 范围(0, len(alphabet)-1): x = np.reshape(seed, (1, len(seed), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) print(int_to_char[seed[0]], "->", int_to_char[index]) seed = [index] model.reset_states() # 演示一个随机的起始点 letter = "K" seed = [char_to_int[letter]] 打印("新起点: ", letter) 对于 i 在 范围(0, 5): x = np.reshape(seed, (1, len(seed), 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) print(int_to_char[seed[0]], "->", int_to_char[index]) seed = [index] 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 |
模型准确率:100.00% A -> B B -> C C -> D D -> E E -> F F -> G G -> H H -> I I -> J J -> K K -> L L -> M M -> N N -> O O -> P P -> Q Q -> R R -> S S -> T T -> U U -> V V -> W W -> X X -> Y Y -> Z 新起点:K K -> B B -> C C -> D D -> E E -> F |
您可以看到网络完美地记忆了整个字母表。它利用样本自身的上下文,学习了预测序列中下一个字符所需的任何依赖关系。
您还可以看到,如果用第一个字母给网络播种,它就能正确地背诵出字母表的其余部分。
您还可以看到,它只学习了完整的字母序列,并且是从冷启动开始的。当被要求预测“K”的下一个字母时,它预测“B”,然后又回到了背诵整个字母表的状态。
要真正预测“K”,网络的状态需要预热,并迭代地输入从“A”到“J”的字母。这表明,您可以通过像这样准备训练数据来实现与“无状态”LSTM 相同的效果:
1 2 3 4 |
---a -> b --ab -> c -abc -> d abcd -> e |
在这里,输入序列固定为 25(a-到-y 预测 z),并且模式以零填充为前缀。
最后,这引出了一个问题:如何使用可变长度输入序列训练 LSTM 网络来预测下一个字符。
LSTM 具有可变长度输入到单字符输出
在上一节中,您发现 Keras 的“有状态”LSTM 实际上只是重播前 n 个序列的捷径,并没有真正帮助我们学习字母表的通用模型。
在本节中,您将探索“无状态”LSTM 的一个变体,它学习字母表的随机子序列,并努力构建一个模型,该模型可以给定任意字母或字母子序列,并预测字母表中的下一个字母。
首先,您正在改变问题的框架。为了简化,您将定义一个最大输入序列长度并将其设置为一个小值,例如 5,以加快训练速度。这定义了将用于训练的字母表子序列的最大长度。在扩展中,这可以设置为完整的字母表(26)或更长,如果您允许循环回到序列的开头。
您还需要定义要创建的随机序列的数量——在本例中为 1000。这个数字也可以多或少。实际需要的模式可能更少。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 准备编码为整数的输入到输出对数据集 num_inputs = 1000 max_len = 5 dataX = [] dataY = [] 对于 i 在 范围(num_inputs): start = np.random.randint(len(alphabet)-2) end = np.random.randint(start, min(start+max_len,len(alphabet)-1)) sequence_in = alphabet[start:end+1] sequence_out = alphabet[end + 1] dataX.添加([char_to_int[char] 用于 char 在 sequence_in]) dataY.添加(char_to_int[sequence_out]) 打印(sequence_in, '->', sequence_out) |
在更广泛的上下文中运行此代码将创建如下所示的输入模式:
1 2 3 4 5 6 7 8 9 |
PQRST -> U W -> X O -> P OPQ -> R IJKLM -> N QRSTU -> V ABCD -> E X -> Y GHIJ -> K |
输入序列的长度在 1 到 **max_len** 之间变化,因此需要零填充。这里,使用 Keras 内置的 **pad_sequences()** 函数进行左侧(前缀)填充。
1 |
X = pad_sequences(dataX, maxlen=max_len, dtype='float32') |
训练后的模型在随机选择的输入模式上进行评估。这也可以是新随机生成的字符序列。这也可以是种子为“A”的线性序列,其输出作为单字符输入反馈。
完整的代码清单如下。
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 |
# LSTM 具有可变长度输入序列到单字符输出 import numpy as np import tensorflow as tf 来自 tensorflow.keras.models 导入 Sequential 来自 tensorflow.keras.layers 导入 Dense 来自 tensorflow.keras.layers 导入 LSTM 来自 tensorflow.keras.utils 导入 to_categorical 来自 tensorflow.keras.preprocessing.sequence 导入 pad_sequences # 设置随机种子以保证结果可复现 np.random.seed(7) tf.random.set_seed(7) # 定义原始数据集 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # 创建字符到整数 (0-25) 的映射及反向映射 char_to_int = dict((c, i) 用于 i, c 在 enumerate(alphabet))) int_to_char = dict((i, c) 用于 i, c 在 enumerate(alphabet))) # 准备编码为整数的输入到输出对数据集 num_inputs = 1000 max_len = 5 dataX = [] dataY = [] 对于 i 在 范围(num_inputs): start = np.random.randint(len(alphabet)-2) end = np.random.randint(start, min(start+max_len,len(alphabet)-1)) sequence_in = alphabet[start:end+1] sequence_out = alphabet[end + 1] dataX.添加([char_to_int[char] 用于 char 在 sequence_in]) dataY.添加(char_to_int[sequence_out]) 打印(sequence_in, '->', sequence_out) # 将列表的列表转换为数组并根据需要填充序列 X = pad_sequences(dataX, maxlen=max_len, dtype='float32') # 将 X 重塑为 [样本,时间步长,特征] 的形式 X = np.reshape(X, (X.shape[0], max_len, 1)) # 归一化 X = X / 浮点数(长度(alphabet)) # 对输出变量进行独热编码 y = to_categorical(dataY) # 创建并拟合模型 batch_size = 1 model = Sequential() model.添加(LSTM(32, input_shape=(X.shape[1], 1))) model.添加(Dense(y.shape[1], activation='softmax')) model.编译(损失='categorical_crossentropy', 优化器='adam', 指标=['accuracy']) model.fit(X, y, epochs=500, batch_size=batch_size, verbose=2) # 总结模型性能 scores = model.evaluate(X, y, verbose=0) 打印("模型准确率: %.2f%%" % (scores[1]*100)) # 演示一些模型预测 对于 i 在 范围(20): pattern_index = np.random.randint(len(dataX)) pattern = dataX[pattern_index] x = pad_sequences([pattern], maxlen=max_len, dtype='float32') x = np.reshape(x, (1, max_len, 1)) x = x / 浮点数(长度(alphabet)) prediction = model.predict(x, verbose=0) index = np.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] 对于 值 在 pattern] 打印(seq_in, "->", result) |
注意:由于算法或评估过程的随机性质,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行此代码会产生以下输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
模型准确率:98.90% ['Q', 'R'] -> S ['W', 'X'] -> Y ['W', 'X'] -> Y ['C', 'D'] -> E ['E'] -> F ['S', 'T', 'U'] -> V ['G', 'H', 'I', 'J', 'K'] -> L ['O', 'P', 'Q', 'R', 'S'] -> T ['C', 'D'] -> E ['O'] -> P ['N', 'O', 'P'] -> Q ['D', 'E', 'F', 'G', 'H'] -> I ['X'] -> Y ['K'] -> L ['M'] -> N ['R'] -> T ['K'] -> L ['E', 'F', 'G'] -> H ['Q'] -> R ['Q', 'R', 'S'] -> T |
您可以看到,尽管模型没有完美地从随机生成的子序列中学习字母表,但它做得非常好。模型没有经过调优,可能需要更多的训练、更大的网络,或者两者兼而有之(留给读者练习)。
这是对上述学习到的“每个批次中所有顺序输入示例”字母模型的一个很好的自然扩展,因为它能够处理临时查询,但这次是任意序列长度(最多到最大长度)。
总结
在这篇文章中,您了解了 Keras 中的 LSTM 循环神经网络以及它们如何管理状态。
具体来说,你学到了:
- 如何开发一个用于单字符到单字符预测的简单 LSTM 网络
- 如何配置一个朴素 LSTM 以学习样本内时间步上的序列
- 如何配置 LSTM 以通过手动管理状态来学习跨样本的序列
您对管理 LSTM 状态或这篇文章有什么问题吗?
在评论中提出您的问题,我将尽力回答。
最近关于 LSTM 网络的系列文章非常棒。请继续努力
谢谢,马克。
对于任何想尝试训练好的 LSTM 的人,这里有一个交互式聊天框:http://www.mlowl.com/post/character-language-model-lstm-tensorflow/
它提供了维基百科、国会演讲、夏洛克·福尔摩斯、南方公园和歌德作品上训练的模型。
我还可以推荐 GitHub 上的 tensorlm 包:https://github.com/batzner/tensorlm
感谢分享。
我很喜欢这篇文章,它给了我很多启发。
谢谢 Atlant。
我可能漏掉了一些东西,但您能否解释一下在字母表示例中为什么需要 LSTM 单元?其中任何输出都直接取决于输入字母,并且不同输入 -> 输出对之间根本没有混淆。
这是对算法学习序列能力的演示。不仅仅是输入-输出对,而是随时间变化的输入-输出对。
嗨,我有点困惑。LSTM 单元是什么意思?
谢谢
嗨 Randy,LSTM 单元是“记忆单元”,你也可以称它们为神经元。
嗨,Jason,
非常感谢您的精彩教程。我从中受益匪浅。
递归网络非常复杂且难以使用。和 Shanbe 一样,我对这些示例有些困惑,因为每个输出似乎并不依赖于之前的输入,而只是与当前输入直接关联。我可能也遗漏了一些东西,但我看不出它如何证明使用内存而不是直接密集层的好处。
实际上,通过只使用最后一层并移除 LSTM 层(但需要更多 epoch),可以达到 100% 的准确率。
我认为这种混淆来自于字母被编码成整数。如果神经网络可以直接处理字母,情况就不会如此。在这个例子中,它们以相同的顺序编码,因此数字之间存在直接关系。我们只是教网络输出等于输入+常量。这很容易通过一个单元的简单回归来实现。但我们仍然需要 26 个输出用于最终的解码。
也许我迷失了,但我认为如果字母被打乱(以避免简单线性组合被单细胞回归解决的可能性),并且我们试图预测前一个字母而不是下一个字母,演示会更有意义。内存的使用将变得有意义。但我没有尝试。
尽管如此,这些例子对于展示解决问题的不同方法仍然非常有趣。
感谢您分享这些教育示例!如果您能详细阐述“批次内的 LSTM 状态”示例,我将不胜感激。令人困惑的部分是解释“LSTM 可以利用批次内序列的上下文更好地学习序列”,这可能暗示 LSTM 的状态在批次训练中被重用,这促使将参数 shuffle 设置为 False。
但我对 Keras 实现的理解是,LSTM 不会重用批次内的状态。事实上,批次中的序列有点像“并行”触发 LSTM——实际上,LSM 的状态对于两个门都应该是 (nsamples, nout) 的形状——每个序列都有单独的状态——这就是 [Keras 文档](https://keras.org.cn/getting-started/faq/#how-can-i-use-stateful-rnns) 中所描述的:状态被第 i 个实例用于连续批次。
这意味着即使将参数 shuffle 设置为 True,它仍然会给出观察到的性能。这也解释了为什么随机模式的预测也很好,这与下一个示例“有状态 LSTM 用于一对一字符映射”中的观察结果相反。将更大的批次大小设置为比第一个示例更好的性能的原因可能是使用了更大的 nb_epoch。
感谢您对此的意见!无论如何,这是一篇很棒的文章!
你说得对,那是批次内序列到序列的状态。
我同意。更大的批次大小带来了更好的结果,因为它更快地达到最小值并在 5000 个 epoch 中获得了良好的准确性。如果当 batch_size = 1 时增加 epoch 数量,我们可以获得 100% 的准确性。
我认为准确率提高是因为网络没有重置,因为所有样本都适合批次大小,并且网络默认重置发生在批次大小的样本数之后,而在批次大小为 1 的情况下,网络在每个批次大小之后都会重置,这实际上等于 1 个样本
我同意你的理解,即每个批次内的状态是独立更新的,而不是作为上下文信息使用的。此讨论提供了关于参数
stateful
的更多详细信息。感谢您的精彩教程。
然而,如果我想预测一个输出序列怎么办?如果我给输出添加一个维度,它就会像一个特征窗口,模型不会将输出视为一个输出序列。这就像输出是独立的。我如何解决这个问题并拥有一个模型,例如,当我给出“BCDE”时,它会生成“FGH”。
Hadi,我为我的特征使用“独热”编码。这使得输出成为特征上的概率分布。然后,您可以使用采样技术选择“下一个”特征,类似于“RNN 的不合理有效性”文章。这种方法包括一个“温度”参数,您可以对其进行调整,以生成与 LSTM 预测更符合或更不符合的输出。
关于这个问题还有一点后续……您可能对不同类型的网络感兴趣,例如生成对抗网络 (GAN) https://github.com/jacobgil/keras-dcgan
同样就我而言,谢谢你的教程。
不过,我有一些相关问题(也在 StackOverflow 上发布了,http://stackoverflow.com/questions/39457744/backpropagation-through-time-in-stateful-rnns):如果我有一个有状态 RNN,每个批次只有一个时间步,反向传播是如何处理的?它会只处理这一个时间步,还是会累积整个序列的更新?我担心它只更新每个批次的时间步,而不会进一步回溯。如果是这样,您认为这是一个主要的缺点吗?或者您知道克服这种缺点的方法吗?
您好 Alex,
我相信更新是在每个批次之后执行的。这可能是批次小的一个缺点。批次大小为 1 本质上会执行在线梯度下降(我猜测)。更熟悉 Keras 内部的开发人员可能能够给出更具体的答案。
嗨,Jason,
非常感谢你的回答。
我把问题问错了,因为我混淆了“批次大小”和“时间步”。如果我的序列形状是 (nb_samples, n, dims),并且我用一个有状态的 LSTM 一个时间步接一个时间步地处理它们(将形状为 (batch_size, 1, dims) 的批次输入网络),那么反向传播会像我一次处理整个序列那样,通过整个序列吗?
答案不变,更新在每个批次之后发生。
“Keras 对 LSTM 的实现会在每个批次后重置网络状态。”
您能解释一下“重置状态”是什么意思吗?每个 epoch 之后网络状态会发生什么?
谢谢!
我不知道它是清楚还是只是不可靠。但它消失了。
您的网络将不会按预期运行。
这意味着神经元中在批次大小内学习到的所有记忆都将丢失,新的序列或样本将使用没有状态的全新神经元或细胞进行训练
我认为这是不正确的。状态与LSTM单元的内部状态有关。状态将早期序列的经验带到当前序列,并将这些过去的经验与新的数据输入“混合”。
如果在批处理结束时重置状态,那么之前批处理(或多个批处理)的经验就会丢失。
另一方面,权重是通过反向传播设置的,并在每次训练和整个周期中得到改进。当LSTM状态重置时,你不会丢失权重。
嗨,Jason,
首先,感谢这篇精彩的教程!
我有两个简单的问题
1) 如果我想在更简单的序列(插入重复模式)上训练“可变长度输入到单字符输出的LSTM”
序列_1 = “aaaabbbccdaaaabbbccdaaaabbbccd….”
甚至
序列_2 = “ababababababababababababababab…”
我的准确率不能超过50%。哪里出错了?
2) 如果我想修改你的代码以实现N字符输出,这怎么可能?也就是说,给定序列“abcd” -> “ef”?
提前感谢!
我发现了我的错误:字符到整数的编码应该是
chars = sorted(list(set(alphabet)))
char_to_int = dict((c, i) for i, c in enumerate(chars))
int_to_char = dict((i, c) for i, c in enumerate(chars))
但是,这并没有太大帮助。
此外,我非常好奇如何预测N个字符
你好,Arnold,
不错的改变。不知道为什么它没有学会。也许你需要更多的节点/层或更长的训练时间?也许是有状态的还是无状态的?
也许是问题的框架?
这是一个序列到序列的问题。我预计你只需要将输出节点从1个神经元更改为你想要的数量即可。
嗨,Jason,
感谢你的建议。我会尝试的。
Arnold,
你实现这种功能了吗? 🙂
我也很感兴趣
嗨,Jason,
感谢这篇精彩的教程和其他文章!
如何修改你的代码以预测N个后续字符,而不仅仅是一个?
例如:“可变长度输入到N字符输出的LSTM”
嗨,Jason,
一如既往的精彩教程。逐行运行你的代码,使用更小的字母表(例如“ABCDE”),更改序列长度等等,以弄清楚模型如何行为,这很有趣。我想我最近开始喜欢LSTM了。
我有一个关于输入张量形状的基本问题。Keras要求我们的输入具有 [samples, time_steps, features] 的形式。你能告诉我“features”属性到底是什么意思吗?
另外,考虑一个用于对音频进行二元分类的LSTM网络训练场景。假设我有一个包含1000个文件的集合,对于每个文件,我提取了13个特征(MFCC值)。还假设每个文件有500帧长,我将 time_steps 设置为1。
我的输入形状会是什么?
1. [文件数量, 时间步数, 特征] = [1000, 1, 13]
或者
2. [文件数量 * 每文件帧数, 时间步数, 特征] = [1000*500, 1, 13]
任何答案都将不胜感激!!谢谢。
谢谢Madhav。
输入中的特征是指数据中的属性或列。
是的,你对问题的框架在我看来是正确的 [1000, 1, 13] 或 [样本, 时间步长, 特征]
感谢您关于LSTM的系列启发性文章!
只是一个小细节,最终示例的完整代码缺少对 `pad_sequences` 的导入。
from keras.preprocessing.sequence import pad_sequences
已修复。
谢谢Rob指出!
你好,Jason
如果我想预测一个非顺序网络怎么办?(例如,A的下一个状态可能是B、C、Z或A本身,这取决于A之前的数值)。我能将同样的逻辑用于这个问题吗?
是的,Panand,LSTM可以学习任意复杂的序列,尽管您可能需要增加存储单元和层数以适应问题的复杂性。
告诉我进展如何。
嗨,Jason,
感谢您的教程,我从中学到了很多。
但是,我仍然不太理解批次大小的含义。在最后一个示例中,您将 batch_size 设置为1,但网络仍然根据整个序列学习下一个字母,或者每次都只基于最后一个字母?
如果将 batch_size=3 并且所有序列的最小长度都为3,会发生什么?
谢谢你
批次大小是指系统权重更新和网络重置后样本的数量,在此示例中,样本是单个字母。
嗨,Jason,
感谢您提供如此精彩的教程。
受此启发,我尝试使用 Theano 中的 RNN 生成一个正弦波。
运行下面的代码后
当我向所有时间步长提供输入进行预测时,绿色预测的正弦曲线生成得很好
但蓝色曲线预测得不好,我只在预测时向第一个时间步长提供输入(这种预测是你用于字符序列生成的那种)
有没有办法能让它工作。因为我需要一个用于生成标题的模型。
#学习正弦波
import theano
import numpy as np
import matplotlib.pyplot as plt
import theano.tensor as T
import math
theano.config.floatX = ‘float64’
## 数据
step_radians = 0.01
steps_of_history = 200
steps_in_future = 1
index = 0
x = np.sin(np.arange(0, 20*math.pi, step_radians))
seq = []
next_val = []
for i in range(0, len(x)-steps_of_history, steps_of_history)
seq.append(x[i: i + steps_of_history])
next_val.append(x[i+1:i + steps_of_history+1])
seq = np.reshape(seq, [-1, steps_of_history, 1])
next_val = np.reshape(next_val, [-1, steps_of_history, 1])
trainX = np.array(seq)
trainY = np.array(next_val)
## 模型
n = 50
nin = 1
nout = 1
u = T.matrix()
t = T.matrix()
h0 = T.vector()
h_in = np.zeros(n).astype(theano.config.floatX)
lr = T.scalar()
W = theano.shared(np.random.uniform(size=(3,n, n), low=-.01, high=.01).astype(theano.config.floatX))
W_in = theano.shared(np.random.uniform(size=(nin, n), low=-.01, high=.01).astype(theano.config.floatX))
W_out = theano.shared(np.random.uniform(size=(n, nout), low=-.01, high=.01).astype(theano.config.floatX))
def step(u_t, h_tm1, W, W_in, W_out)
h_t = T.tanh(T.dot(u_t, W_in) + T.dot(h_tm1, W[0]))
h_t1 = T.tanh(T.dot(h_t, W[1]) + T.dot(h_tm1, W[2]))
y_t = T.dot(h_t1, W_out)
return h_t, y_t
[h, y], _ = theano.scan(step,
sequences=u,
outputs_info=[h0, None],
non_sequences=[W, W_in, W_out])
error = ((y – t) ** 2).sum()
prediction = y
gW, gW_in, gW_out = T.grad(error, [W, W_in, W_out])
fn = theano.function([h0, u, t, lr],
error,
updates={W: W – lr * gW,
W_in: W_in – lr * gW_in,
W_out: W_out – lr * gW_out})
predict = theano.function([h0, u], prediction)
for e in range(10)
for i in range(len(trainX))
fn(h_in,trainX[i],trainY[i],0.001)
print(‘训练结束’)
x = np.sin(np.arange(20*math.pi, 24*math.pi, step_radians))
seq = []
for i in range(0, len(x)-steps_of_history, steps_of_history)
seq.append(x[i: i + steps_of_history])
seq = np.reshape(seq, [-1, steps_of_history, 1])
testX = np.array(seq)
# 预测未来值
predictY = []
for i in range(len(testX))
p = testX[i][0].reshape(1,1)
for j in range(len(testX[i]))
p = predict(h_in, p)
predictY= predictY + p.tolist()
print(predictY)
# 绘制结果
plt.plot(x, ‘r-‘, label=’实际值’)
plt.plot(np.asarray(predictY), ‘gx’, label=’预测值’)
predictY = []
for i in range(len(testX))
predictY= predictY + predict(h_in, testX[i]).tolist()
plt.plot(np.asarray(predictY), ‘bo’, label=’预测值’)
plt.show()
非常感谢。
你好,
我认为“批次内的LSTM状态”这一段有错误。
你说“Keras 的 LSTM 实现在每个批次后会重置网络状态”。
但事实是,它甚至在批次内的输入之间也会重置其状态。
你可以看到这个试图记住其最后输入的 LSTM 性能不佳(大约0.5):http://pastebin.com/u5NnAx9r
作为比较,这是一个表现良好的有状态 LSTM:http://pastebin.com/qEKBVqJJ
我认为你是对的。它在每次输入后重置状态。但它在一个输入内的时间步之间保持状态。
非常感谢。
这是我见过关于LSTM最好的描述。我从你的帖子中受益匪浅!
谢谢Mazen。
首先,感谢您创建了这个简单但非常有用的例子,帮助我们更好地学习和理解LSTM网络的工作原理。
我只想问您关于第一部分的问题,为什么我们使用32个单元?这是一个随机的决定还是有理论基础?
我将感谢您的回答。
一个临时的选择,费尔南多。好问题。
嗨,Jason,
感谢这篇精彩的教程!
很高兴你觉得它有用,leila。
感谢您的这篇文章,但我是一名初学者,我有一个问题
你说“网络状态在每个模式后重置”,这是什么意思?
LSTM维护内部状态,这是使用它们的优点。
此内部状态可以自动重置(每个批次后)或通过设置“stateful”参数手动重置。
很棒的教程。问题:是否可以使用具有可变输入的有状态LSTM(就像教程中的最后一个例子)?我很好奇你为什么对那个例子使用朴素的无状态LSTM。
是的,你可以。
感谢这篇精彩的教程。逐步讲解(从简单开始并在此基础上构建)真的很有帮助。
不客气,Bikram。
我对LSTM有一个问题。我在这里Reddit上详细问过这个问题,然后才了解到您的教程。
我觉得有状态的LSTM让我理解了RNN的要点,但它是否曾成功用于视频处理——比如我在这里描述的问题 https://www.reddit.com/r/AskReddit/comments/681c76/understanding_lstm_stateful_stateless_how_to_go/
基本上,在这里,当网络被输入“K”时,有状态的LSTM预测了B,这是错误的。这是否意味着它只是记忆了序列,并且无论输入什么都会预测B。我们如何将其扩展到通用的视频预测任务。请查看上面的链接。
先谢谢了
该链接似乎无法使用。
LSTM需要非常仔细地构建序列问题,才能有效地学习。它们也往往需要比对MLP直觉所暗示的更大、训练时间更长的网络。
你好。
我有一个关于RNN-LSTM网络的问题,我们如何决定input_shape大小?我们如何绘制这个问题的RNN形状?
很好的问题。
Keras 中的 LSTM 期望形状为 [样本, 时间步长, 特征]。
每个样本都是一个独立的序列。序列步长是时间步长。每次观测测量都是一个特征。
如果你有一个非常长的序列,你可以将其分解为多个较短的序列。
希望这能有所帮助。
我们能否在Keras中使用LSTMS RNN来创建聊天机器人对话模型。
或许可以,但我目前没有这方面的例子,抱歉。
有时从某些时间步提取LSTM输出很有帮助。例如,我们可能希望从文本中获取最后30个单词对应的输出,或者从文本主体中获取第100到200个单词。写一个关于它的说明会很棒。
感谢您的建议。
你能举个例子说明你的意思吗?
嗨,尊敬的先生(Jason)。
我想用LSTM处理单词而不是字母。我该如何实现呢?此外,我可以用它进行词性标注吗?词性标注也是一个序列问题,因为它也依赖于上下文。
谢谢
可以,但我目前没有这方面的例子,抱歉。
嗨,Jason,
感谢这篇精彩的教程。我非常欣赏你的LSTM代码。
我有一个关于“特征”的问题:你提到了它们,但我没有看到你在哪里包含了它们。它们代表了一个多元问题吗?我们如何处理它?
非常感谢,
卡洛斯
我目前没有多元示例,但您可以在输入中指定特征。格式是 [样本, 时间步, 特征]。
感谢这篇精彩的教程!
我有一个问题。我正在处理类似的问题。但是,序列中的每个字符在我的输入数据中都是一个特征向量。
我不是 [‘A’, ‘B’, ‘C’] -> D,而是 [[0, 0,1, 1.3], [6,3,1,1.5], [6, 4, 1.4, 4.5]] -> [1, 3, 4]
所以考虑到所有序列,我的数据是3D形状的。
谁能帮我如何配置LSTM的输入?
LSTM 输入必须是 3D 的,结构为 [样本, 时间步, 特征]。
如果你进行独热编码,那么标签的数量(二进制向量的长度)就是特征的数量。
这有帮助吗?
确实如此!为了澄清
如果我有2000个句子(单词序列)
每个句子有3个单词,
每个单词都转换为长度为5的向量(5个特征)
X1 = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12,13,14,15]], ….., X2000
Data_X = [X1, …, X2000]
那么我的X_train会是这样吗?
X = numpy.reshape(Data_X, (2000, 3, 5))
是的。
但不要害怕探索其他可能在你的问题上产生更好技能的表示。例如,也许提供n个句子作为时间步长是有意义的。
谢谢 Jason!
我非常感谢您在这些教程中所做的努力。
我正在处理一个庞大且不平衡的序列文件,您对改进这类不平衡文件有什么建议吗?
我没有处理不平衡序列数据的经验。
也许处理不平衡非序列数据的方法会给你一些想法
https://machinelearning.org.cn/tactics-to-combat-imbalanced-classes-in-your-machine-learning-dataset/
顺便说一句,假设输出不是高维的
Y1 = [11, 22, 33]
你好,在这个例子中,你可以从 ABC -> D (3 到 1)
有没有办法训练一个模型从 ABC -> BCD
或者其他模式,比如 ABC -> CBA (交换第一个和最后一个)
是的,你可以在输出层设置3个神经元。
很棒的教程,也是最接近解决我困惑的教程。然而,我仍然不完全清楚。如果我有一个长度为5000的时间序列训练数据,每行数据包含两个特征,我能否将我的输入数据 (x) 塑形成单个批次 (5000,1,2)?如果是这样,在什么情况下我需要将时间步长增加到大于1?我很难理解如果LSTM在整个批次中记忆(在我的上述场景中就像时间步长为5000对吗?),时间步长维度有什么价值?
是的。
时间步的数量应该等于你的输入序列中的时间步数量。在实践中,将时间步设置为1通常不是一个好主意,除非是演示目的。
嗨,Jason,
感谢您的教程,我只是对Keras中的有状态LSTM有些困惑。
我知道隐藏状态会通过不同的时间步传递。隐藏状态会在一个批次中传递吗?
或者一个批次中的样本有不同的初始化隐藏状态?
内部状态在每个批次结束时重置。
你可以通过将层设置为有状态,并控制何时重置状态,从而将状态跨批次传递。
这有帮助吗?
嗨,Jason,
X是否需要独热编码?因为它们也是分类的。
是的,输入可以进行独热编码。
这是一篇很棒的教程,但我仍然有些问题。当我们使用LSTM/RNN时,我们通常会用一些随机的方式(比如正交)初始化LSTM/RNN的状态,因此,当我们使用LSTM进行预测时,初始状态可能或者必须与训练不同,它如何总是得到正确的预测。更甚者,当我们训练一个stateful=False的LSTM时,初始状态会被重置,这意味着随机初始化,它如何总是得到一个正确的模型?
等待你的回答,谢谢!
请参阅此帖子,了解如何在给定其随机性质的情况下评估像LSTM这样的神经网络的建议
https://machinelearning.org.cn/evaluate-skill-deep-learning-models/
我尝试了所有示例。对我来说,似乎只有一个示例“用于单字符到单字符映射的有状态LSTM”展示了LSTM/RNN的本质。所有其他示例都像普通的密集网络一样工作。
我曾尝试处理序列“ABCDEFABCXYZ”
它在有状态网络上学习得很好。
模型准确率:100.00%
[‘A’] -> B
[‘B’] -> C
[‘C’] -> D
[‘D’] -> E
[‘E’] -> F
[‘F’] -> A
[‘A’] -> B
[‘B’] -> C
[‘C’] -> X
[‘X’] -> Y
[‘Y’] -> Z
但在随机测试中它会出错
测试随机模式
[‘Y’] -> B
[‘A’] -> C
[‘C’] -> D
[‘A’] -> E
[‘X’] -> F
[‘Y’] -> A
[‘D’] -> B
[‘B’] -> C
[‘A’] -> X
[‘A’] -> Y
[‘B’] -> Z
[‘C’] -> Z
[‘E’] -> Z
[‘B’] -> Z
[‘C’] -> Z
[‘C’] -> A
[‘B’] -> A
[‘C’] -> A
[‘A’] -> B
[‘F’] -> B
如何解决这个问题?
需要开发一个更通用的模型,也许是一个利用最后n个字符(时间步长)的上下文来预测下一个字符的模型。
在最后的可变长度输入示例中,通过调用 `pad_sequences()` 生成的向量是否合理?
例如:
RS -> T
变为
[[0], [0], [0], [17], [18]]
这相当于
AAARS -> T
这真的是想要的吗?
这可能是一个错误。谢谢!
是的,我无法判断这是好事还是坏事。
有利的一面是,它确实进行了训练(如果你增加批次大小和训练周期,最终会接近100%的准确率)。
另一方面,训练数据似乎是错误的,尽管如此,它仍然学习到了正确的结果。我尝试了一个替代数据集,其中填充数据是随机选择的字母。对于仅包含1个字母的序列效果不佳,但更长的序列似乎可以。
我已将新版本粘贴在下面。请注意,我增加了数据集大小、批次大小和训练周期。
这个LSTM业务比我最初想象的要微妙得多……不过还是感谢您发布这篇教程,我非常喜欢前几个例子中的“这是错误的做法”的风格。非常有用。
import numpy
来自 keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
from keras.preprocessing.sequence import pad_sequences
# 设置随机种子以保证结果可复现
numpy.random.seed(7)
# 定义原始数据集
alphabet = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”
# 创建字符到整数 (0-25) 的映射及反向映射
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# 准备编码为整数的输入到输出对数据集
num_inputs = 10000
max_len = 5
dataX = []
dataY = []
for i in range(num_inputs)
start = numpy.random.randint(len(alphabet)-2)
end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1))
sequence_in = alphabet[start:end+1]
sequence_out = alphabet[end + 1]
dataX.append([char_to_int[char] for char in sequence_in])
dataY.append(char_to_int[sequence_out])
print(sequence_in, ‘->’, sequence_out)
for n in range(len(dataX))
while len(dataX[n]) “, result)
#测试任意模式
pattern = [20, 21, 22]
while len(pattern) “, result)
那个列表被截断了吗?
import numpy
来自 keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
from keras.preprocessing.sequence import pad_sequences
# 设置随机种子以保证结果可复现
numpy.random.seed(7)
# 定义原始数据集
alphabet = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”
# 创建字符到整数 (0-25) 的映射及反向映射
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# 准备编码为整数的输入到输出对数据集
num_inputs = 10000
max_len = 5
dataX = []
dataY = []
for i in range(num_inputs)
start = numpy.random.randint(len(alphabet)-2)
end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1))
sequence_in = alphabet[start:end+1]
sequence_out = alphabet[end + 1]
dataX.append([char_to_int[char] for char in sequence_in])
dataY.append(char_to_int[sequence_out])
print(sequence_in, ‘->’, sequence_out)
for n in range(len(dataX))
while len(dataX[n]) “, result)
#测试任意模式
pattern = [20, 21, 22]
while len(pattern) “, result)
嗯,我似乎无法发布完整列表。抱歉。
发布代码时请考虑使用 `pre` HTML 标签。
我认为纯粹的解决方案是将字母重新映射到1-26的范围,并为填充保留0。
嘿,布朗利教授,
你做得非常棒。我很欣赏这些勤奋而详细的教程——非常感谢你。
我的问题是关于LSTM状态如何应用于非“顺序”(但仍然时间相关)数据,类似于您的空气污染教程——
https://machinelearning.org.cn/multivariate-time-series-forecasting-lstms-keras/
1) 将LSTM设置为 [stateful = false] 并使用批次大小1,基本上是否将LSTM变成了一个更复杂的Feedforward网络,即一个没有记忆或序列知识的神经网络?
2) 在训练了上述“有状态”模型后,您会重置状态,然后进行预测。这意味着(相对于标准的LSTM方程,在此处找到 – https://en.wikipedia.org/wiki/Long_short-term_memory)之前的单元状态和隐藏状态都为零。您可以通过使用诸如 model.get_layer(index = 1).states[0].eval() 的方法来看到这一点。对于无状态模型也是如此——Keras中状态被列为 [none]。令人困惑的是,重置状态会使遗忘门和“U”权重归零(根据方程)。然而,正如我们在您上面的教程中看到的,您仍然可以进行准确的预测!这让我怀疑我们为什么还需要遗忘门和U权重呢?
如果我的问题有任何不清楚之处,请告诉我。提前感谢您的关注,以及这些精彩的教程。
嗨,Dan,很好的问题。
不,LSTM 记忆单元与前馈网络中的简单神经元仍然非常不同。
请参阅此教程,该教程强制模型仅使用内部状态来预测结果
https://machinelearning.org.cn/memory-in-a-long-short-term-memory-network/
亲爱的Jason;
感谢您的有用教程。我有一个关于“准确率”的问题。正如我们在第一个示例“朴素LSTM”中看到的,准确率是84%,但没有一个预测是正确的。我的问题是这个准确率是如何计算的?
一如既往的精彩文章,Jason。我尤其欣赏你花时间展示无状态模型替代方案的缺点,比如堆叠额外的特征。你说你不是教授,但你像老师一样思考,而且很少有人会用如此新的技术这样做。
我有一个问题,关于当LSTM是有状态时,我们需要指定批次大小的要求。Philippe Remy 在他关于这个主题的博客文章中(http://philipperemy.github.io/keras-stateful-lstm/)说(我的转述)
* 对于输入数据中的每个观测值(行),Keras 保存 output_dim 个状态(其中 output_dim = LSTM 中的单元数),因此
* 在处理完一批数据后,它将收集一个大小为 (batch_size, output_dim) 的状态信息数组
* 由于状态跨越批次边界,我们必须在模型定义中指定该状态数组的形状 (batch_size, output_dim)。
我的问题
* 这是否是准确的描述?
* 我能否推断状态信息跨越观测值(ip数据的行、序列,无论你称为什么)?
* 我能否推断即使在“无状态”LSTM中,状态信息也跨越观测值?(即使它不跨越批次)
* 如果LSTM为整个序列保留旧的状态信息,这是否意味着它可能在旧状态(比如4、5或50个单元之前的状态)上操作?
* 如果是这样,这是否让LSTM类似于具有“巨大”回顾的自回归例程,从而直接(不仅仅是隐式)依赖于许多步之前发生的状态值?
* Hochreiter 的原始LSTM论文中对此有解释吗?
谢谢John。
我不能评论那篇帖子,我没读过。
批次是一组样本。批次结束后,权重会更新,状态会重置。一个 epoch 包含1个或更多批次。
LSTM在自回归方面表现不佳。它们没有学习一个关于滞后观测的静态函数,而是学习一个更复杂的函数,内部状态作为函数的内部变量。过去的观测值不是函数的直接输入,而是影响内部状态和t-1输出。
不知道这是否有帮助。
是的,它有帮助,因为它排除了过去状态(早于t-1)直接影响当前状态的可能性。但是,为什么LSTM算法会为批次中的每一个样本(序列)保留状态信息呢?
如果它不为每个样本保留过去的状态信息,那么为什么有状态的LSTM需要知道批次大小?
LSTM 在暴露于每个样本时累积状态,它不会为每个单独的样本保留状态。
有状态 LSTM 只需要知道批次大小,这出于 Keras 的原因,例如在不同硬件上运行时实现效率。
亲爱的Jason;
感谢您的有用教程。我有一个关于“准确率”的问题。正如我们在第一个示例“朴素LSTM”中看到的,准确率是84%,但没有一个预测是正确的。我的问题是这个准确率是如何计算的?
嗨,Jason,
我尝试了您在“有状态LSTM用于单字符到单字符映射”上的代码,训练期间的准确率出奇地低(例如约30%)。有什么问题吗?然而,我通过将批次大小更改为25并切换迭代次数到更大的值(例如3000)可以达到100%,但我猜这不是您在此处的目的。
另外,如果我们手动在每个 epoch 后重置状态,模型如何记住事情?
谢谢。
我建议您尝试调整配置并多次重新运行示例,以克服算法的随机性。
我们通常希望在样本之间重置内存。在此处了解更多关于 BPTT 的信息
https://machinelearning.org.cn/gentle-introduction-backpropagation-time/
你好,Jason。
在 Keras Stateful 模型中,我想将 batch_size 设置为大于2。
我想在一个模型中学习多个独立数据集。为此,batch_size 必须设置为独立数据集的数量。
但我只能这样想,我不知道如何将数据放入模型中。
如果您有好的网站或示例代码,请分享。
我总是很欣赏精彩的帖子并认真阅读。
当你将LSTM设置为有状态时,批次大小和状态重置是解耦的。批次大小只会控制何时更新权重。
嗨,Jason,
我加入了所有感谢您精彩帖子的行列。我也有一个关于“不止一个数据集”的问题。
正如您在帖子中提到的,无状态LSTM在每个批次结束时更新权重并重置状态,而有状态LSTM在每个批次结束时更新权重,但只在每个周期结束时重置状态,在那里我们可以控制重置函数,对吗?
假设我有N条独立的轨迹,我想能够泛化到新的轨迹。我在每个时间步的输入是最新的 (x,y) 点,我想预测下一个点,使LSTM有状态,以便保留以前输入的记忆,并希望能随着获得越来越多的点而改进预测。因此,我的问题是
– 如果我将我的整体数据集重塑为 (len(single_dataset)*N, 1, len(features)),将一个数据集追加到另一个数据集之后,并设置 batch_size=len(single_dataset),有没有办法在批次之间重置内存(意味着每次我切换到新数据集时)?我认为没有,因为我应该在 model.fit() 函数内部执行此操作,但我仍然想听听您的意见;
– 或者我应该像以前一样重塑我的整体数据集,但现在设置 batch_size=N,让单个批次包含所有N个数据集的第一个点,然后第二个批次包含第二个点,依此类推?我相信这会更好,但我想知道在测试时,我是否可以使用相同的模型(或定义一个新模型)并设置 batch_size=1,然后以有状态的方式只针对单个轨迹进行一对一预测;
– 您能想到解决这个问题的第三种方法吗?
谢谢。
是的,这都是代码。你可以按照你希望的任何方式遍历你的数据并重置状态。也许可以尝试进行实验以建立信心?
此外,我认为timesteps=1是一种代码异味。尝试另一种框架方式。
哈喽 杰森,
我想知道 `sequence_length` 和 `number_units` 之间是否存在关系!!
我想象中 `number_units` 应该大于(或至少等于) `sequence_length`,这样 LSTM 才能处理长度为 `sequence_length` 的样本而不丢失序列信息。
例如,如果我们有样本,每个样本的长度 = 30(从 t0 到 t29),那么 `number_units=32` 可能合适,因为第一个单元处理 t0,第二个单元处理 t1 并考虑第一个单元的输出,……,第30个单元处理最后一个时间步 t29 并考虑所有经过的时间步(即 t0 到 t28)。因此,如果我们在此处选择例如 `number_units=20`,那么最后十个时间步(即 t20 到 t29)将不会被 LSTM 处理,从而模型会丢失序列信息。
这正确吗?
非常感谢您的帮助,
此致,
Mazen
没有关系(除了网络容量的模糊概念)。
谢谢你。
好的,对于模型编译和运行来说没有关系。但是为了拥有一个能够处理整个预期序列数据的合适模型,`num_units` 应该 >= `sequence_length` 吗?因为每个 LSTM 单元处理一个时间步,因此如果 `num_units` < `sequence_length`,那么那些超过 `num_units` 的时间步将不会被处理,它们的序列信息就会丢失!!
这种理解正确吗?
抱歉,我的评论提交后总是有些损坏 🙁
所以,我的意思是我的理解是否准确
这种理解正确吗?
很抱歉,我的评论提交后总是有些损坏 🙁
所以,我的意思是我的理解是否准确。
例如,如果我们有样本,每个样本的长度 = 30(从 t0 到 t29),那么 `number_units=32` 可能合适,因为第一个单元处理 t0,第二个单元处理 t1 并考虑第一个单元的输出,……,第30个单元处理最后一个时间步 t29 并考虑所有经过的时间步(即 t0 到 t28)。因此,如果我们在此处选择例如 `number_units=20`,那么最后十个时间步(即 t20 到 t29)将不会被 LSTM 处理,从而模型会丢失序列信息。
我的理解正确吗?
单元数量和序列长度之间没有关系。
嗨。Jason。
我想知道为什么当你使用窗口方法时,你会像上面那样重塑 'X' 和 'x'。
我的意思是,当你设计简单的 LSTM 模型(一个字符到另一个字符的映射)时,你将 'dataX' 重塑为 'X'。而 'X' 的排列方式是 (len(dataX), seq_length, 1)。
然而,当你设计使用窗口方法的 LSTM 模型时,你将 'X' 重塑为 (len(dataX), 1, seq_length) 的数组。
我想知道这些数组之间有什么区别。
很抱歉,我是一名机器学习和 Python 的新手。
所以你能具体解释一下吗?谢谢你。
这篇文章可能会有帮助
https://machinelearning.org.cn/reshape-input-data-long-short-term-memory-networks-keras/
嗨,Jason,
感谢这篇教程。我用其他几个时间序列数据集尝试了您的代码,我感觉它工作得“太好了”。我不是神经网络专家,但我对机器学习足够了解,可以判断我的预测“可能”有问题——过拟合或类似情况(例如,在您使用的数据集上,我得到的结果肯定比您的好,但这可能取决于Keras的不同版本,以及数据的不同划分等……)。所以,我正在努力弄清楚。
那么我的问题是关于LSTM的有状态模式。它也影响预测吗?或者更确切地说,模型在预测模式下也是有状态的吗?
例如,假设我的测试集由100个时间点(顺序示例)组成,我按照您的方式,将它们一个接一个地按正确顺序输入网络。那么,当模型预测第i个示例的值时,它是否会存储或考虑,到目前为止,序列是示例1到i-1?它是否会以某种方式保留并使用这些历史信息进行第i个预测?
PS:如果这个问题已经问过,请原谅。我尝试在帖子中搜索,但它很长。
我最好的建议是测试并找出有状态LSTM是否对您的数据集有影响。
很棒的教程。嗨,Jason,我有一个输入和输出都可变的序列,比如ABC->DEF,AB->CDEF,C->DEFG等。你能帮我解决一下吗?
谢谢 🙂
也许你可以把上面的帖子作为起点?
嗨,Jason,你的帖子对于我理解LSTM非常有帮助。我只是有一个关于状态的问题。
让我们假设以下设置
time_steps = 10
batch_size = 5
这意味着在每个批次中,我有5个样本,每个样本有10行顺序数据(总共50行)。
我知道设置 Stateful=True 意味着隐藏状态从一个批次转移到另一个批次,但每个批次内部呢?
根据keras文档,“如果为True,则批次中索引i的每个样本的最后一个状态将用作下一个批次中索引i的样本的初始状态。”
这听起来像是批次1中第一个样本的隐藏状态被用于批次2中第一个样本,批次1中第二个样本的隐藏状态被用于批次2中第二个样本。
这是否意味着隐藏状态不会在批次内的样本之间传输?即,我的批次中第一个时间步的隐藏状态不会传输到第二个样本。
在这个假设下,似乎我在处理时间序列数据时,批次大小应该为1。
谢谢!
不,这意味着状态在批次内的样本上累积,并用作下一个批次的第一个状态。
实际上,在“有状态LSTM用于单字符到单字符映射”一节中,网络已经显示出过拟合的迹象。
当您开始新的种子“K”时,网络预测的下一个字符是“B”而不是“L”。我自己还没有尝试过这个网络,但我有一种感觉,无论您输入什么种子,它总是会预测下一个字符为“B”。
有什么办法可以解决这个问题吗?
也许减少训练次数或使用更大的训练集。
我直接复制了您用于有状态LSTM的代码来学习单字符到单字符映射,但代码没有输出与您文章中相同的结果。我不明白问题出在哪里。模型的准确率非常差。
模型准确率:36.00%
A -> B
B -> C
C -> C
C -> D
D -> E
E -> F
F -> G
G -> H
H -> I
I -> I
I -> L
L -> L
L -> M
M -> O
O -> P
P -> P
P -> R
R -> S
S -> S
S -> U
U -> U
U -> U
U -> Y
Y -> Z
Z -> Z
新开始:K
K -> B
B -> C
C -> C
C -> D
D -> D
或许可以尝试多运行几次这个例子?
嗨,Jason,
首先非常感谢您的博客。我从特别是关于LSTM的博客中受益匪浅。我非常感谢您的贡献。
然而,我在实践所学知识时遇到了一些困难。
我尝试使用告警数据提前一天预测设备故障。让我简要总结一下数据。
训练数据是一个100*1000的数组,只包含0和1。行代表天数索引(连续100天)。
列的大小是告警字典,这意味着有1000种不同的告警。如果告警发生,我将其记为1,否则记为0。
更清楚地说,如果在第i天发生一个告警(对应于列索引200),则X[i,200] = 1。
作为Y值(标签),假设我有一个大小为(100*50)的数组。Y值代表设备故障。我尝试预测50种不同类型的设备故障。
我按天记录设备故障的分布,例如[0, 0, 0, 1, 0, 1, …],与训练数据类似。
因此,我想利用前100天的告警和设备故障数据,预测第101天将发生的设备故障。
我认为这是一种序列到序列的预测。对于这类问题,我应该使用有状态的LSTM,并且设置return_sequence = True吗?以及如何对输入和输出数据进行整形?
或者设计50个不同的LSTM,对每个设备故障进行二元分类,这是一种更好的方法吗?
如果您能给我任何建议,我将不胜感激。
提前感谢。
好问题。
我们无法知道问题的最佳模型。我建议测试一套不同的框架和建模方法,看看哪种效果最好。
我建议尝试一个模型而不是多个模型,以提供跨案例进行泛化的机会。
如果这是一个时间序列分类问题,听起来确实是这样,也许可以尝试1D CNN。我发现它们非常有效。
告诉我进展如何。
有人能告诉我numpy.reshape(dataX, (len(dataX), 1, seq_length))中的time_step的正确含义吗?
一个序列是一个样本,由多个时间步组成,每个时间步记录一个观测值。
也许这会有帮助。
https://machinelearning.org.cn/reshape-input-data-long-short-term-memory-networks-keras/
谢谢你的回复。谢谢你的精彩教程。
你好Jason,我为你实现了一个字符到字符映射的有状态LSTM代码,但准确率未能超过32%,你能告诉我我可能做错了什么吗?另外,当我们在'for'循环中训练模型时,语句“model.reset_states()”会在当前epoch的训练完成后,在下一个epoch的训练开始之前重置模型。这样,在这两者之间状态会被重置,LSTM的当前状态将不会提供给下一个epoch,因此它就不会表现出有状态LSTM的行为。你能告诉我我哪里理解错了。
没错。
我在这里有一些建议可能会有所帮助
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
互联网上最简单的LSTM介绍。非常感谢。
谢谢。
嗨,Jason
感谢您的教程,我有一个关于“时间步长”的问题。假设我有一个数据集,其中有100个序列样本,每个样本有10个特征,所以我的数据集是100*11(10个特征,1个因变量)。如果我设置时间步长=1(如您在https://machinelearning.org.cn/multivariate-time-series-forecasting-lstms-keras/中使用的那样),这是否意味着我只通过昨天的信息来预测今天的信息?如果是这样,这是否意味着我没有充分利用LSTM的能力,因为LSTM应该能够利用长时信息?
谢谢。
您可以根据您具体的问题,以任何您希望的方式定义输入和输出。
你好,你能帮我解释一下LSTM自动确定时滞是什么意思吗?时滞又是什么意思?它和时间步长有什么不同?
非常感谢!
它会学习要关注哪些时间步以及如何关注这些时间步来进行预测。
在另一篇文章中,我发现它写道:“LSTM非常适合对具有未知持续时间滞后的时间序列进行分类、处理和预测。”
你好Jason,我知道你是这个领域的专家,所以你能帮我解释一下“具有未知持续时间滞后”是什么意思吗?
这意味着你不需要分析和指定要提供的具体滞后观测值,你只需提供时间步序列,让模型从中学习。
通常,LSTM在时间序列预测方面表现不佳,你可能需要考虑使用CNN。
这里的“批次内的LSTM状态”示例有点误导,因为批次内的样本是并行处理的,因此每个样本的LSTM状态是独立的,实际上并不能以任何顺序方式从一个样本帮助到另一个样本。在该示例中使用shuffle=True可以达到相同的效果。
它在训练数据上达到100%准确率的主要原因主要是由于epoch数量多达5000次。
“批次内的LSTM状态”示例也让我感到困惑。对我来说,它似乎暗示着一个序列末尾的隐藏状态和单元状态会被传递到下一个序列的起始,如果下一个序列在同一个批次中。这听起来不对,因为序列通常是独立的,即使在同一个批次中也彼此不相关。
我们可以选择在批次结束时或样本/序列结束时重置状态。我们有控制权。
对于序列之间存在排序关系的数据集,跨样本/序列维护状态是有意义的。
在“有状态LSTM用于一个字符到另一个字符映射”中,当我将批次大小更改为5时,它会给我一个错误,例如
无法将输入数组从形状 (5,26) 广播到形状 (1,26)。
为什么我不能设置batch_size=5或任何其他数字?我的输入数组应该更改为什么形状?
可以,但模型必须与相同的批量大小一起使用,即使在进行预测时也是如此。
我在这里更详细地解释了这一点以及解决方法:
https://machinelearning.org.cn/use-different-batch-sizes-training-predicting-python-keras/
非常感谢详细的解释和示例。对初学者非常有帮助。
不客气!
感谢这个很好的教程!
我有一个关于归一化的困惑之处,如下所示。
重塑后,我们可以将输入整数归一化到0到1的范围,这是LSTM网络使用的sigmoid激活函数的范围。
您需要将输入归一化为[0, 1]的原因是LSTM的激活sigmoid函数范围是[0, 1]吗?这意味着所有类型的LSTM输入在使用之前都需要归一化为[0, 1]吗?
提前感谢!
此外,由于它在训练时对输入进行了归一化,那么测试输入是否也需要归一化?
是的。
这是一个好习惯,除非你将LSTM改为使用不同的激活函数,例如ReLU。
嗨,Jason,
非常感谢这篇文章!它帮助我学到了很多关于LSTM的知识,我在处理更复杂的例子时会不断地参考它。我发现了一个小错误,想提出来。
在最后一个例子(变长输入序列)中,您创建了一个从0到25的字符到整数映射(如之前的例子),但随后您也对序列进行了填充。keras.preprocessing.sequence.pad_sequences的默认填充值为0.0,这意味着字母“A”和填充字符都具有零值。因此,序列['A', 'B', 'C']将被转换为[0, 0, 0, 1, 2],序列['B', 'C']也将转换为[0, 0, 0, 1, 2]。显然这是不正确的,因为填充后的序列与以重复字母'A'开头的序列无法区分。
一个简单的解决方法是创建一个从1到26的字母字符到整数映射,而不是从0到25(将零只用于表示填充字符)。代码更改如下:
# create mapping of characters to integers (1-26) and the reverse
char_to_int = dict((c, i+1) for i, c in enumerate(alphabet))
int_to_char = dict((i+1, c) for i, c in enumerate(alphabet))
一个简单的改动,但解决了问题——在这个例子中并没有对模型准确性产生太大影响,但可能对其他序列产生更大的影响。
太棒了,谢谢Brian指出来!
如果没有验证,LSTM网络会记忆所有信息
是的,有可能会。
我相信有状态LSTM会记住所有信息。
我修改了测试用例,尝试从K开始热启动,手动输入序列中的下一个输出,而不是获取上一个LSTM输出。这些结果似乎表明信息正在被记忆。(我还尝试了其他一些具有相同LSTM输出,字母按B开始的顺序排列的测试)
新开始:K
K -> B
L -> C
M -> D
N -> E
O -> F
P -> G
Q -> H
R -> I
S -> J
T -> K
U -> L
V -> M
这些测试是否合理地表明文章中的以下声明被推翻了?
“我们还可以看到,如果我们用第一个字母来初始化网络,它就能正确地背诵出字母表的其余部分。
我们还可以看到,它只学会了完整的字母序列,并且是从冷启动开始的。当被要求预测“K”之后的下一个字母时,它预测“B”并回到重复整个字母表。”
尽管如此,这篇文章对于理解LSTM的特性仍然非常有帮助!我同意该声明说明了如何正确训练一个训练有素的LSTM。我尝试在 epoch 之间滚动X和y值,但损失没有收敛(使用相同的网络结构)。关于如何更改网络结构或参数以防止这种过拟合并正确地以有状态方式训练此示例,有什么建议吗?
谢谢。
是的,从这里开始
https://machinelearning.org.cn/start-here/#better
我仍然有一个疑问:哪种任务严格要求使用有状态LSTM?
很好的问题!
那些您需要对模型内部状态何时重置进行精细控制的任务。
更具体地说,当批次中的样本之间存在依赖关系时。
在“带有可变长度输入到单字符输出的LSTM”示例中,请您审查“end”变量的定义限制。
例如,如果从以下行
“end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1)) ”
随机选择的值是
“end = 25” 因为“len(alphabet)-1 = 25”
那么“sequence_out = alphabet[end + 1]”将获取字母表的第26个字符,这不存在。
抱歉,我没理解您的评论。
或许您可以详细说明或重新提出您的问题?
你好Jason,感谢你的精彩文章。
你为什么说当“ABC”以3个时间步的1个特征输入模型时,LSTM学习效果更好,而不是以1个时间步的3个特征输入?这与数据量有关吗?一般来说,这种方法什么时候效果更好?任何相关文章的参考也会有帮助吗?提前谢谢你。
最适合问题的框架取决于模型和数据集。
你好 Jason,你能回答以下问题吗?
1) 无状态LSTM确实有状态,并且它在一个批次内从一个样本传递到后续样本。这正确吗?
2) 在“带有可变长度输入到单字符输出的LSTM”中,您又回到了无状态LSTM,shuffle 已打开,它会跨样本传递状态吗?
正确。
是的。
你好Jason,你的文章非常有用。
但我对输入特征仍有一些疑问。我还阅读了你的另一篇文章:https://machinelearning.org.cn/multivariate-time-series-forecasting-lstms-keras/。它使用(t-1)的7个特征[污染、露点、温度、压力、风速、雪、雨]来预测(t)的[污染]。
据我所知,LSTM单元会将t-1的输出与当前的t输入连接起来。我们是否必须自己将之前的输出与之前的输入连接起来?因此,您将(t-1)的污染(这是(t-2)的输出)作为输入特征之一。或者Keras LSTM层会自动完成此操作?
换句话说,我们能否只使用(t-1)的[露点、温度、压力、风速、雪、雨]来预测t的污染?这样,污染就不包含在输入特征列表中。
实际上,我想使用LSTM来预测公交车到达时间,输入是[纬度、经度、行驶时间、速度]。我想知道我是否需要将之前的到达时间作为输入之一?
谢谢!
谢谢!
LSTM将时间步序列作为输入。你可以定义输入多少个时间步——这取决于你。你可以以任何你喜欢的方式构建问题——并且测试多种方法并发现哪种效果好是一个好主意。
LSTM输入可能会令人困惑,这是我所知道的最好的解释方式。
https://machinelearning.org.cn/faq/single-faq/what-is-the-difference-between-samples-timesteps-and-features-for-lstm-input
我尝试这样做,但仍然收到错误“pandas.errors.ParserError: Error tokenizing data.”
请问问题出在哪里?
谢谢你
很抱歉听到这个消息,也许这些提示会有帮助。
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
你好 Jason,
非常感谢您提供了我在这个领域发现过的最好的教程!
在上面“批次内的LSTM状态”一章中
在第28行的完整代码示例块中,您认为使用填充的X序列
来重塑输入而不是使用原始dataX怎么样?
当然,这不影响这个特定的情况,但可能更通用。
哈拉德
此致
Harald
谢谢!
也许可以尝试使用掩码层进行填充并比较结果。
你好,你的帖子极大地帮助了我对LSTM的知识和理解。我有一个简单的问题,因为我试图尝试类似的方法,但同时结合了你关于双向时间序列预测方法的文章。其中一串字母是训练数据集,预测时应该例如如果你有“ABCD”,它会预测E,或者如果字母之间有间隙,它会预测下一个。类似于时间序列预测帖子中模型在预测70,80,90后接近预测100的方式。当我尝试创建一个预测函数时,遇到了一个错误,即使将X,y从字符转换为整数,仍然出现“Input 0 of layer sequential is incompatible with the layer: expected ndim=3, found ndim=2. Full shape received: (None, 3)”的错误。因为我使用了x_input = array(['B','C', 'D']) 但无法获得输出。因为这段代码中的预测函数在我去掉一个字母后无法预测值。
我不太确定你的问题是什么。也许你尝试在将输入传递到网络之前重塑你的输入?
抱歉,这是我提到的另一部分:https://machinelearning.org.cn/how-to-develop-lstm-models-for-time-series-forecasting/
感谢这份关于LSTM的教程。这是一个很好的入门。
我想问一下这是否算作数据泄漏?本质上,测试集和训练集是相同的。
你能指出测试集与训练集相同的地方吗?一般来说,数据泄漏是指输出通过转换或缩放等方式间接出现在输入中。
嗨,Jason,
非常感谢这篇精彩的文章。我有一个问题,关于无状态模型在批次内是否会保持样本之间的状态。
从“批处理中的LSTM状态”示例来看,您证明了LSTM即使是无状态的也能够在批处理中学习序列。但是这个示例的 epoch 数是5000,比“朴素LSTM学习一对一字符映射”示例大10倍。我尝试用5000个 epoch 训练朴素LSTM,它也给出了100%的准确率。
你好Jiansheng,
你可能正在处理回归问题并实现零预测误差。
或者,你可能正在处理分类问题并实现 100% 的准确率。
这很不寻常,原因有很多,包括:
你不小心在训练集上评估了模型性能。
你的保留数据集(训练集或验证集)太小或不具代表性。
你的代码中引入了一个错误,它正在做一些与你预期不同的事情。
你的预测问题很容易或微不足道,可能不需要机器学习。
最常见的原因是你的保留数据集太小或不代表更广泛的问题。
可以通过以下方法解决:
使用 k 折交叉验证来估计模型性能,而不是训练/测试拆分。
收集更多数据。
使用不同的数据拆分进行训练和测试,例如 50/50。
我关注您的频道。这对我们来说是一个非常有价值的频道。我订阅了并推荐给我的朋友们。您对LSTM主题解释得非常好,谢谢。我有一个问题。我正在研究如何使用网络摄像头通过眼部图像估计PC屏幕上显示的坐标。我能够使用CNN实现这种预测。目前,我正在研究如何用LSTM实现这一点。
我有PC屏幕上视点与眼部图像对应的坐标信息。我从眼部图像中用CNN获取每张图像的特征。我通过LSTM传递这些特征,创建了一个端到端模型,并对其进行训练。
————-
首先,我使用CNN分布式时间获取每40个眼部图像的数组,并在CNN的最后一层将其展平。展平结果为(40, 5120)
然后我将展平结果导出到LSTM并进行端到端训练。
——
我的问题是,
1- 我是否需要将目标坐标数据移动1步?
2- 只用一张图像进行Model.predict时,在请求屏幕上的坐标估计时会给出尺寸错误。我认为这个错误是因为训练中使用了40张图像,所以模型仍然需要40张图像。我如何只用1张图像实现这一点。据我在网上了解,它说对于这个问题,解决方案是需要stateful=true状态信息。如果您能提供帮助,我将不胜感激。
你好 Furkan……根据您的描述,您可能会受益于 CNN+LSTM 模型。
https://machinelearning.org.cn/cnn-long-short-term-memory-networks/
一旦重塑,您可以将输入整数归一化到0到1的范围,这是LSTM网络使用的sigmoid激活函数的范围。
Keras中LSTM的默认激活函数似乎是
tanh
,对应于-1到1的范围。虽然上面提到了sigmoid激活函数,但在创建LSTM单元时并未指定。因此,似乎应该在创建LSTM单元时指定sigmoid激活函数,或者将数据缩放到-1到1的范围。除非我遗漏了什么。感谢您的文章。你好 Corey……ReLU 激活函数可能也很有益。
https://machinelearning.org.cn/rectified-linear-activation-function-for-deep-learning-neural-networks/
仅用一张图像进行Model.predict时,在请求屏幕上的坐标估计时会给出尺寸错误。我认为这个错误是因为训练中使用了40张图像,所以模型仍然需要40张图像。我如何只用1张图像实现这一点。据我在网上了解,它说对于这个问题,解决方案是需要stateful=true状态信息。如果您能提供帮助,我将不胜感激。
很棒的教程!
我有一个澄清的问题。我的理解是,“批处理中的LSTM状态”和“用于学习单字符到单字符映射的朴素LSTM”结果之间的关键区别在于批处理大小。
但在您的示例中,“批处理中的LSTM状态”训练了5000个epoch,而朴素LSTM只训练了500个epoch。当我改为将朴素LSTM训练5000个epoch时,它达到了与“批处理中的LSTM状态”相同的性能。同样,当我只将“批处理中的LSTM状态”训练500个epoch时,其准确率低于30%,比朴素方法差得多!
基于此,在我看来,“批处理中的LSTM状态”实际上并不比朴素方法更好,它只是训练时间更长。
我在这里错过了什么吗?
你好 Nate……非常欢迎!你的理解是正确的。对于这个应用程序,朴素方法是一个更好的选择。你可能希望研究调整每个模型的超参数并比较结果。此外,比较深度学习模型与“经典”方法可能也很有趣。
https://machinelearning.org.cn/compare-machine-learning-algorithms-python-scikit-learn/