深度學習卷積神經網路——經典網路GoogLeNet(Inception V3)網路的搭建與實現
一、Inception網路(google公司)——GoogLeNet網路的綜述
獲得高質量模型最保險的做法就是增加模型的深度(層數)或者是其寬度(層核或者神經元數),
但是這裡一般設計思路的情況下會出現如下的缺陷:
1.引數太多,若訓練資料集有限,容易過擬合;
2.網路越大計算複雜度越大,難以應用;
3.網路越深,梯度越往後穿越容易消失,難以優化模型。
解決上述兩個缺點的根本方法是將全連線甚至一般的卷積都轉化為稀疏連線。為了打破網路對稱性和提高
學習能力,傳統的網路都使用了隨機稀疏連線。但是,計算機軟硬體對非均勻稀疏資料的計算效率很差,
所以在AlexNet中又重新啟用了全連線層,目的是為了更好地優化並行運算。
既能保持網路結構的稀疏性,又能利用密集矩陣的高計算效能。
二、 Inception模組介紹
Inception架構的主要思想是找出如何用密整合分來近似最優的區域性稀疏結。
對上圖做以下說明:
1 . 採用不同大小的卷積核意味著不同大小的感受野,最後拼接意味著不同尺度特徵的融合;
2 . 之所以卷積核大小採用1*1、3*3和5*5,主要是為了方便對齊。設定卷積步長stride=1之後,
只要分別設定padding =0、1、2,採用same卷積可以得到相同維度的特徵,然後這些特徵直接拼接在一起;
3 . 文章說很多地方都表明pooling挺有效,所以I
4 . 網路越到後面特徵越抽象,且每個特徵涉及的感受野也更大,隨著層數的增加,3x3和5x5卷積的比例也要增加。
Inception的作用:代替人工確定卷積層中的過濾器型別或者確定是否需要建立卷積層和池化層,即:不需要人為的
決定使用哪個過濾器,是否需要池化層等,由網路自行決定這些引數,可以給網路新增所有可能值,將輸出連線
起來,網路自己學習它需要什麼樣的引數。
naive版本的Inception網路的缺陷:計算成本。使用5x5的卷積核仍然會帶來巨大的計算量,約需要1.2億次的計算量。
為減少計算成本,採用1x1卷積核來進行降維
在3x3和5x5的過濾器前面,max pooling後分別加上了1x1的卷積核,最後將它們全部以通道/厚度為軸拼接起來,
最終輸出大小為28*28*256,卷積的引數數量比原來減少了4倍,得到最終版本的Inception模組:
三 、googLeNet介紹
1、googLeNet——Inception V1結構
googlenet的主要思想就是圍繞這兩個思路去做的:
(1).深度,層數更深,文章採用了22層,為了避免上述提到的梯度消失問題,
googlenet巧妙的在不同深度處增加了兩個loss來保證梯度回傳消失的現象。
(2).寬度,增加了多種核 1x1,3x3,5x5,還有直接max pooling的,
但是如果簡單的將這些應用到feature map上的話,concat起來的feature map厚度將會很大,
所以在googlenet中為了避免這一現象提出的inception具有如下結構,在3x3前,5x5前,
max pooling後分別加上了1x1的卷積核起到了降低feature map厚度的作用。
對上圖做如下說明: (1)顯然GoogLeNet採用了Inception模組化(9個)的結構,共22層,方便增添和修改; (2)網路最後採用了average pooling來代替全連線層,想法來自NIN,引數量僅為AlexNet的1/12,效能優於AlexNet, 事實證明可以將TOP1 accuracy提高0.6%。但是,實際在最後還是加了一個全連線層,主要是為了方便finetune; (3)雖然移除了全連線,但是網路中依然使用了Dropout ; (4)為了避免梯度消失,網路額外增加了2個輔助的softmax用於向前傳導梯度。 文章中說這兩個輔助的分類器的loss應該加一個衰減係數,但看caffe中的model也沒有加任何衰減。 此外,實際測試的時候,這兩個額外的softmax會被去掉。
(5)上述的GoogLeNet的版本成它使用的Inception V1結構。
2、Inception V2結構
大尺寸的卷積核可以帶來更大的感受野,也意味著更多的引數,比如5x5卷積核引數是3x3卷積核的25/9=2.78倍。
為此,作者提出可以用2個連續的3x3卷積層(stride=1)組成的小網路來代替單個的5x5卷積層,這便是Inception V2結構,
保持感受野範圍的同時又減少了引數量,如下圖:
3、Inception V3結構
大卷積核完全可以由一系列的3x3卷積核來替代,那能不能分解的更小一點呢。
文章考慮了nx1 卷積核,如下圖所示的取代3x3卷積:
於是,任意nxn的卷積都可以通過1xn卷積後接nx1卷積來替代。實際上,作者發現在網路的前期使用這種分解效果
並不好,還有在中度大小的feature map上使用效果才會更好,對於mxm大小的feature map,建議m在12到20之間。
用nx1卷積來代替大卷積核,這裡設定n=7來應對17x17大小的feature map。該結構被正式用在GoogLeNet V2中。
4、Inception V4結構,它結合了殘差神經網路ResNet。
參考連結:http://blog.csdn.net/stdcoutzyx/article/details/51052847
http://blog.csdn.net/shuzfan/article/details/50738394#googlenet-inception-v2
5、Inception——ResNet V1 & Inception——ResNet V2
四、整體架構程式碼實現
GoogLeNet.py檔案實現Inception v3網路前向傳播過程以及網路的引數:
(一)slim應用介紹
slim這個模組是在16年新推出的,其主要目的是來做所謂的“程式碼瘦身”。
tensorflow官方對它的描述是:此目錄中的任何程式碼未經官方支援,可能會隨時更改或刪除。每個目錄下都有指定的所有者。它旨在包含額外功能和貢獻,最終會合併到核心TensorFlow中,但其介面可能仍然會發生變化,或者需要進行一些測試,看是否可以獲得更廣泛的接受。所以slim依然不屬於原生tensorflow。
slim是一個使構建,訓練,評估神經網路變得簡單的庫。它可以消除原生tensorflow裡面很多重複的模板性的程式碼,讓程式碼更緊湊,更具備可讀性。另外slim提供了很多計算機視覺方面的著名模型(VGG, AlexNet等),我們不僅可以直接使用,甚至能以各種方式進行擴充套件。
1、slim的匯入方法
2、slim子模組及功能介紹
(1)arg_scope:除了基本的namescope,variabelscope外,又加了arg_scope,它是用來控制每一層的預設超引數的。如果你的網路有大量相同的引數,如下所示:
net = slim.conv2d(inputs, 64, [11, 11], 4, padding='SAME',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv1')
net = slim.conv2d(net, 128, [11, 11], padding='VALID',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv2')
net = slim.conv2d(net, 256, [11, 11], padding='SAME',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv3')
用arg_scope處理一下:with slim.arg_scope([slim.conv2d], padding='SAME',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
weights_regularizer=slim.l2_regularizer(0.0005)):
net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
net = slim.conv2d(net, 256, [11, 11], scope='conv3')
arg_scope作用範圍內:是定義了指定層的預設引數,若想特別指定某些層的引數,可以重新賦值(相當於重寫)with slim.arg_scope([slim.conv2d, slim.fully_connected],
activation_fn=tf.nn.relu,
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005)):
with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'):
net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1')
net = slim.conv2d(net, 256, [5, 5],
weights_initializer=tf.truncated_normal_initializer(stddev=0.03),
scope='conv2')
net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')
(2)layers: 這個比較重要,slim的核心和精髓,一些複雜層的定義。
對比分別用tensorflow和slim實現一個卷積層的案例:
#tensorflow實現卷積層
with tf.name_scope('conv1_1') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32,
stddev=1e-1), name='weights')
conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32),
trainable=True, name='biases')
bias = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(bias, name=scope)
#slim實現卷積層
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
比較吸引人的是slim中的repeat和stack操作,假設定義三個相同的卷積層,
在slim中的repeat操作可減少程式碼量:
net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
stack是處理卷積核或者輸出不一樣的情況:假設定義三層FC:
# Verbose way:
x = slim.fully_connected(x, 32, scope='fc/fc_1')
x = slim.fully_connected(x, 64, scope='fc/fc_2')
x = slim.fully_connected(x, 128, scope='fc/fc_3')
使用stack操作:
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
卷積層使用stack操作:
# 普通方法:
x = slim.conv2d(x, 32, [3, 3], scope='core/core_1')
x = slim.conv2d(x, 32, [1, 1], scope='core/core_2')
x = slim.conv2d(x, 64, [3, 3], scope='core/core_3')
x = slim.conv2d(x, 64, [1, 1], scope='core/core_4')
# 簡便方法:
slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')
(3)nets: 包含一些經典網路,VGG等,用的也比較多.
(4)variables:這個比較有用,slim管理變數的機制.
變數分為兩類:模型變數和區域性變數。區域性變數是不作為模型引數儲存的,而模型變數會再save的時候儲存下來。諸如global_step之類的就是區域性變數。slim中可以寫明變數存放的裝置,正則和初始化規則。還有獲取變數的函式也需要注意一下,get_variables是返回所有的變數。
slim中定義一個變數的例項:
# Model Variables
weights = slim.model_variable('weights',
shape=[10, 10, 3 , 3],
initializer=tf.truncated_normal_initializer(stddev=0.1),
regularizer=slim.l2_regularizer(0.05),
device='/CPU:0')
model_variables = slim.get_model_variables()
# Regular variables
my_var = slim.variable('my_var',
shape=[20, 1],
initializer=tf.zeros_initializer())
regular_variables_and_model_variables = slim.get_variables()
(5)regularizers:包含一些正則規則.
(6)metrics:評估模型的度量標準.
(7)queues:文字佇列管理,比較有用。
(8)learning、losses
(二)inception_v3的網路結構
(三)inception_v3用slim實現的具體步驟及程式碼
1、定義函式 inception_v3_arg_scope 用來生成網路中經常用到的函式的預設引數
import tensorflow as tf
import tensorflow.contrib.slim as slim
#定義簡單的函式產生截斷的正態分佈
trunc_normal = lambda stddev:tf.truncated_normal_initializer(0.0,stddev)
#定義函式 inception_v3_arg_scope 用來生成網路中經常用到的函式的預設引數
def inception_v3_arg_scope(weight_decay=0.00004,stddev=0.1,
batch_norm_var_collection="moving_vars"):
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]
}
}
with slim.arg_scope([slim.conv2d,slim.fully_connected],
weights_regularizer=slim.l2_regularizer(weight_decay)):
#對卷積層生成函式的幾個引數賦予預設值
with slim.arg_scope([slim.conv2d],
weights_regularizer = tf.truncated_normal_initializer(stddev=stddev),
activation_fc = tf.nn.relu,
normalizer_fc = slim.batch_norm,
normalizer_params = batch_norm_params) as scope:
return scope
2、定義Inception V3的卷積部分
#定義Inception V3的卷積部分
def inception_v3_base(inputs,scope=None):
end_points = {}
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,num_outputs=32,kernel_size=[3,3],stride=2,scope="Conv2d_1a_3x3")
net = slim.conv2d(net,num_outputs=32,kernel_size=[3,3],scope="Conv2d_2a_3x3")
net = slim.conv2d(net,num_outputs=64,kernel_size=[3,3],padding="SAME",scope="Conv2d_2b_3x3")
net = slim.max_pool2d(net,kernel_size=[3,3],stride=2,scope="MaxPool_3a_3x3")
net = slim.conv2d(net,num_outputs=80,kernel_size=[1,1],scope="Conv2d_3b_1x1")
net = slim.conv2d(net,num_outputs=192,kernel_size=[3,3],scope="Conv2d_4a_3x3")
net = slim.max_pool2d(net,kernel_size=[3,3],stride=2,scope="MaxPool_5a_3x3")
3.1、定義第一個Inception模組組
#定義第一個Inception模組組
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"):
batch_0 = slim.conv2d(net,num_outputs=64,kernel_size=[1,1],scope="Conv2d_0a_1x1")
with tf.variable_scope("Branch_1"):
batch_1 = slim.conv2d(net,num_outputs=48,kernel