使用 Python 实现 LSTM 网络(使用 TensorFlow 和 Keras)的两种方法





5.00/5 (5投票s)
在前一篇文章中,我们讨论了强大的循环神经网络——长短期记忆(LSTM)网络的工作原理。现在,我们将学习如何实现这种网络。
在前一篇文章中,我们讨论了强大的循环神经网络——长短期记忆(LSTM)网络的工作原理。它们不仅仅是将输出信息传递到下一个时间步,还会存储和传递所谓的 LSTM 单元的状态。这个单元内部包含四个神经网络——门(gates),用于决定哪些信息会被存储在单元状态中并被传递到输出。因此,网络在某个时间步的输出不仅依赖于前一个时间步,还依赖于n个前时间步。
好的,理论部分就到这里,为实际实现这类网络做好准备。在本文中,我们将考虑两个类似的语言建模问题,并使用两种不同的 API 来解决它们。首先,我们将创建一个能够根据提供的文本片段预测单词的网络,为此我们将使用 TensorFlow。在第二个实现中,我们将使用 Keras 对 IMDB 数据集中的评论进行分类。
在我们深入探讨要解决的问题和代码本身之前,请确保已设置好您的环境。与本系列所有之前的文章一样,我将使用 Python 3.6。此外,我使用的是 Anaconda 和 Spyder,但您可以使用任何您偏好的 IDE。然而,重要的是要安装 TensorFlow 和 Keras。
TensorFlow 实现的问题
假设我们想训练一个 LSTM,通过示例文本来预测下一个单词。我们示例中的简单文本将是我非常喜欢的马可·奥勒留《沉思录》中的一段:
从某种意义上说,人是我们真正的关注对象。我们的职责是善待他们,容忍他们。但当他们阻碍我们正常的任务时,他们就变得与我们无关了——就像太阳、风、动物一样。我们的行动可能会被他们阻碍,但我们的意图或决心却无法被阻碍。因为我们可以适应和调整。心灵会适应并将其应对行动的障碍转化为自身的用途。阻碍行动的因素促进了行动。挡在路上的东西就是道路。
请注意,这段文本被稍微修改过。每个标点符号都被空格包围,因此每个单词和每个标点符号都被视为一个符号。为了演示 LSTM 网络的工作原理,我们将使用简化的过程。我们将输入三个符号的序列作为输入,输出一个符号。通过重复这个过程,网络将学会根据前面的三个单词来预测下一个单词。
TensorFlow 实现
所以,我们的计划是:将三个符号的序列和对应的输出输入 LSTM 网络,并让它学会预测该输出。然而,由于我们使用的是数学模型,我们需要做的第一件事是准备这些数据(文本),以便进行任何类型的操作。这些网络理解数字,而不是我们文本中的string
s,所以我们需要将这些符号转换为数字。我们将通过根据出现频率为每个符号分配唯一的整数来实现这一点。
为此,我们将使用DataHandler
类。此类有两个目的:从文件中加载数据,以及为每个符号分配一个数字。代码如下:
import numpy as np
import collections
class DataHandler:
def read_data(self, fname):
with open(fname) as f:
content = f.readlines()
content = [x.strip() for x in content]
content = [content[i].split() for i in range(len(content))]
content = np.array(content)
content = np.reshape(content, [-1, ])
return content
def build_datasets(self, words):
count = collections.Counter(words).most_common()
dictionary = dict()
for word, _ in count:
dictionary[word] = len(dictionary)
reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
return dictionary, reverse_dictionary
第二个方法build_datasets
用于创建两个字典。第一个字典标记为dictionary
,包含符号作为键,其对应的数字作为值。第二个字典reverse_dictionary
包含相同的信息,只是键是数字,值是符号本身。使用我们在此示例中使用的示例文本创建它们时,它们将是这样的:
太棒了!我们的数据已准备就绪。现在开始有趣的部分,创建模型。为此,我们将创建一个新类,该类能够基于传入的参数生成 LSTM 网络。看看代码:
import tensorflow as tf
from tensorflow.contrib import rnn
class RNNGenerator:
def create_LSTM(self, inputs, weights, biases, seq_size, num_units):
# Reshape input to [1, sequence_size] and split it into sequences
inputs = tf.reshape(inputs, [-1, seq_size])
inputs = tf.split(inputs, seq_size, 1)
# LSTM with 2 layers
rnn_model = rnn.MultiRNNCell([rnn.BasicLSTMCell(num_units),rnn.BasicLSTMCell(num_units)])
# Generate prediction
outputs, states = rnn.static_rnn(rnn_model, inputs, dtype=tf.float32)
return tf.matmul(outputs[-1], weights['out']) + biases['out']
我们使用BasicLSTMCell
方法创建了两个 LSTM 层。这些层中的每一层都有由参数num_units
定义的单元数。除此之外,我们使用MultiRNNCell
将这两层组合成一个网络。然后我们使用static_rnn
方法来构建网络并生成预测。
最后,我们将使用SessionRunner
类。此类包含我们的模型将被运行和评估的环境。代码看起来是这样的:
import tensorflow as tf
import random
import numpy as np
class SessionRunner():
training_iters = 50000
def __init__(self, optimizer, accuracy, cost, lstm, initilizer, writer):
self.optimizer = optimizer
self.accuracy = accuracy
self.cost = cost
self.lstm = lstm
self.initilizer = initilizer
self.writer = writer
def run_session(self, x, y, n_input, dictionary, reverse_dictionary, training_data):
with tf.Session() as session:
session.run(self.initilizer)
step = 0
offset = random.randint(0, n_input + 1)
acc_total = 0
self.writer.add_graph(session.graph)
while step < self.training_iters:
if offset > (len(training_data) - n_input - 1):
offset = random.randint(0, n_input+1)
sym_in_keys = [ [dictionary[ str(training_data[i])]]
for i in range(offset, offset+n_input) ]
sym_in_keys = np.reshape(np.array(sym_in_keys), [-1, n_input, 1])
sym_out_onehot = np.zeros([len(dictionary)], dtype=float)
sym_out_onehot[dictionary[str(training_data[offset+n_input])]] = 1.0
sym_out_onehot = np.reshape(sym_out_onehot,[1,-1])
_, acc, loss, onehot_pred = session.run([self.optimizer, self.accuracy,
self.cost, self.lstm], feed_dict={x: sym_in_keys, y: sym_out_onehot})
acc_total += acc
if (step + 1) % 1000 == 0:
print("Iteration = " + str(step + 1) + ", Average Accuracy= " +
"{:.2f}%".format(100*acc_total/1000))
acc_total = 0
step += 1
offset += (n_input+1)
我们的模型正在运行 50000 次迭代。我们将模型、优化器、损失函数等注入构造函数,因此该类拥有这些信息。当然,我们需要做的第一件事是将提供的数据切片,并制作编码的输出(分别为sym_in_keys
和sym_out_onehot
)。此外,我们正在向模型注入随机序列以避免过拟合。这由offset
变量处理。最后,我们将运行会话并获得准确率。不要被代码中的最终 if 语句混淆。它仅用于显示目的(每 1000 次迭代显示平均准确率)。
我们的主脚本将所有这些结合在一起,代码如下:
import tensorflow as tf
from DataHandler import DataHandler
from RNN_generator import RNNGenerator
from session_runner import SessionRunner
log_path = '/output/tensorflow/'
writer = tf.summary.FileWriter(log_path)
# Load and prepare data
data_handler = DataHandler()
training_data = data_handler.read_data('meditations.txt')
dictionary, reverse_dictionary = data_handler.build_datasets(training_data)
# TensorFlow Graph input
n_input = 3
n_units = 512
x = tf.placeholder("float", [None, n_input, 1])
y = tf.placeholder("float", [None, len(dictionary)])
# RNN output weights and biases
weights = {
'out': tf.Variable(tf.random_normal([n_units, len(dictionary)]))
}
biases = {
'out': tf.Variable(tf.random_normal([len(dictionary)]))
}
rnn_generator = RNNGenerator()
lstm = rnn_generator.create_LSTM(x, weights, biases, n_input, n_units)
# Loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=lstm, labels=y))
optimizer = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(cost)
# Model evaluation
correct_pred = tf.equal(tf.argmax(lstm,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# Initializing the variables
initilizer = tf.global_variables_initializer()
session_runner = SessionRunner(optimizer, accuracy, cost, lstm, initilizer, writer)
session_runner.run_session(x, y, n_input, dictionary, reverse_dictionary, training_data)
Keras 实现的问题
这个TensorFlow
示例相当直接且简单。我们使用了少量数据,网络能够很快地学习。如果我们有一个更复杂的问题呢?例如,假设我们要对某个网站上每部电影评论的情感进行分类。幸运的是,有一个专门为此问题设计的数据集
——大型电影评论数据集(通常称为 IMDB 数据集
)。
这个数据集
由斯坦福大学的研究人员于 2011 年收集。它包含 25000 条用于训练的电影评论(好评或差评),以及相同数量的评论用于测试。我们的目标是创建一个网络,能够确定这些评论中哪些是积极的,哪些是消极的。我们将使用 Keras API,它内置了这个数据集
。
Keras 实现
Keras 的强大之处在于它抽象了我们在使用TensorFlow
时需要处理的许多事情。但是,它也提供了较少的灵活性。当然,一切都是权衡。所以,让我们开始这个实现,导入必要的类和库。
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Dropout, Embedding, LSTM
from keras.datasets import imdb
这是一个很棒的技术,可以将每条电影评论映射到一个实数向量域。单词被编码为高维空间中的实值向量,其中单词在含义上的相似性转化为向量空间中的接近度。我们可以注意到,我们导入了keras.datasets
中提供的imdb
数据集
。但是,要从该数据集
加载数据,我们需要这样做:
num_words = 1000
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=num_words)
X_train = sequence.pad_sequences(X_train, maxlen=200)
X_test = sequence.pad_sequences(X_test, maxlen=200)
在填充时,我们使用了数字200
,这意味着我们的序列长度将为200
个单词。训练输入数据在执行此操作后看起来是这样的:
现在我们可以定义、编译和拟合 LSTM 模型了:
# Define network architecture and compile
model = Sequential()
model.add(Embedding(num_words, 50, input_length=200))
model.add(Dropout(0.2))
model.add(LSTM(100, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(250, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
正如我们在之前的文章中学到的,Sequential
用于模型组合。添加到其中的第一个层是Embedding
,我们在上一章中解释了它的用途。在单词嵌入完成后,我们添加了一个 LSTM 层。最后,由于这是一个分类问题,我们试图弄清楚评论是好是坏,因此添加了一个带有 sigmoid 函数的Dense
层。最后,模型被编译,并使用了binary_crossentorpy
和Adam
优化器。我们的模型看起来是这样的:
让我们将数据拟合到模型并获得准确率:
.gist table { margin-bottom: 0; }
我们获得了 85.12% 的准确率。
结论
我们看到了创建 LSTM 网络的两种方法。这两种方法都处理了简单的问题,并且每种方法都使用了不同的 API。这样,人们就可以看出TensorFlow
更详细、更灵活,然而,与使用 Keras 相比,您需要处理更多事情。Keras 更简单、更直接,但它不像纯TensorFlow
那样提供灵活性和可能性。这两种示例都提供了不错的结果,但它们可以做得更好。特别是第二个示例,我们通常会结合使用 CNN 和 RNN 来获得更高的准确率,但这将是另一篇文章的主题。
感谢阅读!