一、理论知识
LSTM是一种拥有三个“门”结构的特殊网络结构,这个结构让信息有选择性地影响循环神经网络中每个时刻的状态。“门”结构就是一个使用 sigmoid 神经网络和一个按位做乘法的操作,这两个操作合在一起就是一个“门”结构。
遗忘门:让循环神经网络“忘记”之前没有用的信息。取决于当前的输入
x
t
x_t
xt,上一时刻的输出
h
t
−
1
h_{t-1}
ht−1和上一时刻的状态
c
t
−
1
c_{t-1}
ct−1。
输入门:“忘记”了部分之前的状态后,需要从当前的输入补充最新的记忆。取决于当前的输入
x
t
x_t
xt和上一时刻的输出
h
t
−
1
h_{t-1}
ht−1。
输出门:LSTM结构在计算得到新的状态
c
t
c_t
ct后需要产生当前时刻的输出。取决于当前的状态
c
t
c_t
ct,上一时刻的输出
h
t
−
1
h_{t-1}
ht−1和当前的输入
x
t
x_t
xt。
具体 LSTM 每个“门”的公式定义如下:
输入值: z = t a n h ( W z [ h t − 1 , x t ] ) z = tanh(W_z[h_{t-1}, x_{t}]) z=tanh(Wz[ht−1,xt])
输入门: i = s i g m o i d ( W i [ h t − 1 , x t ] ) i = sigmoid(W_i[h_{t-1}, x_{t}]) i=sigmoid(Wi[ht−1,xt])
遗忘门: f = s i g m o i d ( W f [ h t − 1 , x t ] ) f = sigmoid(W_f[h_{t-1}, x_{t}]) f=sigmoid(Wf[ht−1,xt])
输出门: o = s i g m o i d ( W o [ h t − 1 , x t ] ) o = sigmoid(W_o[h_{t-1}, x_{t}]) o=sigmoid(Wo[ht−1,xt])
新状态: c t = f ⋅ c t − 1 + i ⋅ z c_t = f · c_{t-1} + i · z ct=f⋅ct−1+i⋅z
输出: h t = o ⋅ t a n h c t h_t = o · tanh c_t ht=o⋅tanhct
其中 W z W_z Wz、 W i W_i Wi、 W f W_f Wf、 W o W_o Wo是4个维度为 [ 2 n , n ] [2n, n] [2n,n] 的参数矩阵。
二、代码实现
- 定义一个LSTM结构。在TensorFLow中,LSTM结构可以很简单地实现,只需一个命令。
lstm = tf.nn.rnn_cell.LSTMCell(lstm_hidden_size)
- 将LSTM中的状态初始化为全0数组。LSTMCell类提供了 zero_state 函数来生成全零的初始状态。state是一个包含两个张量的 LSTMStateTuple 类,其中 state.c 和 state.h 分别是此时刻的新状态和此时刻的输出。
state = lstm.zero_state(batch_size, tf.float32)
- 深层循环神经网络。TensorFlow中提供了 MultiRNNCell 类来实现深层循环神经网络的前向传播过程,只需要在 LSTMCell 的基础上再封装一层 MultiRNNCell 就可以了非常容易地实现深层循环神经网络。
# 定义一个基本的 LSTM 结构作为循环体的基础结构。深层循环网络也支持使用其他的循环体结构。
lstm_cell = tf.nn.rnn_cell.LSTMCell
# 通过 MultiRNNCell 类实现深层循环神经网络中每一个时刻的前向传播过程。其中 number_of_layers表示有多少层
stacked_lstm = tf.nn.rnn_cell.MultiRNNCell(
[lstm_cell(lstm_size) for _ in range(number_of_layers)])
# 和经典的循环神经网络一样,可以通过 zero_state 函数来获取初始状态
state = stacked_lstm.zero_state(batch_size, tf.float32)
- 实现dropout。在TensorFlow中,使用 tf.nn.rnn_cell.DropoutWrapper 类可以很容易实现 dropout 功能。通过两个参数来控制 dropout 的概率,一个参数为 input_keep_prob,它可以用来控制输入的 dropout 概率;另一个 output_keep_prob ,它可以用来控制输出的 dropout 概率。
# 定义LSTM结构
lstm_cell = tf.nn.rnn_cell.LSTMCell
stacked_lstm = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.DropoutWrapper(lstm_cell(lstm_size))
for _ in range(number_of_layers)])
- tf.nn.dynamic_rnn 实现的功能就是可以让不同迭代传入的batch可以是长度不同数据,但同一次迭代一个batch内容的所有数据长度仍然是固定的(后边的文章再进行详细介绍)。
- 介绍了一些主要的网络实现之后,接下来给出一个具体的TensorFlow程序来实现这些循环神经网络的前向传播过程。以时序预测为例,利用循环神经网络实现对函数 sinx 取值的预测。
import numpy as np
import tensorflow as tf
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
HIDDEN_SIZE = 30 # LSTM中隐藏节点的个数
NUM_LAYERS = 2 # LSTM的层数
TIMESTEPS = 10 # 循环神经网络的训练序列长度
TRAINING_STEPS = 10000 # 训练轮数
BATCH_SIZE = 32 # batch大小
TRAINING_EXAMPLES = 10000 # 训练数据个数
TESTING_EXAMPLES = 1000 # 测试数据个数
SAMPLE_GAP = 0.01 # 采样间隔
def generate_data(seq):
X = []
y = []
# 序列的第i项和后面的TIMESTEPS-1项合在一起作为输入,在i+TIMESTEPS项作为输出
# 即用sin函数前面的TIMESTEPS个点的信息,预测i+TIMESTEPS个点的函数值
for i in range(len(seq) - TIMESTEPS):
X.append([seq[i: i + TIMESTEPS]])
y.append([seq[i + TIMESTEPS]])
return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32)
def lstm_model(X, y, is_training):
# 使用多层的LSTM结构
cell = tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.LSTMCell(HIDDEN_SIZE) for _ in range(NUM_LAYERS)])
# 使用tensorflow接口将多层的LSTM结构连接成RNN网络并计算其前向传播结果
outputs, _ = tf.nn.dynamic_rnn(cell, X, dtype=tf.float32)
# outputs是顶层LSTM在每一步的输出结果,它的维度是 [batch_size, time, HIDDEN_SIZE]
output = outputs[:, -1, :]
# 对LSTM网络的输出再做加一层全连接层并计算损失,注意这里默认的损失为平均平方差损失函数
predictions = tf.contrib.layers.fully_connected(output, 1, activation_fn=None)
# 只在训练时计算损失函数和优化步骤,测试时直接返回预测结果
if not is_training:
return predictions, None, None
# 计算损失函数
loss = tf.losses.mean_squared_error(labels=y, predictions=predictions)
# 创建模型优化器并得到优化步骤
train_op = tf.contrib.layers.optimize_loss(loss, tf.train.get_global_step(),
optimizer='Adagrad', learning_rate=0.1)
return predictions, loss, train_op
def train(sess, train_X, train_Y):
# 将训练数据以数据集的方式提供给计算图
ds = tf.data.Dataset.from_tensor_slices((train_X, train_Y))
ds = ds.repeat().shuffle(1000).batch(BATCH_SIZE)
X, y = ds.make_one_shot_iterator().get_next()
# 调用模型,得到预测结果、损失函数和训练操作
with tf.variable_scope('model'):
predictions, loss, train_op = lstm_model(X, y, True)
# 初始化变量
sess.run(tf.global_variables_initializer())
for i in range(TRAINING_STEPS):
_, l = sess.run([train_op, loss])
if i % 100 == 0:
print('train step: ' + str(i) + ', loss: ' + str(l))
def run_eval(sess, test_X, test_y):
# 将测试数据以数据集的方式提供给计算图
ds = tf.data.Dataset.from_tensor_slices((test_X, test_y))
ds = ds.batch(1)
X, y = ds.make_one_shot_iterator().get_next()
# 调用模型得到计算结果,这里不需要输入真实的y值
with tf.variable_scope('model', reuse=True):
prediction, _, _ = lstm_model(X, [0, 0], False)
# 将预测结果存入一个数组
predictions = []
labels = []
for i in range(TESTING_EXAMPLES):
p, l = sess.run([prediction, y])
predictions.append(p)
labels.append(l)
# 计算rmse作为评价指标
predictions = np.array(predictions).squeeze()
labels = np.array(labels).squeeze()
rmse = np.sqrt(((predictions - labels) ** 2).mean(axis=0))
print('Mean Square Error is: %f' % rmse)
# 对预测的sin函数曲线进行绘图
plt.figure()
plt.plot(predictions, alpha=0.5, linewidth=3, label='predictions')
plt.plot(labels, linewidth=1, label='real_sin')
plt.legend()
plt.show()
plt.savefig('./sin.png')
test_start = (TRAINING_EXAMPLES + TIMESTEPS) * SAMPLE_GAP
test_end = test_start + (TESTING_EXAMPLES + TIMESTEPS) * SAMPLE_GAP
train_X, train_y = generate_data(np.sin(np.linspace(
0, test_start, TRAINING_EXAMPLES + TIMESTEPS, dtype=np.float32)))
test_X, test_y = generate_data(np.sin(np.linspace(
test_start, test_end, TESTING_EXAMPLES + TIMESTEPS, dtype=np.float32)))
with tf.Session() as sess:
train(sess, train_X, train_y)
run_eval(sess, test_X, test_y)
上边的代码涉及到了数据的处理(tf.data.Dataset),这个在后边的文章中进行讲解。
下图为最终的预测结果,可以看到:预测得到的结果和真实的sin函数几乎是重合的,也就是说通过循环神经网络可以非常好地预测sin函数的取值。
这段代码已经放到GitHub上,如需要可进行下载代码。