tensorflow學習筆記——AlexNet

  2012年,Alex Krizhevsky、Ilya Sutskever在多倫多大學Geoff Hinton的實驗室設計出了一個深層的卷積神經網絡AlexNet,奪得了2012年ImageNet LSVRC的冠軍,且準確率遠超第二名(top5錯誤率爲15.3%,第二名爲26.2%),引發了很大的轟動。AlexNet能夠說是具備歷史意義的一個網絡結構,在此以前,深度學習已經沉寂了很長時間,自2012年AlexNet誕生以後,後面的ImageNet冠軍都是用卷積神經網絡(CNN)來作的,而且層次愈來愈深,使得CNN成爲在圖像識別分類的核心算法模型,帶來了深度學習的大爆發。html

  下面首先學習AlexNet網絡的創新點,而後在學習AlexNet的網絡結構,最後在用代碼實現AlexNet。git

1,AlexNet網絡的創新點

  AlexNet將LeNet的思想發揚光大,把CNN的基本原理應用到了很深很寬的網絡中。AlexNet主要使用到的新技術點以下:算法

  (1)成功使用ReLU做爲CNN的激活函數,並驗證其效果在較深的網絡超過了Sigmoid,成功解決了Sigmoid在網絡較深時的梯度彌散問題。雖然ReLU激活函數在好久以前就被提出了,但直到AlexNet的出現纔將其發揚光大。服務器

  在最初的感知機模型中,輸入和輸出的關係以下:網絡

   雖然只是單純的線性關係,這樣的網絡結構有很大的侷限性:即便用不少這樣結構的網絡層疊加,其輸出和輸入仍然是線性關係,沒法處理有非線性關係的輸入輸出。所以,對每一個神經元的輸出作個非線性的轉換也就是,將上面的加權求和的結果輸入到一個非線性函數,也就是激活函數中。這樣,因爲激活函數的引入,多個網絡層的疊加就再也不是單純的線性變換,而是具備更強的表現能力。session

  在網絡層較少時,Sigmoid函數的特性可以很好的知足激活函數的做用:它把一個實數壓縮至0到1之間,當輸入的數字很是大的時候,結果會接近1,;當輸入很是大的負數時,則會獲得接近0的結果。這種特性,可以很好的模擬神經元在受刺激後,是否被激活向後傳遞信息(輸出爲0,幾乎不被激活;輸出爲1,徹底被激活)。Sigmoid函數一個很大的問題就是梯度飽和。觀察Sigmoid函數的曲線,當輸入的數字較大(或較小)時,其函數值趨於不變,其導數變得很是的小。這樣在層數不少的網絡結構中,進行反向傳播時,因爲不少個很小的Sigmoid導數累成,致使其結果趨於0,權值更新較慢。架構

  (2)訓練時使用Dropout隨機忽略一部分神經元,以免模型過擬合。Dropout雖有單獨的論文論述,可是AlexNet將其實用化,經過實踐證明了它的效果。在AlexNet中主要是最後幾個全鏈接層使用了Dropout。app

  Dropout應該是AlexNet網絡中一個很大的創新,如今神經網絡中的必備結構之一。Dropout也能夠看作是一種模型組合,每次生成的網絡結構都不同,經過組合多個模型的方式可以有效地減小過擬合。Dropout只須要兩倍的訓練時間便可實現模型組合(相似去平均)的效果。很是高效。dom

  (3)在CNN中使用重疊的最大池化層。此前CNN中廣泛使用平均池化,AlexNet所有使用最大池化,避免平均池化的模糊化效果,而且AlexNet中提出讓步長比池化核的尺寸小,這樣池化層的輸出之間會有重疊和覆蓋,提高了特徵的豐富性。機器學習

  通常的池化(pooling)是不重疊的,池化區域的窗口大小與步長相等,以下圖所示:

   在AlexNet中使用的池化(pooling)倒是可重疊的,也就是說,在池化的時候,每次移動的步長小於池化的窗口長度。

  AlexNet池化的大小爲3*3的正方形,每次池化移動步長爲2,這樣就會出現重疊。重疊池化能夠避免過擬合,這個策略共享了0.3%的Top-5錯誤率。

  (4)提出了LRN層,對局部神經元的活動建立競爭機制,使得其中響應比較大的值變得相對更大,並抑制其餘反饋較小的神經元,加強了模型的泛化能力。 

  LRN層,全稱 Local  Response Normalization(局部響應歸一化)。核心思想就是利用臨近的數據作歸一化,這個策略貢獻了1.2%的Top-5錯誤率。

  ReLU具備良好性質:當輸入爲正,其導數爲1,有效的避免神經元中止學習,也就是死掉。

  在神經生物學有一個概念叫作「側抑制」(lateral inhibitio),指的是被激活的神經元抑制相鄰神經元。歸一化(normalization)的目的是「抑制」,局部歸一化就是借鑑了「側抑制」的思想來實現局部抑制,尤爲當使用ReLU時這種「側抑制」很管用,由於ReLU的響應結果是無界的(能夠很是大),因此須要歸一化。使用局部歸一化的方案有助於增長泛化能力。

  在神經網絡中,咱們用激活函數將神經元的輸出作一個非線性映射,但tanh和sigmoid這些傳統的激活函數的值域都是有範圍的,可是ReLU激活函數獲得的值域沒有一個區間,因此要對ReLU獲得的結果進行歸一化。也就是Local Response Normalization。局部響應歸一化的方法以下圖的公式:

    表明的時ReLU 在第 i 個 kernel 的(x,y)位置的輸出, n表示的是 的鄰居個數。N表示該 kernel 的總數量, 表示的時 LRN的結果。ReLU輸出的結果和他周圍必定範圍的鄰居作一個局部的歸一化,怎麼理解呢?我以爲這裏有些相似於咱們的最大最小歸一化,假設有一個向量 X = [X_1, X_2, ....X_n] 那麼將全部的數歸一化到 0~1之間的歸一化規則是:

   上面那個公式有着相似的功能,只不過稍微負載一些,首先運算略微複雜,其次還有一些其餘的參數 alpah, beta, k。

   咱們看看上圖,每個矩陣表示的是一個卷積核生成的feature map,全部的pixel已經通過的了ReLU 激活函數,如今咱們都要對具體的 pixel進行局部的歸一化。假設綠色箭頭指向的是第 i 個 kernel對應的map,其他的四個藍色箭頭是它周圍的鄰居 kernel層對應的map,假設矩陣中間的綠色的 pixel 的位置爲(x,  y),那麼咱們須要提取出來進行局部歸一化的數據就是周圍鄰居 kernel 對應的 map(x, y) 位置的 pixel 的值。也就是上面式子的 ,而後把這些鄰居 pixel 的值平方再加和。乘以一個稀疏 alpha 再加上一個常數 k ,而後 beta 次冪,就算分母,分子就是第 i 個 kernel 對應的 map 的(x, y)位置的 pixel的值。這樣理解後我以爲不是那麼的複雜了。

  關鍵是參數 alpha,beta,k 如何肯定,論文中說是在驗證集中肯定,最終肯定的結果爲:

  (5)使用CUDA加速神經卷積網絡的訓練,利用GPU強大的並行計算能力,處理神經網絡訓練時大量的矩陣運算。AlexNet 使用了兩塊 GTX 580 GPU 進行訓練,單個 CTX 580 只有3GB顯存,這限制了可訓練的網絡的最大規模。所以做者將AlexNet分佈在兩個GPU上,在每一個GPU的顯存中存儲一半的神經元的參數。所以GPU之間通訊方便,能夠互相訪問顯存,而不須要經過主機內存,因此同時使用多塊GPU也是很是高效的。同時,AlexNet的設計讓GPU之間的通訊只在網絡的某些層進行,控制了通訊的性能損耗。

  (6)數據加強,隨機的從256*256的原始圖像中截取224*224大小的區域(以及水平翻轉的鏡像),至關於增長了(256-224)2*2=2048倍的數據量。若是沒有數據加強,僅靠原始的數據量,參數衆多的CNN會陷入過擬閤中,使用了數據加強後能夠大大減輕過擬合,提升泛化能力。進行預測時,則是取圖片的四個角加中間共5個位置,並進行左右翻轉,一共得到10張圖片,對他們進行預測並對10次結果求均值。同時,AlexNet論文中提到了會對圖像的RGB數據進行PCA處理,並對主成分作一個標準差爲0.1的高斯擾動,增長了一些噪聲,這個Trick會讓錯誤率再下降1%。

  有一種觀點認爲神經網絡是靠數據喂出來的,若是可以增長訓練數據,提供海量數據進行訓練,則可以有效提高算法的準確率,由於這樣能夠避免過擬合,從而能夠進一步增大、加深網絡結構。而當訓練數據有限時,能夠經過一些變換從已有的訓練數據集中生成一些新的數據,以快速地擴充訓練數據。
  其中,最簡單、通用的圖像數據變形的方式:水平翻轉圖像,從原始圖像中隨機裁剪、平移變換,顏色、光照變換。

  AlexNet在訓練時候,在數據擴充(data  augmentation)這樣處理:

  • (1)隨機裁剪,對256×256的圖片進行隨機裁剪到224×224,而後進行水平翻轉,至關於將樣本數量增長了((256-224)^2)×2=2048倍;
  • (2)測試的時候,對左上、右上、左下、右下、中間分別作了5次裁剪,而後翻轉,共10個裁剪,以後對結果求平均。做者說,若是不作隨機裁剪,大網絡基本上都過擬合;
  • (3)對RGB空間作PCA(主成分分析),而後對主成分作一個(0, 0.1)的高斯擾動,也就是對顏色、光照做變換,結果使錯誤率又降低了1%

  (7) Overlapping Pooling(重疊池化)

  在傳統的CNN中,卷積以後通常鏈接一個池化層,這個池化運算是沒有重疊的池化運算。更加確切的說,池化能夠被看做是由間隔爲 s 的池化網絡構成的,將 z*z 的池化單元的中心放在這個網絡上以完成池化操做,若是 s = z ,那麼這個操做就是傳統的 CNN 池化操做,若是 s < z ,就是重疊池化操做,本文就是使用這個方法。

   通常的池化層由於沒有重疊,因此pool_size 和 stride通常是相等的,例如8×8的一個圖像,若是池化層的尺寸是2×2 ,那麼通過池化後的操做獲得的圖像是 4×4大小,這種設置叫作不覆蓋的池化操做,若是 stride < pool_size, 那麼就會產生覆蓋的池化操做,這種有點相似於convolutional化的操做,這樣能夠獲得更準確的結果。在top-1,和top-5中使用覆蓋的池化操做分別將error rate下降了0.4%和0.3%。論文中說,在訓練模型過程當中,覆蓋的池化層更不容易過擬合。

2,AlexNet網絡的網絡結構

  整個AlexNet有8個須要訓練參數的層(不包括池化層和LRN層),前5層爲卷積層,後3層爲全鏈接層,以下圖所示,ALexNet最後一層是有1000類輸出的Softmax層用做分類。LRN層出如今第一個及第二個卷積層後,而最大池化層出如今兩個LRN層及最後一個卷積層後,ReLU激活函數則應用在這8層每一層的後面。由於AlexNet訓練時使用了兩塊GPU,所以這個結構圖中很多組件被拆爲兩部分。如今咱們GPU的顯存能夠放下所有的模型參數,所以只考慮一塊GPU的狀況便可。

   AlexNet 每層的超參數如上圖所示,其中輸入的圖片尺寸爲224*224,第一個卷積層使用了較大的卷積核尺寸11*11,步長爲4,有96個卷積核;緊接着一個LRN層;而後是一個3*3的最大池化層,步長爲2,。這以後的卷積核尺寸都比較小,都是5*5 或者3*3 的大小,而且步長都爲1,即會掃描全圖全部像素;而最大池化層依然保持爲3*3,而且步長爲2。咱們會發現一個比較有意思的現象,在前幾個卷積層,雖然計算量很大,但參數量很小,都在1M 左右甚至更小,只佔AlexNet 總參數量的很小一部分。這就是卷積層有用的地方,能夠經過較小的參數量提取有效的特徵,而若是前幾層直接使用全鏈接層,那麼參數量和計算量將成爲天文數字。雖然每個卷積層佔整個網絡的參數量的1%都不到,可是若是去掉任何一個卷積層,都會使網絡的分類性能大幅的降低。

  (注意:這個224,其實通過計算(224-11)/4=54.75,而不是論文中的55*55,而是227*227,因此假如使用224*224,就讓padding=2)

  其實下圖更能讓人理解AlexNet網絡結構:

   AlexNet 每層的超參數及參數數量:

   那下面對照着圖,咱們再詳細分析一波這個網絡:

  其實論文中是使用兩塊GPU來處理的,這就致使了第一個AlexNet的網絡結構是分開的,那後面咱們用一個架構網絡來分析,如今也都9012年了,科技更發達了,因此使用一塊GPU徹底能夠運算,咱們下面來詳細分析一下這8層網絡,包括5層卷積,3層全鏈接。(論文中提到過,五層卷積,去掉任意一層都會使結果很差,因此這個網絡的深度彷佛比較重要。)

2.1 卷積C1

  該層的處理流程是:卷積——》  ReLU ——》池化 ——》 歸一化

  • 卷積:輸入的是227*227,使用96個11*11*3的卷積核,獲得的FeatureMap爲55*55*96
  • ReLU:將卷積層輸出的FeatureMap 輸入到ReLU函數中。
  • 池化:使用3*3 步長爲2的池化單位(重疊池化,步長小於池化單元的寬度),輸出爲27*27*96((55-3)/2+1=27)
  • 局部響應歸一化:使用k =2,n=5,alpha=10-4, beta=0.75進行局部歸一化,輸出仍然是27*27*96,輸出分爲兩組,每組爲27*27*48。

  卷積後的圖形大小是怎麼樣的呢?

  width = (227 + 2 * padding - kerne_size)/ stride + 1 = 55

  height = (227 + 2 * padding - kerne_size)/ stride + 1 = 55

  dimention = 96

2.2 卷積層C2

  該層的處理流程是:卷積——》  ReLU ——》池化 ——》 歸一化

  • 卷積:輸入的是2組27*27*48,使用2組,每組128個尺寸爲5*5*48的卷積核,並作了邊緣填充 padding=2,卷積的步長爲1,則輸出的FeatureMap爲2組,每組的大小爲 27*27*128,((27 + 2*2-5)/1+1=27)
  • ReLU:將卷積層輸出的FeatureMap 輸入到ReLU函數中。
  • 池化:使用3*3 步長爲2的池化單位(重疊池化,步長小於池化單元的寬度),池化後圖像的尺寸爲(27-3)/2+1=12,輸出爲13*13*256
  • 局部響應歸一化:使用k =2,n=5,alpha=10-4, beta=0.75進行局部歸一化,輸出仍然是13*13*256,輸出分爲兩組,每組爲13*13*128。

2.3 卷積層C3

該層的處理流程是: 卷積——》ReLU

  • 卷積,輸入是,13*13*256,使用2組共384尺寸爲3*3*256的卷積核,作了邊緣填充padding=1,卷積的步長爲1,則輸出的FeatureMap 爲13*13*384

  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中

2.4 卷積層C4

該層的處理流程是: 卷積——》ReLU

  • 卷積,輸入是13*13*384,分爲兩組,每組13*13*192,作了邊緣填充padding=1,卷積的步長爲1,則輸出的Featuremap爲13*13*384

  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中

2.5 卷積層C5

  該層的處理流程是:卷積——》  ReLU ——》池化 

  • 卷積:輸入的是13*13*384,分爲兩組,每組13*13*192,使用兩組,每組爲128,尺寸爲3*3*192的卷積核,作了邊緣填充padding=1,卷積的步長爲1,則輸出的FeatureMap爲13*13*256
  • ReLU:將卷積層輸出的FeatureMap 輸入到ReLU函數中。
  • 池化:使用3*3 步長爲2的池化單位(重疊池化,步長小於池化單元的寬度),池化後圖像的尺寸爲(13-3)/2+1=12,輸出爲6*6*256

2.6 全鏈接層FC6

流程爲:(卷積)全鏈接——》  ReLU ——》 Dropout

  • 卷積(全鏈接):輸入爲6*6*256,該層有4096個卷積核,每一個卷積核的大小爲6*6*256.因爲卷積核的尺寸恰好與待處理特徵圖(輸入)的尺寸相同,即卷積核中的每一個係數只與特徵圖(輸入)尺寸的一個像素值相乘,一一對應,所以,該層被稱爲全鏈接層。因爲卷積核與特徵圖的尺寸相同,卷積運算後只有一個值,所以,卷積後的像素層尺寸爲 4096*1*1,即有4096個神經元。
  • ReLU:這4096個運算結果經過ReLU激活函數生成4096個值
  • Dropout:抑制過擬合,隨機的斷開某些神經元的鏈接或者是不激活某些神經元

2.7 全鏈接層FC7

流程爲:全鏈接——》  ReLU ——》 Dropout

  • 全鏈接:輸入爲4096的向量
  • ReLU:這4096個運算結果經過ReLU激活函數生成4096個值
  • Dropout:抑制過擬合,隨機的斷開某些神經元的鏈接或者是不激活某些神經元

2.8 輸出層

   第七層的神經元的個數爲 4096,第八層最終輸出 softmax爲1000個。

3,AlexNet 參數數量

  卷積層的參數 = 卷積核的數量 * 卷積核 + 偏置

  • C1:96個11*11*3的卷積核,96*11*11*3 + 96 = 34848
  • C2:2組,每組128個5*5*48的卷積核,(128*5*5*48+128)*2=307456
  • C3:384個3*3*256的卷積核,3*3*256*384+384=885120
  • C4:2組,每組192個3*3*192的卷積核,(3*3*192*192+192)*2=663936 
  • C5:2組,每組128個3*3*192的卷積核,(3*3*192*128+128)*2=442624
  • FC6:4096個6*6*256的卷積核,6*6*256*4096+4096=37752832
  • FC7:4096*4096+4096=16781312
  • output:4096+1000=4096000

  卷積層 C2,C4,C5中的卷積核只和位於同一GPU的上一層的FeatureMap相連。從上面能夠看出,參數大多數集中在全鏈接層,在卷積層因爲權值共享,權值參數較少。

4,AlexNet網絡的TensorFlow實現

   由於使用 ImageNet數據集訓練一個完整的AlexNet耗時很是長,所以下面學習的AlexNet的實現將不涉及實際數據的訓練。咱們會創建一個完整的ALexNet卷積神經網絡,而後對它每一個batch的前饋計算(forward)和反饋計算(backward)的速度進行測試。這裏使用隨機圖片數據來計算每輪前饋,反饋的平均耗時。固然也能夠下載ImageNet數據並使用AlexNet網絡完成訓練,並在測試集上進行測試。

   首先,定義一個用來顯示網絡每一層結構的函數 print_actications,展現每個卷積層或池化層輸出 tensor 的尺寸。這個函數通常接受一個 tensor做爲輸入,並顯示其名稱(t.op.name)和 tensor尺寸(t.get_shape.as_list())。

# 定義一個用來顯示網絡每一層結構的函數 print_actications 展現每個卷積層或者輸出tensor的尺寸
# 此函數接受一個tensor做爲輸入,並顯示其名稱(t.op.name)和tensor尺寸
def print_activations(t):
    print(t.op.name, ' ', t.get_shape().as_list())

  接下來設計AlexNet的網絡結構,咱們先定義函數 inference,它接受 images做爲輸入,返回最後一層 pool5(第5個池化層)及 paramenters(AlexNet 中全部須要訓練的模型參數)。這個 inference 函數將會很大,包括多個卷積和池化層,所以下面將拆分學習。

  首先是第一個卷積層 conv1,這裏使用 TensorFlow 中的 name_scope,經過 with.tf.name_scope('conv1') as scope 能夠將 scope 內生成的 Variable 自動命名爲 conv1/xxx,便於區分不一樣卷積層之間的組件。而後定義第一個卷積層,和以前同樣使用 tf.truncated_normal 截斷的正態分佈函數(標準差爲0.1)初始化卷積核的參數 kernel。卷積核尺寸爲11*11,顏色通道爲 3 ,卷積核數量爲64.準確好了 kernel ,再使用 tf.nn.conv2d 對輸入 images 完成卷積操做,咱們將 strides 步長設置爲4*4(即在圖片上每4*4 區域只取樣一次,橫向間隔是4,縱向間隔也爲4,每次取樣的卷積核大小都是11*11),padding模式設爲SAME。將卷積層的biases所有初始化爲0,再使用 tf.nn.bias_add 將 conv 和 biases 加起來,並使用激活函數 tf.nn.relu 對結果進行非線性處理。最後使用 print_actations 將這一層最後輸出的 tensor conv1 的結構打印出來,並將這一層可訓練的參數 kernel,biases 添加到 parameters中。

def inference(images):
    parameters = []

    with tf.name_scope('conv1') as scope:
        kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64],
                                                 dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope)
        print_activations(conv1)
        parameters += [kernel, biases]
        

  在第一個卷積層後再添加LRN層和最大池化層。先使用 tf.nn.lrn 對前面輸出的 tensor conv1 進行 LRN 處理,這裏使用的 depth_radius 設爲4,bias設爲1,alpha爲0.001/9,beta爲0.75,基本都是AlexNet 的論文中的推薦值。不過目前除了 AlexNet,其餘經典的卷積神經網絡模型都放棄了 LRN (主要是效果不明顯),而咱們使用 LRN 也會讓前饋,反饋的速度大大降低(總體速度降到1/3),你們能夠支柱選擇是否使用 LRN。下面使用 tf.nn.max_pool 對前面的輸出  lrn1 進行最大池化處理,這裏的池化尺寸爲 3*3,即將3*3大小的像素塊降爲 1*1 的像素,取樣的步長爲 2*2,padding 模式設爲 VALID,即取樣時不能超過邊框,不像SAME模式那樣能夠填充邊界外的點。最後將輸出結果 pool1 的結構打印出來。

lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001/9, beta=0.75, name='lrn1')
pool1 = tf.nn.max_pool(lrn1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                       padding='VALID', name='pool1')
print_activations(pool1)

  接下來設計第二個卷積層,大部分步驟和第一個卷積層相同,只有幾個參數不一樣。主要區別在於咱們的卷積核尺寸是 5*5,輸入通道數(即上一層輸出通道數,也就是上一層卷積核數量)爲64,卷積核數量爲192。同時,卷積的步長也所有設爲1,即掃描全圖像素。

with tf.name_scope('conv2') as scope:
    kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192],
                                             dtype=tf.float32, stddev=1e-1),
                         name='weights')
    conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = tf.Variable(tf.constant(0.0, shape=[192],
                                     dtype=tf.float32),
                         trainable=True, name='biases')
    bias = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(bias, name=scope)
    parameters += [kernel, biases]
print_activations(conv2)

  接下來對第二個卷積層的輸出  conv2 進行處理,一樣先作 LRN處理,再進行最大池化處理,參數和以前徹底同樣,這裏再也不贅述了。

# 接下來對第二個卷積層的輸出 conv2 進行處理,一樣是作了LRN處理,再進行最大池化處理
lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001/9, beta=0.75, name='lrn2')
pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                           padding='VALID', name='pool2')
print_activations(pool2)

  接下來建立第三個卷積層,基本結構和前面兩個相似,也只是參數不一樣。這一層的卷積核尺寸爲3*3,輸入的通道數爲192,卷積核數量繼續擴大爲384,一樣卷積的步長所有爲1,其餘地方和前面的保持一致。

# 第三個卷積層
with tf.name_scope('conv3') as scope:
    kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
                                             dtype=tf.float32,stddev=1e-1),
                         name='weights')
    conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
    biases = tf.Variable(tf.constant(0.0, shape=[384],
                                     dtype=tf.float32),
                         trainable=True, name='biases')
    bias = tf.nn.bias_add(conv, biases)
    conv3 = tf.nn.relu(bias, name=scope)
    parameters += [kernel, biases]
    print_activations(conv3)

  第四個卷積層和以前也相似,這一層的卷積核尺寸爲3*3,輸入通道數爲384,可是卷積核數量降爲256。

# 第四個卷積層,核尺寸爲3*3,輸入通道數爲384, 可是卷積核數量下降爲256
with tf.name_scope('conv4') as scope:
    kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
                                             dtype=tf.float32, stddev=1e-1),
                         name='weights')
    conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
    biases = tf.Variable(tf.constant(0.0, shape=[256],
                                     dtype=tf.float32),
                          trainable=True, name='biases')
    bias = tf.nn.bias_add(conv, biases)
    conv4 = tf.nn.relu(bias, name=scope)
    parameters += [kernel, biases]
    print_activations(conv4)

  最後的第五個卷積層一樣是3*3 大小的卷積核,輸入通道數爲256,卷積核數量也爲256。

# 第五個卷積層一樣是3*3大小的卷積核,輸入通道數爲256, 卷積核數量也是256
    with tf.name_scope('conv5') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
                                                 dtype=tf.float32, stddev=1e-1),
                             name='weights')
        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256],
                                         dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv5 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv5)

  在第五個卷積層以後,還有一個最大池化層,這個池化層和前兩個卷積層後的池化層一致,最後咱們返回這個池化層的輸出 pool5。至此,inference函數就完成了,它能夠建立 AlexNet 的卷積部分。在正式使用 AlexNet來訓練或預測時,還須要添加3個全鏈接層,隱含節點數分別爲 4096,4096和1000。因爲最後三個全鏈接層的計算量很小,就沒放到計算速度測評中,他們對計算耗時的影響很是小。你們在正式使用 AlexNet時須要自行添加這三個全鏈接層,全鏈接層在TensorFlow中的實現方法在以前已經學習過了,這裏再也不贅述。

pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                           padding='VALID', name='pool5')
print_activations(pool5)

return pool5, parameters

  接下來實現一個評估 AlexNet每輪計算時間的函數 time_tensorflow_run。這個函數的第一個輸入是 TensorFlow的Session,第二個變量是須要評測的運算算子,第三個變量是測試的名稱。先定義預測輪數  num_steps_burn_in=10,它的做用是給程序熱身,頭幾輪迭代有顯存加載,cache命中等問題所以能夠跳過,咱們只靠量10輪迭代以後的計算時間。同時,也記錄總時間 total_duration 和平方和 total_duration_squared 用以計算方差。

def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0  # 記錄總時間
    total_duration_squared = 0.0  # 記錄平方和total_duration_squared用於計算方差

  咱們進行  num_batches + num_steps_burn_in 次迭代計算,使用 time.time() 記錄時間,每次迭代經過 session.run(target)執行。在初始熱身的 num_steps_burn_in 次迭代後,每10輪迭代顯示當前迭代所須要的時間。同時每輪將 total_duration 和 total_duration_dquared累加,以便後面每輪耗時的均值和標準差。

for i in range(num_batches + num_steps_burn_in):
    start_time = time.time()
    _ = session.run(target)
    duration = time.time() - start_time
    if i >= num_steps_burn_in:
        if not i % 10:
            print('%s: stpe %d, duration=%.3f'%(datetime.now(),
                                                i-num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration

  在循環結束後,計算每輪迭代的平均耗時 mn 和 標準差  sd,最後將結果顯示出來。這樣就完成了計算每輪迭代耗時的評測函數 time_tensorflow_run。

mn = total_duration / num_batches
vr = total_duration_squared / num_batches - mn*mn
sd = math.sqrt(vr)
print('%s: %s across %d steps, %.3f +/- %.3f sec / batch'%(datetime.now(),
                                                           info_string, num_batches, mn, sd))

  接下來是主函數  run_benchmark。首先是要 with  tf.Graph().as_default() 定義默認的 Graph 方便後面使用。如前面所說,咱們這裏不使用  ImageNet 數據集來訓練,只使用隨機圖片數據測試前饋和反饋計算的耗時。咱們使用 tf.random_normal 函數構造正態分佈(標準差爲0.1)的隨機 tensor ,第一個維度是 batch_size,即每輪迭代的樣本數,第二個和第三個維度是圖片的尺寸 image_size = 224,第四個維度是圖片的顏色通道數。接下來,使用前面定義的  inference 函數構建整個 AlexNet網絡,獲得最後一個池化層的輸出 pool5 和網絡中須要訓練的參數的集合  parameters。接下來,咱們使用 tf.Session()  建立新的 Session 並經過 tf.global_variables_initializer() 初始化全部參數。

def run_benchmark():
    with tf.Graph().as_default():
        image_size = 224
        images = tf.Variable(tf.random_normal([batch_size,
                                               image_size,
                                               image_size, 3],
                                              dtype=tf.float32,
                                              stddev=1e-1))
        pool5, parameters = inference(images)

        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

  下面進行 AlexNet的 forward 計算的測評,這裏直接使用 time_tensorflow_run 統計運算時間,傳入的 target 就是 pool5,即卷積網絡最後一個池化層的輸出。而後進行 backward 即訓練過程的評測,這裏和 forward 計算有些不一樣,咱們須要給最後的輸出 pool5 設置一個優化目標 loss。咱們使用 tf.nn.l2_loss 計算 pool5的loss,再使用 tf.gradients求相對於 loss 的全部模型參數的梯度,這樣就模擬了一個訓練的額過程。固然,訓練時還有一個根據梯度更新參數的過程,不過這個計算量很小,就不統計在評測程序裏了。最後咱們使用 time_tensorflow_run 統計 backward 的運行時間,這裏的  target 就是求整個網絡梯度 grad 的操做。 

time_tensorflow_run(sess, 'pool5', 'Forward')

objective = tf.nn.l2_loss(pool5)
grad = tf.gradients(objective, parameters)
time_tensorflow_run(sess, grad, 'Forward-backward')

  最後執行主函數:

if __name__ == '__main__':
    run_benchmark()

  程序顯示的結果有三段,首先是 AlexNet的網絡結構,能夠看到咱們定義的 5個卷積層中第1 個,第二個和第五個卷積後面還鏈接着池化層,另外每一層輸出 tensor 的尺寸也顯示出來了。

conv1   [32, 56, 56, 64]
pool1   [32, 27, 27, 64]
conv2   [32, 27, 27, 192]
pool2   [32, 13, 13, 192]
conv3   [32, 13, 13, 384]
conv4   [32, 13, 13, 256]
conv5   [32, 13, 13, 256]
pool5   [32, 6, 6, 256]

  而後顯示的是 forward 計算的時間。我是在服務器上運行,配置了GPU,因此在LRN層時每輪迭代時間大約爲0.010s;去除LRN層後,每輪的迭代時間大約0.030s,速度也快了3倍多。由於 LRN層對最終準確率的影響不是很大,因此你們能夠考慮是否使用 LRN。

2019-09-10 18:03:08.871731: stpe 0, duration=0.010
2019-09-10 18:03:08.969771: stpe 10, duration=0.010
2019-09-10 18:03:09.066901: stpe 20, duration=0.010
2019-09-10 18:03:09.163687: stpe 30, duration=0.010
2019-09-10 18:03:09.260273: stpe 40, duration=0.010
2019-09-10 18:03:09.356837: stpe 50, duration=0.010
2019-09-10 18:03:09.453185: stpe 60, duration=0.010
2019-09-10 18:03:09.549359: stpe 70, duration=0.010
2019-09-10 18:03:09.645590: stpe 80, duration=0.010
2019-09-10 18:03:09.741800: stpe 90, duration=0.010
2019-09-10 18:03:09.828454: Forward across 100 steps, 0.001 +/- 0.003 sec / batch

  而後就是顯示的 backward 運算的時間。在使用 LRN 層時,每輪的迭代時間爲 0.031s,在去除LRN層後,每輪迭代時間約爲0.025s,速度也快了3倍多。另外能夠發現不管是否有 LRN 層,咱們 backward 運算的耗時大約是 forward 耗時的三倍。

2019-09-10 18:03:10.404082: stpe 0, duration=0.031
2019-09-10 18:03:10.712611: stpe 10, duration=0.031
2019-09-10 18:03:11.020057: stpe 20, duration=0.031
2019-09-10 18:03:11.326645: stpe 30, duration=0.031
2019-09-10 18:03:11.632946: stpe 40, duration=0.030
2019-09-10 18:03:11.940875: stpe 50, duration=0.031
2019-09-10 18:03:12.249667: stpe 60, duration=0.031
2019-09-10 18:03:12.556174: stpe 70, duration=0.031
2019-09-10 18:03:12.863977: stpe 80, duration=0.031
2019-09-10 18:03:13.172933: stpe 90, duration=0.031
2019-09-10 18:03:13.451794: Forward-backward across 100 steps, 0.003 +/- 0.009 sec / batch

  CNN的訓練過程(即backward計算)一般都比較耗時,並且不想預測過程(即 forward計算),訓練一般須要過不少遍數據,進行大量的迭代。所以應用CNN的主要瓶頸仍是在訓練,用 CNN 作預測問題不大。

  至此,AlexNet的TensorFlow實現和運算時間評測就完成了。AlexNet爲卷積神經網絡和深度學習正名,以絕對優點拿下ILSVRC 2012 冠軍,引發學術界的極大關注,爲復興神經網絡作出來很大的貢獻。ALexNet 在 ILSVRC 數據集上可達到 16.4% 的錯誤率(你們能夠自行下載數據集測試,可是要注意 batch_size 可能要設爲1 才能復現論文中的結果),其中用到的許多網絡結構和 Trick 給深度學習的發展帶來了深入地影響。固然,咱們決不能忽略 ImageNet 數據集給深度學習帶來的 貢獻。訓練深度卷積神經網絡,必須擁有一個像ImageNet這樣超大的數據集才能避免過擬合,發展深度學習的優點。能夠說,傳統機器學習模型適合學習一個小型數據集,可是對於大型數據集,咱們須要有更大的學習容量(Learning Capacity)的模型,即深度學習模型。

完整代碼以下:

from datetime import datetime
import math
import time
import tensorflow as tf

batch_size = 32
num_batches = 100


def print_activations(t):
    print(t.op.name, ' ', t.get_shape().as_list())


def inference(images):
    parameters = []
    # conv1
    with tf.name_scope('conv1') as scope:
        kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope)
        print_activations(conv1)
        parameters += [kernel, biases]

    # pool1
    lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='lrn1')
    pool1 = tf.nn.max_pool(lrn1,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool1')
    print_activations(pool1)

    # conv2
    with tf.name_scope('conv2') as scope:
        kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
    print_activations(conv2)

    # pool2
    lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='lrn2')
    pool2 = tf.nn.max_pool(lrn2,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool2')
    print_activations(pool2)

    # conv3
    with tf.name_scope('conv3') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv3 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv3)

    # conv4
    with tf.name_scope('conv4') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv4 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv4)

    # conv5
    with tf.name_scope('conv5') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv5 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv5)

    # pool5
    pool5 = tf.nn.max_pool(conv5,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool5')
    print_activations(pool5)

    return pool5, parameters


def time_tensorflow_run(session, target, info_string):
    #  """Run the computation to obtain the target tensor and print timing stats.
    #
    #  Args:
    #    session: the TensorFlow session to run the computation under.
    #    target: the target Tensor that is passed to the session's run() function.
    #    info_string: a string summarizing this run, to be printed with the stats.
    #
    #  Returns:
    #    None
    #  """
    num_steps_burn_in = 10
    total_duration = 0.0
    total_duration_squared = 0.0
    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            if not i % 10:
                print('%s: step %d, duration = %.3f' %
                      (datetime.now(), i - num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration
    mn = total_duration / num_batches
    vr = total_duration_squared / num_batches - mn * mn
    sd = math.sqrt(vr)
    print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
          (datetime.now(), info_string, num_batches, mn, sd))


def run_benchmark():
    #  """Run the benchmark on AlexNet."""
    with tf.Graph().as_default():
        # Generate some dummy images.
        image_size = 224
        # Note that our padding definition is slightly different the cuda-convnet.
        # In order to force the model to start with the same activations sizes,
        # we add 3 to the image_size and employ VALID padding above.
        images = tf.Variable(tf.random_normal([batch_size,
                                               image_size,
                                               image_size, 3],
                                              dtype=tf.float32,
                                              stddev=1e-1))

        # Build a Graph that computes the logits predictions from the
        # inference model.
        pool5, parameters = inference(images)

        # Build an initialization operation.
        init = tf.global_variables_initializer()

        # Start running operations on the Graph.
        config = tf.ConfigProto()
        config.gpu_options.allocator_type = 'BFC'
        sess = tf.Session(config=config)
        sess.run(init)

        # Run the forward benchmark.
        time_tensorflow_run(sess, pool5, "Forward")

        # Add a simple objective so we can calculate the backward pass.
        objective = tf.nn.l2_loss(pool5)
        # Compute the gradient with respect to all the parameters.
        grad = tf.gradients(objective, parameters)
        # Run the backward benchmark.
        time_tensorflow_run(sess, grad, "Forward-backward")


if __name__ == '__main__':
    run_benchmark()

 

5,Keras實現AlexNet網絡

  下面粘貼的是網友在Keras上實現的AlexNet網絡代碼。

  因爲AlexNet是使用兩塊顯卡進行訓練的,其網絡結構的實際是分組進行的。而且,在C2,C4,C5上其卷積核只和上一層的同一個GPU上的卷積核相連。 對於單顯卡來講,並不適用,本文基於Keras的實現,忽略其關於雙顯卡的的結構,而且將局部歸一化操做換成了BN。其網絡結構以下

  利用Keras實現AlexNet:

from keras.models import Sequential
from keras.layers import Dense, Flatten, Dropout
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.utils.np_utils import to_categorical
import numpy as np
seed = 7
np.random.seed(seed)

# 建立模型序列
model = Sequential()
#第一層卷積網絡,使用96個卷積核,大小爲11x11步長爲4, 要求輸入的圖片爲227x227, 3個通道,不加邊,激活函數使用relu
model.add(Conv2D(96, (11, 11), strides=(1, 1), input_shape=(28, 28, 1), padding='same', activation='relu',
                 kernel_initializer='uniform'))
# 池化層
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第二層加邊使用256個5x5的卷積核,加邊,激活函數爲relu
model.add(Conv2D(256, (5, 5), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
#使用池化層,步長爲2
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第三層卷積,大小爲3x3的卷積核使用384個
model.add(Conv2D(384, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
# 第四層卷積,同第三層
model.add(Conv2D(384, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
# 第五層卷積使用的卷積核爲256個,其餘同上
model.add(Conv2D(256, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))

model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.summary()
參考來源:(https://blog.csdn.net/qq_41559533/article/details/83718778 )

  

  本文是學習AlexNet網絡的筆記,參考了《tensorflow實戰》這本書中關於AlexNet的章節,寫的很是好,因此在此作了筆記,侵刪。

參考文獻:https://www.jianshu.com/p/00a53eb5f4b3

https://blog.csdn.net/luoluonuoyasuolong/article/details/81750190

https://www.cnblogs.com/wangguchangqing/p/10333370.html

https://my.oschina.net/u/876354/blog/1633143

相關文章
相關標籤/搜索