Inception V3源代碼(Slim實(shí)現(xiàn))
整體架構(gòu)
Google的Tensorflow已經(jīng)在Github上開源了,找到了這樣的一個(gè)源代碼,由于非科班出身拧粪,所以也無法斷定是否這個(gè)就是inception的源代碼了铐懊。暫時(shí)就以這個(gè)作為對(duì)象進(jìn)行研究了
[文章編寫的時(shí)候參考如下代碼,已經(jīng)失效]
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
分析源代碼的時(shí)候祟蚀,可以將上節(jié)的圖和代碼一起觀看工窍。(暫時(shí)沒有找到V4的圖片,所以前酿,只能研究V3了患雏。如果大家有興趣也可以研究最牛逼的ResNet深度殘差網(wǎng)絡(luò))
從代碼上看,整個(gè)深度網(wǎng)絡(luò)的結(jié)構(gòu)體系可能是這樣子的罢维。從輸入端開始淹仑,先有3個(gè)卷積層,然后是1個(gè)pool層。然后又是2個(gè)卷積層匀借,一個(gè)pool層颜阐。這個(gè)和上面那張神經(jīng)網(wǎng)絡(luò)構(gòu)造圖是完全一致的。前3個(gè)是卷積層(黃色)吓肋,然后是1個(gè)MaxPool(綠色)凳怨,然后是2個(gè)卷積層,1個(gè)Maxpool是鬼。
后面的11個(gè)混合層(Mixed)具體的代碼還需要進(jìn)一步檢查肤舞。
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個(gè)卷積層,在繼續(xù)閱讀代碼之前均蜜,想去網(wǎng)絡(luò)上找一下關(guān)于slim的API資料李剖,可惜暫時(shí)沒有太多的資料。
TensorFlow-Slim@github
slim操作的源代碼
TF-Slimを使ってTensorFlowを簡潔に書く
從下面這個(gè)例子可以看到兆龙,slim的conv2d構(gòu)造的是一個(gè)激活函數(shù)為Relu的卷積神經(jīng)網(wǎng)絡(luò)杖爽。(其實(shí)slim估計(jì)和keras一樣,是一套高級(jí)的API函數(shù)紫皇,語法糖)
//使用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])
第一個(gè)卷積層的輸入?yún)?shù) 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 代表的含義慰安,在源代碼中可以看到,是圖片的默認(rèn)尺寸聪铺。(The default image size used to train this network is 299x299.)
后面一個(gè)3 表示深度Depth(有時(shí)候叫做Chanel)化焕,原始的JPEG圖片的每個(gè)像素具有RGB 3個(gè)不同的數(shù)值,在卷積層中則設(shè)置了3個(gè)通道铃剔。下面的測試代碼中撒桨,整個(gè)張量:
- 第一維表示每次投入的圖片數(shù)為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))
然后看一下第一個(gè)卷基層自身的參數(shù):
表示輸出層的深度為32键兜,卷積核是 3 * 3 ,步長為2凤类。這里輸入層深度為3輸出層深度為32.
(這里應(yīng)該使用了32個(gè)不同的Filter,每個(gè)Filter應(yīng)該是 3 x 3 x 3普气,高度谜疤,寬度,深度都是3现诀。高和寬是3的原因是卷積核大小是[3, 3]夷磕,深度是3的原因是輸入層的深度是3)
在上面兩個(gè)公式中,W2是卷積后Feature Map的寬度仔沿;W1是卷積前圖像的寬度坐桩;F是filter的寬度;P是Zero Padding數(shù)量封锉,Zero Padding是指在原始圖像周圍補(bǔ)幾圈0绵跷,如果的值是1膘螟,那么就補(bǔ)1圈0;S是步幅抖坪;H2是卷積后Feature Map的高度萍鲸;H1是卷積前圖像的高度。
按照公式可以推導(dǎo)出卷積之后的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個(gè)特征圖脊阴。
關(guān)于padding的細(xì)節(jié)
如果再往下看代碼,會(huì)看到一個(gè)padding的參數(shù)設(shè)定
# 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有兩種參數(shù)可以設(shè)定蚯瞧,分別是SAME和VALID:
What is the difference between 'SAME' and 'VALID' padding in tf.nn.max_pool of tensorflow?
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).
這個(gè)例子很清楚的解釋了兩個(gè)參數(shù)的含義嘿期。如果Input的寬度是13,卷積核寬度是6埋合,步長是5的情況下备徐,VALID將只做2次卷積(1-6,6-11)甚颂,第三次由于寬度不夠(11-16蜜猾,但是14,15振诬,16缺失)蹭睡,就被舍棄了。SAME的情況下赶么,則自動(dòng)在外層補(bǔ)零(Zero Padding)肩豁,保證所有的元素都能夠被卷積使用到。
注意:如果conv2d方法沒有特別設(shè)定padding辫呻,則需要看一下arg_scope是否標(biāo)明了padding清钥。
前三層卷積的總結(jié)
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
注意:前三層默認(rèn)是步長為1,padding為VALID放闺。
以下文字祟昭,需要業(yè)內(nèi)人士幫忙看一下是否正確:
輸入的時(shí)候,原始圖像大小是 299 x 299 的怖侦。
在圖像預(yù)處理的時(shí)候篡悟,根據(jù) R G B 三個(gè)通道,將圖像分為了3個(gè)深度础钠。
這樣的話,輸入層是 高度299 寬度 299 深度3
第一個(gè)卷積層叉谜,由于Depth是32旗吁,則認(rèn)為一共有32個(gè)深度為3,高度和寬度為3的Filter停局。步長為2
卷積之后很钓,結(jié)果為32個(gè)特征圖香府,高度和寬度為149.
前面我們已經(jīng)講了深度為1的卷積層的計(jì)算方法,如果深度大于1怎么計(jì)算呢码倦?其實(shí)也是類似的企孩。如果卷積前的圖像深度為D,那么相應(yīng)的filter的深度也必須為D袁稽。我們擴(kuò)展一下式1勿璃,得到了深度大于1的卷積計(jì)算公式:
卷積深度
說明
不管深度為多少,經(jīng)過一個(gè)Filter推汽,最后都通過上面的公式變成一個(gè)深度為1的特征圖补疑。
下面的例子中,輸入層是高度和寬度是 7 x 7 歹撒,深度是3.
兩個(gè)Filter的莲组,每個(gè)Filter的高度和寬度是 3 x 3 ,深度因?yàn)橐洼斎雽颖3忠恢屡玻砸脖仨毷?3
最左邊的輸入層(Input Volume)和Filter W0 進(jìn)行計(jì)算(輸入的第一層和Filter的第一層進(jìn)行運(yùn)算锹杈,第二層和第二層進(jìn)行運(yùn)算,第三層和第三層進(jìn)行運(yùn)算迈着,最后三層結(jié)果累加起來)竭望,獲得了 Output Volume 的第一個(gè)結(jié)果(綠色的上面一個(gè)矩陣);和Filter W1 進(jìn)行計(jì)算寥假,獲得了 Output Volume 的第二個(gè)結(jié)果(綠色的下面一個(gè)矩陣)市框。
訪問 http://upload-images.jianshu.io/upload_images/2256672-958f31b01695b085.gif 觀看動(dòng)態(tài)圖片
參數(shù)估算
第一層輸入為 深度為 3,第一層卷積核為[3,3]糕韧,輸出深度為32
需要32個(gè)不同的Filter枫振,每個(gè)Filter的參數(shù)是 3 x 3 x 3 = 27個(gè)∮┎剩總共需要參數(shù) 27 x 32 = 864 個(gè)粪滤。
第二層輸入深度為32,第二層卷積核為[3,3]雀扶,輸出深度為32
需要32個(gè)不同的Filter杖小,每個(gè)Filter的參數(shù)是 3 x 3 x 32 = 288個(gè)∮弈梗總共需要參數(shù) 288 x 32 = 9612 個(gè)予权。
第三層輸入深度為32,第三層卷積核為[3,3]浪册,輸出深度為64
需要64個(gè)不同的Filter扫腺,每個(gè)Filter的參數(shù)是 3 x 3 x 32 = 288個(gè)〈逑螅總共需要參數(shù) 288 x 64 = 18432 個(gè)笆环。
前三層的參數(shù)大約為28908個(gè)攒至。
MaxPool
Pool是一個(gè)將卷積參數(shù)進(jìn)行減少的過程,這里是將 3 x 3 的區(qū)域進(jìn)行步長為2的Max的下采樣躁劣。
這里同樣可以使用步長和寬度的計(jì)算公式迫吐,獲得輸出層的高度和寬度。
W2 = (147 - 3 + 2 * 0)/ 2 + 1 = 73
和卷積層相比账忘,這里就沒有什么深度計(jì)算了志膀。這里只是單純的進(jìn)行特征圖的壓縮而已。
對(duì)于深度為D的Feature Map闪萄,各層獨(dú)立做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
按照這個(gè)思路整理Inception V3的Mixed Layer之前的代碼败去,應(yīng)該沒有什么問題了放航。
# 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.
經(jīng)過一系列處理之后广鳍,尺寸變成了 35 * 35 ,深度則上升為 192.
卷積使用的激活函數(shù)是Relu吓妆。Pooling使用的是 Max Pooling赊时。
輔助塊
在整個(gè)Mixed層的中間,可以看到有一個(gè)分支塊行拢。這個(gè)分支包含一個(gè)AvgPool層祖秒,兩個(gè)Conv層,和一個(gè)Fully Connect層舟奠,一個(gè)Softmax層竭缝。
這個(gè)層是用來干什么的呢?從代碼的注釋看:
Auxiliary Head logits 如果直譯的話:輔助用頭部洛基特幾率沼瘫。
這個(gè)東西的用法抬纸,在模型里面無法找到答案,那么我們看一下測試用代碼里面是不是有答案耿戚。
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])
這個(gè)看上去應(yīng)該是用來做檢證的湿故,看一下張量的形狀是不是和我們預(yù)期的一樣。并沒有什么特別的意義膜蛔。
最后3層
最后3層的理解應(yīng)該是比較容易的坛猪。
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層:
這個(gè)層的作用是隨機(jī)除去一些神經(jīng)元,使得整個(gè)模型不至于過擬合皂股。
至于為什么這樣做能夠防止過擬合墅茉,網(wǎng)絡(luò)上有很多說明文檔,這里就不再啰嗦了。
這里一般選擇keep_prob = 0.8 (這個(gè)參數(shù)值在代碼中定義躁锁,可以修改),保留80%的神經(jīng)元卵史。至于為什么是0.8战转,這個(gè)應(yīng)該是很多實(shí)驗(yàn)得出的結(jié)果。
理解dropout
FullConnect
全連接層以躯,在整個(gè)過程的最后槐秧,才使用全連接,訓(xùn)練出權(quán)重忧设。
(僅僅這里進(jìn)行訓(xùn)練權(quán)重刁标?還是filter也需要訓(xùn)練?)
Softmax
這個(gè)神經(jīng)網(wǎng)絡(luò)的最后是softmax層址晕。softmax層也就是分類專用的層膀懈,使用一個(gè)概率來表示待分類對(duì)象有多大概率屬于某個(gè)類。
從代碼里面看谨垃,最后一層一個(gè)有2048個(gè)元素(下圖中784個(gè)元素)启搂,輸出層的class nums為1000。(下圖為10)
最后的概率矩陣看上去應(yīng)該是這個(gè)樣子的刘陶。