一、实验目的
- 熟悉CNN卷积神经网络的前向计算过程和误差反传过程
- 学习tensorflow上的简单操作
- 识别mnist数据集上的手写数字
二、从全连接网络到CNN
卷积神经网络由全连接神经网络变化而来,图片在计算机中以矩阵的形式储存,按照全连接神经网络的思路,要将图片中的每个像素作为输入层,当图片像素比较高时,需要的参数将会非常多,一个28x28的灰度图像有784个像素,对应着784个输入神经元,如果隐藏层选择1000个神经元,则会有784x1000个权值和1000个偏置,输出层选择10个神经元,从隐藏层到输出层会有1000x10个权值和10个偏置,综合起来共有(784+1)x1000+(1000+1)x10个参数,为了减少参数,我们选择了两种方法:
(1)局部感受野:每个神经元不再与一个像素相连,而是与一片像素相连代表一部分像素的特征,这个神经元的局部感受野就是与之相连的那部分像素区域,当多个神经元把特征提取出来后,再组合到一起就可以代表整张图片的特征,一个直观的理解:
添加了局部感受野后,相当于隐藏层的神经元不再与输入层的每个神经元相连,而是与一部分输入层神经元相连,假如隐藏层的每个神经元与25个输入层神经元相连,则输入层到隐藏层权值参数数量变为1000x25,大大减少。局部感受野就相当于对25个像素进行了卷积操作,对应的25个参数成为卷积核。
(2)权值共享:上面的参数数量仍然很大,所以我们继续用权值共享来减少参数,在局部连接时,每个隐藏层神经元对应25个参数,如果所有隐藏层神经元对应的25个参数都相等,那么权值参数的数量就从1000x25变成了25,参数已经变为很少。对权值共享的理解就是:一种卷积核提取了整张图片的某种特征。显然,一种特征不足以用来识别图片,所以我们可以采用多个卷积核对图片进行操作,相当于提取了多种特征。
1、卷积操作(通道数为1):
2、多核卷积(通道数为4):
两种卷积核生成了两张特征图,hidden layer m为生成的feature map
3、池化操作:
对于复杂的图片,我们肯定会选择较多的卷积核去提取特征值,所以参数仍然会很多,为了训练方便,我们仍然需要减少参数,我们可以对卷积后不同位置的特征进行聚合统计,也就是池化操作来把特征降维,常见的池化操作有最大池化和平均池化,以下是最大池化。
4、全连接
最后将多次卷积和池化得到的特征作为全连接神经网络的输入进行训练,误差反传和全连接神经网络的相似。
三、BP算法参数更新推导
权值和偏置更新公式为:
E为损失函数,η为学习率,b为偏置,w为权值
则权值和偏置更新公式变为:
(1)所以更新公式主要是求δ,这里附上BP神经网络参数更新推导的手稿:
接下来看一下卷积层和池化层的灵敏度反传:
(2)卷积层到池化层:假设:
当我们得到C层的灵敏度时,如何的到P层的灵敏度,先看一下卷积操作,我们发现P1与C1有关,P2与C1和C2有关…
所以我们得到灵敏度反向传播的公式:
其实上述公式可以用一种卷积操作完成(可以自己算一下):
(3)池化层到卷积层:根据下采样的方式将池化层进行上采样:
第一种是平均下采样,第二种最大下采样,将池化层的灵敏度按如图权值方式分配。
(4)有了灵敏度就可以进行参数更新了。
四、基于tensorflow的cnn源代码:
# -*- coding: utf-8 -*-
#导入包和数据
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#输入数据和创建可插入回话
sess = tf.InteractiveSession()
##创建cnn网络
#卷积函数
def conv(x,w):
return tf.nn.conv2d(x,w,strides=[1,1,1,1],padding = 'SAME')
#池化函数,最大2x2下采样
def pool(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
#权值初始化
def weight(shape):
initial=tf.truncated_normal(shape,stddev=0.1)
return tf.Variable(initial)
#偏置初始化
def basic(shape):
initial=tf.constant(0.1,shape=shape)
return tf.Variable(initial)
#设置样本输入和标准输出的占位符
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])
#第一个卷积层
w_conv1=weight([5,5,1,30])
b_conv1=weight([30])
h_conv1=tf.nn.relu(conv(x_img,w_conv1)+b_conv1)
#第一个池化层
h_pool1=pool(h_conv1)
#第二个卷积层
w_conv2=weight([5,5,30,60])
b_conv2=basic([60])
h_conv2=tf.nn.relu(conv(h_pool1,w_conv2)+b_conv2)
#第二个池化层
h_pool2=pool(h_conv2)
h_flat=tf.reshape(h_pool2,[-1,7*7*60])
#全连接层隐藏层
w1=weight([7*7*60,1000])
b1=basic([1000])
h1=tf.nn.relu(tf.matmul(h_flat,w1)+b1)
#随机失活,防止过拟合
keep_live=tf.placeholder(tf.float32)
h1_drop=tf.nn.dropout(h1,keep_live)
#输出层
w2=weight([1000,10])
b2=basic([10])
y_conv=tf.nn.softmax(tf.matmul(h1_drop,w2)+b2)
#交叉熵损失函数
#y_clipped = tf.clip_by_value(y_conv, 1e-10, 0.9999999)
#loss=-tf.reduce_mean(tf.reduce_sum(y_ * tf.log(y_clipped)+(1-y_)*tf.log(1-y_clipped),axis=1))
loss=-tf.reduce_sum(y_ * tf.log(y_conv))
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss) --学习率设为0.0001
#正确率
correct_predition=tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
correct_rate=tf.reduce_mean(tf.cast(correct_predition,"float"))
#运行会话
sess.run(tf.initialize_all_variables())
for i in range(1000):
batch = mnist.train.next_batch(50) --50组数据一个batch
if i%100 ==0:
train_accuracy= correct_rate.eval(feed_dict={x:batch[0] , y_:batch[1],keep_live:1.0})
print("step %d,the correct rate of train is %g" %(i,train_accuracy))
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_live:0.5})
test_rate=correct_rate.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_live:1.0})
print ("The correct rate on test sets is"+" "+str(test_rate))
四、CNN流程图