转子:https://www.leiphone.com/news/201705/zW49Eo8YfYu9K03J.html

最近在看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.5

if 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和Y

raw_x, raw_y = raw_data

data_length = len(raw_x)

# 首先将数据切分成batch_size份,0-batch_size,batch_size-2*batch_size。。。

batch_partition_length = data_length // batch_size

data_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,不是LSTM

return 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 = 0

return 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,不是LSTM

return 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)

TensorFlow 实现 RNN 入门教程的更多相关文章

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

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

  2. Tensorflow 2.x入门教程

    前言 至于为什么写这个教程,首先是为了自己学习做个记录,其次是因为Tensorflow的API写的很好,但是他的教程写的太乱了,不适合新手学习.tensorflow 1 和tensorflow 2 有 ...

  3. TensorFlow 2.0 入门教程实战案例

    中文文档 TensorFlow 2 / 2.0 中文文档 知乎专栏 欢迎关注知乎专栏 https://zhuanlan.zhihu.com/geektutu 一.实战教程之强化学习 TensorFlo ...

  4. RNN 入门教程 Part 2 – 使用 numpy 和 theano 分别实现RNN模型

    转载 - Recurrent Neural Networks Tutorial, Part 2 – Implementing a RNN with Python, Numpy and Theano 本 ...

  5. RNN 入门教程 Part 1 – RNN 简介

    转载 - Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs Recurrent Neural Networks (RN ...

  6. RNN 入门教程 Part 4 – 实现 RNN-LSTM 和 GRU 模型

    转载 - Recurrent Neural Network Tutorial, Part 4 – Implementing a GRU/LSTM RNN with Python and Theano ...

  7. TensorFlow 中文资源全集,官方网站,安装教程,入门教程,实战项目,学习路径。

    Awesome-TensorFlow-Chinese TensorFlow 中文资源全集,学习路径推荐: 官方网站,初步了解. 安装教程,安装之后跑起来. 入门教程,简单的模型学习和运行. 实战项目, ...

  8. TensorFlow 中文资源精选,官方网站,安装教程,入门教程,实战项目,学习路径。

    Awesome-TensorFlow-Chinese TensorFlow 中文资源全集,学习路径推荐: 官方网站,初步了解. 安装教程,安装之后跑起来. 入门教程,简单的模型学习和运行. 实战项目, ...

  9. Step by Step 真正从零开始,TensorFlow详细安装入门图文教程!帮你完成那个最难的从0到1

    摘要: Step by Step 真正从零开始,TensorFlow详细安装入门图文教程!帮你完成那个最难的从0到1 安装遇到问题请文末留言. 悦动智能公众号:aibbtcom AI这个概念好像突然就 ...

随机推荐

  1. 通过java.net.URLConnection发送HTTP请求(原生、爬虫)

    目录 1. 运用原生Java Api发送简单的Get请求.Post请求 2. 简单封装 3. 简单测试 如何通过Java发送HTTP请求,通俗点讲,如何通过Java(模拟浏览器)发送HTTP请求.Ja ...

  2. css兼容性写法大全

    淘宝初始化代码 body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset ...

  3. (八) .launch文件 ---编写简单的启动脚本文件

    下面我们将介绍,如何编写一个启动脚本程序:(.launch文件) 还记得我们在 创建ROS软件包教程 中创建的第一个程序包(beginner_tutorials)吗,现在我们要使用它. 在 begin ...

  4. 编码补充 daty 6

    ---恢复内容开始--- 1.  用id求内存地址 id 查询内存地址 name = 'alex' print(id(name)) li = [1,2,3] print(id(li)) 结果: 2. ...

  5. 对于读txt文件一点总结

    txt 内容 中间有比如如空格,制表符(tab)在txt为空格符(Spaces).回车符.换行符,有空字符串等情况,在读取过滤中要充分考虑到 1:打开文件 var sr=new StreamReade ...

  6. UML 图C#

    继承关系(类1继承类2) 代码: class Class1:Class2 { } class Class2 { } 实现(实现接口) 代码: interface interface1 { void s ...

  7. Liunx mv(转)

    转竹子—博客:http://www.cnblogs.com/peida/archive/2012/10/27/2743022.html mv命令是move的缩写,可以用来移动文件或者将文件改名(mov ...

  8. 使用vim编程步骤

    先用vim 名字.cpp //创建一个.cpp文件进行代码编写 可以调用g++ 名字.cpp的形式进行编译,更好的方法是采用CMakeLists.txt touch CMakeLists.txt // ...

  9. How to update XENTRY Connect C5 software with .iso file

    07.2018 Xentry Mercedes SD Connect c5 software update manual for newbies: Important: If you have XDO ...

  10. eclipse中查找某一个字符串

    想要完全匹配查找一个字符串,如iCard,而iCardSys或iiCard这种都不行,可以用正则表达式来查找,如下: 参考链接:http://blog.csdn.net/u014656992/arti ...