前言
  
  学习RNN的时候很多人应该都有看过Andrej Karpathy写的The Unreasonable Effectiveness of Recurrent Neural Networks,使用基于字符粒度的RNN让机器学会自己生成文本,比如令自己训练的RNN学会写歌词、写代码、写小说、写诗,听着就很新奇。
  
  github上虽然已经有实现好的Char RNN,比如
  
  但是想要学习,最好的方式就是自己动手实现一遍。自己写一遍好处还是很多的,比如加深对RNN(LSTM)的理解,可以熟悉深度学习的框架。因为我主要用tensorflow,所以就基于tensorflow实现了一遍Char-RNN。
  
  注:本文使用的tensorflow版本为1.0.0
  
  个人经验,在实现的过程中最好是抛开别人代码的影响,只根据基本理论以及所用的框架的API文档一步步把代码写出来跑通,这样自己的收益才是最大的。
  
  模型选择
  
  要让机器生成文本,本质上是需要一个语言模型。语言模型可以用来评估一句话是自然语言的概率,即根据一句话中已观测到的词,预测下一个词出现的概率。也就是要能够处理序列数据,根据已有的序列数据,推断接下来可能的数据。如一句话“已经到了午餐时间,我正准备去吃{?}”,根据前面的描述,可以推断“吃”字背后是要接上可食用的东西,并且是可以作为午餐的,可能是“饭”、“面”等等,通常不可能是“汽车”、“树木”之类…因此我们需要一个能够处理序列数据,并且能够抽象出过去序列与任务相关方面的信息,再根据这些信息预测未来的模型。
  
  神经网络中,RNN天然适合用于处理序列数据,它可以提取任意长度序列(x(t),x(t−1),...,x(1))的摘要,选择性地精确保留过去序列的某些方面。而保留这些信息的方式则是通过RNN内部的隐藏状态。
  
  但是RNN又有很多变体,因为基本RNN只有一个隐藏状态,对长距离的记忆效果不好,在模型参数迭代优化的时候存在梯度弥散的问题,因此又有了采用LSTM单元的RNN以及其他的变体,如GRU等等。
  
  因此,在Char RNN的实践当中,就选用LSTM作为基本的模型。
  
  因为tensorflow中已经实现了LSTM的单元,如果不是为了学习LSTM的原理,可以不需要自己去实现它。相应的API为
  
  模型定义
  
  我们需要定义一个class用来定义网络的结构,以及实现inference的接口。如果初次接触RNN,刚开始动手写的时候可能会一头雾水,我们已经有了LSTM的API,怎么把它拓展成可以接受文本的训练数据进行训练,最后再根据输入的一些文字,输出接下来文字的模型呢?
  
  我的做法是先明确输入与输出,以及我所知道的必备要素,然后再把它们衔接拼凑起来。
  
  基本LSTM单元
  
  首先我们要用到LSTMCell,它的必填参数是num_units,也就是每个LSTM Cell中的单元数,与输入向量的维度是一致的。我们的输入是词向量,维度是我们自己定义的,这里用一个参数rnn_size来表示。定义基本LSTM Cell的代码如下
  
  # 定义基本lstm单元
  
  lstm_cell_list = [tf.contrib.rnn.LSTMCell(rnn_size) for _ in xrange(layer_size)]
  
  # 使用MultiRNNCell 接口连接多层lstm, 并加上dropout
  
  self.cell = tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.MultiRNNCell(lstm_cell_list), output_keep_prob=output_keep_prob)
  
  明确输入
  
  在训练的过程中,每次都feed进一个batch的数据,batch的大小也是我们定义的,用batch_size表示,因此LSTM模型所接受输入的shape为(batch_size, rnn_size)。
  
  如果我们使用预训练好的词向量作为输入,那么这里就可以写成
  
  tf.input_data = tf.placeholder(tf.float32, shape=[batch_size, rnn_size], name='input_data')
  
  但我们希望词向量可以在train的过程中被改变,更适应我们的训练数据。那就要用Variable来表示词向量矩阵。因此我们要定义一个变量来表示,词向量矩阵的维度应该是 vocab_size * rnn_size。 即每一行代表一个词,列数就是我们需要自己定义的词向量维度。定义了词向量矩阵的变量,每次输入的时候,还需要为输入的词找到对应的词向量,这些tensorflow都为我们封装好了,代码如下
  
  embedding = tf.Variable(tf.truncated_normal([vocab_size, rnn_size], stddev=0.1), name='embedding')
  
  inputs = tf.nn.embedding_lookup(embedding, self.input_data)
  
  tf.nn.embedding_lookup这个函数就是用于返回所查找的词向量Tensor的。
  
  embedding_lookup(params, ids, partition_strategy=’mod’, name=None, validate_indices=True, max_norm=None)
  
  其中params是词向量矩阵,ids是需要需要查找的词的id。举个简单的例子如下
  
  # 假设有词向量空间x
  
  x = [[1.0,2.0,3.0],[4.0,5.0,6.0],[7.0,8.0,9.0]]
  
  vx = tf.Variable(x, name='vx')
  
  ids = tf.placeholder(tf.int32, name='ids')
  
  inputs = tf.nn.embedding_lookup(vx, ids)
  
  # 假如每个batch有3个句子,每个句子有两个词,词的id如下
  
  input_data = [[0,1],[1,2],[0,2]]
  
  with tf.Session() as sess:
  
  sess.run(tf.global_variables_initializer())
  
  sess.run(inputs, feed_dict={ids:input_data})
  
  # 输出结果如下
  
  >>> array([[[ 1., 2., 3.],
  
  [ 4., 5., 6.]],
  
  [[ 4., 5., 6.],
  
  [ 7., 8., 9.]],
  
  [[ 1., 2., 3.],
  
  [ 7., 8., 9.]http://www.ysbyl.biz/]], dtype=float32)
  
  输出结果的shape为(3,2,3)
  
  用上述方式就可以查出来一个batch中每个句子的每个词对应的词向量。所以我们原始输入的batch中,每个元素是一个sequence,sequence中的元素又是每个词对应的id。
  
  这部分的完整代码如下
  
  self.input_data = tf.placeholder(tf.int32, shape=[batch_size, sequence_length], name='input_data')
  
  # 指定这部分使用CPU进行计算
  
  with tf.device('/cpu:0'):
  
  embedding = tf.Variable(tf.truncated_normal([vocab_size, rnn_size], stddev=0.1), name='embedding')
  
  inputs = tf.nn.embedding_lookup(embedding, self.input_data)
  
  明确输出
  
  因为在Char RNN中,每一时刻的输出都是下一时刻的输入,因此LSTM的输出ot与输入xt维度是一样的。但ot并不是Char RNN模型的输出,ot之后还需要跟全连接层以及softmax层来判断每个词出现的概率。每一时刻都有一个输出,在训练的阶段,需要收集每一时刻的输出,以便与targets进行比较来计算loss。因此需要有一个循环来展开整个lstm。展开的这部分tensorflow也有API可以调用,但是为了更好的理解,还是自己实现一遍比较好。代码如下
  
  # 定义初始状态
  
  self.initial_state = self.cell.zero_state(batch_size, tf.float32)
  
  with tf.variable_scope('RNN'):
  
  for time_step in xrange(sequence_length):
  
  # 因为LSTM Cell调用__call__(027yeshenghuowang.com)方法时,会使用到get_variable()获取内部变量
  
  # 如果reuse的flag是False,调用get_variable()后会查找该variable_scope中有没有重名的变量,如果有就报错
  
  # 如果reuse的flag是True,调用get_variable()后则是在当前的variable_scope找不到变量时报错
  
  # 因此在这部分需要reuse的时候要定义一个variable_scope,否则之后想用get_variable()定义新变量都会报错
  
  if time_step > 0:
  
  tf.get_variable_scope().reuse_variables()
  
  if time_step == 0:
  
  output, state = self.cell(inputs[:, time_step, :], self.initial_state)
  
  else:
  
  output, state = self.cell(inputs[:, time_step, :], state)
  
  outputs.append(output)
  
  self.final_state = state
  
  softmax_w = tf.Variable(tf.truncated_normal([rnn_size, vocab_size], stddev=0.1), name='softmax_w')
  
  softmax_b = tf.Variable(tf.zeros([vocab_size]www.22yigouyule.cn/), name='softmax_b')
  
  # 执行完循环以后,outputs的shape=(sequence_length, batch_size, rnn_size)
  
  # 而matmul接受的矩阵的rank必须是2,因此还需要做一下转换
  
  # tf.concat()转换后的outputs的shape为(batch_size * sequence_size, rnn_size)
  
  outputs = tf.concat(outputs, 0)
  
  self.logits = tf.matmul(outputs, softmax_w) + softmax_b
  
  self.prob = tf.nn.softmax(self.logits)
  
  定义loss与train_op
  
  要定义loss函数首先要有正确的输入,因此先定义targets。在实际feed的时候,要注意targets中的顺序必须与outputs中预测结果是对应的。这个之后写一个辅助函数来对输入的targets进行转换。
  
  loss函数的定义使用cross_entropy,tensorflow中有相应的API tf.losses.softmax_cross_entropy, 这个API封装了softmax步骤,因此应该传入logits而不是把softmax之后的prob传进去。
  
  定义完loss之后就需要定义optimizer与train_op。
  
  通常可以直接train_op = tf.train.AdamOptimizer(self.lr).minimize(self.cost)。但是RNN的训练中很有可能因为梯度过大导致训练过程不稳定而不收敛,因此需要对计算出的梯度做一步裁剪,再手动更新梯度。
  
  这部分的代码如下
  
  self.targets = tf.placeholder(tf.int32, shape=[None, vocab_size], name='targets')
  
  self.cost = tf.losses.softmax_cross_entropy(self.targets, self.logits)
  
  self.lr = tf.Variable(0.0, trainable=False)
  
  tvars = tf.trainable_variables(www.yihuanyule.cn)
  
  grads, _ = tf.clip_by_global_norm(tf.gradients(www.xuancayule.com self.cost, tvars), grad_clip)
  
  optimizer = tf.train.AdamOptimizer(self.lr)
  
  self.train_op = optimizer.apply_gradients(zip(grads, tvars))
  
  -
  
  到这里为止Char RNN的主要部分,即模型的结构及其训练所需的op都定义完成了, 还剩下inference的接口,以及启动训练模型的部分未完成。

github上虽然已经有实现好的Char RNN的更多相关文章

  1. 清空Github上某个文件的历史版本

    title: 清空Github上某个文件的历史版本 author: 青南 date: 2015-01-08 16:04:53 categories: [经验] tags: [Github,histor ...

  2. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(四)-使用Travis自动部署Hexo(2)

    前言 前面一篇文章介绍了Travis自动部署Hexo的常规使用教程,也是个人比较推荐的方法. 前文最后也提到了在Windows系统中可能会有一些小问题,为了在Windows系统中也可以实现使用Trav ...

  3. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(三)-使用Travis自动部署Hexo(1)

    前言 前面两篇文章介绍了在github上使用hexo搭建博客的基本环境和hexo相关参数设置等. 基于目前,博客基本上是可以完美运行了. 但是,有一点是不太好,就是源码同步问题,如果在不同的电脑上写文 ...

  4. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(二)-Hexo参数设置

    前言 前文手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置介绍了github注册.git相关设置以及hexo基本操作. 本文主要介绍一下hexo的常用参数设置. ...

  5. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置

    前言 有朋友问了我关于博客系统搭建相关的问题,由于是做开发相关的工作,我给他推荐的是使用github的gh-pages服务搭建个人博客. 推荐理由: 免费:github提供gh-pages服务是免费的 ...

  6. 命令行提交本地项目到github上

    1.github账号要有. 2.配置ssh key ①  defaults write com.apple.finder AppleShowAllFiles -bool true     终端 显示隐 ...

  7. git+github上传与管理

    1.首先下载并安装git,方便管理github上的代码 https://git-scm.com/downloads 2.然后点击安装好的git bash(注册好自己的github) git confi ...

  8. 【iOS 使用github上传代码】详解

    [iOS 使用github上传代码]详解 一.github创建新工程 二.直接添加文件 三.通过https 和 SSH 操作两种方式上传工程 3.1https 和 SSH 的区别: 3.1.1.前者可 ...

  9. Github上传代码菜鸟超详细教程

      最近需要将课设代码上传到Github上,之前只是用来fork别人的代码. 这篇文章写得是windows下的使用方法. 第一步:创建Github新账户 第二步:新建仓库 第三部:填写名称,简介(可选 ...

随机推荐

  1. SpringBoot_异常_01_Caused by: java.lang.BootstrapMethodError: java.lang.NoSuchMethodError

    一.异常信息 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gsonB ...

  2. listen and translation exercise 49

    Huh? Appears to Be Universally Understood What's the most universal utterance in languages across th ...

  3. codeforces 627B B. Factory Repairs(线段树)

    B. Factory Repairs time limit per test 4 seconds memory limit per test 256 megabytes input standard ...

  4. Android: 利用SurfaceView绘制股票滑动直线解决延迟问题

    1.背景介绍 最近项目要绘制股票走势图,并绘制能够跟随手指滑动的指示线(Indicator)来精确查看股票价格和日期.如下图所示: 上图中的那条白色直线就是股票的指示线,用来跟随手指精确确定股票的时间 ...

  5. TYVJ P1728 普通平衡树

    P1728 普通平衡树 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 此为平衡树系列第一道:普通平衡树 描述 您需要写一种数据结构(可参考题目标题),来维护 ...

  6. Gym 101142 I.Integral Polygons(计算几何)

    题意:给定一个凸包,现在让你连接凸包上两点,把凸包变为两个多边形,满足两个多边形的面积都是整数. 思路:我们知道整点的三角形面积S=叉积/2,则S要么是整数,要么是整数+0.5.那么多边形有多个三角形 ...

  7. ACM学习历程—HDU5407 CRB and Candies(数论)

    Problem Description CRB has N different candies. He is going to eat K candies.He wonders how many co ...

  8. ACM学习历程—HDU 5012 Dice(ACM西安网赛)(bfs)

    Problem Description There are 2 special dices on the table. On each face of the dice, a distinct num ...

  9. [转]JS的内存泄露处理

    问题: 1.给DOM对象添加的属性是一个对象的引用.范例: var MyObject = {}; document.getElementByIdx_x('myDiv').myProp = MyObje ...

  10. JSR-303规范,Bean Validation

    一: JSR 303是JAVA EE 6中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator,此实现与Hibernate ORM没有任何关系.JSR ...