理解ResNet结构与TensorFlow代码分析
該博客主要以TensorFlow提供的ResNet代碼為主,但是我并不想把它稱之為代碼解析,因為代碼和方法,實踐和理論總是缺一不可。?
github地址,其中:
resnet_model.py為殘差網絡模型的實現,包括殘差模塊,正則化,批次歸一化,優化策略等等;
resnet_main.py為主函數,主要定義了測試、訓練、總結、打印的代碼和一些參數。
cifar_input.py為數據準備函數,主要把cifar提供的bin數據解碼為圖片tensor,并組合batch
為了保證行號的一致性,下面的內容如果涉及到行號的話,均以github上的為準,同時為了節省篇幅,下面如果出現代碼將去掉注釋,建議在閱讀本博客是同時打開github網址,因為下面的內容并沒有多少代碼。
既然是在說殘差模型,那么當然就要說resnet_model.py這個代碼,整個代碼就是在聲明一個類——ResNet:
第38行到55行:
class ResNet(object):def __init__(self, hps, images, labels, mode):self.hps = hpsself._images = imagesself.labels = labelsself.mode = modeself._extra_train_ops = []- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
上面是構造函數在初始化對象時的四個參數,實例化對象時也就完成初始化,參數賦值給類中的數據成員,其中self._images為私有成員。此外又定義了一個新的私有數組成員:self._extra_train_ops用來執行滑動平均操作。
構造函數的參數有hps,images,labels,mode。
hps在resnet_main.py在初始化的:
hps = resnet_model.HParams(batch_size=batch_size,num_classes=num_classes,min_lrn_rate=0.0001,lrn_rate=0.1,num_residual_units=5,use_bottleneck=False,weight_decay_rate=0.0002,relu_leakiness=0.1,optimizer='mom')- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
其中的HParams字典在resnet_mode.py的32行定義,變量的意義分別是:
HParams = namedtuple('HParams','一個batch內的圖片個數', '分類任務數目', '最小的學習率', '學習率', '一個殘差組內殘差單元數量', '是否使用bottleneck', 'relu泄漏','優化策略')- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
images和labels是cifar_input返回回來的值(115行),注意這里的值已經是batch了,畢竟image和label都加了復數。?
mode決定是訓練還是測試,它在resnet_main.py中定義(29行)并初始化(206行)。
除了__init__的構造函數外,類下還定義了12個函數,把殘差模型構建中用到功能模塊化了,12個函數貌似很多的樣子,但是都是一些很簡單的功能,甚至有一些只有一行代碼(比如可以看下65行),之所有單拉出來是因為功能是獨立的,或者反復出現,TensorFlow提供的代碼還是非常規范和正規的!
按照自上而下的順序依次是:
build_graph(self):?
構建TensorFlow的graph
_stride_arr(self, stride):?
定義卷積操作中的步長
_build_model(self):?
構建殘差模型
_build_train_op(self):?
構建訓練優化策略
_batch_norm(self, name, x):?
批次歸一化操作
_residual(self, x, in_filter, out_filter, stride,activate_before_residual=False):?
不帶bottleneck的殘差模塊,或者也可以叫做殘差單元,總之注意不是殘差組!
_bottleneck_residual(self, x, in_filter, out_filter, stride,activate_before_residual=False):?
帶bottleneck的殘差模塊
decay(self):?
L2正則化
_conv(self, name, x, filter_size, in_filters, out_filters, strides):?
卷積操作
_relu(self, x, leakiness=0.0):?
激活操作
_fully_connected(self, x, out_dim):?
全鏈接
_global_avg_pool(self, x, out_dim):?
全局池化
注意:?
1.在代碼里這12個函數是并列的,但是講道理的話它們并不平級(有一些函數在調用另一些)。比如卷積,激活,步長設置之類肯定是被調用的。而有三個函數比較重要,分別是:build_graph(self):、_build_model(self):、_build_train_op(self):。第一個是由于TensorFlow就是在維護一張圖,所有的數據以tensor的形式在圖上流動;第二個決定了殘差模型;第三個決定了優化策略。
2.個人認為_stride_arr(self, stride):函數不應該出現在該位置(65行),如果把它放后面,前三個函數就分別是構件圖,構建模型,構建優化策略。這樣邏輯上就很清晰。
3.這套代碼沒有常規的池化操作,一方面是因為RenNet本身就用步長為2的卷積取代池化,但是在進入殘差組之前還是應該有一個常規池化的,只是這個代碼沒有。
4.這個代碼有一個很不講理的地方,第一層卷積用了3*3的核,不是7*7,也不是3個3*3(73行)
5.這套代碼使用的是bin封裝的cifar數據,所以要想改成自己的數據集需要把input的部分換掉。
6.這套代碼沒有設終止條件,會一直訓練/測試,直到手動停止。
到這里代碼的結構起碼說清楚了,帶著上面的注意事項,我們就可以看代碼。?
圖構建沒什么好說的,我們直接進入_build_model(self)好了(69行):?
71-73行定義殘差網絡的第一個卷積層?
。?
75-82行使用哪種殘差單元(帶bottleneck還是不帶bottleneck),并分別對兩種情況定義了殘差組中的特征通道數。
90-109行構建了三個殘差組,每個組內有4個單元,這個數量是由hps參數決定的。
111-124行是殘差組結束后模型剩余的部分(池化+全連接+softmax+loss function+L2),這已經和殘差網絡的特性沒什么關系了,每個卷積神經網絡差不多都是這樣子。
126行將損失函數計算出的cost加入summary。
所以殘差模型最關鍵的東西,最能表征殘差特性的東西,都在90-109行,當然這十幾行里是調用了其他函數的。這個本文的最后后再說,下面為保證代碼部分的連貫性,先往下說_build_train_op(self)(128行):
130-131行獲取學習率并加入到summary。
133-134行根據cost與權系數計算梯度。
136-136行選擇使用隨機梯度下降還是帶動量梯度下降。
141-143行執行梯度下降優化。
145行將梯度下降優化操作與bn操作合并(帶op的變量是一種操作)。
146行得到最后的結果,在這里定義了一個新的數組成員:self.train_op,而這個變量最終被用到了resnet_main.py中(113行):
while not mon_sess.should_stop():mon_sess.run(model.train_op)- 1
- 2
如果沒有達到終止條件的話,代碼將一直執行優化操作,model是類實例化出來的一個對象,在resnet_main.py中的model和在resnet_model.py中的self是一個東西。
到這里重要的代碼就都說完了,最后說回殘差網絡最核心的東西:兩種殘差單元。?
殘差網絡的結構非常簡單,就是不斷的通過一組一組的殘差組鏈接,這是一個Resnet50的結構圖,不同的網絡結構在不同的組之間會有不同數目的殘差模塊,如下圖:?
?
舉個例子,比如resnet50中,2-5組中分別有3,4,6,3個殘差模塊。
樸素殘差模塊(不帶bottleneck):?
?
左側為正常了兩個卷積層,而右側在兩個卷積層前后做了直連,這個直連解釋殘差,左側的輸出為H(x)=F(x),而加入直連后的H(x)=F(x)+x,一個很簡單的改進,但是取得了非常優異的效果。?
至于為什么直連要跨越兩個卷積層,而不是一個?這個是實驗驗證的結果,在一個卷積層上加直連性能并沒有太大提升。
bottleneck殘差模塊:?
bottleneck殘差模塊讓殘差網絡可以向更深的方向上走,原因就是因為同一通道數的情況下,bottleneck殘差模塊要比樸素殘差模塊節省大量的參數,一個單元內的參數少了,對應的就可以做出更深的結構。?
?
上面這樣圖能夠說明二者的區別,左側的通道數是64(它常出現在50層內的殘差結構中),右側的通道數是256(常出現在50層以上的殘差結構中),從右面的圖可以看到,bottleneck殘差模塊將兩個3*3換成了1*1,3*3,1*1的形式,第一個1*1用來降通道,3*3用來在降通道的特征上卷積,第二個1*1用于升通道。而參數的減少就是因為在第一個1*1將通道數降了下來。我們可以舉一個例子驗證一下:
假設樸素殘差模塊與bottleneck殘差模塊通道數都是256,那么:
樸素殘差模塊的參數個數:?
3*3*256*256+3*3*256*256 = 10616832?
bottleneck殘差模塊的參數個數:?
1*1*256*64+3*3*64*64+1*1*64*256 = 69632?
可以看到,參數的減少非常明顯。
再回到上面的圖:?
Resnet34余Resnet50層每一組中的模塊個數并沒有變化,層數的上升是因為以前兩個卷積層變成了3個,前者的參數為3.6億,后者參數為3.8億。這樣來看的話參數為什么反而多了?這是因為組內的通道數發生了變化,前者各組通道數為[64,128,256,512],而后者的各組通道數為[256,512,1024,2048]。這也是殘差網絡在設計時的一個特點,使用bottleneck殘差模塊時,組內的通道數要明顯高于使用樸素殘差模塊。
TensorFlow提供的代碼也是這樣,可以看下77行:
if self.hps.use_bottleneck:res_func = self._bottleneck_residualfilters = [16, 64, 128, 256]else:res_func = self._residualfilters = [16, 16, 32, 64]- 1
- 2
- 3
- 4
- 5
- 6
通過上面的理論說明,就可以再回頭看下代碼中的:_residual()函數和_bottleneck_residual()函數了。
原文地址:http://blog.csdn.net/chaipp0607/article/details/75577305總結
以上是生活随笔為你收集整理的理解ResNet结构与TensorFlow代码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tensorflow[实战篇]——Fac
- 下一篇: TensorFlow-CIFAR10 C