卷积神经网络在图像识别领域无限风光,通过一张图片,算法可以知道图片上的物体是什么,着实令人震惊,但是很多人和我一样,对于其背后的原理,都非常好奇,卷积神经网络是如何进行图像识别的呢?
如果你的英文主够好的话,可以阅读这篇论文:
(从薛定谔开始,猫就一直被各种科学家拿出来说事情,当然汪星人也时不时出镜)
人类是如何识别猫咪的?借用知乎的一个回答:
现在假设要做一个图像的分类问题,比如辨别一个图像里是否有一只猫,我们可以先判断是否有猫的头,猫的尾巴,猫的身子等等,如果这些特征都具备,那么我就判定这应该是一只猫。当然,如果图像是下面这样一只老实本分的猫咪,则一切都好办了。
但是喵星人不但品种不同,颜色繁多,各种销魂的动作也层出不穷,所以,机器识别猫还是很困难的。
这样,我们必须要让机器知道,猫,到底应该长成什么样子。
第一次考虑怎么处理这个问题,一个很自然的想法浮想在脑海里面:
将所有猫咪的图片放在一起,提取出猫咪的共同特征,做成一个识别猫的模型。然后对于每张图片,使用模型,看一下是猫的概率为多少。但是如果真的这样做的话,可能每种物体都必须要有一个专门的模型了,这样可能是不行的,计算量可能也是一个问题。特别对于扭曲的猫,这样子的例子非常难处理,我们不太可能穷举出所有猫的正常和非正常形态。(毛色,眼神,是否有物体和猫进行交互)
当然,可以考虑,将猫进行分解,就如知乎网友所说,猫头,猫尾巴,猫爪子,独立进行识别。这样不管猫怎么扭曲,都无所谓了。当然,如果你是资深猫奴,你可以很高兴的说出猫的组成特征,但是,这样本质上还是加入了太多的领域专家的干涉。如果要识别大型粒子加速器,这个是不是要请物理学家参与呢?所以,机器应该完全屏蔽领域知识才可以做到泛用。
虽然不是科班出身,但是以前或多或少看过一些图像处理的书籍。
一般的图像处理都是通过矩阵操作完成的。
具体的颜色矩阵文章:
其实我认为卷积核这个概念,应该是从图像处理矩阵这个概念来的。通过不同的图像处理矩阵,可以突出图像的某些特征,屏蔽掉某些细节。
处理后的图片,屏蔽了颜色,突出了轮廓特征。(猫的轮廓特征保留下来了,颜色特征暂时消失了)
当然,实际处理的时候,可能使用的卷积核可能更加复杂。不过,如果真的看一下卷积核的工作方式,一般来说,卷积用来进行特征的提取,而不是进行图像的预处理的(或者说,是将图像针对特征进行压缩的一个过程)。
上面所说的都大半是猜测,无论如何也应该看一下真实的算法到底是怎么样的。图像识别上最有名气的算法大概就是 Inception 模型。整个算法的架构大概是这样的,深度也是叹为观止。(当前 ResNet 神经网络已经 152 层了,计算量相当相当相当可怕)
原始图像经过了深深的流水线之后,最后在 Softmax 层进行分类。这个过程中到底发生了什么事情,图像在 Softmax 层变成了什么,这个可能是所有人都关心的问题。本文也想通过长期的研究,能或多或少搞清楚里面的奥秘。这个过程应该是极其艰苦的,非常困难的。但是对于机器学习的思考却非常有帮助。
Google 的 Tensorflow 已经在 Github 上开源了,找到了这样的一个源代码,由于非科班出身,所以也无法断定是否这个就是 inception 的源代码了。暂时就以这个作为对象进行研究了
然后按照 ReadMe 的指示看到以下的工程
最新的 V3 代码在以下链接里面
分析源代码的时候,可以将上节的图和代码一起观看。(暂时没有找到 V4 的图片,所以,只能研究 V3 了。如果大家有兴趣也可以研究最牛逼的 ResNet 深度残差网络)
从代码上看,整个深度网络的结构体系可能是这样子的。从输入端开始,先有 3 个卷积层,然后是 1 个 pool 层。然后又是 2 个卷积层,一个 pool 层。这个和上面那张神经网络构造图是完全一致的。前 3 个是卷积层(黄色),然后是 1 个 MaxPool(绿色),然后是 2 个卷积层,1 个 Maxpool。
后面的 11 个混合层(Mixed)具体的代码还需要进一步检查。
- Here is a mapping from the old_names to the new names:
- Old name | New name
- =======================================
- conv0 | Conv2d_1a_3x3
- conv1 | Conv2d_2a_3x3
- conv2 | Conv2d_2b_3x3
- pool1 | MaxPool_3a_3x3
- conv3 | Conv2d_3b_1x1
- conv4 | Conv2d_4a_3x3
- pool2 | MaxPool_5a_3x3
- mixed_35x35x256a | Mixed_5b
- mixed_35x35x288a | Mixed_5c
- mixed_35x35x288b | Mixed_5d
- mixed_17x17x768a | Mixed_6a
- mixed_17x17x768b | Mixed_6b
- mixed_17x17x768c | Mixed_6c
- mixed_17x17x768d | Mixed_6d
- mixed_17x17x768e | Mixed_6e
- mixed_8x8x1280a | Mixed_7a
- mixed_8x8x2048a | Mixed_7b
- mixed_8x8x2048b | Mixed_7c
先看一下最前面的第 1 个卷积层,在继续阅读代码之前,想去网络上找一下关于 slim 的 API 资料,可惜暂时没有太多的资料。
从下面这个例子可以看到,slim 的 conv2d 构造的是一个激活函数为 Relu 的卷积神经网络。(其实 slim 估计和 keras 一样,是一套高级的 API 函数,语法糖)
- //使用TensorFlow的代码
- W_conv1 = weight_variable([5, 5, 1, 32])
- b_conv1 = bias_variable([32])
- h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
- //使用slim的代码
- h_conv1 = slim.conv2d(x_image, 32, [5, 5])
第一个卷积层的输入参数 299 x 299 x 3 :
- # 299 x 299 x 3
- end_point = 'Conv2d_1a_3x3'
- net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
前面的 299 x 299 代表的含义,在源代码中可以看到,是图片的默认尺寸。(The default image size used to train this network is 299x299.)
后面一个 3 表示深度 Depth,原始的 JPEG 图片的每个像素具有 RGB 3 个不同的数值,在卷积层中则设置了 3 个通道。(这里只是我的主观推测而已)
然后看一下第一个卷基层自身的参数:
表示有 32 个不同的 Filter(32 套不同的参数,最终形成 32 个 FeatureMap)。卷积核是 3 * 3 , 步长为 2。
(每个 Filter 的深度也应该是 3,如果要表示这个 Filter 的张量,应该是 3 x 3 x 3,高度,宽度,深度都是 3)
在上面两个公式中,W2 是卷积后 Feature Map 的宽度;W1 是卷积前图像的宽度;F 是 filter 的宽度;P 是 Zero Padding 数量,Zero Padding 是指在原始图像周围补几圈 0,如果的值是 1,那么就补 1 圈 0;S 是步幅;H2 是卷积后 Feature Map 的高度;H1 是卷积前图像的高度。
按照公式可以推导出卷积之后的 Feature Map 为 149 x 149
W2 = (299 - 3 + 2 * 0)/ 2 + 1 = 149
第一层的卷积输出就是第二层的卷积输入,所以第二层的第一行表示输入的注释是这样的:
- # 149 x 149 x 32
- end_point = 'Conv2d_2a_3x3'
- net = slim.conv2d(net, depth(32), [3, 3], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
149 x 149 x 32 :卷积前的特征图(FeatureMap)的大小是 149 x 149 , 一共有 32 个特征图。
如果再往下看代码,会看到一个 padding 的参数设定
- # 147 x 147 x 32
- end_point = 'Conv2d_2b_3x3'
- net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
padding 有两种参数可以设定,分别是 SAME 和 VALID:
If you like ascii art:
In this example:
Input width = 13
Filter width = 6
Stride = 5
Notes:
"VALID" only ever drops the right-most columns (or bottom-most rows).
"SAME" tries to pad evenly left and right, but if the amount of columns to be added is odd, it will add the extra column to the right, as is the case in this example (the same logic applies vertically: there may be an extra row of zeros at the bottom).
这个例子很清楚的解释了两个参数的含义。如果 Input 的宽度是 13,卷积核宽度是 6,步长是 5 的情况下,VALID 将只做 2 次卷积(1-6,6-11),第三次由于宽度不够(11-16,但是 14,15,16 缺失),就被舍弃了。SAME 的情况下,则自动在外层补零(Zero Padding),保证所有的元素都能够被卷积使用到。
注意:如果 conv2d 方法没有特别设定 padding,则需要看一下 arg_scope 是否标明了 padding。
- with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
- stride=1, padding='VALID'):
- # 299 x 299 x 3
- end_point = 'Conv2d_1a_3x3'
- net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 149 x 149 x 32
- end_point = 'Conv2d_2a_3x3'
- net = slim.conv2d(net, depth(32), [3, 3], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 147 x 147 x 32
- end_point = 'Conv2d_2b_3x3'
- net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
注意:前三层默认是步长为 1,padding 为 VALID。
以下文字,需要业内人士帮忙看一下是否正确:
输入的时候,原始图像大小是 299 x 299 的。
在图像预处理的时候,根据 R G B 三个通道,将图像分为了 3 个深度。
这样的话,输入层是 高度 299 宽度 299 深度 3
第一个卷积层,由于 Depth 是 32,则认为一共有 32 个深度为 3,高度和宽度为 3 的 Filter。步长为 2
卷积之后,结果为 32 个特征图,高度和宽度为 149.
不管深度为多少,经过一个 Filter,最后都通过上面的公式变成一个深度为 1 的特征图。
下面的例子中,输入层是高度和宽度是 7 x 7 ,深度是 3.
两个 Filter 的,每个 Filter 的高度和宽度是 3 x 3 ,深度因为要和输入层保持一致,所以也必须是 3
最左边的输入层(Input Volume)和 Filter W0 进行计算(输入的第一层和 Filter 的第一层进行运算,第二层和第二层进行运算,第三层和第三层进行运算,最后三层结果累加起来),获得了 Output Volume 的第一个结果(绿色的上面一个矩阵);和 Filter W1 进行计算,获得了 Output Volume 的第二个结果(绿色的下面一个矩阵)。
访问 观看动态图片
Pool 是一个将卷积参数进行减少的过程,这里是将 3 x 3 的区域进行步长为 2 的 Max 的下采样。
这里同样可以使用步长和宽度的计算公式,获得输出层的高度和宽度。
W2 = (147 - 3 + 2 * 0)/ 2 + 1 = 73
和卷积层相比,这里就没有什么深度计算了。这里只是单纯的进行特征图的压缩而已。
对于深度为 D 的 Feature Map,各层独立做 Pooling,因此 Pooling 后的深度仍然为 D。
- # 147 x 147 x 64
- end_point = 'MaxPool_3a_3x3'
- net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 73 x 73 x 64
- end_point = 'Conv2d_3b_1x1'
- net = slim.conv2d(net, depth(80), [1, 1], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
按照这个思路整理 Inception V3 的 Mixed Layer 之前的代码,应该没有什么问题了。
- # 299 x 299 x 3
- end_point = 'Conv2d_1a_3x3'
- net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 149 x 149 x 32
- end_point = 'Conv2d_2a_3x3'
- net = slim.conv2d(net, depth(32), [3, 3], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 147 x 147 x 32
- end_point = 'Conv2d_2b_3x3'
- net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 147 x 147 x 64
- end_point = 'MaxPool_3a_3x3'
- net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 73 x 73 x 64
- end_point = 'Conv2d_3b_1x1'
- net = slim.conv2d(net, depth(80), [1, 1], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 73 x 73 x 80.
- end_point = 'Conv2d_4a_3x3'
- net = slim.conv2d(net, depth(192), [3, 3], scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 71 x 71 x 192.
- end_point = 'MaxPool_5a_3x3'
- net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point)
- end_points[end_point] = net
- if end_point == final_endpoint: return net, end_points
- # 35 x 35 x 192.
原始的图片大小是 299 x 299 ,由于有三元色,则深度为 3.
经过一系列处理之后,尺寸变成了 35 * 35 ,深度则上升为 192.
卷积使用的激活函数是 Relu。Pooling 使用的是 Max Pooling。
未完待续
来源: http://www.cnblogs.com/TextEditor/p/6667992.html