最近在看RNN模型,为简单起见,本篇就以简单的二进制序列作为训练数据,而不实现具体的论文仿真,主要目的是理解RNN的原理和如何在TensorFlow中构造一个简单基础的模型架构。其中代码参考了这篇博客。

数据集

首先我们看一下实验数据的构造:

输入数据X:在时间t,Xt的值有50%的概率为1,50%的概率为0;

输出数据Y:在实践t,Yt的值有50%的概率为1,50%的概率为0,除此之外,如果`Xt-3 == 1`,Yt为1的概率增加50%, 如果`Xt-8 == 1`,则Yt为1的概率减少25%, 如果上述两个条件同时满足,则Yt为1的概率为75%。

可知,Y与X有两个依赖关系,一个是t-3,一个是t-8。我们实验的目的就是检验RNN能否捕捉到Y与X之间的这两个依赖关系。实验使用交叉熵作为评价标准,则有下面三条理想的实验结果:

如果RNN没有学习到任何一条依赖,那么Yt为1的概率就是0.625(0.5+0.5*0.5-0.5*0.25),所以所获得的交叉熵应该是0.66(-(0.625 * np.log(0.625) + 0.375 * np.log(0.375)))。

如果RNN学习到第一条依赖关系,即Xt-3为1时Yt一定为1。那么,所以最终的交叉熵应该是0.52(-0.5 * (0.875 * np.log(0.875) + 0.125 * np.log(0.125)) -0.5 * (0.625 * np.log(0.625) + 0.375 * np.log(0.375)))。

如果RNN学习到了两条依赖, 那么有0.25的概率全对,0.5的概率正确率是75%,还有0.25的概率正确率是0.5。所以其交叉熵为0.45(-0.50 * (0.75 * np.log(0.75) + 0.25 * np.log(0.25)) - 0.25 * (2 * 0.50 * np.log (0.50)) - 0.25 * (0))。

数据预处理

这部分主要是生成实验数据,并将其按照RNN模型的输入格式进行切分和batch化。代码入下:

1,生成实验数据:

def gen_data(size=100000):X = np.array(np.random.choice(2, size=(size,)))Y = []for i in range(size):threshold = 0.5#判断X[i-3]和X[i-8]是否为1,修改阈值if X[i-3] == 1:threshold += 0.5if X[i-8] == 1:threshold -= 0.25#生成随机数,以threshold为阈值给Yi赋值if np.random.rand() > threshold:Y.append(0)else:Y.append(1)return X, np.array(Y)

接下来将生成的数据按照模型参数设置进行切分,这里需要用得到的参数主要包括:batch_size和num_steps,分别是批量数据大小和RNN每层rnn_cell循环的次数,也就是下图中Sn中n的大小。代码入下:

def gen_batch(raw_data, batch_size, num_steps):#raw_data是使用gen_data()函数生成的数据,分别是X和Yraw_x, raw_y = raw_datadata_length = len(raw_x)# 首先将数据切分成batch_size份,0-batch_size,batch_size-2*batch_size。。。batch_partition_length = data_length // batch_sizedata_x = np.zeros([batch_size, batch_partition_length], dtype=np.int32)data_y = np.zeros([batch_size, batch_partition_length], dtype=np.int32)for i in range(batch_size):data_x[i] = raw_x[batch_partition_length * i:batch_partition_length * (i + 1)]data_y[i] = raw_y[batch_partition_length * i:batch_partition_length * (i + 1)]#因为RNN模型一次只处理num_steps个数据,所以将每个batch_size在进行切分成epoch_size份,每份num_steps个数据。注意这里的epoch_size和模型训练过程中的epoch不同。 epoch_size = batch_partition_length // num_steps#x是0-num_steps, batch_partition_length -batch_partition_length +num_steps。。。共batch_size个for i in range(epoch_size):x = data_x[:, i * num_steps:(i + 1) * num_steps]y = data_y[:, i * num_steps:(i + 1) * num_steps]yield (x, y)#这里的n就是训练过程中用的epoch,即在样本规模上循环的次数
def gen_epochs(n, num_steps):for i in range(n):yield gen_batch(gen_data(), batch_size, num_steps)

根据上面的代码我们可以看出来,这里的数据划分并没有将数据完全的按照原先的数据顺序,而是每隔一段取num_steps个数据,这样组成的batch进行训练==这里是为了省事还是另有原因还有待后面学习中考证。

模型构建

RNN的具体原理我们就不再进行赘述,主要是隐层状态和输入连接后计算新的隐层状态和输出。这里用的是单层的RNN。公式和原理图如下所示:

St=tanh(W(Xt @ St−1)+bs)
Pt=softmax(USt+bp)

至于使用TensorFlow构建RNN模型,主要就是定义rnn_cell类型,然后将其复用即可。代码如下所示:

x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
#RNN的初始化状态,全设为零。注意state是与input保持一致,接下来会有concat操作,所以这里要有batch的维度。即每个样本都要有隐层状态
init_state = tf.zeros([batch_size, state_size])#将输入转化为one-hot编码,两个类别。[batch_size, num_steps, num_classes]
x_one_hot = tf.one_hot(x, num_classes)
#将输入unstack,即在num_steps上解绑,方便给每个循环单元输入。这里可以看出RNN每个cell都处理一个batch的输入(即batch个二进制样本输入)
rnn_inputs = tf.unstack(x_one_hot, axis=1)#定义rnn_cell的权重参数,
with tf.variable_scope('rnn_cell'):W = tf.get_variable('W', [num_classes + state_size, state_size])b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
#使之定义为reuse模式,循环使用,保持参数相同
def rnn_cell(rnn_input, state):with tf.variable_scope('rnn_cell', reuse=True):W = tf.get_variable('W', [num_classes + state_size, state_size])b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))#定义rnn_cell具体的操作,这里使用的是最简单的rnn,不是LSTMreturn tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)state = init_state
rnn_outputs = []
#循环num_steps次,即将一个序列输入RNN模型
for rnn_input in rnn_inputs:state = rnn_cell(rnn_input, state)rnn_outputs.append(state)
final_state = rnn_outputs[-1]#定义softmax层
with tf.variable_scope('softmax'):W = tf.get_variable('W', [state_size, num_classes])b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
#注意,这里要将num_steps个输出全部分别进行计算其输出,然后使用softmax预测
logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]
predictions = [tf.nn.softmax(logit) for logit in logits]# Turn our y placeholder into a list of labels
y_as_list = tf.unstack(y, num=num_steps, axis=1)#losses and train_step
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=label, logits=logit) for \logit, label in zip(logits, y_as_list)]
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)

模型训练

定义好我们的模型之后,接下来就是将数据传入,然后进行训练,代码入下:

def train_network(num_epochs, num_steps, state_size=4, verbose=True):with tf.Session() as sess:sess.run(tf.global_variables_initializer())training_losses = []#得到数据,因为num_epochs==1,所以外循环只执行一次for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps)):training_loss = 0#保存每次执行后的最后状态,然后赋给下一次执行training_state = np.zeros((batch_size, state_size))if verbose:print("\nEPOCH", idx)#这是具体获得数据的部分,应该会执行1000000//200//5 = 1000次,即每次执行传入的数据是batch_size*num_steps个(1000),共1000000个,所以每个num_epochs需要执行1000次。for step, (X, Y) in enumerate(epoch):tr_losses, training_loss_, training_state, _ = \sess.run([losses,total_loss,final_state,train_step],feed_dict={x:X, y:Y, init_state:training_state})training_loss += training_loss_if step % 100 == 0 and step > 0:if verbose:print("Average loss at step", step,"for last 250 steps:", training_loss/100)training_losses.append(training_loss/100)training_loss = 0return training_losses
training_losses = train_network(1,num_steps)
plt.plot(training_losses)
plt.show()

实验结果如下所示:

从上图可以看出交叉熵最终稳定在0。52,按照我们上面的分析可以知道:RNN模型成功的学习到了第一条依赖关系,因为我们的循环步长选择的是5,所以他只能学习到t-3的第一条依赖关系,而无法学习到t-8的第二条依赖。 
接下来可以尝试num_steps==10,区捕捉第二条依赖关系。最终的结果图如下所示:

从上图可以看出,我们的RNN模型成功的学习到了两条依赖关系。最终的交叉熵未定在0.46附近。

几点改进

1,首先上面的代码中,为了尽可能详细的解释TensorFlow中RNN模型的构造方法,将rnn_cell的定义写的很详细,其实这些工作tf已经封装好了,我们只需要一行命令就可以实现,所以第一个要改进的地方就是将rnn_cell的定义和循环使用部分的代码简化:

#定义rnn_cell的权重参数,
with tf.variable_scope('rnn_cell'):W = tf.get_variable('W', [num_classes + state_size, state_size])b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
#使之定义为reuse模式,循环使用,保持参数相同
def rnn_cell(rnn_input, state):with tf.variable_scope('rnn_cell', reuse=True):W = tf.get_variable('W', [num_classes + state_size, state_size])b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))#定义rnn_cell具体的操作,这里使用的是最简单的rnn,不是LSTMreturn tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)state = init_state
rnn_outputs = []
#循环num_steps次,即将一个序列输入RNN模型
for rnn_input in rnn_inputs:state = rnn_cell(rnn_input, state)rnn_outputs.append(state)
final_state = rnn_outputs[-1]#----------------------上面是原始代码,定义了rnn_cell,然后使用循环的方式对其进行复用,简化之后我们可以直接调用BasicRNNCell和static_rnn两个函数实现------------------------cell = tf.contrib.rnn.BasicRNNCell(state_size)
rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell, rnn_inputs, initial_state=init_state)

2,使用动态rnn模型,上面的模型中,我们将输入表示成列表的形式,即rnn_inputs是一个长度为num_steps的列表,其中每个元素是[batch_size, features]的tensor(即每个rnn_cell要处理的数据),这样做事比较麻烦的,我们还可以使用tf提供的dynamic_rnn函数,这样做不仅会使编程更加简单,还可以提高计算效率。使用dynamic_rnn 时,我们直接将输入表示成[batch_size, num_steps, features]的三维Tensor即可。最终的动态RNN模型代码如下所示:

x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
init_state = tf.zeros([batch_size, state_size])rnn_inputs = tf.one_hot(x, num_classes)
#注意这里去掉了这行代码,因为我们不需要将其表示成列表的形式在使用循环去做。
#rnn_inputs = tf.unstack(x_one_hot, axis=1)cell = tf.contrib.rnn.BasicRNNCell(state_size)
#使用dynamic_rnn函数,动态构建RNN模型
rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)with tf.variable_scope('softmax'):W = tf.get_variable('W', [state_size, num_classes])b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
logits = tf.reshape(tf.matmul(tf.reshape(rnn_outputs, [-1, state_size]), W) + b,[batch_size, num_steps, num_classes])
predictions = tf.nn.softmax(logits)losses = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)

至此,我们就实现了一个很简单的RNN模型的构造,在这个过程中,我们需要注意的主要有以下三点:

  • 如何将数据转化成rnn所能接受的输入格式,需要注意batch_size和num_steps之间的关系。

  • 定义rnn_cell,这里使用的是下面这条命令:cell = tf.contrib.rnn.BasicRNNCell(state_size)

  • 定义RNN模型,可以使用下面这两条命令分别静态和动态构建:

rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell, rnn_inputs, initial_state=init_state)
rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)
====================================分割线================================
本文作者:AI研习社
本文转自雷锋网禁止二次转载,原文链接

简单实用的 TensorFlow 实现 RNN 入门教程相关推荐

  1. TensorFlow发布语音识别入门教程,附1GB数据集代码

    原标题:TensorFlow发布语音识别入门教程,附1GB数据集&代码 机械鸡的鸡友经常问:如何开始入门深度学习语音和其他音频识别,例如关键字检测或语音命令. 虽然有一些伟大的开源语音识别系统 ...

  2. [github 教程]手把手教你最简单的开源项目托管GitHub入门教程_github 教程

    [github 教程]手把手教你最简单的开源项目托管GitHub入门教程--简介 自从google code关闭了下载服务了之后,GitHub作为了目前最好用的免费 开源 项目托管站点,众多开源项目都 ...

  3. 阵列卡u盘安装系统步骤_最简单实用的raid重装系统图文教程

    raid重装系统的方法,可能听过这种方法和使用过这种方法的网友都是比较少的,这种系统重装方法虽然现在使用的人不多,但是也是非常好用的.今天小编就要把raid重装系统的方法推荐给大家,下面就是最简单实用 ...

  4. RNN 入门教程 Part 3 – 介绍 BPTT 算法和梯度消失问题

    转载 - Recurrent Neural Networks Tutorial, Part 3 – Backpropagation Through Time and Vanishing Gradien ...

  5. Tensorflow 2.x入门教程

    Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/cou ...

  6. TensorFlow基本原理,入门教程网址

    TensorFlow TensorFlow™ 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库. 节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节 ...

  7. TensorFlow人工智能引擎入门教程之二 CNN卷积神经网络的基本定义理解。

    摘要: 这一章节我们将 tensorFlow怎么实现卷积神经网络 ,CNN ,其实CNN可以用来训练声音的,不过效果一般,所以一般都用于图像方面,因为图像像素局部共享 参数共享 ,图像金字塔法则.可以 ...

  8. TensorFlow人工智能引擎入门教程之十 最强网络 RSNN深度残差网络 平均准确率96-99%

    摘要: 这一章节我们讲一下 RSNN深度残差网络 ,他准确率非常好,比CNN还要高.而且非常新 出现在2015 residual network http://blog.csdn.net/sunbai ...

  9. 简单实用 铃铛运动动画制作AE教程

    铃铛的摆动是有一定幅度和要求的,我们要如何通过AE制作铃铛动画呢?接下来就给大家分享一波铃铛晃动的技巧. 铃铛晃动动画AE教程 1.在绘图工具中绘制铃铛本体和铛簧,选中之后导入AE: 2.铃铛本体锚点 ...

最新文章

  1. 为什么会出现网络流量管理?
  2. 浅析_tmain()与main()的区别
  3. twisted Unhandled error in Deferred scrapy
  4. 在网页中显示数字时钟
  5. magento php 所需模块,Magento 博客
  6. Consider static factory methods instead of constructor
  7. keepalived高可用集群配置
  8. C++学习笔记:(五)继承 多态
  9. 一个人的Scrum之准备工作
  10. 网络管理与维护作业5
  11. spring mvc路径匹配原则
  12. 数据库系统常用的数据模型
  13. PADS9.5实战攻略与高速PCB设计-强烈推荐教程资料(完整书签)
  14. GRUB4DOS入门
  15. 语音信号的短时平均过零率
  16. manjaro下6.828实验环境配置
  17. 矩阵理论——线性空间
  18. python的常见矩阵除法_Python矩阵除法
  19. HTML期末大作业—— 游戏网页(5个页面) ~ 全屏游戏美术大赛作品征集网页 HTML+CSS+JS ~ web课程设计网页规划与设计...
  20. IOS小组件(4-2):创建可配置小组件(动态修改配置数据)

热门文章

  1. 在delphi中如何动态建立类的实例
  2. 在linux下一般用scp这个命令来通过ssh传输文件
  3. 自定义request链路跟踪
  4. 组策略参考文档1-共享打印机
  5. ssh服务优化与客户端命令使用
  6. 通用的“关于本软件”对话框
  7. linux的安装方式(一)
  8. 浅谈Nginx性能调优
  9. 工程师们在摔倒后如何不尴尬
  10. SpringCloud 微服务网关Gateway介绍及简单路由配置