长短期记忆(LSTM)网络是一种循环神经网络(RNN)类型,它能够学习输入序列中元素之间的关系。
LSTM 的一个很好的演示是学习如何使用像加法这样的数学运算来组合多个项,并输出计算结果。
初学者经常犯的一个错误是仅仅学习从输入项到输出项的映射函数。在解决此类问题时,LSTM 的一个很好的演示是学习字符的序列输入(“50+11”)并以字符(“61”)预测序列输出。使用序列到序列(seq2seq)或堆叠 LSTM 配置,LSTM 可以学习这个难题。
在本教程中,您将了解如何使用 LSTM 来解决随机生成整数序列的加法问题。
完成本教程后,您将了解:
- 如何学习输入项到输出项的朴素映射函数以进行加法。
- 如何构建加法问题(及类似问题)并对输入和输出进行适当编码。
- 如何使用 seq2seq 范式解决真正的序列预测加法问题。
开始您的项目,阅读我的新书 《Python 中的长短期记忆网络》,其中包含分步教程和所有示例的Python 源代码文件。
让我们开始吧。
- 更新于 2018 年 8 月:修正了模型配置描述中的拼写错误。

如何使用 seq2seq 循环神经网络学习加法
摄影:Lima Pix,部分权利保留。
教程概述
本教程分为3个部分;它们是
- 加法
- 加法作为映射问题(初学者的错误)
- 加法作为 seq2seq 问题
环境
本教程假定您已安装 SciPy、NumPy、Pandas,并拥有 Python 2 或 Python 3 开发环境。
本教程还假定已安装 scikit-learn 和 Keras v2.0+,并使用 Theano 或 TensorFlow 后端。
如果您在环境设置方面需要帮助,请参阅此帖:
加法
任务是给定一个随机选择的整数序列,返回这些整数的总和。
例如,给定 10 + 5,模型应输出 15。
模型将使用随机生成的示例进行训练和测试,以便学习通用的加法问题,而不是记住特定情况。
需要 LSTM 帮助进行序列预测吗?
参加我的免费7天电子邮件课程,了解6种不同的LSTM架构(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
加法作为映射问题
(初学者的错误)
在本节中,我们将逐步解决该问题,并使用 LSTM 解决它,展示初学者很容易犯的错误,即未能利用循环神经网络的强大功能。
数据生成
首先,让我们定义一个函数来生成随机整数序列及其总和作为训练和测试数据。
我们可以使用 randint() 函数在最小值和最大值之间生成随机整数,例如 1 到 100 之间。然后我们可以对序列求和。此过程可以重复固定次数,以创建数字输入序列和匹配的输出总和值的对。
例如,此代码片段将创建 100 个示例,用于将 1 到 100 之间的 2 个数字相加。
1 2 3 4 5 6 7 8 9 10 11 |
from random import seed from random import randint seed(1) X, y = list(), list() for i in range(100): in_pattern = [randint(1,100) for _ in range(2)] out_pattern = sum(in_pattern) print(in_pattern, out_pattern) X.append(in_pattern) y.append(out_pattern) |
运行示例将打印出每个输入-输出对。
1 2 3 4 5 6 7 8 |
... [2, 97] 99 [97, 36] 133 [32, 35] 67 [15, 80] 95 [24, 45] 69 [38, 9] 47 [22, 21] 43 |
一旦我们有了模式,我们就可以将列表转换为 NumPy 数组并重新缩放值。我们必须重新缩放值以适应 LSTM 使用的激活范围。
例如
1 2 3 4 5 |
# 格式化为 NumPy 数组 X,y = array(X), array(y) # 归一化 X = X.astype('float') / float(100 * 2) y = y.astype('float') / float(100 * 2) |
将所有内容整合在一起,我们可以定义函数random_sum_pairs(),该函数接受指定数量的示例、每个序列中的数字数量以及要生成的最大数字,并返回用于建模的 X、y 数据对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from random import randint from numpy import array # 生成随机整数及其总和的示例 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) # 格式化为 NumPy 数组 X,y = array(X), array(y) # 归一化 X = X.astype('float') / float(largest * n_numbers) y = y.astype('float') / float(largest * n_numbers) return X, y |
我们可能希望稍后反转对数字的重缩放。这将有助于将预测与预期值进行比较,并以原始数据的相同单位获得误差分数。
下面的invert()函数反转了传入的预测值和预期值的归一化。
1 2 3 |
# 反转归一化 def invert(value, n_numbers, largest): return round(value * float(largest * n_numbers)) |
配置 LSTM
现在我们可以定义一个 LSTM 来模拟这个问题。
这是一个相对简单的问题,因此模型不需要很大。输入层将期望 1 个输入特征和 2 个时间步(在加两个数字的情况下)。
定义了两个隐藏 LSTM 层,第一个有 6 个单元,第二个有 2 个单元,然后是一个全连接的输出层,该层返回一个单独的总和值。
使用高效的 ADAM 优化算法来拟合模型,并使用均方误差损失函数,因为网络的输出是实值。
1 2 3 4 5 6 |
# 创建 LSTM model = Sequential() model.add(LSTM(6, input_shape=(n_numbers, 1), return_sequences=True)) model.add(LSTM(6)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') |
网络将训练 100 个 epoch,每个 epoch 都会生成新的示例,并在每个 batch 结束时执行权重更新。
1 2 3 4 5 |
# 训练 LSTM for _ in range(n_epoch): X, y = random_sum_pairs(n_examples, n_numbers, largest) X = X.reshape(n_examples, n_numbers, 1) model.fit(X, y, epochs=1, batch_size=n_batch, verbose=2) |
LSTM 评估
我们评估网络在 100 个新模式上的表现。
这些模式被生成,并为每个模式预测一个总和值。实际和预测的总和值都被重新缩放到原始范围,并计算均方根误差(RMSE)分数,该分数与原始数据的比例相同。最后,列出了约 20 个预期值和预测值作为示例。
最后,列出了 20 个预期值和预测值作为示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 对一些新模式进行评估 X, y = random_sum_pairs(n_examples, n_numbers, largest) X = X.reshape(n_examples, n_numbers, 1) result = model.predict(X, batch_size=n_batch, verbose=0) # 计算误差 expected = [invert(x, n_numbers, largest) for x in y] predicted = [invert(x, n_numbers, largest) for x in result[:,0]] rmse = sqrt(mean_squared_error(expected, predicted)) print('RMSE: %f' % rmse) # 显示一些示例 for i in range(20): error = expected[i] - predicted[i] print('Expected=%d, Predicted=%d (err=%d)' % (expected[i], predicted[i], error)) |
完整示例
我们可以将所有这些整合在一起。完整的代码示例如下所示。
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 seed from random import randint from numpy import array from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from math import sqrt from sklearn.metrics import mean_squared_error # 生成随机整数及其总和的示例 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) # 格式化为 NumPy 数组 X,y = array(X), array(y) # 归一化 X = X.astype('float') / float(largest * n_numbers) y = y.astype('float') / float(largest * n_numbers) 返回 X, y # 反转归一化 def invert(value, n_numbers, largest): return round(value * float(largest * n_numbers)) # 生成训练数据 seed(1) n_examples = 100 n_numbers = 2 largest = 100 # 定义 LSTM 配置 n_batch = 1 n_epoch = 100 # 创建 LSTM model = Sequential() model.add(LSTM(10, input_shape=(n_numbers, 1))) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') # 训练 LSTM for _ in range(n_epoch): X, y = random_sum_pairs(n_examples, n_numbers, largest) X = X.reshape(n_examples, n_numbers, 1) model.fit(X, y, epochs=1, batch_size=n_batch, verbose=2) # 对一些新模式进行评估 X, y = random_sum_pairs(n_examples, n_numbers, largest) X = X.reshape(n_examples, n_numbers, 1) result = model.predict(X, batch_size=n_batch, verbose=0) # 计算误差 expected = [invert(x, n_numbers, largest) for x in y] predicted = [invert(x, n_numbers, largest) for x in result[:,0]] rmse = sqrt(mean_squared_error(expected, predicted)) print('RMSE: %f' % rmse) # 显示一些示例 for i in range(20): error = expected[i] - predicted[i] print('Expected=%d, Predicted=%d (err=%d)' % (expected[i], predicted[i], error)) |
运行示例会为每个 epoch 打印一些损失信息,最后打印运行的 RMSE 和一些示例输出。
注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
结果并非完美,但许多示例都得到了正确预测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
RMSE: 0.565685 Expected=110, Predicted=110 (err=0) Expected=122, Predicted=123 (err=-1) Expected=104, Predicted=104 (err=0) Expected=103, Predicted=103 (err=0) Expected=163, Predicted=163 (err=0) Expected=100, Predicted=100 (err=0) Expected=56, Predicted=57 (err=-1) Expected=61, Predicted=62 (err=-1) Expected=109, Predicted=109 (err=0) Expected=129, Predicted=130 (err=-1) Expected=98, Predicted=98 (err=0) Expected=60, Predicted=61 (err=-1) Expected=66, Predicted=67 (err=-1) Expected=63, Predicted=63 (err=0) Expected=84, Predicted=84 (err=0) Expected=148, Predicted=149 (err=-1) Expected=96, Predicted=96 (err=0) Expected=33, Predicted=34 (err=-1) Expected=75, Predicted=75 (err=0) Expected=64, Predicted=64 (err=0) |
初学者的错误
全部完成,对吧?
错误。
我们解决的问题有多个输入,但技术上不是序列预测问题。
事实上,您可以使用多层感知器(MLP)轻松解决它。例如:
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 |
from random import seed from random import randint from numpy import array from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from math import sqrt from sklearn.metrics import mean_squared_error # 生成随机整数及其总和的示例 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) # 格式化为 NumPy 数组 X,y = array(X), array(y) # 归一化 X = X.astype('float') / float(largest * n_numbers) y = y.astype('float') / float(largest * n_numbers) 返回 X, y # 反转归一化 def invert(value, n_numbers, largest): return round(value * float(largest * n_numbers)) # 生成训练数据 seed(1) n_examples = 100 n_numbers = 2 largest = 100 # 定义 LSTM 配置 n_batch = 2 n_epoch = 50 # 创建 LSTM model = Sequential() model.add(Dense(4, input_dim=n_numbers)) model.add(Dense(2)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') # 训练 LSTM for _ in range(n_epoch): X, y = random_sum_pairs(n_examples, n_numbers, largest) model.fit(X, y, epochs=1, batch_size=n_batch, verbose=2) # 对一些新模式进行评估 X, y = random_sum_pairs(n_examples, n_numbers, largest) result = model.predict(X, batch_size=n_batch, verbose=0) # 计算误差 expected = [invert(x, n_numbers, largest) for x in y] predicted = [invert(x, n_numbers, largest) for x in result[:,0]] rmse = sqrt(mean_squared_error(expected, predicted)) print('RMSE: %f' % rmse) # 显示一些示例 for i in range(20): error = expected[i] - predicted[i] print('Expected=%d, Predicted=%d (err=%d)' % (expected[i], predicted[i], error)) |
注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
运行示例可以完美地解决问题,并且需要更少的 epoch。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
RMSE: 0.000000 Expected=108, Predicted=108 (err=0) Expected=143, Predicted=143 (err=0) Expected=109, Predicted=109 (err=0) Expected=16, Predicted=16 (err=0) Expected=152, Predicted=152 (err=0) Expected=59, Predicted=59 (err=0) Expected=95, Predicted=95 (err=0) Expected=113, Predicted=113 (err=0) Expected=90, Predicted=90 (err=0) Expected=104, Predicted=104 (err=0) Expected=123, Predicted=123 (err=0) Expected=92, Predicted=92 (err=0) Expected=150, Predicted=150 (err=0) Expected=136, Predicted=136 (err=0) Expected=130, Predicted=130 (err=0) Expected=76, Predicted=76 (err=0) Expected=112, Predicted=112 (err=0) Expected=129, Predicted=129 (err=0) Expected=171, Predicted=171 (err=0) Expected=127, Predicted=127 (err=0) |
问题在于我们对领域进行了过多编码,使其从序列预测问题转变为函数映射问题。
也就是说,输入的顺序不再重要。我们可以随意打乱它,仍然可以学习这个问题。
MLP 旨在学习映射函数,可以轻松解决学习如何加法的问题。
一方面,这是解决特定加法问题的更好方法,因为模型更简单,结果也更好。另一方面,这是对循环神经网络的糟糕利用。
这是我在网上看到的许多“LSTM 入门”教程中反复出现的初学者错误。
加法作为序列预测问题
还有另一种构建加法的方式,使其成为一个明确的序列预测问题,从而使其更难解决。
我们可以将加法构建为输入和输出字符字符串,让模型弄清楚字符的含义。整个加法问题可以构建为一个字符字符串,例如“12+50”,输出为“62”,或者更具体地说:
- 输入:[‘1’, ‘2’, ‘+’, ‘5’, ‘0’]
- 输出:[‘6’, ‘2’]
模型不仅必须学习字符的整数性质,还必须学习执行数学运算的性质。
请注意,序列现在变得重要了,随机打乱输入会创建一个无意义的序列,无法与输出序列相关联。
另请注意,问题是如何转换为同时具有输入序列和输出序列。这被称为序列到序列预测问题,或 seq2seq 问题。
我们可以保持加法问题的简单性,即两个数字相加,但我们可以看到如何将其扩展到可变数量的项和数学运算,这些可以作为输入提供给模型进行学习和泛化。
请注意,此构建以及本示例的其余部分灵感来自 Keras 项目中的 加法 seq2seq 示例,尽管我从头开始重新开发了它。
数据生成
seq2seq 定义问题的序列数据生成要复杂得多。
我们将把每个部分开发为一个独立的函数,以便您可以进行尝试并了解它们的工作原理。请坚持下去。
第一步是生成随机整数序列及其总和,就像之前一样,但没有归一化。我们可以将其放入一个名为 random_sum_pairs() 的函数中,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from random import seed from random import randint # 生成随机整数及其总和的列表 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) 返回 X, y seed(1) n_samples = 1 n_numbers = 2 largest = 10 # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) print(X, y) |
仅运行此函数会打印出两个介于 1 到 10 之间的随机整数相加的一个示例。
1 |
[[3, 10]] [13] |
下一步是将整数转换为字符串。输入字符串的格式为“99+99”,输出字符串的格式为“99”。
此函数关键在于对数字进行填充,以确保每个输入和输出序列具有相同的字符数。填充字符应与数据不同,以便模型能够学会忽略它们。在这种情况下,我们使用空格字符进行填充(“ ”),并在字符串左侧进行填充,将信息保留在最右侧。
还有其他填充方法,例如单独填充每个项。尝试一下,看看是否能提高性能。请在下面的评论中报告您的结果。
填充要求我们知道最长序列可能有多长。我们可以通过计算能生成的最大整数的 *log10()* 并取其上限来轻松计算出所需字符数。我们将最大数字加 1,以确保我们期望 3 个字符而不是 2 个字符(例如,当最大数字为 200 时)。然后,我们需要加上相应数量的加号。
1 |
max_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 |
对输出序列重复类似的过程,当然不包括加号。
1 |
max_length = ceil(log10(n_numbers * (largest+1))) |
下面的示例添加了 *to_string()* 函数,并演示了它与单个输入/输出配对的用法。
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 |
from random import seed from random import randint from math import ceil from math import log10 # 生成随机整数及其总和的列表 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) 返回 X, y # 将数据转换为字符串 def to_string(X, y, n_numbers, largest): max_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 Xstr = list() for pattern in X: strp = '+'.join([str(n) for n in pattern]) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp Xstr.append(strp) max_length = ceil(log10(n_numbers * (largest+1))) ystr = list() for pattern in y: strp = str(pattern) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp ystr.append(strp) return Xstr, ystr seed(1) n_samples = 1 n_numbers = 2 largest = 10 # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) print(X, y) # 转换为字符串 X, y = to_string(X, y, n_numbers, largest) print(X, y) |
运行此示例后,首先会打印出整数序列及其相应序列的填充字符串表示。
1 2 |
[[3, 10]] [13] [' 3+10'] ['13'] |
接下来,我们需要将字符串中的每个字符编码为整数值。毕竟,我们必须在神经网络中使用数字,而不是字符。
整数编码将问题转化为一个分类问题,其中输出序列可以被视为具有 11 个可能值的类别输出。这恰好是具有某种序数关系的整数(前 10 个类别值)。
为了执行此编码,我们必须定义字符串编码中可能出现的所有符号的完整字母表,如下所示:
1 |
alphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ' '] |
然后,整数编码就变成了一个简单的过程,即构建一个字符到整数偏移的查找表,然后逐个转换每个字符串的每个字符。
下面的示例提供了用于整数编码的 *integer_encode()* 函数,并演示了如何使用它。
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 |
from random import seed from random import randint from math import ceil from math import log10 # 生成随机整数及其总和的列表 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) 返回 X, y # 将数据转换为字符串 def to_string(X, y, n_numbers, largest): max_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 Xstr = list() for pattern in X: strp = '+'.join([str(n) for n in pattern]) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp Xstr.append(strp) max_length = ceil(log10(n_numbers * (largest+1))) ystr = list() for pattern in y: strp = str(pattern) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp ystr.append(strp) return Xstr, ystr # 整数编码字符串 def integer_encode(X, y, alphabet): char_to_int = dict((c, i) for i, c in enumerate(alphabet)) Xenc = list() for pattern in X: integer_encoded = [char_to_int[char] for char in pattern] Xenc.append(integer_encoded) yenc = list() for pattern in y: integer_encoded = [char_to_int[char] for char in pattern] yenc.append(integer_encoded) return Xenc, yenc seed(1) n_samples = 1 n_numbers = 2 largest = 10 # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) print(X, y) # 转换为字符串 X, y = to_string(X, y, n_numbers, largest) print(X, y) # 整数编码 alphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ' '] X, y = integer_encode(X, y, alphabet) print(X, y) |
运行示例会打印出每个字符串编码模式的整数编码版本。
注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
我们可以看到,空格字符(“ ”)被编码为 11,三字符(“3”)被编码为 3,依此类推。
1 2 3 |
[[3, 10]] [13] [' 3+10'] ['13'] [[11, 3, 10, 1, 0]] [[1, 3]] |
下一步是对整数编码序列进行二进制编码。
这包括将每个整数转换为长度与字母表相同的二进制向量,并在特定整数位置标记为 1。
例如,整数 0 代表字符“0”,将被编码为一个 11 元素向量,其中第 0 个位置为 1:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]。
下面的示例定义了用于二进制编码的 *one_hot_encode()* 函数,并演示了如何使用它。
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 77 78 79 80 81 |
from random import seed from random import randint from math import ceil from math import log10 # 生成随机整数及其总和的列表 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) 返回 X, y # 将数据转换为字符串 def to_string(X, y, n_numbers, largest): max_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 Xstr = list() for pattern in X: strp = '+'.join([str(n) for n in pattern]) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp Xstr.append(strp) max_length = ceil(log10(n_numbers * (largest+1))) ystr = list() for pattern in y: strp = str(pattern) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp ystr.append(strp) return Xstr, ystr # 整数编码字符串 def integer_encode(X, y, alphabet): char_to_int = dict((c, i) for i, c in enumerate(alphabet)) Xenc = list() for pattern in X: integer_encoded = [char_to_int[char] for char in pattern] Xenc.append(integer_encoded) yenc = list() for pattern in y: integer_encoded = [char_to_int[char] for char in pattern] yenc.append(integer_encoded) return Xenc, yenc # One-Hot 编码 def one_hot_encode(X, y, max_int): Xenc = list() for seq in X: pattern = list() for index in seq: vector = [0 for _ in range(max_int)] vector[index] = 1 pattern.append(vector) Xenc.append(pattern) yenc = list() for seq in y: pattern = list() for index in seq: vector = [0 for _ in range(max_int)] vector[index] = 1 pattern.append(vector) yenc.append(pattern) return Xenc, yenc seed(1) n_samples = 1 n_numbers = 2 largest = 10 # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) print(X, y) # 转换为字符串 X, y = to_string(X, y, n_numbers, largest) print(X, y) # 整数编码 alphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ' '] X, y = integer_encode(X, y, alphabet) print(X, y) # One-Hot 编码 X, y = one_hot_encode(X, y, len(alphabet)) print(X, y) |
运行示例会打印出每个整数编码的二进制编码序列。
注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
我添加了一些新行,以使输入和输出的二进制编码更清晰。
您可以看到,单个求和模式变成了一个包含 5 个二进制编码向量的序列,每个向量包含 11 个元素。输出或和变成了一个包含 2 个二进制编码向量的序列,同样,每个向量包含 11 个元素。
1 2 3 4 5 6 7 8 9 10 |
[[3, 10]] [13] [' 3+10'] ['13'] [[11, 3, 10, 1, 0]] [[1, 3]] [[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0]]] |
我们可以将所有这些步骤整合到一个名为 *generate_data()* 的函数中,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 生成编码数据集 def generate_data(n_samples, n_numbers, largest, alphabet): # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) # 转换为字符串 X, y = to_string(X, y, n_numbers, largest) # 整数编码 X, y = integer_encode(X, y, alphabet) # 独热编码 X, y = one_hot_encode(X, y, len(alphabet)) # 转换为 numpy 数组 X, y = array(X), array(y) return X, y |
最后,我们需要反转编码,将输出向量转换回数字,以便我们可以将期望的输出整数与预测的整数进行比较。
下面的 *invert()* 函数执行此操作。关键是首先使用 *argmax()* 函数将二进制编码转换回整数,然后使用字母表中整数到字符的反向映射将整数转换回字符。
1 2 3 4 5 6 7 8 |
# 反转编码 def invert(seq, alphabet): int_to_char = dict((i, c) for i, c in enumerate(alphabet)) strings = list() for pattern in seq: string = int_to_char[argmax(pattern)] strings.append(string) return ''.join(strings) |
现在我们有了准备此示例数据所需的一切。
请注意,这些函数是为这篇帖子编写的,我没有编写任何单元测试,也没有用各种输入进行实际测试。如果您发现任何明显的错误,请在下面的评论中告知我。
配置和拟合 seq2seq LSTM 模型
我们现在可以为此问题拟合一个 LSTM 模型。
我们可以将模型看作由两个关键部分组成:编码器和解码器。
首先,输入序列一次一个编码字符地显示给网络。我们需要一个编码层来学习输入序列中各步之间的关系,并发展出对这些关系的内部表示。
网络的输入(对于两个数字的情况)是一系列 5 个编码字符(每个数字 2 个,加上“+”1 个),其中每个向量包含 11 个特征,对应于序列中的每个项可能具有的 11 个字符。
编码器将使用一个具有 100 个单元的单个 LSTM 隐藏层。
1 2 |
model = Sequential() model.add(LSTM(100, input_shape=(5, 11))) |
解码器必须将输入序列的学习内部表示转换为正确的输出序列。为此,我们将使用一个具有 50 个单元的隐藏层 LSTM,然后是一个输出层。
该问题被定义为需要两个二进制输出向量来表示两个输出字符。我们将使用相同的全连接层(Dense)来输出每个二进制向量。为了使用相同的层两次,我们将它包装在一个 TimeDistributed() 包装层中。
输出全连接层将使用 softmax 激活函数来输出 [0,1] 范围内的值。
1 2 |
model.add(LSTM(50, return_sequences=True)) model.add(TimeDistributed(Dense(11, activation='softmax'))) |
不过,有一个问题。
我们必须连接编码器和解码器,但它们不匹配。
也就是说,编码器将为序列中的每个输入向量(共 5 个)生成一个包含 100 个输出的二维矩阵。解码器是一个 LSTM 层,它期望一个 [样本数, 时间步数, 特征数] 的三维输入,以便生成一个包含 2 个时间步(每个时间步包含 11 个特征)的解码序列。
如果您尝试强制将这些部分连接起来,您会收到类似这样的错误:
1 |
ValueError: Input 0 is incompatible with layer lstm_2: expected ndim=3, found ndim=2 |
正如我们所预料的那样。
我们可以使用 RepeatVector 层来解决这个问题。此层只是将提供的二维输入重复 n 次以创建三维输出。
RepeatVector 层可以用作适配器,将网络的编码器和解码器部分连接起来。我们可以将 RepeatVector 配置为重复输入 2 次。这将创建一个三维输出,由编码器输出的序列的两个副本组成,然后我们可以使用相同的全连接层对这两个副本进行解码,以获得所需的两个输出向量。
1 |
model.add(RepeatVector(2)) |
该问题被定义为一个具有 11 个类别的分类问题,因此我们可以优化对数损失(*categorical_crossentropy*)函数,甚至可以跟踪每个训练 epoch 的准确率和损失。
将这些组合起来,我们得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 定义 LSTM 配置 n_batch = 10 n_epoch = 30 # 创建 LSTM model = Sequential() model.add(LSTM(100, input_shape=(n_in_seq_length, n_chars))) model.add(RepeatVector(n_out_seq_length)) model.add(LSTM(50, return_sequences=True)) model.add(TimeDistributed(Dense(n_chars, activation='softmax'))) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) print(model.summary()) # 训练 LSTM for i in range(n_epoch): X, y = generate_data(n_samples, n_numbers, largest, alphabet) print(i) model.fit(X, y, epochs=1, batch_size=n_batch) |
为什么要使用 RepeatVector 层?
为什么不将编码器的序列输出作为解码器的输入?
也就是说,每个 LSTM 在输入序列的每个时间步输出一次,而不是每个 LSTM 对整个输入序列输出一次。
1 |
model.add(LSTM(100, input_shape=(n_in_seq_length, n_chars), return_sequences=True)) |
每个输入序列时间步的输出使解码器能够访问输入序列的中间表示。这可能有用,也可能无用。在输入序列结束时提供最终 LSTM 输出可能更合乎逻辑,因为它捕获了关于整个输入序列的信息,可以映射到或计算输出。
此外,这使得网络中没有东西可以指定解码器的大小,除了输入,为每个输入时间步(5 个而不是 2 个)提供一个输出值。
您可以将输出重构为由空格填充的 5 个字符序列。网络将执行比必需更多的工作,并且可能会丢失编码器-解码器范例提供的某些压缩能力。尝试一下,看看结果。
Keras GitHub 项目中标题为“序列到序列学习是否正确?”的问题,提供了关于您可以尝试的替代表示的一些很好的讨论。
评估 LSTM 模型
与之前一样,我们可以生成新一批示例,并在模型拟合后评估算法。
我们可以计算预测的 RMSE 分数,尽管为了简洁起见,我在这里省略了。
1 2 3 4 5 6 7 8 9 |
# 对一些新模式进行评估 X, y = generate_data(n_samples, n_numbers, largest, alphabet) result = model.predict(X, batch_size=n_batch, verbose=0) # 计算误差 expected = [invert(x, alphabet) for x in y] predicted = [invert(x, alphabet) for x in result] # 显示一些示例 for i in range(20): print('Expected=%s, Predicted=%s' % (expected[i], predicted[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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
from random import seed from random import randint from numpy import array from math import ceil from math import log10 from math import sqrt from numpy import argmax from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from keras.layers import TimeDistributed from keras.layers import RepeatVector # 生成随机整数及其总和的列表 def random_sum_pairs(n_examples, n_numbers, largest): X, y = list(), list() for i in range(n_examples): in_pattern = [randint(1,largest) for _ in range(n_numbers)] out_pattern = sum(in_pattern) X.append(in_pattern) y.append(out_pattern) 返回 X, y # 将数据转换为字符串 def to_string(X, y, n_numbers, largest): max_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 Xstr = list() for pattern in X: strp = '+'.join([str(n) for n in pattern]) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp Xstr.append(strp) max_length = ceil(log10(n_numbers * (largest+1))) ystr = list() for pattern in y: strp = str(pattern) strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp ystr.append(strp) return Xstr, ystr # 整数编码字符串 def integer_encode(X, y, alphabet): char_to_int = dict((c, i) for i, c in enumerate(alphabet)) Xenc = list() for pattern in X: integer_encoded = [char_to_int[char] for char in pattern] Xenc.append(integer_encoded) yenc = list() for pattern in y: integer_encoded = [char_to_int[char] for char in pattern] yenc.append(integer_encoded) return Xenc, yenc # One-Hot 编码 def one_hot_encode(X, y, max_int): Xenc = list() for seq in X: pattern = list() for index in seq: vector = [0 for _ in range(max_int)] vector[index] = 1 pattern.append(vector) Xenc.append(pattern) yenc = list() for seq in y: pattern = list() for index in seq: vector = [0 for _ in range(max_int)] vector[index] = 1 pattern.append(vector) yenc.append(pattern) return Xenc, yenc # 生成编码数据集 def generate_data(n_samples, n_numbers, largest, alphabet): # 生成配对 X, y = random_sum_pairs(n_samples, n_numbers, largest) # 转换为字符串 X, y = to_string(X, y, n_numbers, largest) # 整数编码 X, y = integer_encode(X, y, alphabet) # 独热编码 X, y = one_hot_encode(X, y, len(alphabet)) # 转换为 numpy 数组 X, y = array(X), array(y) 返回 X, y # 反转编码 def invert(seq, alphabet): int_to_char = dict((i, c) for i, c in enumerate(alphabet)) strings = list() for pattern in seq: string = int_to_char[argmax(pattern)] strings.append(string) return ''.join(strings) # 定义数据集 seed(1) n_samples = 1000 n_numbers = 2 largest = 10 alphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ' '] n_chars = len(alphabet) n_in_seq_length = n_numbers * ceil(log10(largest+1)) + n_numbers - 1 n_out_seq_length = ceil(log10(n_numbers * (largest+1))) # 定义 LSTM 配置 n_batch = 10 n_epoch = 30 # 创建 LSTM model = Sequential() model.add(LSTM(100, input_shape=(n_in_seq_length, n_chars))) model.add(RepeatVector(n_out_seq_length)) model.add(LSTM(50, return_sequences=True)) model.add(TimeDistributed(Dense(n_chars, activation='softmax'))) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) print(model.summary()) # 训练 LSTM for i in range(n_epoch): X, y = generate_data(n_samples, n_numbers, largest, alphabet) print(i) model.fit(X, y, epochs=1, batch_size=n_batch) # 对一些新模式进行评估 X, y = generate_data(n_samples, n_numbers, largest, alphabet) result = model.predict(X, batch_size=n_batch, verbose=0) # 计算误差 expected = [invert(x, alphabet) for x in y] predicted = [invert(x, alphabet) for x in result] # 显示一些示例 for i in range(20): print('Expected=%s, Predicted=%s' % (expected[i], predicted[i])) |
运行示例几乎完美地匹配了问题。事实上,运行更多 epoch 或将权重更新增加到每个 epoch(*batch_size=1*)都可以达到目标,但训练时间将是现在的 10 倍。
注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。请考虑运行示例几次并比较平均结果。
我们可以看到,在我们查看的前 20 个示例中,预测结果与预期结果匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
... 纪元 1/1 1000/1000 [==============================] - 2s - loss: 0.0579 - acc: 0.9940 Expected=13, Predicted=13 Expected=13, Predicted=13 Expected=13, Predicted=13 Expected= 9, Predicted= 9 Expected=11, Predicted=11 Expected=18, Predicted=18 Expected=15, Predicted=15 Expected=14, Predicted=14 Expected= 6, Predicted= 6 Expected=15, Predicted=15 Expected= 9, Predicted= 9 Expected=10, Predicted=10 Expected= 8, Predicted= 8 Expected=14, Predicted=14 Expected=14, Predicted=14 Expected=19, Predicted=19 Expected= 4, Predicted= 4 Expected=13, Predicted=13 Expected= 9, Predicted= 9 Expected=12, Predicted=12 |
扩展
本节列出了一些您可以探索的此教程的自然延伸。
- 整数编码。探索使用单独的整数编码是否能更好地学习问题。大多数输入之间的序数关系可能非常有用。
- 可变数量。将示例更改为支持每个输入序列可变数量的项。只要进行足够的填充,这应该是直接的。
- 可变数学运算。更改示例以改变数学运算,使网络能够进一步泛化。
- 括号。允许使用括号以及其他数学运算。
你尝试过这些扩展吗?
在评论中分享您的发现;我很想看看您发现了什么。
进一步阅读
本节列出了一些供您进一步阅读的资源和其他相关示例。
论文
- Sequence to Sequence Learning with Neural Networks, 2014 [PDF]
- Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014 [PDF]
- LSTM can Solve Hard Long Time Lag Problems [PDF]
- Learn to Execute, 2014 [PDF]
代码和帖子
- Keras 加法示例
- Lasagne 中的加法示例
- RNN 加法(一年级)和笔记本
- 任何人都可以用 Python 学习编码 LSTM-RNN(第一部分:RNN)
- 用 50 行代码在 Tensorflow 中实现 LSTM 的简单方法
总结
在本教程中,您将学习如何使用 seq2seq 堆叠 LSTM 范例开发一个 LSTM 网络来学习将随机整数相加。
具体来说,你学到了:
- 如何学习输入项到输出项的朴素映射函数以进行加法。
- 如何构建加法问题(及类似问题)并对输入和输出进行适当编码。
- 如何使用 seq2seq 范式解决真正的序列预测加法问题。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
爱你
谢谢,很高兴对您有帮助。
如何编写解码器网络的代码,其输入是编码器的内存加上前一个时间步的输出?
好问题,我还没这样做过。这可能需要仔细的网络设计。
你好,
非常有意义的文章。我写了一个用于处理更复杂表达式的扩展(可在 GIST 上找到:https://gist.github.com/giuseppebonaccorso/d6e5bee6d50480344493b66f88fc414b)
不幸的是,仍然有很多错误,但这可能是由于训练数据集的大小(因为它不包含所有可能的示例)造成的。这可能是 Seq2Seq 最难的部分,我指的是创建一个模型,该模型还可以学习语义,以便能够用更少的示例和始终更好的性能进行训练。
我将继续我的实验!
做得好,继续实验吧 Giuseppe!
抱歉,我遇到了这个错误。哪里出错了?
使用 Theano 后端。
回溯(最近一次调用)
File “test.py”, line 110, in
model.add(LSTM(100, input_shape=(n_in_seq_length, n_chars)))
File “/usr/local/lib/python2.7/dist-packages/keras/models.py”, line 430, in add
layer(x)
File “/usr/local/lib/python2.7/dist-packages/keras/layers/recurrent.py”, line 257, in __call__
return super(Recurrent, self).__call__(inputs, **kwargs)
File “/usr/local/lib/python2.7/dist-packages/keras/engine/topology.py”, line 578, in __call__
output = self.call(inputs, **kwargs)
File “/usr/local/lib/python2.7/dist-packages/keras/layers/recurrent.py”, line 295, in call
preprocessed_input = self.preprocess_input(inputs, training=None)
File “/usr/local/lib/python2.7/dist-packages/keras/layers/recurrent.py”, line 1028, in preprocess_input
timesteps, training=training)
File “/usr/local/lib/python2.7/dist-packages/keras/layers/recurrent.py”, line 58, in _time_distributed_dense
x = K.reshape(x, (-1, timesteps, output_dim))
File “/usr/local/lib/python2.7/dist-packages/keras/backend/theano_backend.py”, line 739, in reshape
y = T.reshape(x, shape)
File “/usr/local/lib/python2.7/dist-packages/Theano-0.9.0-py2.7.egg/theano/tensor/basic.py”, line 4910, in reshape
rval = op(x, newshape)
File “/usr/local/lib/python2.7/dist-packages/Theano-0.9.0-py2.7.egg/theano/gof/op.py”, line 615, in __call__
node = self.make_node(*inputs, **kwargs)
File “/usr/local/lib/python2.7/dist-packages/Theano-0.9.0-py2.7.egg/theano/tensor/basic.py”, line 4748, in make_node
raise TypeError(“Shape must be integers”, shp, shp.dtype)
TypeError: (‘Shape must be integers’, TensorConstant{[ -1. 5. 100.]}, ‘float64’)
确保您已准确复制了示例,没有任何多余的空格。
另外,请确保您使用的是 Keras 2.0 或更高版本。
谢谢你的帮助,但是
我仔细写了
line model.add(LSTM(100, input_shape=(n_in_seq_length, n_chars)))
raise TypeError(“Shape must be integers”, shp, shp.dtype)
TypeError: (‘Shape must be integers’, TensorConstant{[ -1. 5. 100.]}, ‘float64’)
我以前从未见过这个错误,抱歉。
您的其他示例在您的系统上可以工作吗?
是的。Keras 是 2.0.4 版本,您的帖子也非常重要和精彩!干得好!
当我复制粘贴代码时,我遇到了几乎相同的错误
TypeError: Value passed to parameter ‘shape’ has DataType float32 not in list of allowed values: int32, int64
抱歉,我没见过这个错误。请确保您已安装所有库的最新版本。
看看您是否可以分解示例并缩小失败的行。它看起来像 numpy 和 .shape 属性。也许您的 numpy 没有更新。shape 总是返回一个整数。我不知道这个错误是什么,
这实际上是由于 n_out_seq_length = ceil(log10(n_numbers * (largest+1))) 行。
我不知道为什么会这样,因为 ceil 应该返回一个整数。
如果我手动将值更改为 n_out_seq_length = 2,它就可以正常工作。
有意思。
Jason,干得漂亮!
对于 seq2seq 问题,RNN 是默认的选择。但是,在 MLP 和 RNN 之间选择用于 seq2vec 问题似乎相当微妙。在讨论的情况下,如果我们“将过多的领域知识编码到问题中”,我们就会将其变成一个函数映射问题,这里“领域知识”指的是“将加法方程分解为被加数和加数”。但是这种“数据预处理”对于输入序列来说是很常见的。
问:您对 seq2vec 问题的模型选择有任何原则或指导方针吗?
顺便说一句:我读了您推荐的 Keras 问题。也许有答案。
很好的问题。
这实际上取决于在您的问题上哪种表现最好。您可能因为其优雅性而选择 seq2seq 框架,但基于映射的解决方案(seq2vec)可能表现更好。
在实践中,我会尝试一套方法,然后专注于最有效的方法。
seq2seq 是否可用于时间序列预测?鉴于您多年来发布了许多关于时间序列的出色帖子,如果将传统时间序列预测转换为 seq2seq 框架怎么样?
您能给我一些建议吗?
是的,如果您为每个输入时间步有不同的输出时间步长。
感谢您的回复。
Jason,你好。感谢您的精彩文章。
有些事情我不清楚。LSTM 能够学习不同长度的序列。为什么我们要使用填充来使序列长度相同?
这里,区别在于输入序列的长度和输出序列的长度之间,而不是输入序列本身的长度差异。
谢谢 Jason。
我做了一些搜索,似乎在填充之后,使用掩码或初始化 sample_weight 来告诉模型忽略空格至关重要。它不会自动忽略填充的字符(我有一个错误的假设)。
我为此问题进行了掩码,结果如下:
10 个 epoch 的无掩码结果是:loss: 0.6787 – acc: 0.8185
10 个 epoch 的有掩码结果是:loss: 0.6202 – acc: 0.8705
正如您所见,有了很大的改进。但是您能否说明我们如何将 sample_weight 应用于此问题?
通过更改代码的这部分实现的改进
model = Sequential()
model.add(Masking(mask_value = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], input_shape=(n_in_seq_length, n_chars)))
model.add(LSTM(100))
注意:请记住添加
from keras.layers import Masking
干得好!感谢您报告您的结果。
我有一个关于掩码的帖子即将发布,应该会提供进一步的帮助。
您说的 sample weight 是什么意思?权重输入样本?我认为神经网络已经这样做了。您也可以对训练数据进行重采样以影响不同类的采样分布。
谢谢 Jason。
我运行了我的模型很多次,似乎改进是由于随机性而不是掩码,很遗憾。
我对您关于掩码的新帖子感到非常兴奋。
是的,我的意思是加权输入样本。
我在 Keras 的 Slack 频道上问了这个问题,一些人告诉我神经网络无法学会忽略时间序列的填充部分,我需要通过加权输入样本来做到这一点。
也许您可以问问这个人是什么意思。
我无法想象加权输入如何帮助掩码。
这是一个很棒的教程,非常感谢!
我有一个关于 LSTM 层大小的问题。您选择为编码器使用 100 个神经元的 LSTM 层,为解码器使用 50 个。您是如何选择这些值的?它们与输入大小和输出大小有任何关系吗?这是否意味着如果我使用的输入大小加倍(4 位数字而不是 2 位数字),我也应该将神经元数量加倍?
提前感谢您!
不,只是反复试验。
尝试不同的值,看看它们如何影响模型性能。
感谢这篇精彩的文章,我想使用编码器-解码器 LSTM 网络来提取洗发水销售数据中的特征,以便将这些特征用于多步预测。
def lstm_autoencoder(train_data, target, timesteps, repeat_vec, batch_size, epochs, ls_cells=[10, 6], lr=0.01)
train_data = train_data.reshape(train_data.shape[0], timesteps, train_data.shape[1])
target = target.reshape(target.shape[0], timesteps, target.shape[1])
model = Sequential()
model.add(LSTM(ls_cells[0], batch_input_shape=(batch_size, train_data.shape[1], train_data.shape[2]), return_sequences=True))
model.add(LSTM(ls_cells[1], batch_input_shape=(batch_size, train_data.shape[1], train_data.shape[2])))
model.add(RepeatVector(repeat_vec))
model.add(LSTM(ls_cells[2], batch_input_shape=(batch_size, train_data.shape[1], train_data.shape[2]), return_sequences=True))
model.add(TimeDistributed(Dense(1, activation=’relu’)))
model.compile(loss=’mse’, optimizer=Adam(lr=lr), callbacks=[early_stopping])
打印(model.summary())
# 训练 LSTM
tr_mse, val_mse = list(), list()
for i in range(epochs)
print(‘Epoch :’, str(i) )
history = model.fit(train_data, target, epochs=1, batch_size=1, verbose=2, shuffle=False, validation_split=0.1)
tr_mse.append(history.history[‘loss’])
val_mse.append(history.history[‘val_loss’])
return model, history, tr_mse, val_mse
model, history, tr_mse, val_mse = lstm_autoencoder(X_scaled, y_scaled, timesteps=1,
repeat_vec=1, batch_size=1,
epochs=80, ls_cells=[20, 16, 7],
lr=0.002)
训练网络后,我正在进行预测……
X_train = X_train.reshape(X_train.shape[0], 1, 1)
result = model.predict(X_train, batch_size=1, verbose=2)
这是正确的方法吗,因为我的想法是以无监督的方式进行……
提前感谢。
我想了解如何以无监督的方式使用网络将维度降低到固定长度向量(编码),然后重构(解码),之后,我将在此固定长度的降维数据上训练 LSTM 进行多步预测。
是的,但它不是无监督的,它是监督的。
您可以使用编码器-解码器来处理时间序列。
关键是选择输入序列的长度和上下文向量的长度,两者都要进行实验。我很想听听您的进展。
这篇帖子可能可以为您提供一个更清晰的模板作为起点。
https://machinelearning.org.cn/encoder-decoder-long-short-term-memory-networks/
感谢这篇详细的文章!
我有一个问题:编码器和解码器的初始状态是什么?
看起来,不是将编码器的最后一个状态“发送”到解码器作为初始状态,而是将编码器的最后一个输出“发送”/馈送到解码器作为输入,对吗?但编码器的最后一个输出实际上对应于最后一个时间步的输入数据……而不是所有输入数据的表示……此外,解码器中两个时间步之间的唯一区别是隐藏状态(因为解码器每个时间步的输入 == 最后一个编码器输出)……
如果我测试了“发送”编码器的最后一个隐藏状态到解码器或使用注意力机制,并且每个解码器输入都是最后一个解码器输出时间步的输出的解决方案,您能否精确说明如何在 Keras 或仅 Tensorflow 中实现这一点?
提前感谢您的回答。
当您提到状态时,您是指每个内存单元内的内部变量,还是指编码器或解码器模型的输出?我认为您可能将这些元素混淆了。
谢谢你的回答!
我指的是隐藏状态和单元状态(对于 LSTM)。它们通过时间步从一个 LSTM 单元传递到另一个……
在每个时间步,一个 LSTM 单元
– 具有输入:X(batch_size,n_features)、前一个隐藏状态 h、前一个单元/内存状态 c,
– 产生:Y(batch_size,n_hidden_units)、隐藏状态 h、单元/内存状态 c。
谢谢!
如果我能说清楚我的问题:解码器的输入是一个恒定的向量,这个向量是编码器的最后一个输出 y(n);但这个最后一个输出适合编码器的输入 x(n)。
例如
编码器中的时间步数 = n
解码器中的时间步数 = m
编码器输入 = x:{x1, x2, x3 … xn}
编码器输出 = y:{y1,y2,y3 … yn}
编码器状态 = he:{he0,he1….hen}
解码器输入 = y’:{yn,yn …yn}(m 次)
解码器输出 = z:{z1,z2 … zm}
解码器状态= hd:{hd0,hd1, hd2… hdm}
所以,我的问题是
– he0 是什么?(初始编码器状态)
– hd0 是什么?(初始解码器状态……我认为它应该是 hen == hd0)
– yn = f[he(n-1), xn] 是解码器的输入,x1 … x(n-1) 的信息在哪里可以正确解码?
谢谢!!
编码器和解码器的初始隐藏状态值为 0。
Jason,谢谢您的帖子。我想了解第一个解决方案,其中使用 MLP 来学习对数字进行求和。训练中使用的值是这样的:经过训练的模型“看到”了 5,000 个示例(n_examples * n_epoch),如果我没记错的话,两个输入数字的可能值在 1 到 100 之间,这使得有 5,050(100 * 99 / 2 + 100)个不同的可能对。当然,这 5,000 个示例并不都是唯一的,但仍然:这是否将 MLP 变成了一个针对此特定示例的所有可能对的哈希表?
不过,在我看来,如果我们增加 `largest` 的值而不改变其他变量,这种方法仍然在很大程度上有效,这可能是由于归一化,我猜。我觉得这很神奇。
实际上,现在仔细想想:模型需要学习一个简单的线性函数,所以只要有足够的训练示例,它就能泛化到任何大小的数字,这是正常的。也许存在一个最小的训练示例数量,足以使其能够处理任何大小的数字。我的实验表明 `n_examples = 100, n_epoch = 100` 就可以完成工作。
是的,模型必须泛化一个映射函数。很高兴您能看到这一点。
谢谢你,Jason!
不客气。
我注意到您正在训练 30,000 个示例(= 30 个 epoch x 每个 epoch 1000 个样本)。
此外,在训练集和测试集中相加的最大数字都是 10,这可能意味着在域中最多有 100 个唯一的加法示例对(10 x 10)。
由于 30,000 个训练示例远大于(>>)可用的唯一加法对(即 100 个),您认为模型是否仅仅记住了结果?
乍一看,似乎“测试数据”可能并非模型正在看到的真正新数据。
尝试将“最大值”增加到100——这意味着有10,000种独特的加法可能性。但这一次——测试准确率非常低(约58%),而且似乎测试集上的预测都错了(尽管在许多情况下都很接近)。
那么——LSTM真的在学习吗??
这是一个需要仔细测试才能回答的好问题。
我不认为它得到了足够的曝光/或有足够的能力来记忆,但你可能是对的。
更好地评估模型的方法是,在可能的对的子集上训练它,并在保留的对上测试它,看看它是否真的学会了加法。
你好,Jason,我的数据输入是层次结构的序列,例如类别、子类别、子子类别(例如,图书类别),另一个输入是跨每个类别的共享(例如,图书出版年份),输出是图书的一个特征。我当时想使用这个博客中的模型,但不太确定。你建议哪种模型?
我建议头脑风暴一套不同的问题表述方式,尝试每一种,看看哪种效果最好。
谢谢Jason,很棒的帖子。我对“单元”问题感到困惑。你为编码器层设置了100个单元,这意味着它包含100个隐藏状态,对吗?一个单元接受最后一个隐藏状态和一些输入x,然后产生一些输出,我说得对吗?我感到困惑的是,这些输入x来自哪里?在我看来,一个单元接收一个输入,就是一个数字或一个“+”。我哪里错了?
每个单元都会接收输入。这意味着输入层中的所有单元都在并行执行相同的工作。
谢谢Jason,很棒的帖子。如果我想将这篇文章翻译成泰语,你愿意吗?
我正在翻译“训练”。
我很抱歉,我的英语不好。
请不要翻译我的帖子,我在这里解释得更清楚
https://machinelearning.org.cn/faq/single-faq/can-i-translate-your-posts-books-into-another-language
哦!!好的,对不起!
你好。我有一个自动编码器,有5层LSTM(512) -> LSTM(64) -> LSTM(32) -> LSTM(64) -> LSTM(512) -> Dense(1),当我运行它的时候,我得到了一个错误:expected dense_4 to have 3 dimensions, but got array with shape (272, 1)。
所以,我尝试在我的LSTM(32)层之后添加这一行:model.add(RepeatVector(2)),然后我得到了这个错误:ValueError: Input 0 is incompatible with layer repeat_vector_2: expected ndim=2, found ndim=3。
基本上,我真的很难让这个东西工作。
这是我在这里回答的一个常见问题
https://machinelearning.org.cn/faq/single-faq/can-you-read-review-or-debug-my-code
你好 Jason,
我有一个问题。如果我想映射到一个非常长的序列怎么办?比如说,我想将长度为100的序列映射到长度为3000的序列。在你看来,模型会是什么样的?你能分享一下你的想法吗?
也许可以尝试编码器-解码器和LSTM自动编码器。
你好Jason,非常感谢您的这篇文章!
我想知道使用独热编码的原因是什么?有可能避免它吗?
你可以在这里了解更多关于独热编码的信息
https://machinelearning.org.cn/why-one-hot-encode-data-in-machine-learning/
你可以使用词嵌入作为输入的替代。
嗨,Jason,
和别的帖子一样棒。
Keras中有CuDNNGRU,但是你见过Keras中的CuDNNRNN吗?因为TensorFlow中有CuDNNRNN。我需要Keras中的CuDNNRNN,因为它比simplernn实现得快得多。
感谢您的回复。
抱歉,我没有关于CuDNNGRU的教程。
嗨,Jason,
>网络适合50个epoch训练,每个epoch都会生成新的例子,并且每2个例子更新一次权重。
我看不出50个epoch,n_epoch = 100。
而且我们每个epoch训练一次,所以我们不是为每个例子更新权重吗?我们是如何每2个例子更新一次的?
你说得对,我已经更新了模型训练的描述。谢谢!
这就是为什么神经网络是个大笑话(我也是,是的,哈哈)
这是逻辑的力量……这是给你的加法
add m 0 = m
add m n = 1 + add m (n-1)
完成!!!
我不同意。
另外请注意,教程是学习算法功能的演示,而不是关于解决加法问题的最佳方法的案例研究(也许我应该更清楚地说明这一点)。
先生,如何使用该模型向用户提供输入,然后检查用户的响应?
例如
模型:10 + 13
用户:26
模型:正确答案
这听起来像是一个通用的编程问题,也许可以查阅Python API的键盘输入?
你好,你可以通过input()函数向用户提供输入,然后将用户的响应存储在一个变量中,通过.predic()函数将相同的问题传递给算法,并通过“if”语句比较用户的响应和算法的预测是否相同。
虽然我认为使用此模型来实现这一点并非最实用,但用一个简单的计算器而不是深度学习模型可能更好。
感谢SL的反馈!
很棒的文章!它帮助我加深了对LSTM的理解,并帮助我改进了我正在使用的LSTM。
我认为可能有一个小的拼写错误?12个元素/特征/类别,不是吗?10个数字加上2个字符?
谢谢Miguel。
你好Jason
感谢您的教程。但您不认为加法是线性的,所以它甚至不需要任何隐藏层吗?
数字的平方呢?我尝试了您的方法,并尝试预测数字的平方,但错误太高,甚至无法考虑。您能对此有所启发吗?
加法是线性的,但这个问题是符号/组合的。
是的,我很乐意您探索其他操作!我预计该技术同样有效。
告诉我进展如何。
嗨,Jason,
在我的模型中,有28个输入和2个输出。所有输入都是3位小数的浮点值。请指导如何创建和训练LSTM模型。one_hot_encode适用于整数,不适用于浮点数。请提供建议。
谢谢你,
AG
没问题,你可以在这里开始
https://machinelearning.org.cn/how-to-develop-lstm-models-for-time-series-forecasting/
你好,一个问题是,当我有不规则列表时,例如,适当的修改是什么?
[[1,2] [3
[1,4,7] 12
[1,2,3] 6
[1,1,9,6]] 17]
我曾想过通过添加零来使所有列表的大小相同,但在我的情况下,这只是一个短期解决方案。
你好SL……你尝试过你提出的方法吗?这可能证明是合理的。