Tensorflow多层LSTM代码分析

1.tf.Graph()

你一旦开始你的任务,就已经有一个默认的图已经创建好了。而且可以通过调用tf.get_default_graph()来访问到。

添加一个操作到默认的图里面,只要简单的调用一个定义了新操作的函数就行。比如下面的例子展示的:

import tensorflow as tf
import numpy as np

c=tf.constant(value=1)
print(c.graph)
print(tf.get_default_graph())

<tensorflow.python.framework.ops.Graph object at 0x107d38fd0>

<tensorflow.python.framework.ops.Graph object at 0x107d38fd0>

另外一种典型的用法就是要使用到Graph.as_default() 的上下文管理器( context manager),它能够在这个上下文里面覆盖默认的图。如下例:

import tensorflow as tf
import numpy as np

c=tf.constant(value=1)
print(c.graph)
print(tf.get_default_graph())

g=tf.Graph()
print("g:",g)
with g.as_default():
    d=tf.constant(value=2)
    print(d.graph)
    print(g)

<tensorflow.python.framework.ops.Graph object at 0x10b0e56d8>
<tensorflow.python.framework.ops.Graph object at 0x10b0e56d8>
g: <tensorflow.python.framework.ops.Graph object at 0x10b0df2e8>
<tensorflow.python.framework.ops.Graph object at 0x10b0df2e8>
<tensorflow.python.framework.ops.Graph object at 0x10b0df2e8>

2.tf.variable_scope()

利用TensorFlow 提供了变量作用域 机制,当构建一个视图时,很容易就可以共享命名过的变量.

变量作用域机制在TensorFlow中主要由两部分组成:

  • tf.get_variable(<name>, <shape>, <initializer>): 通过所给的名字创建或是返回一个变量.
  • tf.variable_scope(<scope_name>): 通过 tf.get_variable()为变量名指定命名空间.

3.

tf.nn.rnn_cell.BasicLSTMCell(num_units=,forget_bias=,state_is_tuple=)
#继承了RNNCell,state_is_tuple官方建议设置为True,此时输入和输出的states为c
#(cell状态)和h(输出)的二元组
#输入输出和cell的维度相同,都是batch_size*num_units
4.
lstm_cell=tf.nn.rnn_cell.DropoutWrapper(lstm_cell,output_keep_prob=config.keep_prob)
#对于rnn的部分不进行dropout,也就是从t-1时候的状态传递到t时刻进行计算时,这个中间不进行
#memory的dropout,仅在同一个t时刻,多层cell之间传递信息的时候进行dropout

5.

cell=tf.nn.rnn_cell.MultiRNNCell([lstm_cell]*config.num_layers,state_is_tuple=True)
#多层lstm cell堆叠起来

tensorflow并不是简单的堆叠了多了single cell,而是将这些cell stack之后当成了一个完整的独立的cell,每个小cell的中间状态还是保存下来了,按照n_tuple存储,但是输出output只用最后那个cell的输出。

这样就定义好了每个t时刻的整体cell,接下来只要每个时刻传入不同的输入,再在时间上展开,就可以得到多个时间上的unroll graph

6.
self._inital_state=cell.zero_state(batch_size,data_type())
#我们刚刚定义好的cell会依次接收num_steps个输入然后产生最后的state(n-tuple,n表示堆叠的层数)
#最后需要[batch_size,堆叠的层数]来存储seq的状态

7.tf.Variable & tf.get_variable()

使用tf.Variable时,如果检测到命名冲突,系统会自己处理。使用tf.get_variable()时,系统不会处理冲突,而会报错。

所以当我们需要共享变量的时候,使用tf.get_variable()

由于tf.Variable() 每次都在创建新对象,所有reuse=True 和它并没有什么关系。对于get_variable(),来说,如果已经创建的变量对象,就把那个对象返回,如果没有创建变量对象的话,就创建一个新的。

8.gradient clipping(修剪)的引入是为了处理gradient explosion或者gradient vanishing的问题,让权重的更新限制在一个合适的范围。

具体的实现细节:

  1. 在solver中先设置一个clip_gradient
  2. 在前向传播与反向传播之后,我们会得到每个权重的梯度diff,这时不像通常那样直接使用这些梯度进行权重更新,而是先求所有权重梯度的平方和再求根号sumsq_diff,如果sumsq_diff > clip_gradient,则求缩放因子scale_factor = clip_gradient / sumsq_diff。这个scale_factor在(0,1)之间。
  3. 最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。
  4. 这样就保证了在一次迭代更新中,所有权重的梯度的平方和在一个设定范围以内,这个范围就是clip_gradient.

tf.clip_by_global_norm(t_list, clip_norm, use_norm=None, name=None) 

t_list是梯度张量,clip_norm是截取的比率,和上面的clip_gradient是相同的东西,返回截取过后的梯度张量和一个所有张量的全局范数。

t_list的更新公式是:

t_list[i] * clip_norm / max(global_norm, clip_norm)

global_norm是所有梯度的平方和再求根号。

9.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import tensorflow as tf
import numpy as np
import math
import gzip
import os
import tempfile
import time
flags=tf.app.flags
logging=tf.logging
flags.DEFINE_string(#这里定义model的值是small
    \'model\',\'small\',\'A type of model.Possible options are:small,medium,large.\'
    )
flags.DEFINE_string(\'data_path\',\'/Users/guoym/Desktop/modles-master\',\'data_path\')
flags.DEFINE_bool(\'use_fp16\',False,\'Train using 16-bit floats instead oof 32bit floats\')
FLAGS=flags.FLAGS
def data_type():
    return tf.float16 if FLAGS.use_fp16 else tf.float32
class PTBModel(object):
    def __init__(self,is_training,config):
        \'\'\'
        参数is_training:是否要进行训练,如果为False,则不会进行参数的修正
        \'\'\'
        self.batch_size = batch_size = config.batch_size
        self.num_steps = num_steps = config.num_steps
        size = config.hidden_size
        vocab_size = config.vocab_size

        self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])    # 输入
        self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])       # 预期输出,两者都是index序列,长度为num_step
        lstm_cell=tf.nn.rnn_cell.BasicLSTMCell(size,forget_bias=0.0,state_is_tuple=True)
        #num_units是指LSTM cell中的单元的数量
        if is_training and keep_prob<1:#在外面包裹一层dropout
            lstm_cell=tf.nn.rnn_cell.DropoutWrapper(lstm_cell,output_keep_prob=config.keep_prob)
            #对于rnn的部分不进行dropout,也就是从t-1时候的状态传递到t时刻进行计算时,这个中间不进行
            #memory的dropout,仅在同一个t时刻,多层cell之间传递信息的时候进行dropout
        cell=tf.nn.rnn_cell.MultiRNNCell([lstm_cell]*config.num_layers,state_is_tuple=True)
        #多层lstm cell堆叠起来
        self._inital_state=cell.zero_state(batch_size,data_type())
        #我们刚刚定义好的cell会依次接收num_steps个输入然后产生最后的state(n-tuple,n表示堆叠的层数)
        #最后需要[batch_size,堆叠的层数]来存储seq的状态
        with tf.device("/cpu:0"):
            embedding=tf.get_variable("embedding",[vocab_size,size],dtype=data_type())
            #将输入序列用embedding表示 shape=[batch,steps,hidden_size]
            inputs=tf.nn.embedding_lookup(embedding,self._input_data)
        if is_training and config.keep_prob<1:
            inputs=tf.nn.dropout(inputs,keep_prob)
        outputs=[]
        state=self._initial_state#state表示各个batch中的状态
        with tf.variable_scope("RNN"):
            for time_step in range(num_steps):
                if time_step>0:
                    tf.get_variable_scope().reuse_variables
                    #当前变量作用域可以用tf.get_variable_scope()进行检索并且reuse 标签可以通过调用tf.get_variable_scope().reuse_variables()设置为True .
                    (cell_output,state)=cell(inputs[:,time_step,:],state)
                    #cell_output 是[batch,hidden_size]
                    outputs.append(cell_output)
        #把之前的list展开,把[batch,hidden_size*num_steps] reshape 成[batch*numsteps,hiddensize]
        output=tf.reshape(tf.concat(1,outputs),[-1,size])
        softmax_w=tf.get_variable(\'softmax_w\',[size,vocab_size],dtype=data_type())
        softmax_b=tf.get_variable(\'softmax_b\',[vocab_size],dtype=data_type())
        logits=tf.matmul(output,softmax_w)+softmax_b
        loss=tf.nn.seq2seq.sequence_loss_by_example(
            [logits],
            [tf.reshape(self._targets,[-1])],
            [tf.ones([batch_size*num_steps],dtype=data_type())])#展开成为一维的列表
        self._cost=cost=tf.reduce_sum(loss)/batch_size#计算得到每批次的误差
        self._final_state=state
        #logits是一个二维的张量,a*btargets就是一个一维的张量,长度为a,并且targets中的元素是不能
        #超过b的整形,weights是一个一维的长度为a的张量。
        #其意义就是针对logits中的每一个num_step,即[batch,vocab_size],对所有vocab_size个预测结果,
        #得出预测值最大的那个类别,与targets中的值相比较计算loss值
        if not is_training:
            return 
        self._lr=tf.Variable(0.0,trainable=True)
        tvars=tf.trainable_variables()#返回的是需要训练的张量的列表
        grads,_=tf.clip_by_global_norm(tf.gradient(cost,tvars),config.max_grad_norm)
        
        optimizer=tf.train.GradientDescentOptimizer(self._lr)
        self._train_op=optimizer.apply_gradients(zip(grads,tvars))#将梯度应用于变量
        self._new_lr=tf.placeholder(f.float32,shape=[],name=\'new_learning_rate\')
        #用于外部向graph输入新的lr的值
        self._lr_update=tf.assign(self._lr,self._new_lr)
        def assign_lr(self,session,lr_value):
            #使用session来调用lr_update操作
            session.run(self._lr_update,feed_dict={self._new_lr:lr_value})
    @property
    def input_data(self):
        return self._input_data

    @property
    def targets(self):
        return self._targets

    @property
    def initial_state(self):
        return self._initial_state

    @property
    def cost(self):
        return self._cost

    @property
    def final_state(self):
        return self._final_state

    @property
    def lr(self):
        return self._lr

    @property
    def train_op(self):
        return self._train_op

def run_epoch(session,model,data,eval_op,verbose=False):
    #epoch_size表示批次的总数,也就是说,需要向session喂这么多次的数据
    epoch_size=((len(data)//model.batch_size-1)//model.num_steps)#//表示整数除法
    start_time=time.time()
    costs=0.0
    iters=0
    state=session.run(model.initial_state)
    for step,(x,y) in enumerate(reader.ptb_iterator(data,model.batch_size,model.num_steps)):
        fetchs=[model.cost,model.final_state,eval_op]#要进行的操作,注意训练时和其他时候的eval_op的区别
        feed_dict={}
        feed_dict[model.input_data]=x
        feed_dict[model.targets]=y
        for i ,(c,h) in enumerate(model.initial_state):
            feed_dict[c] = state[i].c   
            feed_dict[h] = state[i].h
           cost,state=session.run(fetch,feed_dict)
           costs+=cost
           iters+=model.num_steps
           if verbose and step % (epoch_size // 10) == 10:  # 也就是每个epoch要输出10个perplexity值
            print("%.3f perplexity: %.3f speed: %.0f wps" %
                  (step * 1.0 / epoch_size, np.exp(costs / iters),
                   iters * model.batch_size / (time.time() - start_time)))

    return np.exp(costs / iters)





class SmallConfig(object):
    init_scale = 0.1        #
    learning_rate = 1.0     # 学习速率
    max_grad_norm = 5       # 用于控制梯度膨胀,
    num_layers = 2          # lstm层数
    num_steps = 20          # 单个数据中,序列的长度。
    hidden_size = 200       # 隐藏层规模
    max_epoch = 4           # epoch<max_epoch时,lr_decay值=1,epoch>max_epoch时,lr_decay逐渐减小
    max_max_epoch = 13      # 指的是整个文本循环13遍。
    keep_prob = 1.0
    lr_decay = 0.5          # 学习速率衰减
    batch_size = 20         # 每批数据的规模,每批有20个。
    vocab_size = 10000      # 词典规模,总共10K个词
if __name__==\'__main__\':
    raw_data=reader.ptb_raw_data(FLAGS.data_path)
    train_data,valid_data,test_data,_=raw_data
    config=SmallConfig()
    eval_config=SmallConfig()
    eval_config.batch_size=1
    eval_config.num_steps=1
    with tf.Graph().as_default(),tf.Session() as session:
        initializer=tf.random_uniform_initializer(-config.init_scale,config.init_scale)
        #生成均匀分布的随机数,参数minval,maxval
        with tf.variable_scope(\'model\',reuse=None,initializer=initializer):
            m=PTBModel(is_training=True,config=config)#训练模型
        with tf.variable_scope(\'model\',reuse=True,initializer=initializer):#交叉检验和测试模型
            mvalid=PTBModel(is_training=False,config=config)
            mtest=PTBModel(is_training=False,config=eval_config)

        summary_writer = tf.summary.FileWriter(\'/tmp/lstm_logs\',session.graph)
        tf.initialize_all_variables().run()  # 对参数变量初始化

        for i in range(config.max_max_epoch):
            #learning rate衰减
            # 遍数<max epoch时,lr_decay=1l >max_epoch,lr_decay=0.5^(i-max_epoch)
            lr_decay = config.lr_decay ** max(i - config.max_epoch, 0.0)
            m.assign_lr(session, config.learning_rate * lr_decay) # 设置learning rate
            print("Epoch: %d Learning rate: %.3f" % (i + 1, session.run(m.lr)))

            train_perplexity = run_epoch(session, m, train_data, m.train_op,verbose=True) # 训练困惑度
            print("Epoch: %d Train Perplexity: %.3f" % (i + 1, train_perplexity))
            valid_perplexity = run_epoch(session, mvalid, valid_data, tf.no_op()) # 检验困惑度
            print("Epoch: %d Valid Perplexity: %.3f" % (i + 1, valid_perplexity))

        test_perplexity = run_epoch(session, mtest, test_data, tf.no_op())  # 测试困惑度
        print("Test Perplexity: %.3f" % test_perplexity)