在Python中用一个长短期记忆网络来演示记忆

长期短期记忆(LSTM)网络是一种能够在长序列上学习的递归神经网络。

这不同于没有记忆的常规多层神经网络,它只能学习输入和输出模式之间的映射。

理解复杂的神经网络如LSTMs对人为的小问题的解决能力是非常重要的,因为这种理解将帮助你把网络扩展到大的甚至是非常大的问题。

在本教程中,您将发现LSTM的记忆和回忆的能力。

完成本教程后,您将知道:

  • 如何定义一个小的序列预测问题,只有像LSTM这样的RNN可以使用记忆来解决。
  • 如何转化问题表示,使之适合LSTM学习。
  • 如何设计一个LSTM来正确解决问题。

让我们开始吧。

演示在一个长期的短期记忆网络中的记忆 图片由crazlei提供。

环境

本教程假定您有一个可运行的Python 2或3环境,其中包含SciPy,具有TensorFlow或Theano后端的Keras 2.0(或更高版本)。

有关设置您的Python环境的帮助,请参阅以下文章:

序列问题描述

问题是一次预测一个序列的值。

给定序列中的一个值,模型必须预测序列中的下一个值。例如,给定值“0”作为输入,模型必须预测值“1”。

有两个不同的序列,模型必须学习并正确预测。

一个皱起(wrinkle)是两个序列之间存在冲突的信息,并且模型必须知道每一步预测的上下文(例如,它当前预测的序列)以正确预测每个完整序列。

这种皱起(wrinkle)对于防止模型记忆每个序列中的每个单步输入-输出值对是很重要的,序列未知模型可能倾向于这样做。

要学习的两个序列如下:

  • 3, 0, 1, 2, 3
  • 4, 0, 1, 2, 4

我们可以看到序列的第一个值与最后一个值是重复的。这是为模型提供上下文的指示器,让模型知道它正在处理哪个序列。

冲突是从每个序列中的倒数第二项到最后一项的过渡。在序列1中,给出“2”作为输入,并且必须预测“3”,而在序列2中给出“2”作为输入并且必须预测“4”。

这是一个多层感知器和其他非递归神经网络无法学习的问题。

这是“ 实验2 ” 的简化版本,用于演示Hochreiter和Schmidhuber在1997年发表的长期短期记忆  (PDF)中的LSTM长期记忆能力。

问题表示

本部分分为三部分; 他们是:

  1. 一位有效编码
  2. 输入-输出对
  3. 重塑数据

一个有效编码

我们将使用一个有效编码来表示LSTM的学习问题。

也就是说,每个输入和输出值将被表示为具有5个元素的二进制向量,因为问题的字母表是5个唯一值。

例如,[0,1,2,3,4]的5个值被表示为以下5个二进制向量:

0: [1, 0, 0, 0, 0]
1: [0, 1, 0, 0, 0]
2: [0, 0, 1, 0, 0]
3: [0, 0, 0, 1, 0]
4: [0, 0, 0, 0, 1]

我们可以用一个简单的函数来完成这个任务,这个函数将会获取一个序列并返回序列中每个值的二元向量列表。下面的encode()函数实现了这个行为。

# 对输入进行二元编码,返回二元向量序列
def encode(pattern, n_unique):
encoded = list()
for value in pattern:
row = [0.0 for x in range(n_unique)]
row[value] = 1.0
encoded.append(row)
return encoded

我们可以在第一个序列上测试它,并打印出二进制向量列表。完整的例子如下所示。

# 对输入进行二元编码,返回二元向量序列
def encode(pattern, n_unique):
encoded = list()
for value in pattern:
row = [0.0 for x in range(n_unique)]
row[value] = 1.0
encoded.append(row)
return encoded
seq1 = [3, 0, 1, 2, 3]
encoded = encode(seq1, 5)
for vector in encoded:
print(vector)

运行该示例打印每个二进制向量。请注意,我们使用浮点值0.0和1.0,因为它们将用作模型的输入和输出。

[0.0, 0.0, 0.0, 1.0, 0.0]
[1.0, 0.0, 0.0, 0.0, 0.0]
[0.0, 1.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 1.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 1.0, 0.0]

输入 - 输出对

下一步是将一系列编码值分成输入-输出对。

这是一个监督学习的问题表示,使机器学习问题可以学习如何将输入模式(X)映射到输出模式(y)。

例如,第一个序列具有以下要学习的输入-输出对:

X,	y
3,	0
0,	1
1,	2
2,	3

我们必须从一位有效编码的二进制向量中创建这些映射对,而不是原始数字。

例如,3->0的第一个输入-输出对将是:

X,	y
[0, 0, 0, 1, 0]	[1, 0, 0, 0, 0]

下面是一个名为to_xy_pairs()的函数,它会根据编码的二进制向量列表创建XY模式列表。

# 创建编码的向量的输入/输出对, 返回X, y
def to_xy_pairs(encoded):
X,y = list(),list()
for i in range(1, len(encoded)):
X.append(encoded[i-1])
y.append(encoded[i])
return X, y

我们可以把它和上面的一位有效编码函数放在一起,并打印第一个序列的编码输入和输出对。

# 对输入进行二元编码,返回二元向量序列
def encode(pattern, n_unique):
encoded = list()
for value in pattern:
row = [0.0 for x in range(n_unique)]
row[value] = 1.0
encoded.append(row)
return encoded
# 创建编码的向量的输入/输出对, 返回X, y
def to_xy_pairs(encoded):
X,y = list(),list()
for i in range(1, len(encoded)):
X.append(encoded[i-1])
y.append(encoded[i])
return X, y
seq1 = [3, 0, 1, 2, 3]
encoded = encode(seq1, 5)
X, y = to_xy_pairs(encoded)
for i in range(len(X)):
print(X[i], y[i])

运行该示例将打印序列中每个步骤的输入和输出对。

[0.0, 0.0, 0.0, 1.0, 0.0] [1.0, 0.0, 0.0, 0.0, 0.0]
[1.0, 0.0, 0.0, 0.0, 0.0] [0.0, 1.0, 0.0, 0.0, 0.0]
[0.0, 1.0, 0.0, 0.0, 0.0] [0.0, 0.0, 1.0, 0.0, 0.0]
[0.0, 0.0, 1.0, 0.0, 0.0] [0.0, 0.0, 0.0, 1.0, 0.0]

重塑数据

最后一步是重塑数据,使其可以被LSTM网络直接使用。

Keras LSTM预期输入模式(X)为维度[ 采样,时间步长,特征 ] 的三维NumPy阵列。

在一个输入数据序列的情况下,维数将是[4,1,5],因为我们有4行数据,每行1个时间步,每行5列。

我们可以从我们的X模式列表创建一个2D NumPy数组,然后将其重塑为所需的3D格式。例如:

df = DataFrame(X)
values = df.values
array = values.reshape(4, 1, 5)

我们还必须将输出模式列表(y)转换为2D NumPy数组。

下面是一个名为to_lstm_dataset()的函数,它将一个序列作为输入和序列字母的大小,并返回一个准备用于LSTM 的XY数据集。它在重新整形数据之前,将序列转换为单热编码和输入 - 输出对。

# convert sequence to x/y pairs ready for use with an LSTM
def to_lstm_dataset(sequence, n_unique):
# one hot encode
encoded = encode(sequence, n_unique)
# convert to in/out patterns
X,y = to_xy_pairs(encoded)
# convert to LSTM friendly format
dfX, dfy = DataFrame(X), DataFrame(y)
lstmX = dfX.values
lstmX = lstmX.reshape(lstmX.shape[0], 1, lstmX.shape[1])
lstmY = dfy.values
return lstmX, lstmY

这个函数可以按照以下顺序调用:

seq1 = [3, 0, 1, 2, 3]
seq2 = [4, 0, 1, 2, 4]
n_unique = len(set(seq1 + seq2))
seq1X, seq1Y = to_lstm_dataset(seq1, n_unique)
seq2X, seq2Y = to_lstm_dataset(seq2, n_unique)

我们现在已经准备好了LSTM的所有数据。

通过LSTM学习序列

在本节中,我们将定义LSTM来学习输入序列。

本部分分为四个部分:

  1. LSTM配置
  2. LSTM训练
  3. LSTM评估
  4. LSTM完整示例

LSTM配置

我们希望LSTM做出一步式预测,我们已经在数据集的格式和形状中定义了这些预测。我们也希望LSTM在每个时间步后都会更新错误,这意味着我们设置批处理为1。

Keras LSTM在默认情况下在批处理之间是无状态的。我们可以通过将LSTM层上的状态参数设置为True并手动管理训练时期来使状态成为有状态,以确保在每个序列之后重置LSTM的内部状态。

我们必须使用batch_input_shape参数来定义批处理的形状,[批处理大小,时间步长和特征]分别为1,1和5。

网络拓扑结构将配置一个20个单元的隐藏LSTM层,以及输出模式中每5个列产生5个输出的正常密集层。由于二进制输出,输出层将使用S形(逻辑)激活函数,而LSTM层将使用默认的tanh(双曲正切)激活函数。

由于二进制输出,在拟合网络时将优化对数(交叉熵)损失函数,并且所有默认参数都将使用有效的ADAM优化算法。

下面列出了为这个问题定义LSTM网络的Keras代码。

model = Sequential()
model.add(LSTM(20, batch_input_shape=(1, 1, 5), stateful=True))
model.add(Dense(5, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam')

LSTM培训

我们必须在每个时期手动适应模型。

在一个时期内,我们可以在每个序列上拟合模型,确保在每个序列之后重置状态。

考虑到问题的简单性,模型不需要长时间的训练; 在这种情况下只需要250个时期。

下面是一个例子,说明这个模型如何适用于所有时期的每个序列。

# train LSTM
for i in range(250):
model.fit(seq1X, seq1Y, epochs=1, batch_size=1, verbose=1, shuffle=False)
model.reset_states()
model.fit(seq2X, seq2Y, epochs=1, batch_size=1, verbose=0, shuffle=False)
model.reset_states()

在拟合网络时,我希望看到有关丢失函数的一些反馈,所以从某个序列开始开启详细的输出。

LSTM评估

接下来,我们可以通过预测学习序列的每一步来评估拟合模型。

我们可以通过预测每个序列的输出来做到这一点。

predict_classes()函数可在LSTM模式上使用,它将直接预测类。它通过对输出二进制向量执行一个argmax(),并返回具有最大输出的预测列的索引。输出索引完美映射到序列中使用的整数(通过上面的精心设计)。下面列出了一个预测的例子:

result = model.predict_classes(seq1X, batch_size=1, verbose=0)

我们可以进行预测,然后在输入模式和序列的每个步骤的预期输出模式的上下文中打印结果。

LSTM完整示例

现在我们可以把整个教程结合在一起。

下面提供了完整的代码清单。

首先,准备数据,然后模型拟合,并打印两个序列的预测。

from pandas import DataFrame
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# binary encode an input pattern, return a list of binary vectors
def encode(pattern, n_unique):
encoded = list()
for value in pattern:
row = [0.0 for x in range(n_unique)]
row[value] = 1.0
encoded.append(row)
return encoded
# create input/output pairs of encoded vectors, returns X, y
def to_xy_pairs(encoded):
X,y = list(),list()
for i in range(1, len(encoded)):
X.append(encoded[i-1])
y.append(encoded[i])
return X, y
# convert sequence to x/y pairs ready for use with an LSTM
def to_lstm_dataset(sequence, n_unique):
# one hot encode
encoded = encode(sequence, n_unique)
# convert to in/out patterns
X,y = to_xy_pairs(encoded)
# convert to LSTM friendly format
dfX, dfy = DataFrame(X), DataFrame(y)
lstmX = dfX.values
lstmX = lstmX.reshape(lstmX.shape[0], 1, lstmX.shape[1])
lstmY = dfy.values
return lstmX, lstmY
# define sequences
seq1 = [3, 0, 1, 2, 3]
seq2 = [4, 0, 1, 2, 4]
# convert sequences into required data format
n_unique = len(set(seq1 + seq2))
seq1X, seq1Y = to_lstm_dataset(seq1, n_unique)
seq2X, seq2Y = to_lstm_dataset(seq2, n_unique)
# define LSTM configuration
n_neurons = 20
n_batch = 1
n_epoch = 250
n_features = n_unique
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, batch_input_shape=(n_batch, 1, n_features), stateful=True))
model.add(Dense(n_unique, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam')
# train LSTM
for i in range(n_epoch):
model.fit(seq1X, seq1Y, epochs=1, batch_size=n_batch, verbose=1, shuffle=False)
model.reset_states()
model.fit(seq2X, seq2Y, epochs=1, batch_size=n_batch, verbose=0, shuffle=False)
model.reset_states()
# test LSTM on sequence 1
print('Sequence 1')
result = model.predict_classes(seq1X, batch_size=n_batch, verbose=0)
model.reset_states()
for i in range(len(result)):
print('X=%.1f y=%.1f, yhat=%.1f' % (seq1[i], seq1[i+1], result[i]))
# test LSTM on sequence 2
print('Sequence 2')
result = model.predict_classes(seq2X, batch_size=n_batch, verbose=0)
model.reset_states()
for i in range(len(result)):
print('X=%.1f y=%.1f, yhat=%.1f' % (seq2[i], seq2[i+1], result[i]))

运行示例提供关于模型在每个时期的第一个序列上的损失的反馈。

在运行结束时,每个序列都在预测的上下文中打印。

...
4/4 [==============================] - 0s - loss: 0.0930
Epoch 1/1
4/4 [==============================] - 0s - loss: 0.0927
Epoch 1/1
4/4 [==============================] - 0s - loss: 0.0925
Sequence 1
X=3.0 y=0.0, yhat=0.0
X=0.0 y=1.0, yhat=1.0
X=1.0 y=2.0, yhat=2.0
X=2.0 y=3.0, yhat=3.0
Sequence 2
X=4.0 y=0.0, yhat=0.0
X=0.0 y=1.0, yhat=1.0
X=1.0 y=2.0, yhat=2.0
X=2.0 y=4.0, yhat=4.0

结果显示了两个重要的信息:

  • LSTM一次一个步骤地正确学习了每个时期。
  • LSTM使用每个序列的上下文来正确地解决冲突的输入对。

实质上,LSTM能够在3个时间步前的序列开始时记住输入模式,以正确预测序列中的最后一个值。

这种记忆和LSTM将观测与遥远的时间联系起来的能力是使LSTM如此强大以及为什么如此广泛使用的关键能力。

虽然这个例子是微不足道的,但是LSTM能够在100秒,甚至1000秒的时间步骤中展现出相同的能力。

扩展

本节列出了本教程中示例的扩展的想法。

  • 调优。经过一些试验和错误之后,选择了LSTM(时代,单位等)的配置。在这个问题上,更简单的配置可能会达到相同的结果。一些参数搜索是必需的。
  • 任意字母表。5个整数的字母表是任意选择的。这可以更改为其他符号和更大的字母。
  • 长序列。这个例子中使用的序列非常短。LSTM能够在100秒和1000秒的更长序列上展示相同的能力。
  • 随机序列。本教程中使用的序列线性增加。可以创建新的随机值序列,允许LSTM设计一个通用的解决方案,而不是专门针对本教程中使用的两个序列。
  • 批量学习。在每个时间步骤之后更新LSTM。探索使用批量更新,看看这是否提高了学习。
  • 时期随机化。在训练期间以及在评估期间再次以相同的顺序显示序列。随机化序列的顺序,使序列1和2适合于一个时期,这可能会改善模型泛化到新的看不见的字母相同的序列。

你有没有探索这些扩展? 在下面的评论中分享你的结果。我很想看看你想出了什么。

进一步阅读

我强烈建议阅读Hochreiter和Schmidhuber最初的1997年LSTM论文; 这很棒。

概要

在本教程中,您发现了LSTM能够记住多个时间步骤的关键能力。

具体来说,你了解到:

  • 如何定义一个小的序列预测问题,只有像LSTM这样的RNN可以使用记忆来解决。
  • 如何转化问题表示,使之适合LSTM学习。
  • 如何设计一个LSTM来正确解决问题。

翻译人:用户1210890,该成员来自云+社区翻译社

原文链接:https://machinelearningmastery.com/memory-in-a-long-short-term-memory-network/

原文作者:Jason Brownlee

发表于 13天前
10

0 条评论

相关文章