TensorFlow构建循环神经网络

原创 2017年08月29日 16:06:04

前言

前面在《循环神经网络》文章中已经介绍了深度学习的循环神经网络模型及其原理,接下去这篇文章将尝试使用TensorFlow来实现一个循环神经网络,该例子能通过训练给定的语料生成模型并实现对字符的预测。这里选择使用最原始的循环神经网络RNN模型。

语料库的准备

这里就简单用纪伯伦的《On Friendship》作为语料吧。

RNN简要说明

用下面两张图简要说明下,RNN模型有多个时刻的输入,从第一个图中看到输入x、隐层s和输出o都与时刻t有关,可以看到上一时刻的隐含层会影响到当前时刻的输出,这也就使循环神经网络具有记忆功能。

为了更加清晰理解,看第二个图,从神经元来看RNN,输入层有3个神经元,隐含层有4个神经元,输出层有2个神经元,保留当前时刻状态参与下一时刻计算。

创建词汇

def create_vocab(text):
    unique_chars = list(set(text))
    print(unique_chars)
    vocab_size = len(unique_chars)
    vocab_index_dict = {}
    index_vocab_dict = {}
    for i, char in enumerate(unique_chars):
        vocab_index_dict[char] = i
        index_vocab_dict[i] = char
    return vocab_index_dict, index_vocab_dict, vocab_size

处理字符首先就是需要创建包含语料中所有的词的词汇,需要一个从字符到词汇位置索引的词典,也需要一个从位置索引到字符的词典。

词汇保存及读取

def save_vocab(vocab_index_dict, vocab_file):
    with codecs.open(vocab_file, 'w', encoding='utf-8') as f:
        json.dump(vocab_index_dict, f, indent=2, sort_keys=True)

def load_vocab(vocab_file):
    with codecs.open(vocab_file, 'r', encoding='utf-8') as f:
        vocab_index_dict = json.load(f)
    index_vocab_dict = {}
    vocab_size = 0
    for char, index in iteritems(vocab_index_dict):
        index_vocab_dict[index] = char
        vocab_size += 1
    return vocab_index_dict, index_vocab_dict, vocab_size

第一次创建词汇后我们需要将它保存下来,后面在使用模型预测时需要读取该词汇,如果不保存而每次都创建的话则可能导致词汇顺序不同。

批量生成器

class BatchGenerator(object):
    def __init__(self, text, batch_size, seq_length, vocab_size, vocab_index_dict):
        self._text = text
        self._text_size = len(text)
        self._batch_size = batch_size
        self.vocab_size = vocab_size
        self.seq_length = seq_length
        self.vocab_index_dict = vocab_index_dict

        segment = self._text_size // batch_size
        self._cursor = [offset * segment for offset in range(batch_size)]
        self._last_batch = self._next_batch()

    def _next_batch(self):
        batch = np.zeros(shape=(self._batch_size), dtype=np.float)
        for b in range(self._batch_size):
            batch[b] = self.vocab_index_dict[self._text[self._cursor[b]]]
            self._cursor[b] = (self._cursor[b] + 1) % self._text_size
        return batch

    def next(self):
        batches = [self._last_batch]
        for step in range(self.seq_length):
            batches.append(self._next_batch())
        self._last_batch = batches[-1]
        return batches

创建一个批量生成器用于将文本生成批量的训练样本,其中text为整个语料,batch_size为批大小,vocab_size为词汇大小,seq_length为序列长度,vocab_index_dict为词汇索引词典。生成器的生成结构大致如下图,按文本顺序竖着填进矩阵,而矩阵的列大小为batch_size。

这里写图片描述

构建图

cell_fn = tf.contrib.rnn.BasicRNNCell
cell = cell_fn(hidden_size)
cells = [cell]
for i in range(rnn_layers - 1):
    higher_layer_cell = cell_fn(hidden_size)
    cells.append(higher_layer_cell)
multi_cell = tf.contrib.rnn.MultiRNNCell(cells)
self.zero_state = multi_cell.zero_state(self.batch_size, tf.float32)

这里我们仅仅使用基础的RNN,所以直接实例化一个BasicRNNCell对象,需要指定隐含层的神经元数hidden_size,另外因为单层的RNN学习能力有限,这里可以设置网络的层数rnn_layers来增强神经网络的学习能力,最终用MultiRNNCell封装起来。

接着需要给我们的multi cell进行初始化状态,初始状态全设为0,即用multi_cell.zero_state来实现。这里需要传入一个batch_size参数,它会生成rnn_layers层的(batch_size ,hidden_size)个初始状态。

self.initial_state = create_tuple_placeholders_with_default(multi_cell.zero_state(self.batch_size, tf.float32), shape=multi_cell.state_size)
self.input_data = tf.placeholder(tf.int64, [self.batch_size, self.seq_length], name='inputs')
self.targets = tf.placeholder(tf.int64, [self.batch_size, self.seq_length], name='targets')

def create_tuple_placeholders_with_default(inputs, shape):
    if isinstance(shape, int):
        result = tf.placeholder_with_default(
            inputs, list((None,)) + [shape])
    else:
        subplaceholders = [create_tuple_placeholders_with_default(
            subinputs, subshape)
            for subinputs, subshape in zip(inputs, shape)]
        t = type(shape)
        if t == tuple:
            result = t(subplaceholders)
        else:
            result = t(*subplaceholders)
    return result

接着我们开始创建占位符,有三个占位符需要创建,分别为初始状态占位符、输入占位符和target占位符。首先看初始状态占位符,这个主要是根据multi_cell.zero_state(self.batch_size, tf.float32)的结构使用tf.placeholder_with_default创建。其次看输入占位符,与批大小和序列长度相关的结构[batch_size, seq_length]。最后是target占位符,结构与输入占位符是一样的。为更好理解这里给输入和target画个图,如下:

这里写图片描述

这里写图片描述

self.embedding = tf.get_variable('embedding', [vocab_size, embedding_size])
inputs = tf.nn.embedding_lookup(self.embedding, self.input_data)

一般我们会需要一个嵌入层将词汇嵌入到指定的维度空间上,维度由embedding_size指定。同时vocab_size为词汇大小,这样就可以将所有单词都映射到指定的维数空间上。嵌入层结构如下图,通过tf.nn.embedding_lookup就能找到输入对应的词空间向量了,这里解释下embedding_lookup操作,它会从词汇中取到inputs每个元素对应的词向量,inputs为2维的话,通过该操作后变为3维,因为已经将词用embedding_size维向量表示了。

这里写图片描述

sliced_inputs = [tf.squeeze(input_, [1]) for input_ in
                         tf.split(axis=1, num_or_size_splits=self.seq_length, value=inputs)]
outputs, final_state = tf.contrib.rnn.static_rnn(multi_cell, sliced_inputs, initial_state=self.initial_state)

上面得到的3维的嵌入层空间向量,我们无法直接传入循环神经网络,需要一些处理。需要根据序列长度切割,通过split后再经过squeeze操作后得到一个list,这个list就是最终要进入到循环神经网络的输入,list的长度为seq_length,这个很好理解,就是由这么多个时刻的输入。每个输入的结构为(batch_size,embedding_size),也即是(20,128)。注意这里的embedding_size,刚好也是128,与循环神经网络的隐含层神经元数量一样,这里不是巧合,而是他们必须要相同,这样嵌入层出来的矩阵输入到神经网络才能刚好与神经网络的各个权重完美相乘。最终得到循环神经网络的输出和最终状态。

flat_outputs = tf.reshape(tf.concat(axis=1, values=outputs), [-1, hidden_size])
flat_targets = tf.reshape(tf.concat(axis=1, values=self.targets), [-1])

softmax_w = tf.get_variable("softmax_w", [hidden_size, vocab_size])
softmax_b = tf.get_variable("softmax_b", [vocab_size])
self.logits = tf.matmul(flat_outputs, softmax_w) + softmax_b

loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits, labels=flat_targets)
mean_loss = tf.reduce_mean(loss)

经过2层循环神经网络得到了输出outputs,但该输出是一个list结构,我们要通过tf.reshape转成tf张量形式,该张量结构为(200,128)。同样target占位符也要连接起来,结构为(200,)。接着构建softmax层,权重结构为[hidden_size, vocab_size],偏置项结构为[vocab_size],输出矩阵与权重矩阵相乘并加上偏置项得到logits,然后使用sparse_softmax_cross_entropy_with_logits计算交叉熵损失,最后求损失平均值。

count = tf.Variable(1.0, name='count')
sum_mean_loss = tf.Variable(1.0, name='sum_mean_loss')
update_loss_monitor = tf.group(sum_mean_loss.assign(sum_mean_loss + mean_loss), count.assign(count + 1),
                                       name='update_loss_monitor')
with tf.control_dependencies([update_loss_monitor]):
    self.average_loss = sum_mean_loss / count
self.global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0.0))

这里逻辑比较清晰了,用于计算平均损失,另外global_step变量用于记录训练的全局步数。

tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(mean_loss, tvars), max_grad_norm)
optimizer = tf.train.AdamOptimizer(self.learning_rate)
self.train_op = optimizer.apply_gradients(zip(grads, tvars), global_step=self.global_step)

最后使用优化器对损失函数进行优化。为了防止梯度爆炸或梯度消失需要用clip_by_global_norm对梯度进行修正。

所以最后构建的图可以用下面的图大致描述。
这里写图片描述

创建会话

with tf.Session(graph=graph) as session:
tf.global_variables_initializer().run()
for i in range(num_epochs):
    model.train(session, train_size, train_batches)

def train(self, session, train_size, train_batches):
    epoch_size = train_size // (self.batch_size * self.seq_length)
    if train_size % (self.batch_size * self.seq_length) != 0:
        epoch_size += 1
    state = session.run(self.zero_state)
    start_time = time.time()
    for step in range(epoch_size):
        data = train_batches.next()
        inputs = np.array(data[:-1]).transpose()
        targets = np.array(data[1:]).transpose()
        ops = [self.average_loss, self.final_state, self.train_op, self.global_step, self.learning_rate]
        feed_dict = {self.input_data: inputs, self.targets: targets,
                     self.initial_state: state}
        average_loss, state, __, global_step, lr = session.run(ops, feed_dict)

创建会话开始训练,设置需要训练多少轮,由num_epochs指定。epoch_size为完整训练一遍语料库需要的轮数。运行self.zero_state得到初始状态,通过批量生成器获取一批样本数据,因为当前时刻的输入对应的正确输出为下一时刻的值,所以用data[:-1]和data[1:]得到输入和target。组织ops并将输入、target和状态对应输入到占位符上,执行。

预测

module_file = tf.train.latest_checkpoint(restore_path)
model_saver.restore(session, module_file)
start_text = 'your'
length = 20
print(model.predict(session, start_text, length, vocab_index_dict, index_vocab_dict))

将前面训练的模型保存下来后就可以加载该模型并且进行RNN预测了。

github

https://github.com/sea-boat/DeepLearning-Lab

========广告时间========

公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以购买。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

欢迎关注:

这里写图片描述

版权声明:本文为博主原创文章,未经博主允许不得转载。

tensorflow实现循环神经网络

一般的RNN存在长期依赖问题,为了解决这个问题,LSTM被设计出来

基于循环神经网络实现基于字符的语言模型(char-level RNN Language Model)-tensorflow实现

前面几章介绍了卷积神经网络在自然语言处理中的应用,这是因为卷积神经网络便于理解并且易上手编程,大多教程(比如tensorflow的官方文档就先CNN再RNN)。但RNN的原理决定了它先天就适合做自然语...

TensorFlow循环神经网络系列实践教程

《How to build a Recurrent Neural Network in TensorFlow》by Erik Hallström

深度学习(07)_RNN-循环神经网络-02-Tensorflow中的实现

关于基本的RNN和LSTM的概念和BPTT算法可以查看这里 本文个人博客地址:http://lawlite.me/2017/06/16/RNN-%E5%BE%AA%E7%8E%AF%E7%A5%9E%...

TensorFlow在MNIST中的应用-循环神经网络RNN

参考: 1. 《TensorFlow技术解析与实战》 2. https://www.cnblogs.com/hellcat/p/7401706.html 3. http://ww...

Tensorflow创建循环神经网络

虽然已经接触deep learning很长一段时间了,也看了很久rnn相关的代码,但是突然想用tensorflow实现一些功能的时候,突然发现丝毫没有头绪,找了一些资料,学习了一波,记录一下。一、te...

双向循环神经网络tensorflow实现

双向循环神经网络不仅能捕获当前状态与之前状态的联系,也能捕获与之后的状态的联系。...

RNN-LSTM循环神经网络-03Tensorflow进阶实现

目录(?)[-] 一模型说明和数据处理 1模型说明2数据处理 二使用tfscan函数和dynamic_rnn 1为什么使用tfscan和dynamic_rnn2使用list的方式static_r...

深入浅出Tensorflow(五):循环神经网络简介

http://www.infoq.com/cn/articles/introduction-of-tensorflow-part05?utm_campaign=rightbar_v2&utm_sour...

[TensorFlow]入门学习笔记(5)-循环神经网络RNN

前言关于循环神经网络的理论推导和证明,推荐去看论文。参考资料。 https://colah.github.io/posts/2015-08-Understanding-LSTMs/ https://r...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:TensorFlow构建循环神经网络
举报原因:
原因补充:

(最多只允许输入30个字)