[不按期更新长文]卷积神经网络工做原理研究

本人只是出于兴趣研究这个话题,本人并不是学者,或者从业者,因此可能在写做中出现技术上的硬伤,请各位指正。
本文可能根据业界最新进展进行更新, 欢迎来评论中进行交流和研究。
本文所引用的资料将严格标明出处和版权。本文首发于简书平台,博客园和本人公众号,请勿转载
html

卷积神经网络在图像识别领域无限风光,经过一张图片,算法能够知道图片上的物体是什么,着实使人震惊,可是不少人和我同样,对于其背后的原理,都很是好奇,卷积神经网络是如何进行图像识别的呢python

图像识别

若是你的英文主够好的话,能够阅读这篇论文:
Visualizing and Understanding Convolutional Networksgit

猫应该长成什么样子

看过女神李飞飞的ImageNet演讲的人,都对于下面两张图片印象深入。
原文请阅读:
ImageNet缔造者:让冰冷的机器读懂照片背后的故事github

(从薛定谔开始,猫就一直被各类科学家拿出来讲事情,固然汪星人也时不时出镜)算法

人类是如何识别猫咪的?借用知乎的一个回答:网络

如今假设要作一个图像的分类问题,好比辨别一个图像里是否有一只猫,咱们能够先判断是否有猫的头,猫的尾巴,猫的身子等等,若是这些特征都具有,那么我就断定这应该是一只猫。固然,若是图像是下面这样一只老实本分的猫咪,则一切都好办了。架构

正常的猫

可是喵星人不但品种不一样,颜色繁多,各类销魂的动做也层出不穷,因此,机器识别猫仍是很困难的。app

扭曲的猫

这样,咱们必需要让机器知道,猫,到底应该长成什么样子。iphone

想象中的图形识别原理

第一次考虑怎么处理这个问题,一个很天然的想法浮想在脑海里面:
将全部猫咪的图片放在一块儿,提取出猫咪的共同特征,作成一个识别猫的模型。而后对于每张图片,使用模型,看一下是猫的几率为多少。可是若是真的这样作的话,可能每种物体都必需要有一个专门的模型了,这样多是不行的,计算量可能也是一个问题。特别对于扭曲的猫,这样子的例子很是难处理,咱们不太可能穷举出全部猫的正常和非正常形态。(毛色,眼神,是否有物体和猫进行交互)机器学习

固然,能够考虑,将猫进行分解,就如知乎网友所说,猫头,猫尾巴,猫爪子,独立进行识别。这样无论猫怎么扭曲,都无所谓了。固然,若是你是资深猫奴,你能够很高兴的说出猫的组成特征,可是,这样本质上仍是加入了太多的领域专家的干涉。若是要识别大型粒子加速器,这个是否是要请物理学家参与呢?因此,机器应该彻底屏蔽领域知识才能够作到泛用。

图像处理矩阵

虽然不是科班出身,可是之前或多或少看过一些图像处理的书籍。
通常的图像处理都是经过矩阵操做完成的。

  • 位置和形状 : 例如图像的拉伸,缩放,旋转(固然能够是各类效果的组合)。
  • 颜色: 灰度调整,透明度,滤镜

具体的颜色矩阵文章:
C++图像处理 -- 颜色矩阵变换

图像处理

其实我认为卷积核这个概念,应该是从图像处理矩阵这个概念来的。经过不一样的图像处理矩阵,能够突出图像的某些特征,屏蔽掉某些细节。

图像滤镜处理算法:灰度、黑白、底片、浮雕 

原图

处理后的图片,屏蔽了颜色,突出了轮廓特征。(猫的轮廓特征保留下来了,颜色特征暂时消失了)

黑白图

固然,实际处理的时候,可能使用的卷积核可能更加复杂。不过,若是真的看一下卷积核的工做方式,通常来讲,卷积用来进行特征的提取,而不是进行图像的预处理的(或者说,是将图像针对特征进行压缩的一个过程)。

卷积核

主流图像识别

上面所说的都大半是猜想,不管如何也应该看一下真实的算法究竟是怎么样的。图像识别上最有名气的算法大概就是Inception模型。整个算法的架构大概是这样的,深度也是叹为观止。(当前ResNet神经网络已经152层了,计算量至关至关至关可怕)
inception_v3_architecture.png

原始图像通过了深深的流水线以后,最后在Softmax层进行分类。这个过程当中到底发生了什么事情,图像在Softmax层变成了什么,这个多是全部人都关心的问题。本文也想经过长期的研究,能或多或少搞清楚里面的奥秘。这个过程应该是极其艰苦的,很是困难的。可是对于机器学习的思考却很是有帮助。

Inception V3源代码(Slim实现)

总体架构

Google的Tensorflow已经在Github上开源了,找到了这样的一个源代码,因为非科班出身,因此也没法判定是否这个就是inception的源代码了。暂时就以这个做为对象进行研究了
https://github.com/tensorflow/models/tree/master/inception
而后按照ReadMe的指示看到如下的工程
https://github.com/tensorflow/models/tree/master/slim
最新的V3代码在如下连接里面
https://github.com/tensorflow/models/blob/master/slim/nets/inception_v3.py

分析源代码的时候,能够将上节的图和代码一块儿观看。(暂时没有找到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

TF-Slim

先看一下最前面的第1个卷积层,在继续阅读代码以前,想去网络上找一下关于slim的API资料,惋惜暂时没有太多的资料。
TensorFlow-Slim@github
slim操做的源代码

TF-Slimを使ってTensorFlowを簡潔に書く
从下面这个例子能够看到,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的细节

若是再往下看代码,会看到一个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:
What is the difference between 'SAME' and 'VALID' padding in tf.nn.max_pool of tensorflow?

If you like ascii art:

padding
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.

前面咱们已经讲了深度为1的卷积层的计算方法,若是深度大于1怎么计算呢?其实也是相似的。若是卷积前的图像深度为D,那么相应的filter的深度也必须为D。咱们扩展一下式1,获得了深度大于1的卷积计算公式:
卷积深度
说明

无论深度为多少,通过一个Filter,最后都经过上面的公式变成一个深度为1的特征图。

下面的例子中,输入层是高度和宽度是 7 x 7 ,深度是3.
两个Filter的,每一个Filter的高度和宽度是 3 x 3 ,深度由于要和输入层保持一致,因此也必须是 3
最左边的输入层(Input Volume)和Filter W0 进行计算(输入的第一层和Filter的第一层进行运算,第二层和第二层进行运算,第三层和第三层进行运算,最后三层结果累加起来),得到了 Output Volume 的第一个结果(绿色的上面一个矩阵);和Filter W1 进行计算,得到了 Output Volume 的第二个结果(绿色的下面一个矩阵)。

访问 //upload-images.jianshu.io/upload_images/2256672-958f31b01695b085.gif 观看动态图片
Filter

MaxPool

Pool是一个将卷积参数进行减小的过程,这里是将 3 x 3 的区域进行步长为2的Max的下采样。
这里一样可使用步长和宽度的计算公式,得到输出层的高度和宽度。
W2 = (147 - 3 + 2 * 0)/ 2 + 1 = 73
和卷积层相比,这里就没有什么深度计算了。这里只是单纯的进行特征图的压缩而已。
对于深度为D的Feature Map,各层独立作Pooling,所以Pooling后的深度仍然为D。

Max

# 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。
Relu

深刻Mixed层

# mixed: 35 x 35 x 256.
      end_point = 'Mixed_5b'
      with tf.variable_scope(end_point):
        with tf.variable_scope('Branch_0'):
          branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1')
        with tf.variable_scope('Branch_1'):
          branch_1 = slim.conv2d(net, depth(48), [1, 1], scope='Conv2d_0a_1x1')
          branch_1 = slim.conv2d(branch_1, depth(64), [5, 5],
                                 scope='Conv2d_0b_5x5')
        with tf.variable_scope('Branch_2'):
          branch_2 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1')
          branch_2 = slim.conv2d(branch_2, depth(96), [3, 3],
                                 scope='Conv2d_0b_3x3')
          branch_2 = slim.conv2d(branch_2, depth(96), [3, 3],
                                 scope='Conv2d_0c_3x3')
        with tf.variable_scope('Branch_3'):
          branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
          branch_3 = slim.conv2d(branch_3, depth(32), [1, 1],
                                 scope='Conv2d_0b_1x1')
        net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3])
      end_points[end_point] = net
      if end_point == final_endpoint: return net, end_points

avgPooling 和 tf.concat

通常作Pooling的时候,使用的是maxPooling,在Mixed层出现了avgPooling。

除了Max Pooing以外,经常使用的还有Mean Pooling——取各样本的平均值。

可能avgpooling就是mean pooling吧。

tf.concat函数也是一个重点知识:这里使用concat将张量进行链接。可是具体的链接形状还须要进一步考证。

concat

Inception V4架构图

v3的架构图,对于mixed的细节并非很清晰,因此这里找了一张v4的架构图来看一下。

Inception v4

看一下右下角,除了参数以外和咱们的Mixed模型很像了。(V4比V3更加的深)

filter

Mixed 层(inception结构)分析

inception结构具备3种不一样的形式,(Mixed_5x,Mixed_6x,Mixed_7x),下面就是这3种形式的示例图。
仔细观察,这里有两个特色:
这里使用了不少 1 x 1的卷积核。

Googlenet 中1*1 卷积核分析

一种简单的解释是用来降维。
For example, an image of 200200 with 50 features on convolution with 20 filters of 11 would result in size of 20020020.
可是,1*1卷积核的做用不只仅于此。

  • 特征变换
    11卷积是在Network in Network 中第一次被提出来,做者的目的是为了获得一个深的网络,但做者并不想直接垂直的堆砌一些layer。做者用了一些11,33的卷积层,所以
    最后的网络架构实际上是going wide. 在googlenet中,1
    1卷积核有两个做用:
    1 为了使得网络更深,做者引入了相似Network in Network 中的"inception module"
    2 为了下降维度
    3 为了增长更多的非线性变换(eg. RELU)
  • 其余做用
    1 11卷积核能够结合max pooling
    2 1
    1卷积核能够设置大的步长,这样能够在丢失很是少的信息降低低数据的维度
    3 取代fc层。

或者将 n x n 的卷积核改写为 n x 1 和 1 x n 。

v3一个最重要的改进是分解(Factorization),将7x7分解成两个一维的卷积(1x7,7x1),3x3也是同样(1x3,3x1),这样的好处,既能够加速计算(多余的计算能力能够用来加深网络),又能够将1个conv拆成2个conv,使得网络深度进一步增长,增长了网络的非线性,还有值得注意的地方是网络输入从224x224变为了299x299,更加精细设计了35x35/17x17/8x8的模块;
做者:无话可说
连接:https://www.zhihu.com/question/50370954/answer/138938524
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

inception结构

辅助块

在整个Mixed层的中间,能够看到有一个分支块。这个分支包含一个AvgPool层,两个Conv层,和一个Fully Connect层,一个Softmax层。
这个层是用来干什么的呢?从代码的注释看:

Auxiliary Head logits 若是直译的话:辅助用头部洛基特概率。
这个东西的用法,在模型里面没法找到答案,那么咱们看一下测试用代码里面是否是有答案。

https://github.com/tensorflow/models/blob/master/slim/nets/inception_v3_test.py

def testBuildEndPoints(self):
    batch_size = 5
    height, width = 299, 299
    num_classes = 1000
    ...
    ...
    self.assertTrue('AuxLogits' in end_points)
    aux_logits = end_points['AuxLogits']
    self.assertListEqual(aux_logits.get_shape().as_list(),
                         [batch_size, num_classes])

这个看上去应该是用来作检证的,看一下张量的形状是否是和咱们预期的同样。并无什么特别的意义。

最后3层

最后3层的理解应该是比较容易的。

def inception_v3(inputs,
                 num_classes=1000,
                 is_training=True,
                 dropout_keep_prob=0.8,
                 min_depth=16,
                 depth_multiplier=1.0,
                 prediction_fn=slim.softmax,
                 spatial_squeeze=True,
                 reuse=None,
                 scope='InceptionV3'):

      # Final pooling and prediction
      with tf.variable_scope('Logits'):
        kernel_size = _reduced_kernel_size_for_small_input(net, [8, 8])
        net = slim.avg_pool2d(net, kernel_size, padding='VALID',
                              scope='AvgPool_1a_{}x{}'.format(*kernel_size))
        # 1 x 1 x 2048
        net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b')
        end_points['PreLogits'] = net
        # 2048
        logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
                             normalizer_fn=None, scope='Conv2d_1c_1x1')
        if spatial_squeeze:
          logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze')
        # 1000
      end_points['Logits'] = logits
      end_points['Predictions'] = prediction_fn(logits, scope='Predictions')

Dropout层:

这个层的做用是随机除去一些神经元,使得整个模型不至于过拟合。
至于为何这样作可以防止过拟合,网络上有不少说明文档,这里就再也不啰嗦了。
这里通常选择keep_prob = 0.8 (这个参数值在代码中定义,能够修改),保留80%的神经元。至于为何是0.8,这个应该是不少实验得出的结果。
理解dropout

Dropout

FullConnect

全链接层,在整个过程的最后,才使用全链接,训练出权重。
(仅仅这里进行训练权重?仍是filter也须要训练?)

FullConnect

Softmax

这个神经网络的最后是softmax层。softmax层也就是分类专用的层,使用一个几率来表示待分类对象有多大几率属于某个类。
softmax
最后的几率矩阵看上去应该是这个样子的。
几率矩阵

关于Filter的意义

本章节参照了zhihu.com的内容。因此我完整引用,不进行任何修改。版权归原做者全部

Paste_Image.png
你眼睛真实看到的图像实际上是上图的下半部分。然后通过大脑的层层映射后才出现了你脑中所“看见”的图像。CNN的卷积层部分能够理解成是学习你的“眼球结构”。

Paste_Image.png

同一个filter内部的权重是相同的,由于它用一个“抓取方式”去侦测特征。好比说“边缘侦测”。 你也注意到了,咱们的眼睛不仅观看一次,等到扫描完该特征后,另外一个filter能够改变“抓取方式”去侦测另外一个特征。所权重在同一个filter内是共享的理解是该filter对整个图片进行了某个特征的扫描。

Paste_Image.png

提取若干个特征后,就能够靠这些特征来判断图片是什么了。

疑问

1.filter的选择问题,各个filter是怎么肯定内部的值的?
2.训练到底只是训练最后的全链接层,仍是整个神经网络?

未完待续

参考资料

卷积神经网络工做原理直观的解释?
[透析] 卷积神经网络CNN到底是怎样一步一步工做的?
TF-Slimを使ってTensorFlowを簡潔に書く
深刻浅出——网络模型中Inception的做用与结构全解析
零基础入门深度学习(4) - 卷积神经网络
cs231n学习笔记-CNN-目标检测、定位、分割
A Note to Techniques in Convolutional Neural Networks and Their Influences III (paper summary)
理解dropout
CNN卷积神经网络架构综述

相关文章
相关标签/搜索