前言
  
  学习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. PHP获取指定日期是星期几的实现方法

    这篇文章主要介绍了PHP获取指定日期是星期几的实现方法,涉及php针对日期的读取.判断与字符串.数组相关运算操作技巧,需要的朋友可以参考下 本文实例讲述了PHP获取指定日期是星期几的实现方法.分享给大 ...

  2. AngularJS-指令command

    directive: 匹配模式restrict:'AEMC'默认为A template templateUrl templateCache:把模板缓存起来,共多个指令使用 var myModule = ...

  3. javaScript-进阶篇(一)

    1.变量 1.必须以字母.下划线或美元符号开头,后面可以跟字母.下划线.美元符号和数字. 2.变量名区分大小写,如:A与a是两个不同变量. 3.不允许使用JavaScript关键字和保留字做变量名. ...

  4. QTableWidget设计原则

    一.组成结构: 列表控件由水平表头(self.horizontalHeader()).垂直表头(self.verticalHeader())和单元格(QTableWidgetItem)组成 其中表头又 ...

  5. 判断iOS系统的Model

    获取iOS系统的Model   (参考网址:https://www.theiphonewiki.com/wiki/Models) + (NSString *)getModel{ struct utsn ...

  6. loadrunner手动生成脚本函数

    1.点击insert

  7. DDoS攻防战(二):CC攻击工具实现与防御理论--删除

    我们将要实现一个进行应用层DDoS攻击的工具,综合考虑,CC攻击方式是最佳选择,并用bash shell脚本来快速实现并验证这一工具,并在最后,讨论如何防御来自应用层的DDoS攻击. 第一步:获取大量 ...

  8. ssh-keygen和ssh-copy-id的简单使用

    实验环境是CentOS7: ssh-keygen产生公钥和私钥对. ssh-copy-id:将本机的公钥使用ssh协议复制到远程的客户端,ssh协议的公钥和私钥一般存放于~/.ssh下 #主机 [ro ...

  9. UML核心元素--包

    包是一种容器,如同文件夹一样,将某些信息分类,形成逻辑单元.包可以容纳任何UML元素,例如用例.业务实体.类图等,也包括子包. 一.分包原则: (1)高内聚:被分入同一个包的元素相互联系紧密,伸至不可 ...

  10. SVN使用技巧和参考文档总结

    以下文章为网上收集: myEclipse 8.5下SVN环境的搭建(重点推荐) SVN建立版本库,配置用户和权限 Tortoise SVN使用方法,简易图解 版本控制软件SVN使用方法详解 学习笔记 ...