目录
1.背景
2.网络结构
3.卷积与池化
4.局部感知和参数共享
5.卷积神经网络的训练技巧
6.基于mnist的一个例子
背景
(1)先举个例子:
假设给定一张图(可能是字母X或者字母O),通过CNN即可识别出是X还是O,如下图所示
(2)图像输入
1.如果采用经典的DNN(全连接深度神经网络),则需要读取整幅图像作为神经网络模型的输入(即全连接的方式),当图像的尺寸越大时,其连接的参数将变得很多,从而导致计算量非常大。举个例子:我们知道,图像是由一个个像素点构成,每个像素点有三个通道,分别代表RGB颜色,那么,如果一个图像的尺寸是(28,28,1),即代表这个图像的是一个长宽均为28,channel为1的图像(channel也叫depth,此处1代表灰色图像)。如果使用全连接的网络结构,即,网络中的神经元与相邻层上的每个神经元均连接,那就意味着我们的网络有28 * 28 =784个神经元,hidden层(隐藏层)采用了15个神经元,那么简单计算一下,我们需要的参数个数(权重w和偏置b)就有:784*15*10+15+10=117625个(隐含层和最后的输出层的10个神经元连接),这个参数太多了,随便进行一次反向传播计算量都是巨大的,从计算资源和调参的角度都不建议用传统的神经网络。DNN如下图所示:
三层神经网络识别手写数字
2.而我们人类对外界的认知一般是从局部到全局,先对局部有感知的认识,再逐步对全体有认知,这是人类的认识模式。在图像中的空间联系也是类似,局部范围内的像素之间联系较为紧密,而距离较远的像素则相关性较弱。因而,每个神经元其实没有必要对全局图像进行感知,只需要对局部进行感知,然后在更高层将局部的信息综合起来就得到了全局的信息。这种模式就是卷积神经网络中降低参数数目的重要神器:局部感受野。如下图所示:
(3)提取特征
如果字母X、字母O是固定不变的,那么最简单的方式就是图像之间的像素一一比对,但在现实生活中,字体都有着各个形态上的变化(例如手写文字识别),例如平移、缩放、旋转、微变形等等,如下图所示:
我们的目标是对于各种形态不一的字母,都能通过CNN准确地识别出来,所以问题的关键在于如何有效地提取特征,作为识别的关键因子。
说回前面提到的局部感受野,对于CNN来说,它是一小块一小块来进行比对,在两幅图像中大致相同的位置找到一些粗糙的特征(小块图像)进行匹配,相比起传统的整幅图逐一比对的方式,CNN的这种小块匹配方式能够更好的比较两幅图像之间的相似性。如下图:
网络结构
这里借用斯坦福CS231N里课程里给出的经典卷积神经网络的层级结构,如下图:
上图CNN要做的事情是:给定一张图片,具体类别未知,现在需要CNN判断这张图片里具体是什么,结果:如果是车,那是什么车。
最左边是数据输入层,对数据做一些处理,比如去均值(把输入数据各个维度都中心化为0,避免数据过多偏差,影响训练效果)、归一化(把所有的数据都归一到同样的范围)、PCA/白化等等。CNN只对训练集做“去均值”这一步。
中间是:
CONV:卷积计算层。
RELU:激励层。
POOL:池化层。
最右边是:FC:全连接层(ps:全连接层现在多用全局平均池化层代替。全连接层的劣势在于会产生大量的计算,需要大量的参数,但在效果上却和全局平均池化层一样,但本文将基于经典卷积神经网络进行介绍)
卷积与池化
(1)什么是卷积
卷积是一种特殊的线性运算。对图像(不同的数据窗口数据)和滤波器(一组固定的权重:因为每个神经元的多个权重固定,所以又可以看做一个恒定的滤波器filter)做内积(逐个元素相乘再求和)的操作就是所谓的卷积。
非严格意义上来讲,下图中红框框起来的部分便可以理解为一个滤波器,即带着一组固定权重的神经元。多个滤波器叠加便成了卷积层。如下图所示:
再举一个例子。下图左边部分是原始输入数据,中间部分是滤波器filter,右边是输出的新的二维数据。
中间滤波器filter与数据窗口做内积,其具体计算过程则是:4*0 + 0*0 + 0*0 + 0*0 + 0*1 + 0*1 + 0*0 + 0*1 + -4*2 = -8
(2)卷积的一些参数
在CNN中,滤波器filter(带着一组固定权重的神经元)对局部输入数据进行卷积计算。每计算完一个数据窗口内的局部数据后,数据窗口不断平移滑动,直到计算完所有数据。这个过程中,有这么几个参数:
a. 深度depth:神经元个数,决定输出的depth厚度。同时代表滤波器个数。
b. 步长stride:决定滑动多少步可以到边缘。
c. 填充值zero-padding:在外围边缘补充若干圈0,方便从初始位置以步长为单位可以刚好滑倒末尾位置,通俗地讲就是为了总长能被步长整除。 可以配合下图理解:
另外,CS231N课程中有一张卷积动图非常形象地描述了卷积的过程,如下:
可以看到:
a.两个神经元,即depth=2,意味着有两个滤波器。
b.数据窗口每次移动两个步长取3*3的局部数据,即stride=2。
c.zero-padding=1。
然后分别以两个滤波器filter为轴滑动数组进行卷积计算,得到两组不同的结果。
左边是输入(7*7*3中,7*7代表图像的像素/长宽,3代表R、G、B 三个颜色通道)
中间部分是两个不同的滤波器Filter w0、Filter w1
最右边则是两个不同的输出
随着左边数据窗口的平移滑动,滤波器Filter w0 / Filter w1对不同的局部数据进行卷积计算。
(3)什么是池化
为了有效地减少计算量,CNN使用的另一个有效的工具被称为“池化(Pooling)”。池化就是将输入图像进行缩小,减少像素信息,只保留重要信息。
池化的操作也很简单,通常情况下,池化区域是2*2大小,然后按一定规则转换成相应的值,例如取这个池化区域内的最大值(max-pooling)、平均值(mean-pooling)等,以这个值作为结果的像素值。具体如下图所示:
上图所展示的是取区域最大,即上图左边部分中 左上角2x2的矩阵中6最大,右上角2x2的矩阵中8最大,左下角2x2的矩阵中3最大,右下角2x2的矩阵中4最大,所以得到上图右边部分的结果:6 8 3 4。
局部感知和参数共享
局部感知和参数共享算是CNN中很重要的两个机制,接下来举例说明。
局部感知机制:数据窗口在滑动变化,而每次滤波器都是针对某一局部的数据窗口进行卷积。打个比方,滤波器就像一双眼睛,人类视角有限,一眼望去,只能看到这世界的局部。如果一眼就看到全世界,无法一下子接受全世界所有信息。当然,即便是看局部,针对局部里的信息人类双眼也是有偏重、偏好的。比如看球员,对球技才是重点关注,而不是球员的外貌,所以球技的输入的权重相对较大。
参数共享机制:数据窗口滑动,导致输入在变化,但中间滤波器的权重(即每个神经元连接数据窗口的权重)是固定不变的。打个比方,某人环游全世界,所看到的信息在变,但采集信息的双眼不变。btw,不同人的双眼 看同一个局部信息 所感受到的不同,即一千个读者有一千个哈姆雷特,所以不同的滤波器 就像不同的双眼,不同的人有着不同的反馈结果。
卷积神经网络的训练技巧
(1)优化卷积核
在实际的卷积训练中,为了加快速度,常常把卷积核裁开。比如一个3*3的滤波器,可以裁成3*1和1*3的两个滤波器,分别对原有输入做卷积操作,这样可以大大提升运算的速度。
原理:在浮点运算中乘法消耗的资源比较多,我们的目的就是尽量减小乘法运算。
比如对一个5*2的原始图片进行一次3*3的同卷积,相当于生成的5*2像素中每一个都要经历3*3次乘法,那么一共是90次。
同样是这个图片,如果先进行一次3*1的同卷积需要30次运算,再进行一次1*3的同卷积还是30次,一共才60次。
这仅仅是一个很小的数据张量,而且随着张量维度的增大,层数的增多,减少的运算会更多。那么运算量减少了,运算结果会等价吗?答案是肯定的。因为有公式来保证3*1的矩阵乘上1*3的矩阵会正好生成3*3的矩阵。
(2)批量归一化
批量归一化就是将每一层运算出来的数据都归一化成均值为0方差为1的标准高斯分布。这样就会在保留样本分布特征的同时,又消除层与层之间的分布差异。
如果不采取批量归一化,就有可能因为网络的内部协变量转移,即正想传播的时不同层的参数会将反向传播时所参照的数据样本分布改变,从而导致梯度爆炸。
基于mnist的一个例子
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# number 1 to 10 data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
def compute_accuracy(v_xs, v_ys):
global prediction
y_pre = sess.run(prediction, feed_dict={xs: v_xs, keep_prob: 1})
correct_prediction = tf.equal(tf.argmax(y_pre,1), tf.argmax(v_ys,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
result = sess.run(accuracy, feed_dict={xs: v_xs, ys: v_ys, keep_prob: 1})
return result
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)#生成维度为shape 标准差为0.1的随机数
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
def conv2d(x, W):
# stride [1, x_movement, y_movement, 1]
# Must have strides[0] = strides[3] = 1
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
# stride [1, x_movement, y_movement, 1]
return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
# define placeholder for inputs to network
xs = tf.placeholder(tf.float32, [None, 784]) # 28x28
ys = tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32)
x_image = tf.reshape(xs, [-1, 28, 28, 1])#-1代表先不考虑输入的图片例子多少这个维度,后面的1是channel的数量
# print(x_image.shape) # [n_samples, 28,28,1]
## conv1 layer ##
W_conv1 = weight_variable([5,5, 1,32]) # patch(卷积核) 5x5, in size 1, out size 32
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) # output size 28x28x32
h_pool1 = max_pool_2x2(h_conv1) # output size 14x14x32
## conv2 layer ##
W_conv2 = weight_variable([5,5, 32, 64]) # patch 5x5, in size 32, out size 64
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) # output size 14x14x64
h_pool2 = max_pool_2x2(h_conv2) # output size 7x7x64
## func1 layer ##
W_fc1 = weight_variable([7*7*64, 1024])
b_fc1 = bias_variable([1024])
# [n_samples, 7, 7, 64] ->> [n_samples, 7*7*64]将三维的数据变成一维的
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
## func2 layer ##
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
# the error between prediction and real data
cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(prediction),
reduction_indices=[1])) # loss
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for i in range(1500):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys, keep_prob: 0.5})
if i % 50 == 0:
print(compute_accuracy(
mnist.test.images[:1000], mnist.test.labels[:1000]))
只需要训练1500次,就可以达到百分之98以上的准确率。
参考:
1.《深度学习之tensorflow 入门、原理与进阶实战》
2.http://cs231n.stanford.edu/
3.https://blog.csdn.net/rogerchen1983/article/details/79353861