如果开始看CNN了,相信大家对于神经网络的基础已经有了一些了解了,那么今天我们就来感性的理解下CNN,尽可能少整数学公式,让在座各位兄弟在学习工作之余,嗑个瓜子,吃根冰棍的同时也能看懂CNN,但是在编程过程中,数学公式可是必不可少的嗷~

CNN简介

这个CNN,不是美国有线电视新闻网,是卷积神经网络(Convolutional Neural Networks)。此概念在195x年被两个研究小猫大脑的美国老铁提出,他们发现这种结构可以极大简化小猫识别物体所需要的神经网络结构。随着近些年深度学习的火热,CNN也得到了广泛的应用。今天的任务是:知道CNN每一步工作原理,为什么CNN简化了神经网络,以及CNN进一步的思考。

首先来看下卷积是啥,提起卷积,我脑中莫名会想起小时候吃的大大卷
大大卷儿
其实呢,卷积就是两个变量在某范围内相乘后求和的结果。

一开始看CNN,我迷茫地像个两百多斤的孩子一样,机械地接受着知识,神马卷积层池化层,一层层叠,然后又来几个全连接层,然后,然后好啦?对啊,我知道池化卷积都是干什么的,可是真正一步步的实现是怎样呢?高大上的论文,教程都不详细说明,仿佛受众都是深度学习老司机一样。当时越看越不安,得好好思考下为什么这么设计网络结构。

那么先来一个CNN可视化图压压惊
可视化图
这是一个训练好的CNN网络,最下面的输入层是我写的一个丑陋的2,后面经过卷积,池化,第二次卷积,池化,最后来两个全连接,输出结果

你也可以去这个链接玩玩CNN-3D模型可视化

感性认知CNN

下面用一只活生生的例子来让大家感性地认知下CNN工作原理,我觉得在看以下内容之前,你可以去教学材料了解下CNN那些构造的基本作用,只知道如何操作即可,这对感性理解会有较大帮助。
首先我们选四个input送进一个训练好的CNN网络,分别是豹子,豹纹钱包,织物钱包,橙色皮钱包:
input

第一次卷积

首先这些input会通过第一层卷积层,也就是滤波器,所谓的kernal,就张这个样子
kernal
下面就用这个滤波器去扫描每张图片33区域,从头扫到尾滴水不漏的扫描,当然你可以规定每次相邻扫描的重叠部分,也就是用步长(stride)去控制,我们这个滤波器是33,所以可选择每次向相邻位置挪动1,2或者3个格子。Look at the kernal,给了左下到右上对角线上赋予权值是正,其余是负,所以每次扫描中,被扫描部分的这个对角线区域就会保留,剩下的都会变成负值,扫描过后会生成一个特征图,就像这样(下图这个例子是5*5的滤波器)
feature
那么我扫完这个图了,如果这个图有很多斜向右的微小结构,那么生成的相应特征图就会有很多正值。用这个滤波器去扫描那个豹子,很有可能得出的特征图是一大片负数值(我们假设豹子皮表面不存在这样的微小结构)。去扫那个织物钱包很有可能就得到充满正值的特征图(假设织物一针一针的呈现X)那么咋识别出豹子皮捏?
要知道这是一个训练好的网络,滤波器应有尽有,所以啥小特征都给你搞定,网络又拿出一堆滤波器(照妖镜)去找豹子皮
zhaoyaojing
有识别左上右下斜线的,有识别人字形的,有C字形的
我们假设豹子表面是这样的,别问我为什么,强行想象一根根豹毛就是这样的
baozimao
哇,豹子的识别出来了!胜利迈出第一步,淘汰掉织物钱包
然而,你会发现,豹子,豹纹钱包和橙色豹皮钱包生成的特征图都有很多正值,就像这样
feature map
大致可以知道哪个对哪个,但是计算机不知道啊,计算机只知道,这几张图有很多正值,都有成为豹子的可能。

第一次池化

我们看那些特征图,每一个最根正苗红的正值像素后面都是一个“人”结构,所以说,每个像素都蕴含了更多的信息。那么为了简化网络结构,我们可以对特征图进行压缩了,这就是池化。如图所示,把每四个相邻的像素选出一个最大值生成新的图像。就像人民代表制度一样,你这个四口之家有根正苗红的红二代么?有请把他叫出来并附上他的身份证明。什么?你家四口都是负值的反动派?矬子里拔将军吧,把最不反动的那位拎出来。每个家庭选出最厉害的一个人进入下一关。经过如此一番折腾,图片尺寸减半
chihua

第二次卷积池化

后面进入第二层卷积(为了简洁,第二次卷积环节同第一次,略)
这次,为了把豹子搞出来,电脑又拿出一堆滤波器,还是33,你会问,咋还是33,说好的检查更复杂程序呢?别急啊,你看,同样是“人”字形滤波器,这次如果你和周围的像素兄弟点亮这个滤波器,会是什么情况?如图所示
filter
你们得有这样的阵型,小人组的大人才能点亮滤波器,因为一个根正苗红的像素代表的是他背后符合上一次滤波器检验的周围邻里街坊啊。
所以呢,同样是3*3滤波器,检测出来的内容更加复杂了。言归正传,这次电脑要从豹纹这个环节来鉴别这些图片,于是电脑祭出这个滤波器
filter
这个可以检测出弧形,把这个来滤波器旋转就可检测不同方向的弧形了,经过一番操作后,由毛皮材质组成的弧形搞出来了

由于橙色毛皮钱包没有纹路,在这轮卷积败下阵来

后面的过程其实也可省略掉了,后面电脑又搞出一些奇技淫巧弄出了豹纹钱包和豹子之间的区别,比如豹子有耳朵钱包没有等等,当然我这个描述中省略了相当的细节,但是对于理解应该会有帮助吧。

下面两图分别是CNN和ACGAN网络
CNN
ACGAN

代码实现

TFboys+MNIST走起来

1
2
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

定义几个函数 权值,偏置,卷积层,池化层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mnist= input_data.read_data_sets("MNIST_data",one_hot=True)
batch_size=100
n_batch = mnist.train.num_examples//batch_size

def weight_variable(shape):
initial=tf.truncated_normal(shape,stddev=0.1)
return tf.Variable(initial)

def bias_variable(shape):
initial=tf.constant(0.1,shape=shape)
return tf.Variable(initial)
//CNN
def conv2d(x,W):
//input tensor of shape [batch, in_height, in_width,in_channels]
//W filter /kernel tensor of shape [filter_height,filter_width.in_hannels,out_channels]
//strides[0]=stride[3]=1, stride[1]=x方向步长,stride[2]代表y方向步长
//padding:A string from: "same","valid"
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")

def max_pool_2x2(x):
//ksize [1,x,y,1]
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")

构造网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
x=tf.placeholder(tf.float32,[None,784])//28*28
y=tf.placeholder(tf.float32,[None,10])
//改变x的格式转为4D向量[batch,in_height,in_width,in_channels]
x_image=tf.reshape(x,[-1,28,28,1])
//初始化第一个卷积层的权值和偏置
W_conv1=weight_variable([5,5,1,32]) //5*5采样窗口,32个卷积核从一个平面抽取特征
b_conv1=bias_variable([32])//每一个卷积核一个偏置值
//把x_image和权值向量进行卷积,再加上偏置值,然后应用于relu激活函数
h_conv1=tf.nn.relu(conv2d(x_image,W_conv1)+ b_conv1)
h_pool1=max_pool_2x2(h_conv1) //进行max-pooling
//初始化第二个卷积层的权值和偏置
W_conv2=weight_variable([5,5,32,64])//5*5采样窗口,64个卷积核从32个平面抽取特征
b_conv2=bias_variable([64])

h_conv2=tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)
//28*28的图片第一次卷积后还是28*28,第一次赤化后变为14*14,第二次赤化后为7*7
//经过上面操作后得到64张7*7的平面
//初始化第一个全连接层的权侄
W_fc1 = weight_variable([7*7*64,1024])
//上一层有7*7*64个神经元,全连接层有1024个神经元
b_fc1=bias_variable([1024])

//把池化层2的输出扁平化为1 维
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)
//keep_prob用来表示神经元的输出概率
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)

//初始化第二个全连接层
W_fc2=weight_variable([1024,10])
b_fc2=bias_variable([10])
//计算输出
prediction=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
//交叉熵代价函数
cross_entropy=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=prediction))

train_step=tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction=tf.equal(tf.argmax(prediction,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

执行训练,例行公事

1
2
3
4
5
6
7
8
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(21):
for batch in range(n_batch):
batch_xs,batch_ys =mnist.train.next_batch(batch_size)
sess.run(train_step,feed_dict={x:batch_xs,y:batch_ys,keep_prob:0.7})
acc=sess.run(accuracy,feed_dict={x:mnist.test.images,y:mnist.test.labels,keep_prob:1.0})
print('iter'+str(epoch)+", test acc ="+str(acc))

简单好吃,物美价廉

以上是比较简单的CNN网络,如果想进阶,推荐如下

论文Gradient-Based Learning Applied to Document Recognition[J]。