tensorflow 實現google Inception Net
Inception 有22層深,但是比alxnet的8層活著vggnet的19層更深,但是引數量更小。他去除了最後的全連線層,用全域性拼婚池化來取代(圖片尺寸變為1*1),全連結層基本上佔據了ALxnet活著vggnet 90%的引數量,而且容易引起過擬合。結構圖如下。
其結構反覆堆疊在一起形成一個大網路。
模型圖中有四個分枝,最左邊的分支對輸入進行1*1的卷積,這是NIN中提出的一個重要的結構,1*1的卷積是一個非常優秀的結構,他可以跨通道組織資訊,提高網路表達能力,同時對輸入通道升維和降維。可以看到inception 的四個分支都用到了1*1的卷積,來進行低成本的特徵變化(每個畫素都變化了下)。
第二個分支使用了1*1的卷積,然後連結3*3的卷積,相當於進行了兩次特徵變化。
第三個分支使用了1*1的卷積,然後連結5*5的卷積,也是兩次的特徵變化。
第四個分支是3*3的最大池化之後使用1*1的卷積。
這四個分支從不同的角度提取了圖片的特徵,最後通過一個聚合操作合併(在輸出通道數這個維度上增加活著聚合)。
人腦的神經元連結是稀疏的,因此研究者認為大型的神經網路應該也是稀疏的。稀疏的網路可以降低引數量,從而對沒有很多的樣本不存在過擬合的狀況。例如卷積神經網路就是稀疏連線的。Inception Net的主要目標就是找到最優的稀疏結構單元(Inception Moudle),這個結構基於Hebbian原理。
Hebbian原理:神經反射活動的持續與重複導致神經元連結穩定性的持久提升。當兩個神經元細胞A和B激勵很近的時候,並且A參與了對B重複,持續的興奮。那麼某些代謝變化會導致A能夠作為B的興奮細胞。即 “一起發射的神經元會連在一起”,學習過程中的刺激會使神經元之間的突出強度增加。應用中:如果資料集的概率分佈可以被一個很大很稀疏的神經網路所表達,那麼構築這個網路的最佳方法是一層一層的構建網路--將上一層高度相關的節點聚類,並將聚類出來的部分連結在一起。如下圖所示
一個好的神經網路總是把相關性高的一部分神經元結點(卷積和)連結在一起。在普通的資料集合中,這可能需要對神經元結點聚類,但是在這個圖片資料中,天然的就是臨近區域的資料相關性高,因此相鄰的畫素點被卷積操作連結在一起,而我們可能有多個卷積核,在同一空間位置在不同管道的卷積核的輸出結果相關性極高。 因此一個1*1的卷積核就可以吧這些相關性很高的,在同一個空間位置,但是不同通道的特徵連線在一起。這就是1*1被廣泛運用的原因。
整個神經網路中,除了最後一層的輸出,其中間節點的分類效果也很好,在Inception中還是用了輔助分類的節點,就是將中間某一層的輸出用作分類,並按照一個較小的權重(0.3)加入分類結構中。相當於做了模型融合,同時給網路增加了反向傳播訊號,也提供了額外的正則化。
Inception V1是比較早的版本,當時使用了SGD訓練(梯度下降的三種形式之一)
Inception V2學習了VGGnet,用兩個3*3的卷積核代替了5*5的大卷積,並且提出了BN的方法。
Bn是一個非常有效的正則化方法,這裡提一下正則化,正則化用於機器學習中的防止過擬合的方式,主要是約束,並且限制要優化的引數,減少變數引數。BN在用於神經網路的某層的時候,會對每一個mini-batch資料的內部進行標準化處理,使輸出規範到N(0,1)的正態分佈,傳統的神經網路在訓練的時候,每一層的輸入的分佈都在變化,導致訓練變得苦難,只能用一個很小的學習速率來解決這個問題,如果對每一層bn之後,就可以有效的解決這個問題,學習速率增加,迭代次數減少,訓練時間縮短。
另外還有一些其他的調整:增大學習速率並加快學習衰減速度以適用BN規範化後的資料,去除Dropout並減輕L2正則(因為BN已經起到了正則的作用),去除LRN(區域性相應歸一化層),跟徹底的對訓練樣本進行shuffle,減小資料增強過程中對資料的光學畸變(因為BN訓練的更快,每個樣本被訓練的次數更少,因此跟真實的樣本對訓練更有幫助)
Inception V3網路則有兩方面的改造,一個是引入 factorization into samll convolutions 的思想,將一個較大的二維卷積拆成兩個較小的一維卷積,比如將7*7卷積拆成1*7卷積和7*1卷積核,這樣做減少了引數,同時增加了一層線性變化的表達能力(從低維度變成高緯度) 。另一方面優化了Inception Moudle的結構,inception moudle只在網路的後部出現,前部還是普通的卷積層。Inception V3除了在Inception Moudle中使用分支,還在分支中使用分支(8*8的結構中)
Inception V4結合了微軟的ResNet而成。程式碼設計的時候使用tf.contrib.slim輔助設計構建42層深的Inception V3
首先定義一個簡單的trunc_normal函式,產生截斷的正太分佈
import tensorflow as tf
slim = tf.contrib.slim
trunc_normal = lambda stddev:tf.truncated_normal_initializer(0.0,stddev)
下面定義函式inception_v3_arg_scope,用來產生網路中經常用到的函式的預設引數,比如卷積的啟用函式,權重初始化方式,標準化器等。設定L2正則的weight_decay預設值為0.00004,標準差stddev預設值為0.1,引數batch_normal_var_collection預設值為moving_vars.
def inception_v3_arg_scope(weight_decay=0.00004,stddev=0.1,batch_norm_var_collection='moving_vars'):
接下來定義batch_normalization的引數字典,定義其衰減係數decay為0.9997,epsilon為0.001,updates_collections為tf.GraphKeys.UPDATE_OPS,然後字典variables_collections中beta和gammma均設定為None,moving_mean和moving_variance設定為前面的btach_normal_var_collection.
batch_norm_params={
'decay':0.9997,
'epsilon':0.001,
'updates_collections':tf.GraphKeys.UPDATE_OPS,
'variables_collections':{'beta':None,'gamma':None,'moving_mean':[batch_norm_var_collection],'moving_variance':[batch_norm_var_collection],}
}
接下來使用slim.arg_scope,這是個非常有用的工具,他可以給函式的引數自動賦予某些預設值
例如 with slim.arg_scope([slim.conv2d,slim.fully_connected],weights_regularizer=slim.12_regularizer(weight_decay)),會對[slim.conv2d,slim.fully_connected]這兩個函式的引數自動賦值,將引數weights_regularizer的值預設設為slim.12_regularizer(weight_decay)使用slim.arg_scope後就不要每次重複設定引數。只需要在修改的地方設定。
with slim.arg_scop([slim.conv2d,slim.fully_connected],weight_regular=slim.12_regularizer(weight_decay))
with slim.arg_scope([slim,conv2d],weight_initializer=tf.truncated_normal_initializer(stddev=stddev),activation_fn=tf.nn.relu,normalize_fn=slim.batch_norm,normalizer_params=batch_norm_params) as sc:
return sc
接下來定義函式inception_v3_base,他可以生成inception_v3的卷積部分,引數input為輸入圖片的tensor,scoope 包含了函式預設引數的環境,我們定義一個字典表end_points,用來儲存某些關鍵點。輸入資料尺寸為299*299*3經過這些層之後變為35*35*192(共5個卷積層,兩個池化層)
def inception_v3_base(inputs,scope=None):
end_points={}
#建立變數,名稱是InceptionV3
with tf.variable_scope(scope,'InceptionV3',[inputs]):
#引數預設賦值
with slim.arg_scope([slim.conv2d,slim.max_pool2d,slim.avg_pool2d],stride=1,padding='VALID'):
net = slim.conv2d(inputs,32,[3,3],stride=2,scope='Conv2d_1a_3*3')
net = slim.conv2d(net,32,[3,3],scope='Conv2d_2a_3*3')
net = slim.conv2d(net,64,[3,3],padding='SAME',scope='Conv2d_2b_3*3')
net = slim.max_pool2d(net,[3,3],stride=2,scope='MaxPool_3a_3*3')
net = slim.conv2d(net,80,[1,1],scope='Conv2d_3b_1*1')
net = slim.conv2d(net,192,[3,3],scope='Conv2d_4a_3*3')
net = slim.max_pool2d(net,[3,3],stride=2,scope='MaxPool_5a_3*3')
接下來是三個連續的Inception模組組,這三個Inception模組組各自有多個inception Moudle
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='SAME'):
with tf.variable_scope('Mixed_5b'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net,48,[1,1],scope='Conv2d_0a_1*1')
branch_1 = slim.conv2d(branch_1,64,[5,5],scope='Conv2d_0b_5*5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
branch_2 = slim.conv2d(branch_2,96,[3,3],scope='Conv2d_0b_3*3')
branch_2 = slim.conv2d(branch_2,96,[3*3],scope='Conv2d_0c_3*3')
with tf.variable_scope('Branch_3'):
branch_3 = slim.avg_pool2d(net,[3,3],scope='AvgPool_0a_3*3')
branch_3 = slim.conv2d(branch_3,32,[1,1],scope='Conv2d_ob_1*1')
#維度上連線起來
net = tf.concat([branch_0,branch_1,branch_2,branch_3],3)
mixed_5c
with tf.variable_scope('Mixed_5c'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net,48,[1,1],scope='Conv2d_ob_1*1')
branch_1 = slim.conv2d(branch_1,64,[5,5],scope='Conv_1_0c_5*5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
branch_2 = slim.conv2d(branch_2,96,[3*3],scope='Conv2d_0b_3*3')
branch_2 = slim.conv2d(branch_2,96,[3*3],scope="Conv2d_0c_3*3")
with tf.variable_scope('Bracnch_3'):
branch_3 = slim.avg_pool2d(net,[3,3],scope='AvgPool_0a_3*3')
branch_3 = slim.conv2d(branch_3,64,[1,1],scope='Conv2d_0b_1*1')
net = tf.concat([branch_0,branch_1,branch_2,branch_3],3)
mined_5d和5c基本上一樣
with tf.variable_scope('Mixed_5d'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net,48,[1,1],scope='Conv2d_ob_1*1')
branch_1 = slim.conv2d(branch_1,64,[5,5],scope='Conv_1_0c_5*5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
branch_2 = slim.conv2d(branch_2,96,[3*3],scope='Conv2d_0b_3*3')
branch_2 = slim.conv2d(branch_2,96,[3*3],scope="Conv2d_0c_3*3")
with tf.variable_scope('Bracnch_3'):
branch_3 = slim.avg_pool2d(net,[3,3],scope='AvgPool_0a_3*3')
branch_3 = slim.conv2d(branch_3,64,[1,1],scope='Conv2d_0b_1*1')
net = tf.concat([branch_0,branch_1,branch_2,branch_3],3)
然後是mined_6a
with tf.variable_scope('Mixed_6a'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net,384,[3,3],padding='VALID',scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net,64,[1,1],scope='Conv2d_ob_1*1')
branch_1 = slim.conv2d(branch_1,96,[3,3],scope='Conv_1_0c_5*5')
branch_1 = slim.conv2d(branch_1,96,[3,3],stride=2,padding='VALID',scope='Conv_1_0c_5*5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.max_pool2d(net,[3,3],stride=2,padding='VALID',scope='MaxPool_1a_3*3')
net = tf.concat([branch_0,branch_1,branch_2],3)
接下來是Inception模組組的第二個Inception Module--Mixed_6b 他有4個分支,第一個分支是一個簡單的192輸出通道的1*1卷積,第二個分支是由三層卷積層組成,第一層是1*1,第二層是128通道的1*7卷積,第三層是192輸出的7*1卷積。第三個分支是有5個卷積層,128輸出通道的1*1,128輸出通道的7*1卷積,128輸出通道的1*7,128*7*1,192*1*7。第四個分支是一個3*3的平均池化,再連線192輸出通道的1*1卷積。最後將是個分支合併,這一層輸出tensor的尺寸既是17*17*(192+192+192+192)= 17*17*768
先寫到這裡吧。