本博客记录使用tensorflow搭建rnn模型并用来对mnist的手写体进行识别的过程,记录其中的学习过程。
实践环境:
Tensorflow version: 1.3.0
python version: 3.5

1.RNN快速介绍:

tensorflow transfomer tensorflow transfomer cnn rnn_git


图1如上图所示,其中x是输入,s旁边的圆圈是隐层,而o旁边的圆圈表示输出。左边是RNN的第一种表示,可以看到在隐层中存在闭环。右图则是左图的展开图,通过在时序维展开 将左图的环转换为一个类似于普通神经网络的结构(从左往右看是不是特别像多层感知机?),使用右图这种普通前向反馈网络我们可以使用之前熟悉的网络处理方法对网络进行训练。但右图与普通神经网络的区别在于:各个“层”里面的参数(如右图所示的U,V,W)是共享的。

特别滴:Tensorflow在处理RNN时,把每一个隐层节点(上图s旁边的那个计算单元)叫做一个RNN cell,每个cell都有一个输入input,一个输出(output),以及一个状态(state,这个state是会从右传递给下一次迭代的,所以state也是下一次输入的一部分)。

这些RNN cell通过一种称为带输出投影的网络结构组成一个深度的RNN结构。深度的RNN就类似于下面这种:

tensorflow transfomer tensorflow transfomer cnn rnn_git_02


展开图中从下往上数发现多个隐层单元,这些隐层单元堆叠在一起就是“深度”RNN网络了。

2.代码讲解:

2.1

首先导入tensorflow以及mnist训练集

#coding:utf-8
__author__ = 'jmh081701'
import  tensorflow as tf
from  tensorflow.examples.tutorials.mnist import  input_data

2.2

打开数据集

mnist=input_data.read_data_sets("./data",one_hot=True)

该过程会先在当前目录下的data寻找mnist的数据文件,如果没有数据文件就会去网上download下来.
我们都知道 mnist里面的数据格式是每一张图片都是28x28像素的图片。

2.3

设置一些基本参数

train_rate=0.001  
train_step=10000
batch_size=128
display_step=10

frame_size=28
sequence_length=28
hidden_num=5
n_classes=10

其中:
train_rate是学习速率,这是一个超参数,目前由经验设置的,当然也可以自适应。

batch_size:每批样本数,rnn也可以使用随机梯度下降进行训练,一批批的灌数据进去,而不是每一次把整个数据集都灌进去。

sequence_size:每个样本序列的长度。因为我们希望把一个28x28的图片当做一个序列输入到rnn进行训练,所以我们需要对图片进行序列化。一种最方便的方法就是我们认为行与行之间存在某些关系,于是把图片的每一行取出来当做序列的一个维度。所以这里sequence_size就是设置为28。

反映到图1里面就是左边循环图展开后右图从左往右xi的数目。

frame_size:序列里面每一个分量的大小。因为每个分量都是一行像素,而一行像素有28个像素点。所以frame_size为28。

反映到图1里面就是最下变的输入中每个xi都是一个长度为frame_size的向量或矩阵。

hidden_num隐层个数,经验设置为5
反映到图1里面就是从下往上数的有hidden_num个隐层单元。

隐层神经元个数,反映到图1 就是每个hidden单元包含的神经个数。因为单层的rnn其实就是一个比较特别的mlp而已。
深度才是图1中由下到上数的隐藏层层数。

n_classes:类别数,10个数字就是设置为10咯

2.4

定义输入,输出

x=tf.placeholder(dtype=tf.float32,shape=[None,sequence_length*frame_size],name="inputx")

y=tf.placeholder(dtype=tf.float32,shape=[None,n_classes],name="expected_y")

使用两个占位符,x是输入占位符,希望输入的时候把每个图像都组成28*28的向量。

而y是输出,每个输出是一个长度为10的向量,因为一种有10个数字嘛,方便用于分类。

2.5

定义权值

weights=tf.Variable(tf.truncated_normal(shape=[hidden_num,n_classes]))
bias=tf.Variable(tf.zeros(shape=[n_classes]))

注意:weights是整个网络的最后一层,它的形状为hidden_numXn_class,至于为什么是这个形状,我们下面来说。
bias最后一层的偏置

2.6

定义RNN网络:

我们使用tf.nn.dynamic_rnn来完成rnn的迭代。
我们先来看tf.nn.dynamic_rnn的官方说明:

tf.nn.dynamic_rnn
dynamic_rnn(
cell,
inputs,
sequence_length=None,
initial_state=None,
dtype=None,
parallel_iterations=None,
swap_memory=False,
time_major=False,
scope=None
)
重要参数:
cell:需要传入tf.nn.rnn_cell空间下的某一类rnn的实例。在本实践中我们传入了tf.nn.rnn_cell.BasicRNNCell(hidden_num),一个包含hidden_num个隐层单元的基本RNN单元。
inputs:输入数据

如果 time_major == False (default)
input的形状必须为 [batch_size, sequence_length, frame_size]

如果 time_major == True
input输入的形状必须为 [sequence_length, batch_size, frame_size]

其中batch_size是批大小,sequence_length是每个序列的大小,而frame_size是序列里面每个分量的大小(可以是shape,例如[3,4])

sequence_length:序列的长度,如果指定了这个参数,那么tf会对长度不够的输入在尾部填0。

initial_state:
初始化状态 (可选), 需要是一个[batch_size, cell.state_size]形状的tensor.一般初始化为0。

函数返回:
一个 outputs, state的tuple:
其中:
outputs: RNN输出tensor:

如果 time_major == False (default)
outputs的形状为:[batch_size, sequence_length, cell.output_size]

如果 time_major == True
outputs的形状: [sequence_length, batch_size, cell.output_size].
其中cell是刚刚传入的第一个参数的cell.

state: 最后的状态. 是[batch_size, cell.state_size]. 的形状。
我们再看看tf.nn.rnn_cell.BasicRNNCell和tf.nn.rnn_cell_BasicLSTMCell这两个函数:

tf.nn.rnn_cell.BasicRNNCell(
num_units,
activation=None,
reuse=None
):
参数:
num_units: int, 隐层个数。
activation: 非线性激活函数 默认: tanh.
reuse: (optional) 不理

tf.nn.rnn_cell.BasicLSTMCell(
num_units,
forget_bias=1.0,
state_is_tuple=True,
activation=None,
reuse=None
)
参数:
num_units: int, 隐层个数.

forget_bias: float, 遗忘门的阈值.

state_is_tuple: If True, accepted and returned states are 2-tuples of the c_state and m_state. If False, they are concatenated along the column axis. The latter behavior will soon be deprecated.

activation:非线性激活函数,默认: tanh.
reuse: 暂时不管

OK,我们使用上面的函数搞事情:

#定义RNN网络
def RNN(x,weights,bias):
    x=tf.reshape(x,shape=[-1,sequence_length,frame_size])
    #先把输入转换为dynamic_rnn接受的形状:batch_size,sequence_length,frame_size这样子的
    rnn_cell=tf.nn.rnn_cell.BasicRNNCell(hidden_num)
    #生成hidden_num个隐层的RNN网络,rnn_cell.output_size等于隐层个数,state_size也是等于隐层个数,但是对于LSTM单元来说这两个size又是不一样的。
    # 这是一个深度RNN网络,对于每一个长度为sequence_length的序列[x1,x2,x3,...,]的每一个xi,都会在深度方向跑一遍RNN,每一个都会被这hidden_num个隐层单元处理。
	    
	output,states=tf.nn.dynamic_rnn(rnn_cell,x,dtype=tf.float32)
	#此时output就是一个[batch_size,sequence_length,rnn_cell.output_size]形状的tensor
    return tf.nn.softmax(tf.matmul(output[:,-1,:],weights)+bias,1)
    #我们取出最后每一个序列的最后一个分量的输出output[:,-1,:],它的形状为[batch_size,rnn_cell.output_size]也就是:[batch_size,hidden_num]所以它可以和weights相乘。这就是2.5中weights的形状初始化为[hidden_num,n_classes]的原因。然后再经softmax归一化。

RNN函数返回的是:[batch_size,hidden_num],而这个就是我们需要的输出,输出的各个batch样本的概率分布。

2.7

计算预计输出

predy=RNN(x,weights,bias)

获得以x为输入,weights,bias为偏置的概率分布。
返回结果是[batch_size,n_classes]的形状,表示batch里面的每个样本的预测输出。

2.8

计算残差:

cost=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=predy,labels=y))

使用交叉熵

2.9

定义优化器:

train=tf.train.AdamOptimizer(train_rate).minimize(cost)

2.10

定义分类的准确率:

correct_pred=tf.equal(tf.argmax(predy,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.to_float(correct_pred))

先试用tf.argmax把第二维最大数的下标取出来,然后再比较多少相等的。
例如:
predy=[[0.0,0.2,0.3,0.4,0.1,0.1,0.2,0.3,0.111,0.212],
[0.0,0.2,0.3,0.4,0.1,0.1,0.2,0.3,0.111,0.212]]
则:tf.argmax(predy,1)返回[3,3]

2.11

启动sess:
sess=tf.Session()
初始化变量:
sess.run(tf.initialize_all_variables())

2.12

循环训练:

testx,testy=mnist.test.next_batch(batch_size)
while step<train_step:
    batch_x,batch_y=mnist.train.next_batch(batch_size)
#    batch_x=tf.reshape(batch_x,shape=[batch_size,sequence_length,frame_size])
    _loss,__=sess.run([cost,train],feed_dict={x:batch_x,y:batch_y})
    if step % display_step ==0:
        
        acc,loss=sess.run([accuracy,cost],feed_dict={x:testx,y:testy})
        print(step,acc,loss)

    step+=1

3.整体代码:

#coding:utf-8
__author__ = 'jmh081701'
import  tensorflow as tf
from  tensorflow.examples.tutorials.mnist import  input_data
from tensorflow.contrib import  rnn

mnist=input_data.read_data_sets("./data",one_hot=True)

train_rate=0.001
train_step=10000
batch_size=1280
display_step=100

frame_size=28
sequence_length=28
hidden_num=100
n_classes=10

#定义输入,输出
x=tf.placeholder(dtype=tf.float32,shape=[None,sequence_length*frame_size],name="inputx")
y=tf.placeholder(dtype=tf.float32,shape=[None,n_classes],name="expected_y")
#定义权值
weights=tf.Variable(tf.truncated_normal(shape=[hidden_num,n_classes]))
bias=tf.Variable(tf.zeros(shape=[n_classes]))

def RNN(x,weights,bias):
    x=tf.reshape(x,shape=[-1,sequence_length,frame_size])
    rnn_cell=tf.nn.rnn_cell.BasicRNNCell(hidden_num)
    init_state=tf.zeros(shape=[batch_size,rnn_cell.state_size])
    # 其实这是一个深度RNN网络,对于每一个长度为n的序列[x1,x2,x3,...,xn]的每一个xi,都会在深度方向跑一遍RNN,跑上hidden_num个隐层单元
    output,states=tf.nn.dynamic_rnn(rnn_cell,x,dtype=tf.float32)
    return tf.nn.softmax(tf.matmul(output[:,-1,:],weights)+bias,1)
predy=RNN(x,weights,bias)
cost=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=predy,labels=y))
train=tf.train.AdamOptimizer(train_rate).minimize(cost)

correct_pred=tf.equal(tf.argmax(predy,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.to_float(correct_pred))

sess=tf.Session()
sess.run(tf.initialize_all_variables())
step=1
testx,testy=mnist.test.next_batch(batch_size)
while step<train_step:
    batch_x,batch_y=mnist.train.next_batch(batch_size)
#    batch_x=tf.reshape(batch_x,shape=[batch_size,sequence_length,frame_size])
    _loss,__=sess.run([cost,train],feed_dict={x:batch_x,y:batch_y})
    if step % display_step ==0:
        
        acc,loss=sess.run([accuracy,cost],feed_dict={x:testx,y:testy})
        print(step,acc,loss)

    step+=1

4.结果:

100 0.653125 1.80767
200 0.804688 1.65695
300 0.83125 1.62595
400 0.928906 1.53505
500 0.941406 1.5194
600 0.95 1.51348
700 0.957031 1.51022
800 0.961719 1.50096
900 0.964063 1.49847
1000 0.964063 1.49862
···
···
···

才训练了几百批测试准确率就有95%以上了,还是蛮厉害的,23333。

如果使用BasicLSTM单元:

100 0.7875 1.67598
200 0.932813 1.5308
300 0.952344 1.51034
400 0.959375 1.50383
500 0.957812 1.50274
···
···
···

在训练完300批次以后就在测试集有95%的准确率了