InceptionV3代码解析

读了Google的GoogleNet以及InceptionV3的论文,决定把它实现一下,尽管很难,但是网上有不少资源,就一条一条的写完了,对于网络的解析都在代码里面了,是在原博主的基础上进行修改的,添加了更多的细节,以及自己的理解。总之,是更详细更啰嗦的一个版本,适合初学者。

  1. import tensorflow as tf
  2. from datetime import datetime
  3. import math
  4. import time
  5.  
  6. ##参考tensorflow实战书籍+博客https://blog.csdn.net/superman_xxx/article/details/65451916,不过丰富了很多细节
  7. ##适合像我一样的初学者
  8. slim = tf.contrib.slim
  9. #Slim is an interface to contrib functions, examples and models.
  10. #只是一个接口作用
  11. trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev)
  12. #匿名函数 lambda x: x * x 实际上就是:返回x的平方
  13. # tf.truncated_normal_initializer产生截断的正态分布
  14.  
  15. ########定义函数生成网络中经常用到的函数的默认参数########
  16. # 默认参数:卷积的激活函数、权重初始化方式、标准化器等
  17. def inception_v3_arg_scope(weight_decay=0.00004, # 设置L2正则的weight_decay
  18. stddev=0.1, # 标准差默认值0.1
  19. batch_norm_var_collection='moving_vars'):
  20.  
  21. # 定义batch normalization(批量标准化/归一化)的参数字典
  22. batch_norm_params = {
  23. 'decay': 0.9997, # 定义参数衰减系数
  24. 'epsilon': 0.001,
  25. 'updates_collections': tf.GraphKeys.UPDATE_OPS,
  26. 'variables_collections': {
  27. 'beta': None,
  28. 'gamma': None,
  29. 'moving_mean': [batch_norm_var_collection],
  30. 'moving_variance': [batch_norm_var_collection],#值就是前面设置的batch_norm_var_collection='moving_vars'
  31. }
  32. }
  33.  
  34. # 给函数的参数自动赋予某些默认值
  35. # slim.arg_scope常用于为tensorflow里的layer函数提供默认值以使构建模型的代码更加紧凑苗条(slim):
  36. with slim.arg_scope([slim.conv2d, slim.fully_connected],
  37. weights_regularizer=slim.l2_regularizer(weight_decay)):
  38. # 对[slim.conv2d, slim.fully_connected]自动赋值,可以是列表或元组
  39. # 使用slim.arg_scope后就不需要每次都重复设置参数了,只需要在有修改时设置
  40. with slim.arg_scope( # 嵌套一个slim.arg_scope对卷积层生成函数slim.conv2d的几个参数赋予默认值
  41. [slim.conv2d],
  42. weights_initializer=trunc_normal(stddev), # 权重初始化器
  43. activation_fn=tf.nn.relu, # 激活函数
  44. normalizer_fn=slim.batch_norm, # 标准化器
  45. normalizer_params=batch_norm_params) as sc: # 标准化器的参数设置为前面定义的batch_norm_params
  46. return sc # 最后返回定义好的scope
  47.  
  48. ########定义函数可以生成Inception V3网络的卷积部分########
  49. #########Inception V3架构见TENSORFLOW实战书-黄文坚 p124-p125页
  50. def inception_v3_base(inputs, scope=None):
  51. '''
  52. Args:
  53. inputs:输入的tensor
  54. scope:包含了函数默认参数的环境
  55. '''
  56. end_points = {} # 定义一个字典表保存某些关键节点供之后使用
  57.  
  58. with tf.variable_scope(scope, 'InceptionV3', [inputs]):
  59. with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], # 对三个参数设置默认值
  60. stride=1, padding='VALID'):
  61. # 正式定义Inception V3的网络结构。首先是前面的非Inception Module的卷积层
  62. #输入图像尺寸299 x 299 x 3
  63. # slim.conv2d的第一个参数为输入的tensor,第二个是输出的通道数,卷积核尺寸,步长stride,padding模式
  64. net = slim.conv2d(inputs, 32, [3, 3], stride=2, scope='Conv2d_1a_3x3')
  65. # 输出尺寸149 x 149 x 32
  66. '''
  67. 因为使用了slim以及slim.arg_scope,我们一行代码就可以定义好一个卷积层
  68. 相比AlexNet使用好几行代码定义一个卷积层,或是VGGNet中专门写一个函数定义卷积层,都更加方便
  69. '''
  70. net = slim.conv2d(net, 32, [3, 3], scope='Conv2d_2a_3x3')
  71. # 输出尺寸147 x 147 x 32
  72. net = slim.conv2d(net, 64, [3, 3], padding='SAME', scope='Conv2d_2b_3x3')
  73. # 输出尺寸147 x 147 x 64
  74. net = slim.max_pool2d(net, [3, 3], stride=2, scope='MaxPool_3a_3x3')
  75. # 输出尺寸73 x 73 x 64
  76. net = slim.conv2d(net, 80, [1, 1], scope='Conv2d_3b_1x1')
  77. #输出尺寸 73 x 73 x 80.
  78. net = slim.conv2d(net, 192, [3, 3], scope='Conv2d_4a_3x3')
  79. #输出尺寸 71 x 71 x 192.
  80. net = slim.max_pool2d(net, [3, 3], stride=2, scope='MaxPool_5a_3x3')
  81. # 输出尺寸35 x 35 x 192.
  82.  
  83. '''上面部分代码一共有5个卷积层,2个池化层,实现了对图片数据的尺寸压缩,并对图片特征进行了抽象
  84. 有个疑问是框架例里给出的表格中是6个卷积和一个池化,并没有1x1的卷积,为什么要这么做,以及scope后面的名字为什么要这样叫。
  85. '''
  86.  
  87. '''
  88. 接下来就是三个连续的Inception模块组,三个Inception模块组中各自分别有多个Inception Module,这部分是Inception Module V3
  89. 的精华所在。每个Inception模块组内部的几个Inception Mdoule结构非常相似,但是存在一些细节的不同
  90. '''
  91. # Inception blocks
  92. with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], # 设置所有模块组的默认参数
  93. stride=1, padding='SAME'): # 将所有卷积层、最大池化、平均池化层步长都设置为1
  94. #注意这个模块已经统一指定了padding='SAME',后面不用再说明
  95. # 第一个模块组包含了三个结构类似的Inception Module
  96. # 第一个模块组第一个Inception Module,Mixed_5b
  97. with tf.variable_scope('Mixed_5b'): # 第一个Inception Module名称。Inception Module有四个分支
  98. with tf.variable_scope('Branch_0'): # 第一个分支64通道的1*1卷积
  99. branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  100. #输出尺寸35*35*64
  101. with tf.variable_scope('Branch_1'): # 第二个分支48通道1*1卷积后一层链接一个64通道的5*5卷积
  102. branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1')
  103. #输出尺寸35*35*48
  104. branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5x5')
  105. #输出尺寸35*35*64
  106. with tf.variable_scope('Branch_2'): #第三个分支64通道1*1卷积后一层链接2个96通道的5*5卷积
  107. branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  108. #输出尺寸35*35*64
  109. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
  110. #输出尺寸35*35*96
  111. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
  112. # 输出尺寸35*35*96
  113. with tf.variable_scope('Branch_3'): # 第四个分支为3*3的平均池化后一层连接32通道的1*1卷积
  114. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  115. # 输出尺寸35*35*192
  116. branch_3 = slim.conv2d(branch_3, 32, [1, 1], scope='Conv2d_0b_1x1')
  117. # 输出尺寸35*35*32
  118. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  119. # 将四个分支的输出合并在一起(第三个维度合并,即输出通道上合并)64+64+96+32=256个通道
  120. # 输出尺寸35*35*256
  121.  
  122. # 第一个模块组第二个Inception Module 名称是:Mixed_5c
  123. with tf.variable_scope('Mixed_5c'): #同样有4个分支,唯一不同的是第4个分支最后接的是64输出通道
  124. with tf.variable_scope('Branch_0'):
  125. branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  126. # 输出尺寸35*35*64
  127. with tf.variable_scope('Branch_1'):
  128. branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0b_1x1')
  129. # 输出尺寸35*35*48
  130. branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv_1_0c_5x5')
  131. # 输出尺寸35*35*64
  132. with tf.variable_scope('Branch_2'):
  133. branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  134. # 输出尺寸35*35*64
  135. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
  136. # 输出尺寸35*35*96
  137. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
  138. # 输出尺寸35*35*96
  139. with tf.variable_scope('Branch_3'):
  140. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  141. # 输出尺寸35*35*192
  142. branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1')
  143. # 输出尺寸35*35*64
  144. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  145. # 将四个分支的输出合并在一起(第三个维度合并,即输出通道上合并)64+64+96+64=288个通道
  146. # 输出尺寸35*35*288
  147.  
  148. # 第一个模块组第3个Inception Module 名称是:Mixed_5d
  149. with tf.variable_scope('Mixed_5d'):
  150. with tf.variable_scope('Branch_0'):
  151. branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  152. with tf.variable_scope('Branch_1'):
  153. branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1')
  154. branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5x5')
  155. with tf.variable_scope('Branch_2'):
  156. branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  157. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
  158. branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
  159. with tf.variable_scope('Branch_3'):
  160. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  161. branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1')
  162. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  163. # 将四个分支的输出合并在一起(第三个维度合并,即输出通道上合并)64+64+96+64=288个通道
  164. # 输出尺寸35*35*288
  165.  
  166. # 第二个Inception模块组是一个非常大的模块组,包含了5个Inception Mdoule,2-5个Inception Mdoule结构非常相似
  167. #第二个模块组第一个Inception Module 名称是:Mixed_6a
  168. #输入是35*35*288
  169. with tf.variable_scope('Mixed_6a'): #包含3个分支
  170. with tf.variable_scope('Branch_0'):
  171. branch_0 = slim.conv2d(net, 384, [3, 3], stride=2,
  172. padding='VALID', scope='Conv2d_1a_1x1')
  173. # padding='VALID'图片尺寸会被压缩,通道数增加
  174. # 输出尺寸17*17*384
  175. with tf.variable_scope('Branch_1'): #64通道的1*1加2个96通道的3*3卷积
  176. branch_1 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
  177. #输出尺寸35 * 35 * 64
  178. branch_1 = slim.conv2d(branch_1, 96, [3, 3], scope='Conv2d_0b_3x3')
  179. #输出尺寸35*35*96
  180. branch_1 = slim.conv2d(branch_1, 96, [3, 3], stride=2,
  181. padding='VALID', scope='Conv2d_1a_1x1')
  182. # 图片被压缩/输出尺寸17*17*96
  183. with tf.variable_scope('Branch_2'):
  184. branch_2 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID',
  185. scope='MaxPool_1a_3x3')
  186. #输出尺寸17 * 17 * 288(这里大家注意,书上还有很多博客上都是384+96+256=736并不是768,所以最后应该加288)
  187. net = tf.concat([branch_0, branch_1, branch_2], 3)
  188. # 输出尺寸定格在17 x 17 x 768
  189.  
  190. # 第二个模块组第二个Inception Module 名称是:Mixed_6b
  191. with tf.variable_scope('Mixed_6b'): #4个分支
  192. with tf.variable_scope('Branch_0'):
  193. branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  194. # 输出尺寸17*17*192
  195. with tf.variable_scope('Branch_1'):
  196. branch_1 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1')
  197. # 输出尺寸17 * 17 * 128
  198. branch_1 = slim.conv2d(branch_1, 128, [1, 7],
  199. scope='Conv2d_0b_1x7')
  200. # 输出尺寸17 * 17 * 128
  201. # 串联1*7卷积和7*1卷积合成7*7卷积,减少了参数,减轻了过拟合
  202. branch_1 = slim.conv2d(branch_1, 192, [7, 1], scope='Conv2d_0c_7x1')
  203. # 输出尺寸17 * 17 * 192
  204. with tf.variable_scope('Branch_2'):
  205. branch_2 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1')
  206. # 输出尺寸17 * 17 * 128
  207. # 反复将7*7卷积拆分
  208. branch_2 = slim.conv2d(branch_2, 128, [7, 1], scope='Conv2d_0b_7x1')
  209. branch_2 = slim.conv2d(branch_2, 128, [1, 7], scope='Conv2d_0c_1x7')
  210. branch_2 = slim.conv2d(branch_2, 128, [7, 1], scope='Conv2d_0d_7x1')
  211. branch_2 = slim.conv2d(branch_2, 192, [1, 7], scope='Conv2d_0e_1x7')
  212. # 这种方法算是利用Factorization into small convolutions 的典范
  213. with tf.variable_scope('Branch_3'): #3*3的平均池化
  214. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  215. # 输出尺寸17 * 17 * 768
  216. branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  217. # 输出尺寸17 * 17 * 192
  218. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  219. # 输出尺寸定格在17 x 17 x (192*4)=17*17*768
  220.  
  221. # 第二个模块组第三个Inception Module 名称是:Mixed_6c
  222. # 同前面一个相似,第二个分支和第三个分支的前几层通道由120升为160
  223. with tf.variable_scope('Mixed_6c'):
  224. with tf.variable_scope('Branch_0'):
  225. '''
  226. 我们的网络每经过一个inception module,即使输出尺寸不变,但是特征都相当于被重新精炼了一遍,
  227. 其中丰富的卷积和非线性化对提升网络性能帮助很大。
  228. '''
  229. branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  230. with tf.variable_scope('Branch_1'):
  231. branch_1 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1')
  232. branch_1 = slim.conv2d(branch_1, 160, [1, 7], scope='Conv2d_0b_1x7')
  233. branch_1 = slim.conv2d(branch_1, 192, [7, 1], scope='Conv2d_0c_7x1')
  234. with tf.variable_scope('Branch_2'):
  235. branch_2 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1')
  236. branch_2 = slim.conv2d(branch_2, 160, [7, 1], scope='Conv2d_0b_7x1')
  237. branch_2 = slim.conv2d(branch_2, 160, [1, 7], scope='Conv2d_0c_1x7')
  238. branch_2 = slim.conv2d(branch_2, 160, [7, 1], scope='Conv2d_0d_7x1')
  239. branch_2 = slim.conv2d(branch_2, 192, [1, 7], scope='Conv2d_0e_1x7')
  240. with tf.variable_scope('Branch_3'):
  241. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  242. branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  243. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  244. # 输出尺寸定格在17 x 17 x (192*4)=17*17*768
  245.  
  246. # 第二个模块组第四个Inception Module 名称是:Mixed_6d
  247. # 和前面一个完全一样,增加卷积和非线性,提炼特征
  248. with tf.variable_scope('Mixed_6d'):
  249. with tf.variable_scope('Branch_0'):
  250. branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  251. with tf.variable_scope('Branch_1'):
  252. branch_1 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1')
  253. branch_1 = slim.conv2d(branch_1, 160, [1, 7], scope='Conv2d_0b_1x7')
  254. branch_1 = slim.conv2d(branch_1, 192, [7, 1], scope='Conv2d_0c_7x1')
  255. with tf.variable_scope('Branch_2'):
  256. branch_2 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1')
  257. branch_2 = slim.conv2d(branch_2, 160, [7, 1], scope='Conv2d_0b_7x1')
  258. branch_2 = slim.conv2d(branch_2, 160, [1, 7], scope='Conv2d_0c_1x7')
  259. branch_2 = slim.conv2d(branch_2, 160, [7, 1], scope='Conv2d_0d_7x1')
  260. branch_2 = slim.conv2d(branch_2, 192, [1, 7], scope='Conv2d_0e_1x7')
  261. with tf.variable_scope('Branch_3'):
  262. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  263. branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  264. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  265. # 输出尺寸定格在17 x 17 x (192*4)=17*17*768
  266.  
  267. # 第二个模块组第五个Inception Module 名称是:Mixed_6e
  268. # 也同前面一样,通道数变为192,但是要将Mixed_6e存储在end_points中
  269. with tf.variable_scope('Mixed_6e'):
  270. with tf.variable_scope('Branch_0'):
  271. branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  272. with tf.variable_scope('Branch_1'):
  273. branch_1 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  274. branch_1 = slim.conv2d(branch_1, 192, [1, 7], scope='Conv2d_0b_1x7')
  275. branch_1 = slim.conv2d(branch_1, 192, [7, 1], scope='Conv2d_0c_7x1')
  276. with tf.variable_scope('Branch_2'):
  277. branch_2 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  278. branch_2 = slim.conv2d(branch_2, 192, [7, 1], scope='Conv2d_0b_7x1')
  279. branch_2 = slim.conv2d(branch_2, 192, [1, 7], scope='Conv2d_0c_1x7')
  280. branch_2 = slim.conv2d(branch_2, 192, [7, 1], scope='Conv2d_0d_7x1')
  281. branch_2 = slim.conv2d(branch_2, 192, [1, 7], scope='Conv2d_0e_1x7')
  282. with tf.variable_scope('Branch_3'):
  283. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  284. branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  285. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  286. # 输出尺寸定格在17 x 17 x (192*4)=17*17*768
  287. end_points['Mixed_6e'] = net
  288. # 将Mixed_6e存储于end_points中,作为Auxiliary Classifier辅助模型的分类
  289.  
  290. # 第三个Inception模块包含了3个Inception Mdoule,后两个个Inception Mdoule结构非常相似
  291. # 第三个模块组第一个Inception Module 名称是:Mixed_7a
  292. with tf.variable_scope('Mixed_7a'): # 3个分支
  293. with tf.variable_scope('Branch_0'):
  294. branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  295. # 输出尺寸17*17*192
  296. branch_0 = slim.conv2d(branch_0, 320, [3, 3], stride=2,
  297. padding='VALID', scope='Conv2d_1a_3x3')
  298. # 压缩图片# 输出尺寸8*8*320
  299. with tf.variable_scope('Branch_1'):
  300. branch_1 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1')
  301. branch_1 = slim.conv2d(branch_1, 192, [1, 7], scope='Conv2d_0b_1x7')
  302. branch_1 = slim.conv2d(branch_1, 192, [7, 1], scope='Conv2d_0c_7x1')
  303. branch_1 = slim.conv2d(branch_1, 192, [3, 3], stride=2,
  304. padding='VALID', scope='Conv2d_1a_3x3')
  305. # 输出尺寸8*8*192
  306. with tf.variable_scope('Branch_2'): # 池化层不会对输出通道数产生改变
  307. branch_2 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID',
  308. scope='MaxPool_1a_3x3')
  309. # 输出尺寸8*8*768
  310. net = tf.concat([branch_0, branch_1, branch_2], 3)
  311. # 输出图片尺寸被缩小,通道数增加,tensor的总size在持续下降中
  312. # 输出尺寸8*8*(320+192+768)=8*8*1280
  313.  
  314. # 第三个模块组第二个Inception Module 名称是:Mixed_7b
  315. '''
  316. 这个模块最大的区别是分支内又有分支,network in network in network
  317. '''
  318. with tf.variable_scope('Mixed_7b'): # 4 个分支
  319. with tf.variable_scope('Branch_0'):
  320. branch_0 = slim.conv2d(net, 320, [1, 1], scope='Conv2d_0a_1x1')
  321. # 输出尺寸8*8*320
  322. with tf.variable_scope('Branch_1'): #第二个分支里还有分支
  323. branch_1 = slim.conv2d(net, 384, [1, 1], scope='Conv2d_0a_1x1')
  324. # 输出尺寸8*8*384
  325. branch_1 = tf.concat([
  326. slim.conv2d(branch_1, 384, [1, 3], scope='Conv2d_0b_1x3'),
  327. slim.conv2d(branch_1, 384, [3, 1], scope='Conv2d_0b_3x1')], 3)
  328. # 输出尺寸8 * 8 * (384+384)=8*8*768
  329. with tf.variable_scope('Branch_2'): #这个分支更复杂:1*1>3*3>1*3+3*1,总共有三层
  330. branch_2 = slim.conv2d(net, 448, [1, 1], scope='Conv2d_0a_1x1')
  331. branch_2 = slim.conv2d(
  332. branch_2, 384, [3, 3], scope='Conv2d_0b_3x3')
  333. branch_2 = tf.concat([
  334. slim.conv2d(branch_2, 384, [1, 3], scope='Conv2d_0c_1x3'),
  335. slim.conv2d(branch_2, 384, [3, 1], scope='Conv2d_0d_3x1')], 3)
  336. # 输出尺寸8 * 8 * (384+384)=8*8*768
  337. with tf.variable_scope('Branch_3'):
  338. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  339. branch_3 = slim.conv2d(
  340. branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  341. # 输出尺寸8 * 8 * (384+384)=8*8*192
  342. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  343. # 输出通道数增加到2048 # 输出尺寸8 * 8 * (320+768+768+192)=8*8*2048
  344.  
  345. # 第三个模块组第三个Inception Module 名称是:Mixed_7c
  346. #同前一个一样
  347. with tf.variable_scope('Mixed_7c'):
  348. with tf.variable_scope('Branch_0'):
  349. branch_0 = slim.conv2d(net, 320, [1, 1], scope='Conv2d_0a_1x1')
  350. with tf.variable_scope('Branch_1'):
  351. branch_1 = slim.conv2d(net, 384, [1, 1], scope='Conv2d_0a_1x1')
  352. branch_1 = tf.concat([
  353. slim.conv2d(branch_1, 384, [1, 3], scope='Conv2d_0b_1x3'),
  354. slim.conv2d(branch_1, 384, [3, 1], scope='Conv2d_0c_3x1')], 3)
  355. with tf.variable_scope('Branch_2'):
  356. branch_2 = slim.conv2d(net, 448, [1, 1], scope='Conv2d_0a_1x1')
  357. branch_2 = slim.conv2d(
  358. branch_2, 384, [3, 3], scope='Conv2d_0b_3x3')
  359. branch_2 = tf.concat([
  360. slim.conv2d(branch_2, 384, [1, 3], scope='Conv2d_0c_1x3'),
  361. slim.conv2d(branch_2, 384, [3, 1], scope='Conv2d_0d_3x1')], 3)
  362. with tf.variable_scope('Branch_3'):
  363. branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
  364. branch_3 = slim.conv2d(
  365. branch_3, 192, [1, 1], scope='Conv2d_0b_1x1')
  366. net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
  367. # 输出尺寸8 * 8 * (320+768+768+192)=8*8*2048
  368.  
  369. return net, end_points
  370. #Inception V3网络的核心部分,即卷积层部分就完成了
  371. '''
  372. 设计inception net的重要原则是图片尺寸不断缩小,inception模块组的目的都是将空间结构简化,同时将空间信息转化为
  373. 高阶抽象的特征信息,即将空间维度转为通道的维度。降低了计算量。Inception Module是通过组合比较简单的特征
  374. 抽象(分支1)、比较比较复杂的特征抽象(分支2和分支3)和一个简化结构的池化层(分支4),一共四种不同程度的
  375. 特征抽象和变换来有选择地保留不同层次的高阶特征,这样最大程度地丰富网络的表达能力。
  376. '''
  377.  
  378. ########全局平均池化、Softmax和Auxiliary Logits(之前6e模块的辅助分类节点)########
  379. def inception_v3(inputs,
  380. num_classes=1000, # 最后需要分类的数量(比赛数据集的种类数)
  381. is_training=True, # 标志是否为训练过程,只有在训练时Batch normalization和Dropout才会启用
  382. dropout_keep_prob=0.8, # 节点保留比率
  383. prediction_fn=slim.softmax, # 最后用来分类的函数
  384. spatial_squeeze=True, # 参数标志是否对输出进行squeeze操作(去除维度数为1的维度,比如5*3*1转为5*3)
  385. reuse=None, # 是否对网络和Variable进行重复使用
  386. scope='InceptionV3'): # 包含函数默认参数的环境
  387.  
  388. with tf.variable_scope(scope, 'InceptionV3', [inputs, num_classes], # 定义参数默认值
  389. reuse=reuse) as scope:
  390. #'InceptionV3'是命名空间
  391. with slim.arg_scope([slim.batch_norm, slim.dropout], # 定义标志默认值
  392. is_training=is_training):
  393. # 拿到最后一层的输出net和重要节点的字典表end_points
  394. net, end_points = inception_v3_base(inputs, scope=scope) # 用定义好的函数构筑整个网络的卷积部分
  395.  
  396. # Auxiliary Head logits作为辅助分类的节点,对分类结果预测有很大帮助,
  397. # 对end_points的结界做平均池化、卷积最后通过1*1的卷积将通道变为1000
  398. with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
  399. stride=1, padding='SAME'): # 将卷积、最大池化、平均池化步长设置为1
  400. aux_logits = end_points['Mixed_6e'] # 通过end_points取到Mixed_6e
  401. with tf.variable_scope('AuxLogits'):
  402. aux_logits = slim.avg_pool2d(
  403. aux_logits, [5, 5], stride=3, padding='VALID', # 在Mixed_6e之后接平均池化。压缩图像尺寸
  404. scope='AvgPool_1a_5x5')
  405. # 输入图像尺寸17*17*768,输出5*5*768
  406. aux_logits = slim.conv2d(aux_logits, 128, [1, 1], # 卷积。压缩图像尺寸。
  407. scope='Conv2d_1b_1x1')
  408. # 输出图像尺寸5*5*128
  409. # Shape of feature map before the final layer.
  410. aux_logits = slim.conv2d(
  411. aux_logits, 768, [5,5],
  412. weights_initializer=trunc_normal(0.01), # 权重初始化方式重设为标准差为0.01的正态分布
  413. padding='VALID', scope='Conv2d_2a_5x5')
  414. # 输出图像尺寸1*1*768
  415. aux_logits = slim.conv2d(
  416. aux_logits, num_classes, [1, 1], activation_fn=None,
  417. normalizer_fn=None, weights_initializer=trunc_normal(0.001),
  418. scope='Conv2d_2b_1x1')
  419. # 输出变为1*1*1000,这里的num_classes表示输出通道数,不用激活和标准化,权重重设为0.001的正态分布
  420. if spatial_squeeze: # tf.squeeze消除tensor中前两个为1的维度。
  421. aux_logits = tf.squeeze(aux_logits, [1, 2], name='SpatialSqueeze')
  422.  
  423. # 这里非常值得注意,tf.squeeze(aux_logits, [1, 2])为什么是[1, 2]而不是[0,1],括号里表示为1的维度
  424. #因为训练的时候是输入的批次,第一维不是1,[32,1,1,1000].
  425. end_points['AuxLogits'] = aux_logits
  426. # 最后将辅助分类节点的输出aux_logits储存到字典表end_points中
  427.  
  428. # 处理正常的分类预测逻辑
  429. # Final pooling and prediction
  430. # 这一过程的主要步骤:对Mixed_7c的输出进行8*8的全局平均池化>Dropout>1*1*1000的卷积>除去维数为1>softmax分类
  431. with tf.variable_scope('Logits'):
  432. net = slim.avg_pool2d(net, [8, 8], padding='VALID',
  433. scope='AvgPool_1a_8x8')
  434. #输入为8*8*2048 输出为1 x 1 x 2048
  435. net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b')
  436. end_points['PreLogits'] = net
  437. # 1*1*2048
  438. logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
  439. normalizer_fn=None, scope='Conv2d_1c_1x1')
  440. # 激活函数和规范化函数设为空 # 输出通道数1*1*1000
  441. if spatial_squeeze: # tf.squeeze去除输出tensor中维度为1的节点
  442. logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze')
  443. end_points['Logits'] = logits
  444. end_points['Predictions'] = prediction_fn(logits, scope='Predictions')
  445. # Softmax对结果进行分类预测
  446. return logits, end_points # 最后返回logits和包含辅助节点的end_points
  447. #end_points里面有'AuxLogits'、'Logits'、'Predictions'分别是辅助分类的输出,主线的输出以及经过softmax后的预测输出
  448.  
  449. '''
  450. 到这里,前向传播已经写完,对其进行运算性能测试
  451. '''
  452. ########评估网络每轮计算时间########
  453. def time_tensorflow_run(session, target, info_string):
  454.  
  455. # Args:
  456. # session:the TensorFlow session to run the computation under.
  457. # target:需要评测的运算算子。
  458. # info_string:测试名称。
  459.  
  460. num_steps_burn_in = 10
  461. # 先定义预热轮数(头几轮跌代有显存加载、cache命中等问题因此可以跳过,只考量10轮迭代之后的计算时间)
  462. total_duration = 0.0 # 记录总时间
  463. total_duration_squared = 0.0 # 总时间平方和 -----用来后面计算方差
  464.  
  465. #迭代计算时间
  466. for i in range(num_batches + num_steps_burn_in): # 迭代轮数
  467. start_time = time.time() # 记录时间
  468. _ = session.run(target) # 每次迭代通过session.run(target)
  469. duration = time.time() - start_time
  470. #每十轮输出一次
  471. if i >= num_steps_burn_in:
  472. if not i % 10:
  473. print ('%s: step %d, duration = %.3f' %
  474. (datetime.now(), i - num_steps_burn_in, duration))
  475. total_duration += duration # 累加便于后面计算每轮耗时的均值和标准差
  476. total_duration_squared += duration * duration
  477. mn = total_duration / num_batches # 每轮迭代的平均耗时
  478. vr = total_duration_squared / num_batches - mn * mn
  479. # 方差,是把一般的方差公式进行化解之后的结果,值得 借鉴
  480. sd = math.sqrt(vr) # 标准差
  481. print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
  482. (datetime.now(), info_string, num_batches, mn, sd))
  483. #输出的时间是处理一批次的平均时间加减标准差
  484.  
  485. # 测试前向传播性能
  486. batch_size = 32 # 因为网络结构较大依然设置为32,以免GPU显存不够
  487. height, width = 299, 299 # 图片尺寸
  488. # 随机生成图片数据作为input
  489. inputs = tf.random_uniform((batch_size, height, width, 3))
  490.  
  491. with slim.arg_scope(inception_v3_arg_scope()):
  492. # scope中包含了batch normalization默认参数,激活函数和参数初始化方式的默认值
  493. logits, end_points = inception_v3(inputs, is_training=False)
  494. # inception_v3中传入inputs获取里logits和end_points
  495.  
  496. init = tf.global_variables_initializer() # 初始化全部模型参数
  497. sess = tf.Session() # 创建session
  498. sess.run(init)
  499. num_batches=100 # 测试的batch数量
  500. time_tensorflow_run(sess, logits, "Forward")
  501. '''
  502. 虽然输入图片比VGGNet的224*224大了78%,但是forward速度却比VGGNet更快。
  503. 这主要归功于其较小的参数量,inception V3参数量比inception V1的700万
  504. 多了很多,不过仍然不到AlexNet的6000万参数量的一半。相比VGGNet的1.4
  505. 亿参数量就更少了。整个网络的浮点计算量为50亿次,比inception V1的15亿
  506. 次大了不少,但是相比VGGNet来说不算大。因此较少的计算量让inception V3
  507. 网络变得非常实用,可以轻松地移植到普通服务器上提供快速响应服务,甚至
  508. 移植到手机上进行实时的图像识别。
  509. '''
  510. #分析结果:前面的输出是当前时间下每10步的计算时间,最后输出的是当前时间下前向传播的总批次以及平均时间+-标准差

InceptionV3代码解析的更多相关文章

  1. VBA常用代码解析

    031 删除工作表中的空行 如果需要删除工作表中所有的空行,可以使用下面的代码. Sub DelBlankRow() DimrRow As Long DimLRow As Long Dimi As L ...

  2. [nRF51822] 12、基础实验代码解析大全 · 实验19 - PWM

    一.PWM概述: PWM(Pulse Width Modulation):脉冲宽度调制技术,通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形. PWM 的几个基本概念: 1) 占空比:占空比是指 ...

  3. [nRF51822] 11、基础实验代码解析大全 · 实验16 - 内部FLASH读写

     一.实验内容: 通过串口发送单个字符到NRF51822,NRF51822 接收到字符后将其写入到FLASH 的最后一页,之后将其读出并通过串口打印出数据. 二.nRF51822芯片内部flash知识 ...

  4. [nRF51822] 10、基础实验代码解析大全 · 实验15 - RTC

    一.实验内容: 配置NRF51822 的RTC0 的TICK 频率为8Hz,COMPARE0 匹配事件触发周期为3 秒,并使能了TICK 和COMPARE0 中断. TICK 中断中驱动指示灯D1 翻 ...

  5. [nRF51822] 9、基础实验代码解析大全 · 实验12 - ADC

    一.本实验ADC 配置 分辨率:10 位. 输入通道:5,即使用输入通道AIN5 检测电位器的电压. ADC 基准电压:1.2V. 二.NRF51822 ADC 管脚分布 NRF51822 的ADC ...

  6. java集合框架之java HashMap代码解析

     java集合框架之java HashMap代码解析 文章Java集合框架综述后,具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 源自http://www.codeceo.com/arti ...

  7. Kakfa揭秘 Day8 DirectKafkaStream代码解析

    Kakfa揭秘 Day8 DirectKafkaStream代码解析 今天让我们进入SparkStreaming,看一下其中重要的Kafka模块DirectStream的具体实现. 构造Stream ...

  8. linux内存管理--slab及其代码解析

    Linux内核使用了源自于 Solaris 的一种方法,但是这种方法在嵌入式系统中已经使用了很长时间了,它是将内存作为对象按照大小进行分配,被称为slab高速缓存. 内存管理的目标是提供一种方法,为实 ...

  9. MYSQL常见出错mysql_errno()代码解析

    如题,今天遇到怎么一个问题, 在理论上代码是不会有问题的,但是还是报了如上的错误,把sql打印出來放到DB中却可以正常执行.真是郁闷,在百度里面 渡 了很久没有相关的解释,到时找到几个没有人回复的 & ...

随机推荐

  1. elasticsearch+filebeat+kibana提取多行日志

    filebeat的配置文件filebeat.yml以下三行去掉注释 multiline.pattern: ^\[ multiline.negate: true //false改为true multil ...

  2. 在select标签中添加a标签

    <!--第一个选项不能写连接--> <select id="" onchange="window.location=this.value"&g ...

  3. vs 利用web deploy发布 常见问题解决

    1. 下载并安装 web deploy, 安装时选择自定义,选择全部安装 https://www.iis.net/downloads/microsoft/web-deploy  2. 确保iis的管理 ...

  4. System.Math.cs

    ylbtech-System.Math.cs 1. 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c56193 ...

  5. System.Web.Mvc.IActionFilter.cs

    ylbtech-System.Web.Mvc.IActionFilter.cs 1.程序集 System.Web.Mvc, Version=5.2.3.0, Culture=neutral, Publ ...

  6. Docker系列(八):Kubernetes横空出世背后的秘密

    Docker与CoreOS的恩怨情仇 2013年2月,Docker建立了一个网站发布它的首个演示版本, 3月,美国加州Alex Polvi正在自己的车库开始 他的 第二次创业 有了第一桶金的Alex这 ...

  7. hibernate annotation 之 注解声明

    @Entity 将一个 POJO 类注解成一个实体 bean ( 持久化 POJO 类 ) @Table 为实体 bean 映射指定具体的表,如果该注解没有被声明,系统将使用默认值 ( 即实体 bea ...

  8. JS之缓冲动画

    原素材 main.html <!DOCTYPE html> <html lang="en"> <head> <link href=&quo ...

  9. HYNB Round 15: PKU Campus 2019

    HYNB Round 15: PKU Campus 2019 C. Parade 题意 将平面上n*2个点安排在长度为n的两行上. 做法 首先可以忽略每个点之间的影响,只用考虑匹配即可 然后把所以点归 ...

  10. UASCO Cow Pedigrees /// oj10140

    题目大意: 输入n,m :二叉树 输出 n个点分为m层 的方案数: 每个点的分支要么是0要么是2 Sample Input 5 3 Sample Output 2 即 两个方案为          O ...