前言
我打算基于lstm构建一个分词系统,通过这个例子来学习下TensorFlow中如何训练循环递归神经网络。我们将从最粗糙的版本开始搭建这个小系统,然后一步步优化其中的每一部分,包括网络架构的优化,数据处理的优化,甚至整个代码架构的优化。希望想我一样的入门选手看到其中的每一步实现以及如何去优化。
关于LSTM网络的介绍,可以看官网推荐的一篇博客,写的实在是太棒了http://colah.github.io/posts/2015-08-Understanding-LSTMs
另外这篇翻译也很赞啊http://www.jianshu.com/p/9dc9f41f0b29,这里不在详述。
我们第一个版本的模型来自官网的tutorials中Recurrent Neural Networks部分内容,官网的数据并不利于我们去直接感受模型训练的带来的结果,所以后来我想了下用它来实现一个中文分词,可能更有利于初学者去直观的感受。第一个版本会我写的很粗糙,主要是为了理解在TensorFlow中如何搭建LSTM网络。
模型搭建
我对官网中的例子用我自己更喜欢的结构重写了下。 首先我们来看下如何搭建这个模型。开始我把模型部分代码主要由inference(),loss()和training()三部分构成,inference()部分负责模型搭建,loss()负责计算损失,为优化做准备,training()负责优化部分,主要是对损失函数应用梯度下降,更新参数。我把上面三部分写封装一个类里面。后来发现这样实现会存在些问题,然后又把inference()的实现直接放在了类的init()函数里面。下面先看下模型的整体实现,
class ptb_lstm():
def __init__(self,config):
...
def loss(self):
....
def train(self):
....
这里,我们在init()中传了一个config类,这个config主要是一些模型参数,大致形式是下面这样,这篇笔记就不详讲了
class config():
'''参数配置类'''
init_scale = 0.1
learning_rate = 1.0
max_grad_norm = 5
num_layers = 2
hidden_size = 200
keep_prob = 1.0
lr_decay = 0.5
batch_size = 50
num_steps = 50
vocab_size = 3000
output_size = 3
learningrate = 0.5
好了,接下来我们先看init()部分
class lstm_model():
def __init__(self, config):
'''
:param config:config类,一些训练参数设置
'''
self.config = config
self.x = tf.placeholder(tf.int32, shape=(config.batch_size, config.num_steps))
self.y = tf.placeholder(tf.int32, shape=(config.batch_size, config.num_steps))
def lstm_cell():
#构建lstm基本单元
return tf.contrib.rnn.BasicLSTMCell(
self.config.hidden_size, forget_bias=0.0, state_is_tuple=True)
attn_cell = lstm_cell
if config.keep_prob < 1:
#如果config.keep_prob参数小于1,对lstm单元进行dropout,防止过拟合
def attn_cell():
return tf.contrib.rnn.DropoutWrapper(
lstm_cell(), output_keep_prob=config.keep_prob)
cell = tf.contrib.rnn.MultiRNNCell(
[attn_cell() for _ in range(config.num_layers)], state_is_tuple=True)
#构建多层的lstm,config.num_layers是层数参数
self._initial_state = cell.zero_state(self.config.batch_size, tf.float32)
#初始化lstm的state
with tf.device("/cpu:0"):
embedding = tf.get_variable(
"embedding", [self.config.vocab_size, self.config.hidden_size], dtype=tf.float32)
inputs = tf.nn.embedding_lookup(embedding,self.x)
#词嵌入
outputs = []
state = self._initial_state
with tf.variable_scope("RNN"):
for time_step in range(self.config.num_steps):
if time_step > 0: tf.get_variable_scope().reuse_variables()
(cell_output, state) = cell(inputs[:, time_step, :], state)
outputs.append(cell_output)
#前向传播,计算每个单元的cell_output和state,把cell_output添加到outputs,把state传递到下个单元,最终outputs的为(config.num_steps,config.batch_size,config.hidden_size)
output = tf.reshape(tf.concat(axis=1, values=outputs), [-1, self.config.hidden_size])
#output的形状为(config.num_steps*config.batch_size,config.hedden_size)
softmax_w = tf.get_variable(
"softmax_w", [self.config.hidden_size, self.config.output_size], dtype=tf.float32)
softmax_b = tf.get_variable("softmax_b", [self.config.output_size], dtype=tf.float32)
self.logits = tf.matmul(output, softmax_w) + softmax_b
#得到最终的网络输出logits形状为(config.num_steps*config.batch_size,config.output_size)
接着是loss(self,logits)
def loss(self):
logits = self.logits
loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
[logits],
[tf.reshape(self.y, [-1])],
[tf.ones([self.config.batch_size * self.config.num_steps], dtype=tf.float32)])
# 交叉熵损失函数,下一篇专门讲下tensorflow中的几个损失函数的实现
cost = tf.reduce_sum(loss) / self.config.batch_size
最后是后向传播参数更新部分training(self)
def training(self):
loss = self.loss()
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars),
self.config.max_grad_norm)
optimizer = tf.train.GradientDescentOptimizer(self.config.learningrate)
#优化器
train_op = optimizer.apply_gradients(
zip(grads, tvars),
global_step=tf.contrib.framework.get_or_create_global_step())
#梯度下降
return train_op
模型部分就搭建完毕了,下一节我们来讲下数据的预处理。