卷积神经网络工作原理研究 - Inception V3源代码

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

最新代码如下
https://github.com/tensorflow/models/tree/master/research/inception

分析源代码的时候,可以将上节的图和代码一起观看。(暂时没有找到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(有时候叫做Chanel),原始的JPEG图片的每个像素具有RGB 3个不同的数值,在卷积层中则设置了3个通道。下面的测试代码中,整个张量:

  • 第一维表示每次投入的图片数为5
  • 第二,三维表示图片的长和宽是299
  • 第四维表示RGB
    batch_size = 5
    height, width = 299, 299
    ...
    #inputs: a tensor of size [batch_size, height, width, channels].
    ...
    inputs = tf.random_uniform((batch_size, height, width, 3))
卷积示例

然后看一下第一个卷基层自身的参数:
表示输出层的深度为32,卷积核是 3 * 3 ,步长为2。这里输入层深度为3输出层深度为32.
(这里应该使用了32个不同的Filter,每个Filter应该是 3 x 3 x 3,高度,宽度,深度都是3。高和宽是3的原因是卷积核大小是[3, 3],深度是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 的第二个结果(绿色的下面一个矩阵)。

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

Filter

参数估算

第一层输入为 深度为 3,第一层卷积核为[3,3],输出深度为32
需要32个不同的Filter,每个Filter的参数是 3 x 3 x 3 = 27个。总共需要参数 27 x 32 = 864 个。
第二层输入深度为32,第二层卷积核为[3,3],输出深度为32
需要32个不同的Filter,每个Filter的参数是 3 x 3 x 32 = 288个。总共需要参数 288 x 32 = 9612 个。
第三层输入深度为32,第三层卷积核为[3,3],输出深度为64
需要64个不同的Filter,每个Filter的参数是 3 x 3 x 32 = 288个。总共需要参数 288 x 64 = 18432 个。
前三层的参数大约为28908个。

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层的中间,可以看到有一个分支块。这个分支包含一个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

从代码里面看,最后一层一个有2048个元素(下图中784个元素),输出层的class nums为1000。(下图为10)


手写数字识别

最后的概率矩阵看上去应该是这个样子的。


概率矩阵
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容