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被调用则需要该参数。
所谓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时才使用这个参数。
该函数用于计算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:此操作的名称(可选)。
构建多重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)
设置LSTM单元的初始化状态,返回[batch_size, 2*len(cells)],或者[batch_size, s]
获取一个已经存在的变量或者创建一个新的变量。
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)
用于定义创建变量(层)的操作的上下文管理器。
此上下文管理器验证(可选)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 ...")
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]]]
'''
重塑张量。
给定tensor,这个操作返回一个张量,它与带有形状shape的tensor具有相同的值。
如果shape的一个分量是特殊值-1,则计算该维度的大小,以使总大小保持不变。特别地情况为,一个[-1]维的shape变平成1维。至多能有一个shape的分量可以是-1。
如果shape是1-D或更高,则操作返回形状为shape的张量,其填充为tensor的值。在这种情况下,隐含的shape元素数量必须与tensor元素数量相同。
tensor:一个Tensor。
shape:一个Tensor;必须是以下类型之一:int32,int64;用于定义输出张量的形状。
name:操作的名称(可选)。
对于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"。
这个函数用于查看可训练的变量
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:操作名字。
简单说该函数就是用于计算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。
该函数的作用是将compute_gradients()返回的值作为输入参数对variable进行更新。
grads_and_vars:以(gradient, variable)pair为元素的list,其作为compute_gradients()的返回值。
global_step:可选变量,用于记录变量更新的次数。
name:(可选)操作名字。默认为Optimizer constructor。
graph:函数创建graph中的global step。如果为None,则表示于默认图中。
通过将 “value” 赋给 “ref” 来更新 “ref”。
此操作输出在赋值后保留新值 “ref” 的张量.这使得更易于链接需要使用重置值的操作。
ref:一个可变的张量.应该来自变量节点。节点可能未初始化。
value:tensor。必须具有与 ref 相同的类型.是要分配给变量的值。
validate_shape:一个可选的 bool。默认为 True.如果为 true,则操作将验证 “value” 的形状是否与分配给的张量的形状相匹配;如果为 false, “ref” 将对 “值” 的形状进行引用。
use_locking:一个可选的 bool。默认为 True。如果为 True,则分配将受锁保护;否则, 该行为是未定义的, 但可能会显示较少的争用。
name:操作的名称(可选)。
在 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实战,黄文坚 唐源 著