深度学习入门系列22:从猜字母游戏中理解有状态的LSTM递归神经网络

大家好,我技术人Howzit,这是深度学习入门系列第二十二篇,欢迎大家一起交流!

深度学习入门系列1:多层感知器概述
深度学习入门系列2:用TensorFlow构建你的第一个神经网络
深度学习入门系列3:深度学习模型的性能评价方法
深度学习入门系列4:用scikit-learn找到最好的模型
深度学习入门系列5项目实战:用深度学习识别鸢尾花种类
深度学习入门系列6项目实战:声纳回声识别
深度学习入门系列7项目实战:波士顿房屋价格回归
深度学习入门系列8:用序列化保存模型便于继续训练
深度学习入门系列9:用检查点保存训练期间最好的模型
深度学习入门系列10:从绘制记录中理解训练期间的模型行为
深度学习入门系列11:用Dropout正则减少过拟合
深度学习入门系列12:使用学习规划来提升性能
深度学习入门系列13:卷积神经网络概述
深度学习入门系列14:项目实战:基于CNN的手写数字识别
深度学习入门系列15:用图像增强改善模型性能
深度学习入门系列16:项目实战:图像中目标识别
深度学习入门系列17:项目实战:从电影评论预测情感
深度学习入门系列18:循环神经网络概述
深度学习入门系列19:基于窗口(window)的多层感知器解决时序问题
深度学习入门系列20:LSTM循环神经网络解决国际航空乘客预测问题
深度学习入门系列21:项目:用LSTM+CNN对电影评论分类
深度学习入门系列22:从猜字母游戏中理解有状态的LSTM递归神经网络
深度学习入门系列23:项目:用爱丽丝梦游仙境生成文本


现在比较有效且盛行的循环神经网络就是LSTM,它的广泛使用是因为它的结构克服了梯度消失和爆炸的问题,而这个问题一直困扰着循环神经网络RNN),也允许它创建更大和更深的网络。就像其他的循环神经网络一样,LSTM网络能够保存状态以及如何在Keras架构中实现都是令人迷惑。在这节课中,你将学习通过Keras深度学习库如何在LSTM中如何维护状态。完成这节课后,你将了解:

  • 如何为时序预测问题开简单地LSTM网络。
  • 如何用批处理和特征来管理LSTM网络的状态。
  • 如何手动管理LSTM网络中的状态(state)用于状态的(stateful)预测。

1 问题描述:学习字母表

在这个教程中,我们将开发和对比一些列不同的 LSTM 循环神经网络模型。这些对比的场景是一个简单的学习字母表的序列问题。也就说,给定字母表中的一个字符,预测字母表中的下个字母。这个一个简单的序列预测问题,一旦你能理解它,就能扩展到其他序列预测问题上,如时序预测和序列分类问题。让我们用 Python 代码处理这个问题,便于我们在例子复用它。首先,我导入我们这节课中要用的类和函数。

import numpy from keras.models 
import Sequential from keras.layers 
import Dense from keras.layers 
import LSTM from keras.utils import np_utils

接下来,我们设置了随机数来保障结果的可复制性。

# fix random seed for reproducibility 
numpy.random.seed(7) 

我们现在定义数据集,也就是字母表。为了方便阅读,我们定义了大写的字母表。神经网络只认识数字,所以我们需要将字母表中的字母映射为整数。我们通过字符的字典很容易做到这。我们也可以创建一个反向字母查找表,用于将预测值转成对应字母。

# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 
# create mapping of characters to integers (0-25) and the reverse 
char_to_int = dict((c, i) for i, c in enumerate(alphabet)) 
int_to_char = dict((i, c) for i, c in enumerate(alphabet))

我们现在需要创建一个可以在训练神经网络时使用输入-输出对。我们也可以通过定义输出序列长度,然后从输入字母表序列读取序列来实现。举个例子,我们使用输入长度为1,从原始的输入数据开头开始,我们读取A,接着读取B,每次移动移动一个字符,一直重复,直到Z

# prepare the dataset of input to output pairs encoded as integers 
seq_length = 1 
dataX = [] 
dataY = [] 
for i in range(0, len(alphabet) - seq_length, 1): 
	seq_in = alphabet[i:i + seq_length] 
	seq_out = alphabet[i + seq_length] 
	dataX.append([char_to_int[char] for char in seq_in]) 
	dataY.append(char_to_int[seq_out]) 
	print(seq_in, '->', seq_out)

我们有打印出输入对用于检查。运行这个代码,将得到下面的输出结果,总结为输入序列为1和单个输出字符。

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网络需要的格式,如[samples, time steps, features]。

# reshape X to be [samples, time steps, features] 
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))

一旦转好,我们就将输入端正则为0-1之间,它也是LSTM 网络使用的sigmoid函数的范围。

# normalize
X = X / float(len(alphabet))

最后,我们认为这个问题是序列分类任务,26个字母代表不同的类。因此,我们使用 Keras 内置的 to_categorical() 函数将output (y) 转成 one hot 编码。

# one hot encode the output variable 
y = np_utils.to_categorical(dataY)

我们现在准备拟合不同的LSTM模型。

2 用于学习单字符到单字符映射的 LSTM

让我们从设计一个简单的LSTM模型开始,这个模型学习了如何在给定一个字符的场景下预测字母表中的下个字符。我们把这个问题定义为单字符输入到单字符输出对的随机集合。正如我们所看到的,对于要学习的LSTM这是个较难的架构。让我们定义一个32单元LSTM 和一个使用 softmax激活函数 的输出层模型做预测。因为这是个多分类问题,我们能够使用log损失函数(Keras中称之为categorical crossentropy),并使用ADAM算法优化网络。使用500迭代周期1批处理拟合模型。

# create and fit the model 
model = Sequential() 
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) 
model.add(Dense(y.shape[1], activation='softmax')) 
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) 
model.fit(X, y, epochs=500, batch_size=1, verbose=2)

我们拟合模型之后,我们能够在整个数据训练集上评估和总结的模型性能。

# summarize performance of the model 
scores = model.evaluate(X, y, verbose=0) 
print("Model Accuracy: %.2f%%" % (scores[1]*100))

我们重新在训练集上运行模型并生成预测,将输入-输出对转回原始的字符格式,是为了搞清楚网络的学习问题的情况。

# demonstrate some model predictions 
for pattern in dataX: 
	x = numpy.reshape(pattern, (1, len(pattern), 1)) 
	x = x / float(len(alphabet)) 
	prediction = model.predict(x, verbose=0) 
	index = numpy.argmax(prediction) 
	result = int_to_char[index] 
	seq_in = [int_to_char[value] for value in pattern] 
	print(seq_in, "->", result)

为了完整性,下面提供了完整的代码。

# Naive LSTM to learn one-char to one-char mapping  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  
# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  
# prepare the dataset of input to output pairs encoded as integers  
seq_length = 1  
dataX = []  
dataY = []  
for i in range(0, len(alphabet) - seq_length, 1):  
    seq_in = alphabet[i:i + seq_length]  
    seq_out = alphabet[i + seq_length]  
    dataX.append([char_to_int[char] for char in seq_in])  
    dataY.append(char_to_int[seq_out])  
    print(seq_in, '->', seq_out)  
  
# reshape X to be [samples, time steps, features]  
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))  
# normalize  
X = X / float(len(alphabet))  
# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  
# create and fit the model  
model = Sequential()  
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
model.fit(X, y, epochs=500, batch_size=1, verbose=2)  
# summarize performance of the model  
scores = model.evaluate(X, y, verbose=0)  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  
  
# demonstrate some model predictions  
for pattern in dataX:  
    x = numpy.reshape(pattern, (1, len(pattern), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)

运行这个代码得到如下结果:

Model Accuracy: 
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结构的滥用,对待它就像一个标准的多层感知器。接下来,我们尝试不同的问题框架,为了给网络提供更多的序列进行学习。

3 特征窗口到单字符映射的LSTM

对于多层感知器,最普遍的做法就是使用窗口方法把更多的上下文信息添加到数据中。这里序列中的上一步作为新的特征输入到网络中,我们能够使用同样的技巧把更多的场景信息提供给LSTM网络。在这,我们增加了序列的长度从1到3,例如:

# prepare the dataset of input to output pairs encoded as integers 
seq_length = 3

创建的训练模式如下:

ABC -> D
BCD -> E 
CDE -> F

序列中的每个元素都作为一个新的输入特征提供给网络。这需要做一次修改,在数据预处理阶段对数据进行调整:

# reshape X to be [samples, time steps, features] 
X = numpy.reshape(dataX, (len(dataX), 1, seq_length))

这还需要做一次修改,当描述来自模型的预测值时,样本格式作何调整。

x = numpy.reshape(pattern, (1, 1, len(pattern)))

为了完整性,下面提供了所有代码。

# Naive LSTM to learn three-char window to one-char mapping  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  
  
# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  
# prepare the dataset of input to output pairs encoded as integers  
seq_length = 3  
dataX = []  
dataY = []  
for i in range(0, len(alphabet) - seq_length, 1):  
    seq_in = alphabet[i:i + seq_length]  
    seq_out = alphabet[i + seq_length]  
    dataX.append([char_to_int[char] for char in seq_in])  
    dataY.append(char_to_int[seq_out])  
    print(seq_in, '->', seq_out)  
  
# reshape X to be [samples, time steps, features]  
X = numpy.reshape(dataX, (len(dataX), 1, seq_length))  
# normalize  
X = X / float(len(alphabet))  
# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  
# create and fit the model  
model = Sequential()  
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
model.fit(X, y, epochs=500, batch_size=1, verbose=2)  
# summarize performance of the model  
scores = model.evaluate(X, y, verbose=0)  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  
# demonstrate some model predictions  
for pattern in dataX:  
    x = numpy.reshape(pattern, (1, 1, len(pattern)))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)

运行上面代码,得到如下的结果。

Model Accuracy: 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

我们能够看到性能有点提升,也可能是幻觉。这个简单的问题,即使我我们使用带有窗口的LSTMs,也不能学习。同样,这是对LSTM网络的误用。的确,字符的序列是一个特征的多个时间步而不是单个特征的单个时间步。我会给网络提供更多的上下文信息,但并没有我们期望的那么多。在下部分,我们会以时间步的形式向网络提供更多的上下文信息。

4 用于单字符映射的单时间步窗口的 LSTM

在Keras中,LSTMs的预期使用是以时间步形式提供上下文信息,而不是像其他网络一样提供窗口特征。我拿第一个例子来看,简单将序列长度从1变成3。

seq_length = 3

再一次,创建了像下面一样的输入-输出对

ABC -> D 
BCD -> E 
CDE -> F 
DEF -> G

不同点是输入数据格式化把序列作为单特征的单时序步输入序列中,而不是多个特征单时间步。

# reshape X to be [samples, time_steps, features] 
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))

这是在 Keras 中为 LSTM 提供序列上下文的预期用途。为了完整性,提供下面的代码。

# Naive LSTM to learn three-char time steps to one-char mapping  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  

# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  

# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  

# prepare the dataset of input to output pairs encoded as integers  
seq_length = 3  
dataX = []  
dataY = []  
for i in range(0, len(alphabet) - seq_length, 1):  
    seq_in = alphabet[i:i + seq_length]  
    seq_out = alphabet[i + seq_length]  
    dataX.append([char_to_int[char] for char in seq_in])  
    dataY.append(char_to_int[seq_out])  
    print(seq_in, '->', seq_out)  
  
# reshape X to be [samples, time steps, features]  
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))  
# normalize  
X = X / float(len(alphabet))  
# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  
# create and fit the model  
model = Sequential()  
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
model.fit(X, y, epochs=500, batch_size=1, verbose=2)  
# summarize performance of the model  
scores = model.evaluate(X, y, verbose=0)  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  
# demonstrate some model predictions  
for pattern in dataX:  
    x = numpy.reshape(pattern, (1, len(pattern), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)

运行这个代码得到下面结果。

Model Accuracy: 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默认实现是每次训练批处理都需要重置网络状态。

5 批处理内样本间保持LSTM状态

KerasLSTM网络的实现机制会在每次迭代中重置网络状态。这表明,如果我们有个足够大的批处理大小来容纳输入模式,如果我们所有的输入模式都是有序的,那么LSTM就能够在批处理使用序列的上下文更好的学习序列。我们可以通过修改第一个例子来学习一对一的映射关系来描述这个问题,批处理大小从1增加到训练集大小。另外,Keras在每次训练周期之前,随机训练集。为了保证训练集数据模型有序,我们关闭了随机。

model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False)

网络使用批处理内序列来学习字符的映射关系,但是在做预测时这个上下文对于网络并不可用。我们可以评估网络随机和按顺序进行预测的能力。为了完整性,下面提供了所有代码。

# Naive LSTM to learn one-char to one-char mapping with all data in each batch  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.preprocessing.sequence import pad_sequences  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  

# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  

# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  

# prepare the dataset of input to output pairs encoded as integers  
seq_length = 1  
dataX = []  
dataY = []  
for i in range(0, len(alphabet) - seq_length, 1):  
    seq_in = alphabet[i:i + seq_length]  
    seq_out = alphabet[i + seq_length]  
    dataX.append([char_to_int[char] for char in seq_in])  
    dataY.append(char_to_int[seq_out])  
    print(seq_in, '->', seq_out)  
  
# convert list of lists to array and pad sequences if needed  
X = pad_sequences(dataX, maxlen=seq_length, dtype='float32')  

# reshape X to be [samples, time steps, features]  
X = numpy.reshape(dataX, (X.shape[0], seq_length, 1))  

# normalize  
X = X / float(len(alphabet))  

# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  

# create and fit the model  
model = Sequential()  
model.add(LSTM(16, input_shape=(X.shape[1], X.shape[2])))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False)  

# summarize performance of the model  
scores = model.evaluate(X, y, verbose=0)  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  

# demonstrate some model predictions  
for pattern in dataX:  
    x = numpy.reshape(pattern, (1, len(pattern), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)  
	
# demonstrate predicting random patterns  
print("Test a Random Pattern:")  
for i in range(0, 20):  
    pattern_index = numpy.random.randint(len(dataX))  
    pattern = dataX[pattern_index]  
    x = numpy.reshape(pattern, (1, len(pattern), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)

运行提供的例子,得到如下结果。

Model Accuracy: 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 

Test a Random Pattern: 
['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%的准确率。更重要的是,网络通过随机选择一个字符能够对下个字母做精准的预测,非常令人赞叹。

6 用于单字符到单字符映射的有状态

我们已经看到,将原始数据分解为了固定大小的序列,而且这种表示也能够被LSTM学习,但是仅仅学习了3字符对1个字符的映射关系。我们也已经看到,我们滥用了批处理大小来为网络提供上下文信息,但是仅仅在训练期间。理想情况下,我们想把整个序列暴露给网络,让它自己学习内在的依赖关系,而不是我们自己显性的定义问题的依赖关系。我们在Keras中通过让LSTM有状态和每层迭代时重置网络状态做到这一点,这也是训练序列的终点。

这是真正我们要想使用的LSTM。我们发现,通过运行网络自己学习字符自己依赖关系,我们需要一个更少的网络(一半的神经元个数)和更少的迭代周期(基本上一半)。我们首先定义我们的LSTM为有状态的(stateful),这样做,我们必须显性的制动批处理大小为输入数据的维度。这也意味着当我们评估模型或者做预测时,我们必须明确而且指定同样大小的批处理。现在这不是问题,因为我们使用的批处理大小为1。这带来的困难是当我们做预测时,当批处理大小不一致时,因此需要在批处理和序列中做处理。

batch_size = 1
model.add(LSTM(16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))

有个重要的不同点就是,训练有状态的LSTM网络时我们训练他需要每次迭代手动重置网络状态。我们在一次循环中做这件事情。再次强调下,我们不能随机输入数据,保存在初始数据的顺序。

for i in range(300): 
	model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) 
	model.reset_states()

正如上面提到的,当我们在整个训练集上评估网络性能时,我们指定了批处理大小。

# summarize performance of the model 
scores = model.evaluate(X, y, batch_size=batch_size, verbose=0)
model.reset_states() 
print("Model Accuracy: %.2f%%" % (scores[1]*100))

最后,网络确实已经学习了整个字母表,我们输入首字母A,进行预测,预测返回作为输入,重复这个过程直到Z。

# demonstrate some model predictions 
seed = [char_to_int[alphabet[0]]] 
for i in range(0, len(alphabet)-1):
	x = numpy.reshape(seed, (1, len(seed), 1))
	x = x / float(len(alphabet)) 
	prediction = model.predict(x, verbose=0) 
	index = numpy.argmax(prediction) 
	print(int_to_char[seed[0]], "->", int_to_char[index]) 
	seed = [index] model.reset_states()

我们能够看到,网络可以从任意字符开始进行预测。

# demonstrate a random starting point 
letter = "K" 
seed = [char_to_int[letter]] 
print("New start: ", letter) 
for i in range(0, 5): 
	x = numpy.reshape(seed, (1, len(seed), 1)) 
	x = x / float(len(alphabet)) 
	prediction = model.predict(x, verbose=0)
	index = numpy.argmax(prediction) 
	print(int_to_char[seed[0]], "->", int_to_char[index]) 
	seed = [index] 
	model.reset_states()

为了完整性,提供了下面完整的代码。

# Stateful LSTM to learn one-char to one-char mapping  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  
# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  
# prepare the dataset of input to output pairs encoded as integers  
seq_length = 1  
dataX = []  
dataY = []  
for i in range(0, len(alphabet) - seq_length, 1):  
    seq_in = alphabet[i:i + seq_length]  
    seq_out = alphabet[i + seq_length]  
    dataX.append([char_to_int[char] for char in seq_in])  
    dataY.append(char_to_int[seq_out])  
    print(seq_in, '->', seq_out)  
# reshape X to be [samples, time steps, features]  
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))  
# normalize  
X = X / float(len(alphabet))  
# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  
# create and fit the model  
batch_size = 1  
model = Sequential()  
model.add(LSTM(16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
for i in range(300):  
    model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False)  
model.reset_states()  
# summarize performance of the model  
scores = model.evaluate(X, y, batch_size=batch_size, verbose=0)  
model.reset_states()  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  
# demonstrate some model predictions  
seed = [char_to_int[alphabet[0]]]  
for i in range(0, len(alphabet) - 1):  
    x = numpy.reshape(seed, (1, len(seed), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    print(int_to_char[seed[0]], "->", int_to_char[index])  
seed = [index]  
model.reset_states()  
# demonstrate a random starting point  
letter = "K"  
seed = [char_to_int[letter]]  
print("New start: ", letter)  
for i in range(0, 5):  
    x = numpy.reshape(seed, (1, len(seed), 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    print(int_to_char[seed[0]], "->", int_to_char[index])  
    seed = [index]  
    model.reset_states()

运行提供的代码,得到如下结果。

Model Accuracy: 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
New start:K 
K -> B 
B -> C
C -> D
D -> E
E -> F

我们能够看到,网络完美的记住了整个字母表。它使用了样本本身的上下文信息,学了序列中所有可能需要预测下个字符的依赖关系。我们也能够看到,如果我们用第一字母中输入到网络,那么就能够很容易预测剩下的字母。我们也能够看到,它仅仅学习了全部的字母表而且从冷启动开始,当我们要求从K预测下个字母时,它预测B而且回过头反思整个字母表。为了真正预测K,网络需要不断的迭代输入从A到J。这告诉我们,通过预处理数据,使用无状态LSTM网络也能获得同样的效果:

---a -> b 
--ab -> c 
-abc -> d 
abcd -> e

输入序列固定在25大小(a-y,预测z),而且模式的前缀用0填充。最后,提出了使用变长输入序列预测下个字符进行训练LSTM网络的问题。

7 具有可变长度输入到单字符输出的 LSTM

在上部分中,我们已经学习了,Keras有状态LSTM仅仅对前n序列的重放是一种捷径,并没有真正帮我们学习一般的字母模型。在这部分中,我们探索无状态LSTM的变种,它学习了字母表的随机子序列,并且努力构建能够预测任意字母和字母子序列的模型。

首先,我们改变了问题的框架。为了简洁,我们将定义一个最大的输入序列,并设置一个小值,如5,来加速训练。这里定义字母表的最大子序列用于训练。在扩展中,我们仅仅被设置为全字母(26)或者更长,如果我们循环整个字母表的话,我们需要定义随机创建序列的数,这个例子中,为1000,或多或少。我期望需要更少的模式。

# prepare the dataset of input to output pairs encoded as integers 
num_inputs = 1000 
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)

在一个更大的场景中运行这个代码,将得到如下的输入模式:

PQRST -> U 
W -> X 
O -> P
OPQ -> R 
IJKLM -> N 
QRSTU -> V 
ABCD -> E 
X -> Y
GHIJ -> K

输入序列的长度在1和最大值之间,因此需要0填充。在这,我们使用 Keras 中内置的pad_sequences() 对左边填充。

X = pad_sequences(dataX, maxlen=max_len, dtype='float32')

随机选择输入模型在已经训练的模型进行评估。这可以很容易地成为新的随机生成的字符序列。我也相信这也可能是一个以 A 为种子的线性序列,输出作为单个字符输入反馈。为了完整性,下面提供了完整的代码。

# LSTM with Variable Length Input Sequences to One Character Output  
import numpy  
from keras.layers import Dense  
from keras.layers import LSTM  
from keras.models import Sequential  
from keras.preprocessing.sequence import pad_sequences  
from keras.utils import np_utils  
  
# fix random seed for reproducibility  
numpy.random.seed(7)  

# define the raw dataset  
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  

# create mapping of characters to integers (0-25) and the reverse  
char_to_int = dict((c, i) for i, c in enumerate(alphabet))  
int_to_char = dict((i, c) for i, c in enumerate(alphabet))  

# prepare the dataset of input to output pairs encoded as integers  
num_inputs = 1000  
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)  
	
# convert list of lists to array and pad sequences if needed  
X = pad_sequences(dataX, maxlen=max_len, dtype='float32')  

# reshape X to be [samples, time steps, features]  
X = numpy.reshape(X, (X.shape[0], max_len, 1))  

# normalize  
X = X / float(len(alphabet))  

# one hot encode the output variable  
y = np_utils.to_categorical(dataY)  

# create and fit the model  
batch_size = 1  
model = Sequential()  
model.add(LSTM(32, input_shape=(X.shape[1], 1)))  
model.add(Dense(y.shape[1], activation='softmax'))  
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
model.fit(X, y, epochs=500, batch_size=batch_size, verbose=2)  

# summarize performance of the model  
scores = model.evaluate(X, y, verbose=0)  
print("Model Accuracy: %.2f%%" % (scores[1] * 100))  

# demonstrate some model predictions  
for i in range(20):  
    pattern_index = numpy.random.randint(len(dataX))  
    pattern = dataX[pattern_index]  
    x = pad_sequences([pattern], maxlen=max_len, dtype='float32')  
    x = numpy.reshape(x, (1, max_len, 1))  
    x = x / float(len(alphabet))  
    prediction = model.predict(x, verbose=0)  
    index = numpy.argmax(prediction)  
    result = int_to_char[index]  
    seq_in = [int_to_char[value] for value in pattern]  
    print(seq_in, "->", result)

运行这个代码得到如下结果:

Model Accuracy: 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

我们能够看到,虽然模型没有从随机生成的子序列中完美的学习字母表,但是效果也不错。模型没有经过调参,可能需要更多的训练或者更大的网络,或者两者都需要。这是对上面学习的每个批处理字母模型中的所有顺序输入示例的一个很好的自然扩展,因为它可以处理零时查询,但这次是任意序列长度(最多可达最大长度)。

8 总结

在本节课中,你已经学习了KerasLSTM循环神经网络 以及如何管理状态,特别是,你已经学到:

  • 如何开发简单的LSTM网络用于单字符到单字符预测。
  • 如何配置简单的LSTM来学习单样例多时步的时序。
  • 如何配置LSTM用于学习多样例的时序,通过手动管理状态。

8.1 接下来

在这节课中,在本课中,您加深了对** LSTM** 网络如何维护简单序列预测问题的状态的理解。接下来,你将基于对LSTM网络的理解来开发文本生产模型。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术人Howzit

钱不钱的无所谓,这是一种鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值