循环神经网络——双向LSTM&GRU

作者:u010751535

gitbook原文阅读地址:循环神经网络——双向LSTM&GRU

任务描述:

本次的代码LV2是紧接着代码LV1的升级版,所学习的内容与先前的一样,不同的是:
* 简单梳理调整了代码结构,方便使用
* 将所有gate的计算并在一个大矩阵乘法下完成提高GPU的利用率
* 除了LSTM(Long-Short Term Memory)以外的cell,还提供了GRU(gate recurrent unit) cell模块
* 双向RNN(可选择任意cell组合)
* 该代码可被用于练习结构改造或实际建模任务

定义LSTMcell类

  • 目的:LSTMcell包含所有学习所需要的parameters以及每一时刻所要运行的step方法
  • 代码
class LSTMcell(object):
def __init__(self, incoming, D_input, D_cell, initializer,
f_bias=1.0, L2=False, h_act=tf.tanh,
init_h=None, init_c=None):
# 属性
self.incoming = incoming # 输入数据
self.D_input = D_input
self.D_cell = D_cell
self.initializer = initializer # 初始化方法
self.f_bias = f_bias # 遗忘门的初始偏移量
self.h_act = h_act # 这里可以选择LSTM的hidden state的激活函数
self.type = 'lstm' # 区分gru
# 如果没有提供最初的hidden state和memory cell,会全部初始为0
if init_h is None and init_c is None:
# If init_h and init_c are not provided, initialize them
# the shape of init_h and init_c is [n_samples, D_cell]
self.init_h = tf.matmul(self.incoming[0,:,:], tf.zeros([self.D_input, self.D_cell]))
self.init_c = self.init_h
self.previous = tf.stack([self.init_h, self.init_c])
# LSTM所有需要学习的参数
# 每个都是[W_x, W_h, b_f]的tuple
self.igate = self.Gate()
self.fgate = self.Gate(bias = f_bias)
self.ogate = self.Gate()
self.cell = self.Gate()
# 因为所有的gate都会乘以当前的输入和上一时刻的hidden state
# 将矩阵concat在一起,计算后再逐一分离,加快运行速度
# W_x的形状是[D_input, 4*D_cell]
self.W_x = tf.concat(values=[self.igate[0], self.fgate[0], self.ogate[0], self.cell[0]], axis=1)
self.W_h = tf.concat(values=[self.igate[1], self.fgate[1], self.ogate[1], self.cell[1]], axis=1)
self.b = tf.concat(values=[self.igate[2], self.fgate[2], self.ogate[2], self.cell[2]], axis=0)
# 对LSTM的权重进行L2 regularization
if L2:
self.L2_loss = tf.nn.l2_loss(self.W_x) + tf.nn.l2_loss(self.W_h)
# 初始化gate的函数
def Gate(self, bias = 0.001):
# Since we will use gate multiple times, let's code a class for reusing
Wx = self.initializer([self.D_input, self.D_cell])
Wh = self.initializer([self.D_cell, self.D_cell])
b = tf.Variable(tf.constant(bias, shape=[self.D_cell]),trainable=True)
return Wx, Wh, b
# 大矩阵乘法运算完毕后,方便用于分离各个gate
def Slice_W(self, x, n):
# split W's after computing
return x[:, n*self.D_cell:(n+1)*self.D_cell]
# 每个time step需要运行的步骤
def Step(self, previous_h_c_tuple, current_x):
# 分离上一时刻的hidden state和memory cell
prev_h, prev_c = tf.unstack(previous_h_c_tuple)
# 统一在concat成的大矩阵中一次完成所有的gates计算
gates = tf.matmul(current_x, self.W_x) + tf.matmul(prev_h, self.W_h) + self.b
# 分离输入门
i = tf.sigmoid(self.Slice_W(gates, 0))
# 分离遗忘门
f = tf.sigmoid(self.Slice_W(gates, 1))
# 分离输出门
o = tf.sigmoid(self.Slice_W(gates, 2))
# 分离新的更新信息
c = tf.tanh(self.Slice_W(gates, 3))
# 利用gates进行当前memory cell的计算
current_c = f*prev_c + i*c
# 利用gates进行当前hidden state的计算
current_h = o*self.h_act(current_c)
return tf.stack([current_h, current_c])

定义GRUcell类

  • 代码
class GRUcell(object):
def __init__(self, incoming, D_input, D_cell, initializer, L2=False, init_h=None):
# 属性
self.incoming = incoming
self.D_input = D_input
self.D_cell = D_cell
self.initializer = initializer
self.type = 'gru'
# 如果没有提供最初的hidden state,会初始为0
# 注意GRU中并没有LSTM中的memory cell,其功能是由hidden state完成的
if init_h is None:
# If init_h is not provided, initialize it
# the shape of init_h is [n_samples, D_cell]
self.init_h = tf.matmul(self.incoming[0,:,:], tf.zeros([self.D_input, self.D_cell]))
self.previous = self.init_h
# 如果没有提供最初的hidden state,会初始为0
# 注意GRU中并没有LSTM中的memory cell,其功能是由hidden state完成的
self.rgate = self.Gate()
self.ugate = self.Gate()
self.cell = self.Gate()
# 因为所有的gate都会乘以当前的输入和上一时刻的hidden state
# 将矩阵concat在一起,计算后再逐一分离,加快运行速度
# W_x的形状是[D_input, 3*D_cell]
self.W_x = tf.concat(values=[self.rgate[0], self.ugate[0], self.cell[0]], axis=1)
self.W_h = tf.concat(values=[self.rgate[1], self.ugate[1], self.cell[1]], axis=1)
self.b = tf.concat(values=[self.rgate[2], self.ugate[2], self.cell[2]], axis=0)
# 对LSTM的权重进行L2 regularization
if L2:
self.L2_loss = tf.nn.l2_loss(self.W_x) + tf.nn.l2_loss(self.W_h)
# 初始化gate的函数
def Gate(self, bias = 0.001):
# Since we will use gate multiple times, let's code a class for reusing
Wx = self.initializer([self.D_input, self.D_cell])
Wh = self.initializer([self.D_cell, self.D_cell])
b = tf.Variable(tf.constant(bias, shape=[self.D_cell]),trainable=True)
return Wx, Wh, b
# 大矩阵乘法运算完毕后,方便用于分离各个gate
def Slice_W(self, x, n):
# split W's after computing
return x[:, n*self.D_cell:(n+1)*self.D_cell]
# 每个time step需要运行的步骤
def Step(self, prev_h, current_x):
# 分两次,统一在concat成的大矩阵中完成gates所需要的计算
Wx = tf.matmul(current_x, self.W_x) + self.b
Wh = tf.matmul(prev_h, self.W_h)
# 分离和组合reset gate
r = tf.sigmoid(self.Slice_W(Wx, 0) + self.Slice_W(Wh, 0))
# 分离和组合update gate
u = tf.sigmoid(self.Slice_W(Wx, 1) + self.Slice_W(Wh, 1))
# 分离和组合新的更新信息
# 注意GRU中,在这一步就已经有reset gate的干涉了
c = tf.tanh(self.Slice_W(Wx, 2) + r*self.Slice_W(Wh, 2))
# 计算当前hidden state,GRU将LSTM中的input gate和output gate的合设成1,
# 用update gate完成两者的工作
current_h = (1-u)*prev_h + u*c
return current_h

定义RNN函数

  • 目的:用于接受cell的实例,并用scan计算所有time steps的hidden states
  • 代码
def RNN(cell, cell_b=None, merge='sum'):
"""
该函数接受的数据需要是[n_steps, n_sample, D_output],
函数的输出也是[n_steps, n_sample, D_output].
如果输入数据不是[n_steps, n_sample, D_input],
使用'inputs_T = tf.transpose(inputs, perm=[1,0,2])'.
"""
# 正向rnn的计算
hstates = tf.scan(fn = cell.Step,
elems = cell.incoming,
initializer = cell.previous,
name = 'hstates')
# lstm的step经过scan计算后会返回4维tensor,
# 其中[:,0,:,:]表示hidden state,
# [:,1,:,:]表示memory cell,这里只需要hidden state
if cell.type == 'lstm':
hstates = hstates[:,0,:,:]
# 如果提供了第二个cell,将进行反向rnn的计算
if cell_b is not None:
# 将输入数据变为反向
incoming_b = tf.reverse(cell.incoming, axis=[0])
# scan计算反向rnn
b_hstates_rev = tf.scan(fn = cell_b.Step,
elems = incoming_b,
initializer = cell_b.previous, # 每个cell自带的初始值
name = 'b_hstates')
if cell_b.type == 'lstm':
b_hstates_rev = b_hstates_rev[:,0,:,:]
# 用scan计算好的反向rnn需要再反向回来与正向rnn所计算的数据进行合并
b_hstates = tf.reverse(b_hstates_rev, axis=[0])
# 合并方式可以选择直接相加,也可以选择concat
if merge == 'sum':
hstates = hstates + b_hstates
else:
hstates = tf.concat(values=[hstates, b_hstates], axis=2)
return hstates
  • 解释:可以使用两个GRU cell进行双向rnn的就算,也可以混搭

网络构建

  • 目的:这里演示的是两层relu feedforward layers后,接一层双向GRU-RNN,最后再接两层relu feedforward layers。
  • 效果图


* 代码

D_input = 39
D_label = 24
learning_rate = 7e-5
num_units=1024
L2_penalty = 1e-4
inputs = tf.placeholder(tf.float32, [None, None, D_input], name="inputs")
labels = tf.placeholder(tf.float32, [None, D_label], name="labels")
# 保持多少节点不被dropout掉
drop_keep_rate = tf.placeholder(tf.float32, name="dropout_keep")
# 用于reshape
n_steps = tf.shape(inputs)[1]
n_samples = tf.shape(inputs)[0]
# 将输入数据从[n_samples, n_steps, D_input],reshape成[n_samples*n_steps, D_input]
# 用于feedforward layer的使用
re1 = tf.reshape(inputs, [-1, D_input])
# 第一层
Wf0 = weight_init([D_input, num_units])
bf0 = bias_init([num_units])
h1 = tf.nn.relu(tf.matmul(re1, Wf0) + bf0)
# dropout
h1d = tf.nn.dropout(h1, drop_keep_rate)
# 第二层
Wf1 = weight_init([num_units, num_units])
bf1 = bias_init([num_units])
h2 = tf.nn.relu(tf.matmul(h1d, Wf1) + bf1)
# dropout
h2d = tf.nn.dropout(h2, drop_keep_rate)
# 将输入数据从[n_samples*n_steps, D_input],reshape成[n_samples, n_steps, D_input]
# 用于双向rnn layer的使用
re2 = tf.reshape(h2d, [n_samples,n_steps, num_units])
# 将数据从[n_samples, n_steps, D_input],转换成[n_steps, n_samples, D_input]
inputs_T = tf.transpose(re2, perm=[1,0,2])
# 实例rnn的正向cell,这里使用的是GRUcell
rnn_fcell = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
# 实例rnn的反向cell
rnn_bcell = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
# 将两个cell送给scan里计算,并使用sum的方式合并两个方向所计算的数据
rnn0 = RNN(rnn_fcell, rnn_bcell)
# 将输入数据从[n_samples, n_steps, D_input],reshape成[n_samples*n_steps, D_input]
# 用于feedforward layer的使用
rnn1 = tf.reshape(rnn0, [-1, num_units])
# dropout
rnn2 = tf.nn.dropout(rnn1, drop_keep_rate)
# 第三层
W0 = weight_init([num_units, num_units])
b0 = bias_init([num_units])
rnn3 = tf.nn.relu(tf.matmul(rnn2, W0) + b0)
rnn4 = tf.nn.dropout(rnn3, drop_keep_rate)
# 第四层
W1 = weight_init([num_units, num_units])
b1 = bias_init([num_units])
rnn5 = tf.nn.relu(tf.matmul(rnn4, W1) + b1)
rnn6 = tf.nn.dropout(rnn5, drop_keep_rate)
# 输出层
W = weight_init([num_units, D_label])
b = bias_init([D_label])
output = tf.matmul(rnn6, W) + b
# loss
loss=tf.reduce_mean((output-labels)**2)
L2_total = tf.nn.l2_loss(Wf0) + tf.nn.l2_loss(Wf1)+ tf.nn.l2_loss(W0) + tf.nn.l2_loss(W1) + tf.nn.l2_loss(W)#+ rnn_fcell.L2_loss + rnn_bcell.L2_loss
# 训练所需的
train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss + L2_penalty*L2_total)

训练

剩下的代码就和代码LV1的相同了, 大家可以结合tensorboard来记录和分析所学习的权重矩阵和loss的下降等。使用方式请参考代码演示LV3,也可以和代码演示LV3中的代码结合着使用,根据自己的需要注意reshape和transpose即可。完整代码在我的github上。

效果

  • loss:训练集的loss在0.022,验证集的loss在0.222,比feedforward要好很多
  • 效果图:另外预测的轨迹也十分的平滑


其他

  • 速度:将所有gates的参数并在一起处理再分离可以节省很多时间,代价自然是更多的memory
  • rnn的dropout:每个gate其实也是一个再cell内部的具有物理意义的神经网络,那么同样也是可以利用dropout来防止gate在拟合物理意义时过拟合。
  • 多层rnn:可以rnn层之后以相同的方式再来一层双向或单向rnn,比如:
# 第一层双向rnn
rnn_fcell = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
rnn_bcell = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
rnn0 = RNN(rnn_fcell, rnn_bcell)
# 第二层双向rnn
rnn_fcell2 = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
rnn_bcell2 = GRUcell(inputs_T, num_units, num_units, orthogonal_initializer)
rnn02 = RNN(rnn_fcell2, rnn_bcell2)
# 后续处理
rnn1 = tf.reshape(rnn02, [-1, num_units])

两层双向GRU-RNN的loss会达到: (190, ‘train:’, 0.024, ‘test:’, 0.193),比0.222又降低了
* 图片里的二维码:并没有搞公众号的打算,只是用来发布些链接,让那些看到不贴原地址的恶意转载的朋友们也可以看到先前的内容(RNN之后的内容就无法从零开始阅读了,必须要有之前的知识作为铺垫)

发表评论

0个评论

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

机器翻译知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?