分享

Tensorlfow 实现基于LSTM的语言模型

 雪柳花明 2017-07-08
# -*- coding: utf-8 -*-
import time
import numpy as np
import tensorflow as tf
import ptb.reader as reader


flags = tf.app.flags
FLAGS = flags.FLAGS

logging = tf.logging

flags.DEFINE_string("save_path", './Out',
"Model output directory.")
flags.DEFINE_bool("use_fp16", False,
"Train using 16-bit floats instead of 32bit floats")



def data_type():
return tf.float16 if FLAGS.use_fp16 else tf.float32

#定义语言模型处理的输入数据的class
class PTBInput(object):
"""The input data."""
#初始化方法
#读取config中的batch_sizenum_steps到本地变量。
def __init__(self, config, data, name=None):
self.batch_size = batch_size = config.batch_size
self.num_steps = num_steps = config.num_steps #num_stepsLSTM的展开步数
#计算每个epoch内需要多好轮训练的迭代
self.epoch_size = ((len(data) // batch_size) - 1) // num_steps
#通过ptb_reader获取特征数据input_datalabel数据targets
self.input_data, self.targets = reader.ptb_producer(
data, batch_size, num_steps, name=name)

#定义语言模型的class
class PTBModel(object):
"""The PTB model."""
#训练标记,配置参数,ptb类的实例input_
def __init__(self, is_training, config, input_):
self._input = input_

batch_size = input_.batch_size
num_steps = input_.num_steps
size = config.hidden_size #hidden_sizeLSTM的节点数
vocab_size = config.vocab_size #vocab_size是词汇表

# Slightly better results can be obtained with forget gate biases
# initialized to 1 but the hyperparameters of the model would need to be
# different than reported in the paper.
def lstm_cell():
#使用tf.contrib.rnn.BasicLSTMCell设置默认的LSTM单元
return tf.contrib.rnn.BasicLSTMCell(
size, forget_bias=0.0, state_is_tuple=True)
#state_is_tuple表示接受和返回的state将是2-tuple的形式

attn_cell = lstm_cell
#如果训练状态且Dropoutkeep_prob小于1,则在前面的lstm_cell之后接一个DropOut层,
#这里的做法是调用tf.contrib.rnn.DropoutWrapper函数
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堆叠函数tf.contrib.rnn.MultiRNNCell将前面构造的lstm_cell多层堆叠得到cell
#堆叠次数,为config中的num_layers.
cell = tf.contrib.rnn.MultiRNNCell(
[attn_cell() for _ in range(config.num_layers)], state_is_tuple=True)
#这里同样将state_is_tuple设置为True

#并掉用cell.zero_state设置LSTM单元的初始化状态为0
self._initial_state = cell.zero_state(batch_size, tf.float32)
#这里需要注意,LSTM单元可以读入一个单词并结合之前存储的状态state计算下一个单词出现的概率,
#并且每次读取一个单词后它的状态state会被更新

#创建网络的词embedding部分,embedding即为将one-hot的编码格式的单词转化为向量的表达形式
#这部分操作在GPU中实现
with tf.device("/cpu:0"):
#初始哈embedding矩阵,其行数设置词汇表数vocab_size,列数(每个单词的向量表达的维数)设为hidden_size
#hidden_sizeLSTM单元中的隐含节点数一致# 在训练过程中,embedding的参数可以被优化和更新。
embedding = tf.get_variable(
"embedding", [vocab_size, size], dtype=tf.float32)

#接下来是使用tf.nn.embedding_lookup查询单词对应的向量表达获得inputs
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)

# Simplified version of models/tutorials/rnn/rnn.py's rnn().
# This builds an unrolled LSTM for tutorial purposes only.
# In general, use the rnn() or state_saving_rnn() from rnn.py.
#
# The alternative version of the code below is:
#
# inputs = tf.unstack(inputs, num=num_steps, axis=1)
# outputs, state = tf.nn.rnn(cell, inputs,
# initial_state=self._initial_state)

#定义输出outputs
outputs = []
state = self._initial_state
#首先使用tf.variable_scope将接下来的名称设为RNN
with tf.variable_scope("RNN"):
#为了控制训练过程,我们会限制梯度在反向传播时可以展开的步数为一个固定的值,而这个步数也是num_steps
#这里设置一个循环,长度为num_steps,来控制梯度的传播
for time_step in range(num_steps):
#并且从第二次循环开始,我们使用tf.get_variable_scope().reuse_variables()设置复用变量
if time_step > 0: tf.get_variable_scope().reuse_variables()
#每次循环内,我们传入inputsstate到堆叠的LSTM单元即(cell)中
#注意,inputs有三个维度,第一个维度是batch中的低级个样本,
#第二个维度代表是样本中的第几个单词,第三个维度是单词的向量表达的维度。
#inputs[:, time_step, :]代表所有样板的第time_step个单词
(cell_output, state) = cell(inputs[:, time_step, :], state)
#这里我们得到输出cell_output和更新后的state

outputs.append(cell_output)
#最后我们将结果cell_output添加到输出列表ouputs

#output的内容用tf.contact串联到一起,并使用tf.reshape将其转为一个很长的一维向量
output = tf.reshape(tf.concat(outputs, 1), [-1, size])
#接下来是softmax层,先定义权重softmax_w和偏置softmax_b
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)
#然后使用tf.matmul将输出output乘上权重并加上偏置得到logits
logits = tf.matmul(output, softmax_w) + softmax_b
#这里直接使用tf.contrib.legacy_seq2seq.sequence_loss_by_example计算输出logits
#targets的偏差

loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
[logits],
[tf.reshape(input_.targets, [-1])],
[tf.ones([batch_size * num_steps], dtype=tf.float32)])
# 这里的sequence_losstarget wordsaverge negative log probability,

self._cost = cost = tf.reduce_sum(loss) / batch_size
#然后再使用tf.reduce_sum汇总batch的误差。
self._final_state = state

if not is_training:
return
#如果此时不是训练状态,直接返回。

#定义学习速率的变量lr,并将其设为不可训练
self._lr = tf.Variable(0.0, trainable=False)

#再使用tf.trainable_variables获取所有可训练的参数tvars
tvars = tf.trainable_variables()
#针对前面得到的cost,计算tvars的梯度,并用tf.clip_by_global_norm设置梯度的最大范数max_grad_norm
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
config.max_grad_norm)
#这即是Gradient Clipping的方法,控制梯度的最大范数,某种程度上起到正则化的效果。
#Gradient Clipping可防止Gradient Explosion梯度爆炸的问题,如果对梯度不加限制,
#则可能会因为迭代中梯度过大导致训练难以收敛

#然后定义GradientDescent优化器
optimizer = tf.train.GradientDescentOptimizer(self._lr)

#再创建训练操作_train_op,用optimizer.apply_gradients将前面clip过的梯度应用到所有可训练的参数tvars上,
#然后使用tf.contrib.framework.get_or_create_global_step()生成全局统一的训练步数
self._train_op = optimizer.apply_gradients(
zip(grads, tvars),
global_step=tf.contrib.framework.get_or_create_global_step())

#设置一个_new_lrplaceholder用以控制学习速率。
self._new_lr = tf.placeholder(
tf.float32, shape=[], name="new_learning_rate")
#同时定义个assign_lr的函数,用以在外部控制模型的学习速率
self._lr_update = tf.assign(self._lr, self._new_lr)

# 同时定义个assign_lr的函数,用以在外部控制模型的学习速率
#方式是将学习速率值传入_new_lr这个place_holder,并执行_update_lr操作完成对学习速率的修改
def assign_lr(self, session, lr_value):
session.run(self._lr_update, feed_dict={self._new_lr: lr_value})

#模型定义完毕,再定义定义这个PTBModel class的一些property
#python中的@property装饰器可以将返回变量设为只读,防止修改变量引发的问题

#这里定义input,initial_state,cost,lr,final_state,train_opproperty,方便外部访问
@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):
"""Small config."""
init_scale = 0.1 #网络中权重值的初始Scale
learning_rate = 1.0 #学习速率的初始值
max_grad_norm = 5 #前面提到的梯度的最大范数
num_layers = 2 # num_layersLSTM可以堆叠的层数
num_steps = 20 #LSTM梯度反向传播的展开步数
hidden_size = 200 #LSTM的隐含节点数
max_epoch = 4 #是初始学习速率的可训练的epoch数,在此之后需要调整学习速率
max_max_epoch = 13 #总共可以训练的epoch
keep_prob = 1.0 #keep_probdorpout层的保留节点的比例
lr_decay = 0.5 #学习速率的衰减速率
batch_size = 20 #每个batch中样板的数量
vocab_size = 10000

#具体每个参数的值,在不同的配置中对比才有意义

#在中等模型中,我们减小了init_state,即希望权重初值不要过大,小一些有利于温和的训练
#学习速率和最大梯度范数不变,LSTM层数不变。
#这里将梯度反向传播的展开步数从20增大到35.
#hidden_sizemax_max_epoch也相应地增大约3;同时,这里开始设置dropoutkeep_prob0.5
#而之前设置1,即没有dropout;
#因为学习迭代次数的增大,因此将学习速率的衰减速率lr_decay也减小了。
#batch_size和词汇表vocab_size的大小保持不变
class MediumConfig(object):
"""Medium config."""
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

#大型模型,进一步缩小了init_scale并大大放宽了最大梯度范数max_grad_norm10
#同时将hidden_size提升到了1500,并且max_epoch,max_max_epoch也相应增大了;
#keep_drop也因为模型复杂度的上升继续下降,学习速率的衰减速率lr_decay也进一步减小
class LargeConfig(object):
"""Large config."""
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


#TstConfig只是测试用,参数都尽量使用最小值,只是为了测试可以完整允许模型
class TstConfig(object):
"""Tiny config, for testing."""
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

#定义训练一个epoch数据的函数run_epoch
def run_epoch(session, model, eval_op=None, verbose=False):
"""Runs the model on the given data."""
# 记录当前时间,初始化损失costs和迭代数iters
start_time = time.time()
costs = 0.0
iters = 0
state = session.run(model.initial_state)
#并执行model.initial_state来初始化状态并获得初始状态

#接着创建输出结果的字典表fetches
#其中包括costfinal_state
fetches = {
"cost": model.cost,
"final_state": model.final_state,
}
#如果有评测操作eval_op,也一并加入fetches
if eval_op is not None:
fetches["eval_op"] = eval_op

# 接着进行循环训练中,次数为epoch_size
for step in range(model.input.epoch_size):
feed_dict = {}
#在每次循环中,我们生成训练用的feed_dict

for i, (c, h) in enumerate(model.initial_state):
feed_dict[c] = state[i].c
feed_dict[h] = state[i].h

# 将全部的LSTM单元的state加入feed_dict,然后传入feed_dict并执行
# fetchees对网络进行一次训练,并且拿到coststate
vals = session.run(fetches, feed_dict)
cost = vals["cost"]
state = vals["final_state"]

#我们累加costcosts,并且累加num_stepsiters
costs += cost
iters += model.input.num_steps

#我们每完成约10%epoch,就进行一次结果的展示,依次展示当前epoch的进度,
# perplexity(即平均cost的自然常数指数,语言模型性能的重要指标,越低代表模型输出的概率分布在预测样本上越好)
#和训练速度(单词/s)

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)))
# 最后返回perplexity作为函数的结果
return np.exp(costs / iters)


#使用reader.ptb_raw_data直接读取解压后的数据,得到训练数据,验证数据和测试数据
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
#需要注意的是测试配置eval_config需和训练配置一致
#这里将测试配置的batch_sizenum_steps修改为1


#创建默认的Graph,并使用tf.random_uniform_initializer设置参数的初始化器
with tf.Graph().as_default():
initializer = tf.random_uniform_initializer(-config.init_scale,
config.init_scale)

with tf.name_scope("Train"):
#使用PTBInputPTBModel创建一个用来训练的模型m
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)
#tf.scalar_summary("Training Loss", m.cost)
#tf.scalar_summary("Learning Rate", m.lr)

with tf.name_scope("Valid"):
# 使用PTBInputPTBModel创建一个用来验证的模型mvalid
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)
#tf.scalar_summary("Validation Loss", mvalid.cost)

with tf.name_scope("Tst"):
# 使用PTBInputPTBModel创建一个用来验证的模型Tst
test_input = PTBInput(config=eval_config, data=test_data, name="TstInput")
with tf.variable_scope("Model", reuse=True, initializer=initializer):
mtst = PTBModel(is_training=False, config=eval_config,
input_=test_input)

#其中训练和验证模型直接使用前面的cofig,测试模型则使用前面的测试配置eval_config


sv = tf.train.Supervisor()
#使用tf.train.Supervisor创建训练的管理器sv

#并使用sv.managed_session()创建默认的session
with sv.managed_session() as session:
#再执行训练多个epoch数据的循环
for i in range(config.max_max_epoch):
#在每个epoch循环内,我们先计算累计的学习速率衰减值,
#这里只需要计算超过max_epoch的轮数,再求lr_decay的超出轮数次幂即可
#然后将初始学习速率乘以累计的衰减,并更新学习速率。
lr_decay = config.lr_decay ** max(i + 1 - config.max_epoch, 0.0)
m.assign_lr(session, config.learning_rate * lr_decay)

#在循环内执行一个epoch的训练和验证,并输出当前的学习速率,训练和验证即的perplexity
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))

#在完成全部训练之后,计算并输出模型在测试集上的perplexity
tst_perplexity = run_epoch(session, mtst)
print("Test Perplexity: %.3f" % tst_perplexity)
#
# if FLAGS.save_path:
# print("Saving model to %s." % FLAGS.save_path)
# sv.saver.save(session, FLAGS.save_path, global_step=sv.global_step)

if __name__ == "__main__":
tf.app.run()

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多