tensorflow实战自学【六】

RNN(循环神经网络)初步之LSTM(长短期记忆)

所用部分函数解读

tf.contrib.rnn.BasicLSTMCell(num_units,forget_bias=1.0,state_is_tuple=True,activation=None,reuse=None,name=None,dtype=None)

BasicLSTMCell类是最基本的LSTM循环神经网络单元。它不被允许cell clipping,projection layer,以及不能使用peep-hole connections。
num_units:int类型,LSTM单元中的神经元数量,即输出神经元数量。
forget_bias:float类型,偏置增加了忘记门,是为了减小在训练开始时忘记操作的范围。从CudnnLSTM训练的检查点(checkpoin)恢复时,必须手动设置为0.0。
state_is_tuple:如果为True,则接受和返回的是由c_state和m_state的2-tuple:(hidden_state,[cell_state,hidden_state]),其中m_state便为hidden_state;如果为False,则他们沿着列轴连接。后一种即将被弃用。
activation:内部状态的激活函数。默认为tanh
reuse:布尔类型,描述是否在现有范围中重用变量。如果不为True,并且现有范围已经具有给定变量,则会引发错误。
name:String类型,层的名称。具有相同名称的层将共享权重,但为了避免错误,在这种情况下需要reuse=True.
dtype:该层默认的数据类型。默认值为None表示使用第一个输入的类型。在call之前build被调用则需要该参数。

tf.contrib.rnn.DropoutWrapper(cell,input_keep_prob=1.0,output_keep_prob=1.0,state_keep_prob=1.0,variational_recurrent=False,input_size=None,dtype=None,seed=None,dropout_state_filter_visitor=None)

所谓dropout,就是指网络中每个单元在每次有数据流入时以一定的概率(keep prob)正常工作,否则输出0值。这是是一种有效的正则化方法,可以有效防止过拟合。在rnn中使用dropout的方法和cnn不同,
cell:一个RNNCell,将要进行dropout的RNNCell层
input_keep_prob:unit Tensor 或者是介于0与1的float,输入保持率;若它为constant与1,则不对输入进行dropout。
output_keep_prob:unit Tensor 或者是介于0与1的float,输出保持率;若它为constant与1,则不对输出进行dropout。
state_keep_prob:unit Tensor 或者是介于0与1的float,输出保持率;若它为constant与1,则不对输出进行dropout。当cell处于output states时执行State dropout。
variational_recurrent:Python bool。若为True,则在每次运行调用时,进行相同的dropout pattern。如果设置了此参数,则必须提供input_size。
input_size: (可选参数) (可能是嵌套的tuple) TensorShape,包含输入用于通过DropoutWrapper的tensor的深度。当且仅当variational_recurrent = True且input_keep_prob < 1时才使用这个参数。

tf.nn.dropout(x,keep_prob,noise_shape=None,seed=None,name=None)

该函数用于计算dropout。使用概率keep_prob,输出按照1/keep_prob的比例放大输入元素,否则输出0。缩放是为了使预期的总和不变。默认情况下,每个元素都是独立保留或删除的。如果已指定noise_shape,则必须将其广播为x的形状,并且只有具有noise_shape[i] == shape(x)[i]的维度才作出独立决定。
例如,如果shape(x) = [k, l, m, n]并且noise_shape = [k, 1, 1, n],则每个批处理和通道组件将独立保存,并且每个行和列将保留或不保留在一起。
x:一个浮点型Tensor。
keep_prob:一个标量Tensor,它与x具有相同类型.保留每个元素的概率。
noise_shape:类型为int32的1维Tensor,表示随机产生的保持/丢弃标志的形状。
seed:一个Python整数。用于创建随机种子。
name:此操作的名称(可选)。

tf.contrib.rnn.MultiRNNCell(cells,state_is_tuple=True)

构建多重RNN
cells:RNNCells的list。
state_is_tuple:如果为True,接受和返回的states是n-tuples,其中n=len(cells),即将每层RNNCell的状态给组成一个n-tuple返回。如果为False,states是concatenated沿着列轴。后者即将弃用。
note:关于它的使用
下面这样直接用一个BasicLSTMCell复制是错误的,会导致各层共享权重

basic_cell = tf.nn.rnn_cell.BasicLSTMCell(rnn_unit)
multi_cell = tf.nn.rnn_cell.MultiRNNCell([basic_cell]*layer_num)

在新版本TensorFlow源码中可以看到,上面这样的写法会给出警告:

if len(set([id(cell) for cell in cells])) < len(cells):
    logging.log_first_n(logging.WARN,
                        "At least two cells provided to MultiRNNCell "
                        "are the same object and will share weights.", 1)

官方推荐的写法,使用列表生成器:

  num_units = [128, 64]
  cells = [BasicLSTMCell(num_units=n) for n in num_units]
  stacked_rnn_cell = MultiRNNCell(cells)

cell.zero_state

设置LSTM单元的初始化状态,返回[batch_size, 2*len(cells)],或者[batch_size, s]

tf.get_variable(name,shape=None,dtype=None,initializer=None,regularizer=None,trainable=True,collections=None,caching_device=None,partitioner=None,validate_shape=True,use_resource=None,custom_getter=None,constraint=None)

获取一个已经存在的变量或者创建一个新的变量。
name:新变量或现有变量的名称。
shape:新变量或现有变量的形状。
dtype:新变量或现有变量的类型(默认为DT_FLOAT)。
ininializer:如果创建了则用它来初始化变量。
regularizer:A(Tensor - > Tensor或None)函数;将它应用于新创建的变量的结果将添加到集合tf.GraphKeys.REGULARIZATION_LOSSES中,并可用于正则化。
trainable:如果为True,还将变量添加到图形集合GraphKeys.TRAINABLE_VARIABLES(参见tf.Variable)。
collections:要将变量添加到的图表集合列表。默认为[GraphKeys.GLOBAL_VARIABLES](参见tf.Variable)。
caching_device:可选的设备字符串或函数,描述变量应被缓存以供读取的位置。默认为Variable的设备。如果不是None,则在另一台设备上缓存。典型用法是在使用变量驻留的Ops的设备上进行缓存,以通过Switch和其他条件语句进行重复数据删除。
partitioner:可选callable,接受完全定义的TensorShape和要创建的Variable的dtype,并返回每个轴的分区列表(当前只能对一个轴进行分区)。
validate_shape:如果为False,则允许使用未知形状的值初始化变量。如果为True,则默认为initial_value的形状必须已知。
use_resource:如果为False,则创建常规变量。如果为true,则使用定义良好的语义创建实验性ResourceVariable。默认为False(稍后将更改为True)。在Eager模式下,此参数始终强制为True。
custom_getter:Callable,它将第一个参数作为true getter,并允许覆盖内部get_variable方法。 custom_getter的签名应与此方法的签名相匹配,但最适合未来的版本将允许更改:def custom_getter(getter,* args,** kwargs)。也允许直接访问所有get_variable参数:def custom_getter(getter,name,* args,** kwargs)。一个简单的身份自定义getter只需创建具有修改名称的变量是:python def custom_getter(getter,name,* args,** kwargs):return getter(name +’_suffix’,* args,** kwargs)

tf.variable_scope( name_or_scope,default_name=None,values=None,initializer=None,regularizer=None,caching_device=None,partitioner=None,custom_getter=None,reuse=None,dtype=None,use_resource=None,constraint=None,auxiliary_name_scope=True)

用于定义创建变量(层)的操作的上下文管理器。
此上下文管理器验证(可选)values是否来自同一图形,确保图形是默认的图形,并推送名称范围和变量范围。
如果name_or_scope不是None,则使用as is.如果scope是None,则使用default_name.在这种情况下,如果以前在同一范围内使用过相同的名称,则通过添加_N来使其具有唯一性。
变量范围允许您创建新变量并共享已创建的变量,同时提供检查以防止意外创建或共享。
name_or_scope:string或者VariableScope表示打开的范围。
default_name:如果name_or_scope参数为None,则使用默认的名称,该名称将是唯一的;如果提供了name_or_scope,它将不会被使用,因此它不是必需的,并且可以是None.
values:传递给操作函数的Tensor参数列表。
initializer:此范围内变量的默认初始值设定项。
regularizer:此范围内变量的默认正规化器。
caching_device:此范围内变量的默认缓存设备。
partitioner:此范围内变量的默认分区程序。
custom_getter:此范围内的变量的默认自定义吸气。
reuse:可以是True、None或tf.AUTO_REUSE;如果是True,则我们进入此范围的重用模式以及所有子范围;如果是tf.AUTO_REUSE,则我们创建变量(如果它们不存在),否则返回它们;如果是None,则我们继承父范围的重用标志.当启用紧急执行时,该参数总是被强制为tf.AUTO_REUSE。
dtype:在此范围中创建的变量类型(默​​认为传入范围中的类型,或从父范围继承)。
use_resource:如果为false,则所有变量都将是常规变量;如果为true,则将使用具有明确定义的语义的实验性 ResourceVariables.默认为false(稍后将更改为true).当启用紧急执行时,该参数总是被强制为true。
constraint:一个可选的投影函数,在被Optimizer(例如用于实现层权重的范数约束或值约束)更新之后应用于该变量.该函数必须将代表变量值的未投影张量作为输入,并返回投影值的张量(它必须具有相同的形状).进行异步分布式培训时,约束条件的使用是不安全的。
auxiliary_name_scope:如果为True,则我们用范围创建一个辅助名称范围;如果为False,则我们不接触名称范围。
例1-创建一个新变量:

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
        assert v.name == "foo/bar/v:0"

例2-共享变量AUTO_REUSE:

def foo():
  with tf.variable_scope("foo", reuse=tf.AUTO_REUSE):
    v = tf.get_variable("v", [1])
  return v

v1 = foo()  # Creates v.
v2 = foo()  # Gets the same, existing v.
assert v1 == v2

例3-使用reuse=True共享变量:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
with tf.variable_scope("foo", reuse=True):
    v1 = tf.get_variable("v", [1])
assert v1 == v

例4-通过捕获范围并设置重用来共享变量:

with tf.variable_scope("foo") as scope:
    v = tf.get_variable("v", [1])
    scope.reuse_variables()
    v1 = tf.get_variable("v", [1])
assert v1 == v

例5-为了防止意外共享变量,我们在获取非重用范围中的现有变量时引发异常.

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
    v1 = tf.get_variable("v", [1])
    #  Raises ValueError("... v already exists ...")

例6-我们在尝试获取重用模式中不存在的变量时引发异常.

with tf.variable_scope("var", reuse=True):
    v = tf.get_variable("v", [1])
    #  Raises ValueError("... v does not exists ...")

tf.concat(values,concat_dim,name=‘concat’)

values:表示要拼接的tensor。
concat_dim:表示在哪个维度上进行拼接。
例1

import tensorflow as tf

v1=[[[1,1,1],[2,2,2],[3,3,3]],[[4,4,4],[5,5,5],[6,6,6]]]
v2=[[[7,7,7],[8,8,8],[9,9,9]],[[10,10,10],[11,11,11],[12,12,12]]]
w1=tf.concat([v1, v2], 0)
w2=tf.concat([v1, v2], 1)
w3=tf.concat([v1, v2], 2)
init=tf.global_variables_initializer()
with tf.Session() as session:
    init.run()
    print('w1:',w1.eval())
    print('\nw2:',w2.eval())
    print('\nw3:',w3.eval())
#结果:
'''
w1: [[[ 1  1  1]
  [ 2  2  2]
  [ 3  3  3]]

 [[ 4  4  4]
  [ 5  5  5]
  [ 6  6  6]]

 [[ 7  7  7]
  [ 8  8  8]
  [ 9  9  9]]

 [[10 10 10]
  [11 11 11]
  [12 12 12]]]

w2: [[[ 1  1  1]
  [ 2  2  2]
  [ 3  3  3]
  [ 7  7  7]
  [ 8  8  8]
  [ 9  9  9]]

 [[ 4  4  4]
  [ 5  5  5]
  [ 6  6  6]
  [10 10 10]
  [11 11 11]
  [12 12 12]]]

w3: [[[ 1  1  1  7  7  7]
  [ 2  2  2  8  8  8]
  [ 3  3  3  9  9  9]]

 [[ 4  4  4 10 10 10]
  [ 5  5  5 11 11 11]
  [ 6  6  6 12 12 12]]]
'''

tf.reshape(tensor,shape,name=None)

重塑张量。
给定tensor,这个操作返回一个张量,它与带有形状shape的tensor具有相同的值。
如果shape的一个分量是特殊值-1,则计算该维度的大小,以使总大小保持不变。特别地情况为,一个[-1]维的shape变平成1维。至多能有一个shape的分量可以是-1。
如果shape是1-D或更高,则操作返回形状为shape的张量,其填充为tensor的值。在这种情况下,隐含的shape元素数量必须与tensor元素数量相同。
tensor:一个Tensor。
shape:一个Tensor;必须是以下类型之一:int32,int64;用于定义输出张量的形状。
name:操作的名称(可选)。

tf.contrib.legacy_seq2seq.sequence_loss_by_example(logits,targets,weights,average_across_timesteps=True,softmax_loss_function=None,name=None)

对于logits(per example)序列的权重交叉熵loss。
logits:2-D tensor list,shape为[batch_size, num_decoder_symbols]。
targets:1-D batch-sized int32 tensor list,与logits长度相同。
weights:1-D batch-sized float tensor list,与logits长度相同
average_across_timesteps:如果为True,则将返回的cost除以总的label权重。
softmax_loss_function:Function (labels, logits) -> loss-batch to be used instead of the standard softmax (the default if this is None)。
注意,为了避免混淆,函数必须接受name参数
name:可选项,操作名字。默认为"sequence_loss_by_example"。

tf.trainable_variables()

这个函数用于查看可训练的变量

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

Gradient Clipping的引入是为了处理gradient explosion(不是gradients vanishing)的问题。当在一次迭代中权重的更新过于迅猛的话,很容易导致loss divergence。Gradient Clipping的直观作用就是让权重的更新限制在一个合适的范围。在我们做求导的时候,会遇到一种情况,求导函数突然变得特别陡峭,是不是意味着下一步的进行会远远高于正常值,这个函数的意义在于,在突然变得陡峭的求导函数中,加上一些判定,如果过于陡峭,就适当减小求导步伐。
返回值:list_clipped:裁剪过list of tensors,与list_t类型相同;global_norm:0-D (scalar) Tensor,表示global norm。
t_list[i]的更新公式:t_list[i] * clip_norm / max(global_norm, clip_norm)
t_list:梯度tensor。
clip_norm:截取比例。
use_norm:使用的范数。
name:操作名字。

optimizer.compute_gradients(loss,var_list=None,gate_gradients=GATE_OP,aggregation_method=None,colocate_gradients_with_ops=False,grad_loss=None)

简单说该函数就是用于计算loss对于指定val_list的导数的,最终返回的是元组列表,即[(gradient, variable),…]。
loss:优化的目标tensor。
var_list:list或tuple。为针对最小化loss,所要进行更新的变量list或者tuple。默认是该图下的变量列表。
gate_gradients:选通梯度,用于控制在应用这个梯度时并行化的程度。其值可以选 GATE_NONE,GATE_OP与GATE_GRAPH。
aggregation_method:Specifies the method used to combine gradient terms. Valid values are defined in the class AggregationMethod.
colocate_gradients_with_ops:If True, try colocating gradients with the corresponding op。
grad_loss:(可选)A Tensor holding the gradient computed for loss。

optimizer.apply_gradients(grads_and_vars,global_step=None,name=None)

该函数的作用是将compute_gradients()返回的值作为输入参数对variable进行更新。
grads_and_vars:以(gradient, variable)pair为元素的list,其作为compute_gradients()的返回值。
global_step:可选变量,用于记录变量更新的次数。
name:(可选)操作名字。默认为Optimizer constructor。

tf.contrib.framework.get_or_creat_global_step(graph=None)

graph:函数创建graph中的global step。如果为None,则表示于默认图中。

tf.assign (ref ,value ,validate_shape = None ,use_locking = None ,name = None)

通过将 “value” 赋给 “ref” 来更新 “ref”。
此操作输出在赋值后保留新值 “ref” 的张量.这使得更易于链接需要使用重置值的操作。
ref:一个可变的张量.应该来自变量节点。节点可能未初始化。
value:tensor。必须具有与 ref 相同的类型.是要分配给变量的值。
validate_shape:一个可选的 bool。默认为 True.如果为 true,则操作将验证 “value” 的形状是否与分配给的张量的形状相匹配;如果为 false, “ref” 将对 “值” 的形状进行引用。
use_locking:一个可选的 bool。默认为 True。如果为 True,则分配将受锁保护;否则, 该行为是未定义的, 但可能会显示较少的争用。
name:操作的名称(可选)。

tf.gradients(ys,xs,grad_ys=None,name=‘gradients’,colocate_gradients_with_ops=False,gate_gradients=False,aggregation_method=None,stop_gradients=None)

在 xs 中构造了 ys 的w.r.t. x和的符号偏导数。
ys 和 xs 是一个张量或一个张量的列表。grad_ys是一个张量列表,持有由ys接收的梯度。该列表必须与ys具有相同长度。
gradients()向图形添加操作以输出ys相对于的偏导数xs。它返回长度为len(xs)的张量列表,其中每个张量ys中y的sum(dy/dx)。
grad_ys是与ys相同长度的张量列表,它包含 y 的初始梯度。当 grad_ys是None时,我们在ys中为每个y填入一个1的形状的张量。用户可以提供自己的初始grad_ys,使用不同的初始梯度为每个y计算导数(例如:如果你想为每个 y 中的每个值不同地加权梯度)。
返回值:该函数返回 xs 中每个 x 的 sum(dy/dx) 的列表。
ys:要区分的张量或者张量列表。
xs:用于微分的张量或者张量列表。
grad_ys:(可选)与 ys 具有相同大小的张量或张量列表,并且对 ys 中的每个 y 计算的梯度。
name:用于将所有渐变操作组合在一起的可选名称.默认为“渐变”。
colocate_gradients_with_ops:如果为 True,请尝试使用相应的操作对齐梯度。
gate_gradients:如果为True,则在操作返回的梯度周围添加一个元组.这避免了一些竞态条件。
aggregation_method:指定用于组合渐变项的方法。接受的值是在类AggregationMethod中定义的常量。

代码实现部分

实现思路

代码主要分为3部分。
第一部分定义类class PTBInput(object),对输入模型的数据信息进行封装。
第二部分定义类class PTBModel(object),用于定义模型的网络结构。
第三部分定义函数def run_epoch(session,model,eval_op=None,verbose=False),用于进行训练。
首先DataSets是一个包含1万个不同单词的文本。在读取数据集后将数据集进行切分成长度相同的句子,句子的长度为timesteps,即RNN细胞展开的步长。接下来设定一个batch的大小batch_size。明确样本是单词后,规定train与label,train为样本词a,a为batch_i中sentence_j的word_k(即第i个batch中,第j个sentence中的第k个work),那么a所对应的label为b,b为batch_i+1中sentence_ j的word_k。
代码的关键部分在于模型网络的构建,使用BasicLSTMCell作为基本的神经元,每层BasicLSTMCell的神经元数量为size,一共堆叠num_layers层BasicLSTMCell,如果是在训练状态下,则在输入层与第一层BasicLSTMCell之间,每层BasicLSTMCell之后加入dropout操作。在对RNN神经网络的创建中有个难点,变量的复用,如果使用下方代码中输入数据的方式,那么需要注意变量复用。最后加上一个全连接的softmax层,最后的输出代表每个train在预测中所对应的label对于整个字典每个词的概率。最后在训练方式上面,为了防止梯度爆炸的情况发生,故将optimizer.minimize分为了两步走,用以控制梯度的大小。

代码

以下为完整代码,以及注释,但不包括reader,reader主要进行一些数据预处理的操作,例如数据切分。

# -*- coding: utf-8 -*-
"""
Created on Fri Jul 26 14:30:40 2019

@author: Administrator
"""
#cd C:\Users\Administrator\learngit\models\tutorials\rnn\ptb

import time
import numpy as np
import tensorflow as tf
import reader

#语言模型处理输入数据
#num_steps为展开步数
#使用reader.ptb_producer获得特征数据,每次执行都会获得一个batch的数据
class PTBInput(object):
    
    def __init__(self,config,data,name=None):
        self.batch_size=batch_size=config.batch_size
        self.num_steps=num_steps=config.num_steps
        
        #epoch_size表示每个epoch内需要多少轮训练迭代
        self.epoch_size=((len(data)//batch_size)-1)//num_steps
        
        #样本与标签的关系,标签词是样本词在下一个epoch中,同一time_step下的词
        #例:样本词a为batch_i中sample_j的word_k,
        #那么a所对应的标签b为batch_(i+1)中sample_j的word_k
        self.input_data,self.targets=reader.ptb_producer(\
                                    data,batch_size,num_steps,name=name)

#LSTM语言模型
class PTBModel(object):
    
    #is_training为训练标记,config为参数配置,input_为PTBInput实例
    def __init__(self,is_training,config,input_):
        self._input=input_
        
        batch_size=input_.batch_size
        num_steps=input_.num_steps
        size=config.hidden_size
        vocab_size=config.vocab_size
        
        #设置默认的BasicLSTMCell
        def lstm_cell():
            return tf.contrib.rnn.BasicLSTMCell(\
                                size,forget_bias=0.0,state_is_tuple=True)
        attn_cell=lstm_cell
        
        #如果在训练状态中且keep_prob<1,则在LSTM层后面接一个Dropout层
        if is_training and config.keep_prob<1:
            def attn_cell():
                return tf.contrib.rnn.DropoutWrapper(\
                                lstm_cell(),output_keep_prob=config.keep_prob)
        
        #使用RNN堆叠函数将前面构造的BasicLSTMCell层堆叠得到多层结构,cell
        #堆叠次数为config中的num_layers
        cell=tf.contrib.rnn.MultiRNNCell(\
                [attn_cell() for _ in range(config.num_layers)],\
                state_is_tuple=True)
        
        #设置LSTM单元的初始化状态为0
        #返回[batch_size, 2*len(cells)],或者[batch_size, s]
        #解释一下此处为何会是2*len(cells),我们将一层LSTM看成一个整体,
        #那么针对每层LSTM,在导入input_data后会有两个返回值c_state,m_state
        self._initial_state=cell.zero_state(batch_size,tf.float32)
        
        #创建网络的词嵌入embedding部分,限定在cpu上进行
        #初始化embedding,行数为vocab_size,列数为size=hidden_size
        with tf.device('/cpu:0'):
            #此处embedding虽然没有初始化,但是在之后会对整个图进行初始化
            embedding=tf.get_variable(\
                            'embedding',[vocab_size,size],\
                            dtype=tf.float32)
            inputs=tf.nn.embedding_lookup(embedding,input_.input_data)
        
        #如果为训练状态,就再加上一层Dropout
        if is_training and config.keep_prob<1:
            inputs=tf.nn.dropout(inputs,config.keep_prob)
        
        #定义输出
        outputs=[]
        state=self._initial_state

        #将接下来的操作名字设置为RNN
        with tf.variable_scope('RNN'):
            for time_step in range(num_steps):
                
                #设置复用变量,LSTM同一层参数共享
                if time_step>0:tf.get_variable_scope().reuse_variables()
                
                #注意,这里的inpus有3个维度,
                #第1个维度代表为batch中第几个样本,即第几个句子,
                #第2个维度代表样本中的第几个单词,
                #第3个代表单词的向量表达的维度
                #这里我们得到输出与更新后的state
                (cell_output,state)=cell(inputs[:,time_step,:],state)
                outputs.append(cell_output)
        
        #第一步tf.concat(outputs,1)将outputs在axis=1,
        #合并为shape=[batch_size,size*num_steps]的大矩阵,
        #第二步通过tf.reshape将其转换为很长的shape=[batch_size*num_steps,size]向量
        #batch_size代表一个batch有多少个句子,num_steps代表每个句子的长度,即多少字
        output=tf.reshape(tf.concat(outputs,1),[-1,size])
        
        #定义softmax层,该层设置vocab_size个神经元
        #logits的shape=[batch_size*num_steps,vocab_size]
        softmax_w=tf.get_variable(\
                        'softmax_w',[size,vocab_size],dtype=tf.float32)
        softmax_b=tf.get_variable('softmax_b',[vocab_size],dtype=tf.float32)
        logits=tf.matmul(output,softmax_w)+softmax_b
        
        #定义loss
        #tf.reshape(input_.targets,[-1])的shape=[batch_size*num_steps],代表每个word的分类
        loss=tf.contrib.legacy_seq2seq.sequence_loss_by_example(\
                            [logits],\
                            [tf.reshape(input_.targets,[-1])],\
                            [tf.ones([batch_size*num_steps],dtype=tf.float32)])
        self._cost=cost=tf.reduce_sum(loss)/batch_size
        self._final_state=state
        
        if not is_training:
            return
        
        #定义学习速率_lr(不可训练),
        #使用tf.trainable_variables()获得全部可训练的参数tvars
        self._lr=tf.Variable(0.0,trainable=False)
        tvars=tf.trainable_variables()
        
        #设置梯度的最大范数,即Gradient Clipping的方法,控制梯度的最大范数,
        #某种程度上起到正则化的效果,
        #Gradient Clipping可以防止Gradient Explosion的问题
        grads,_=tf.clip_by_global_norm(tf.gradients(cost,tvars),\
                                       config.max_grad_norm)
        optimizer=tf.train.GradientDescentOptimizer(self._lr)
        self._train_op=optimizer.apply_gradients(zip(grads,tvars),\
                global_step=tf.contrib.framework.get_or_create_global_step())
        
        #设置_new_lr用于控制学习速率
        self._new_lr=tf.placeholder(\
                        tf.float32,shape=[],name='new_learning_rate')
        self._lr_update=tf.assign(self._lr,self._new_lr)
    
    #定义assign_lr用以在外部控制模型的学习速率
    def assign_lr(self,session,lr_value):
        session.run(self._lr_update,feed_dict={self._new_lr:lr_value})
    
    #装饰器部分,将返回变量设置为只读,防止修改变量引发的问题
    @property
    def input(self):
        return self._input
    
    @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

#设置小、中、大三种规模的模型参数
class SmallConfig(object):
    init_scale=0.1#网络中权重值的初始scale
    learning_rate=1.0#学习率的初始值
    max_grad_norm=5#梯度的最大范数
    num_layers=2#LSTM可以堆叠的层数
    num_steps=20#LSTM梯度反向传播的展开步数
    hidden_size=200#LSTM的隐含节点数
    max_epoch=4#初始学习率可以训练的epoch数,在此后需要调整学习率
    max_max_epoch=13#总共可以训练的epoch数
    keep_prob=1.0#dropout层的保留节点比例
    lr_decay=0.5#学习率的衰减速度
    batch_size=20#每个batch中的样本数量
    vocab_size=10000#词汇表大小
    
class MediumConfig(object):
    init_scale=0.05
    learning_rate=1.0
    max_grad_norm=5
    num_layers=2
    num_steps=35
    hidden_size=650
    max_epoch=6
    max_max_epoch=39
    keep_prob=0.5
    lr_decay=0.8
    batch_size=20
    vocab_size=10000

class LargeConfig(object):
    init_scale=0.04
    learning_rate=1.0
    max_grad_norm=10
    num_layers=2
    num_steps=35
    hidden_size=1500
    max_epoch=14
    max_max_epoch=55
    keep_prob=0.35
    lr_decay=1/1.15
    batch_size=20
    vocab_size=10000

#测试模型运行
class TestConfig(object):
    init_scale=0.1
    learning_rate=1.0
    max_grad_norm=1
    num_layers=1
    num_steps=2
    hidden_size=2
    max_epoch=1
    max_max_epoch=1
    keep_prob=1.0
    lr_decay=0.5
    batch_size=20
    vocab_size=10000

#记录当前时间,初始化损失与迭代数
#verbose用于判断是否在训练过程中
def run_epoch(session,model,eval_op=None,verbose=False):
    start_time=time.time()
    
    #损失
    costs=0.0
    
    #迭代数
    iters=0
    
    #初始化模型状态
    state=session.run(model.initial_state)
    
    #创建输出结果的字典表
    fetches={'cost':model.cost,'final_state':model.final_state}
    #eval_op为评测操作,如果有评测操作,则加入字典表
    if eval_op is not None:
        fetches['eval_op']=eval_op
        
    for step in range(model.input.epoch_size):
        feed_dict={}
        for i,(c,h) in enumerate(model.initial_state):
            feed_dict[c]=state[i].c
            feed_dict[h]=state[i].h
        
        vals=session.run(fetches,feed_dict)
        cost=vals['cost']
        state=vals['final_state']
        
        costs+=cost
        iters+=model.input.num_steps
        
        if verbose and step%(model.input.epoch_size//10)==10:
            print('%.3f perplexity:%.3f speed: %.0f wps'%\
                  (step*1.0/model.input.epoch_size,np.exp(costs/iters),\
                   iters*model.input.batch_size/(time.time()-start_time)))
    return np.exp(costs/iters)

raw_data=reader.ptb_raw_data('simple-examples/data/')
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():
    initializer=tf.random_uniform_initializer(\
                                -config.init_scale,config.init_scale)
    #训练部分
    with tf.name_scope('Train'):
        train_input=PTBInput(config=config,data=train_data,name='TrainInput')
        with tf.variable_scope('Model',reuse=None,initializer=initializer):
            m=PTBModel(is_training=True,config=config,input_=train_input)
    
    #验证部分
    with tf.name_scope('Valid'):
        valid_input=PTBInput(config=config,data=valid_data,name='ValidInput')
        with tf.variable_scope('Model',reuse=True,initializer=initializer):
            mvalid=PTBModel(is_training=False,config=config,input_=valid_input)
    
    #测试部分
    with tf.name_scope('Test'):
        test_input=PTBInput(config=eval_config,data=test_data,name='TestInput')
        with tf.variable_scope('Model',reuse=True,initializer=initializer):
            mtest=PTBModel(is_training=False,\
                           config=eval_config,input_=test_input)
            
    sv=tf.train.Supervisor()
    with sv.managed_session() as session:
        for i in range(config.max_max_epoch):
            lr_decay=config.lr_decay**max(i+1-config.max_epoch,0.0)
            m.assign_lr(session,config.learning_rate*lr_decay)
            print('i,lr_decay,lr:',i,lr_decay,session.run(m.lr))
            
            print('Epoch: %d Learning rate:%.3f'%(i+1,session.run(m.lr)))
            train_perplexity=run_epoch(\
                            session,m,eval_op=m.train_op,verbose=True)
            print('Epoch:%d Train Perplexity:%.3f'%(i+1,train_perplexity))
            valid_perplexity=run_epoch(session,mvalid)
            print('Epoch:%d Valid Perplexity:%.3f'%(i+1,valid_perplexity))
            
        test_perplexity=run_epoch(session,mtest)
        print('Test Perplexity:%.3f'%test_perplexity)

补充几句

模型所用到的数据,我尝试用reader里的函数下载,但是失败了,故我直接在reader.py中找到了数据下载地址,直接从网站上将数据下载为本地文件。

环境

电脑型号:Lenovo Y410P i5,GTX755M
操作系统:windows 10
python版本:3.7.3
tensorflow版本:1.13.1
Anaconda版本:4.7.10

参考资料

Tensorflow实战,黄文坚 唐源 著

你可能感兴趣的:(自学,Tensorflow,RNN)