天天看點

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

[AlexNet - Paper]

[原文位址]

關于 AlexNet 的介紹. 部落客 Yuens 對論文和網絡結構了解真心透徹,膜拜.

轉載并非常感謝原部落客 - Yuens’s Blog.

這篇文章是Alex Krizhevsky在2012年提出的,Alex師從Hinton(也是本文的第三作者),之後也是FeiFei

Li的學生。下面這幅圖是模型的架構圖(五個卷積後接三個全連接配接,最後是處理1000個元素的feature

map的softmax,但softmax沒畫出來。後文會具體說明):

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

本文做了這麼件事:訓練了一個深度卷積網絡(6千萬參數、65萬神經元,結構為五層卷積,某些卷積層後接max-pooling層,後接三個全連接配接層,圖中的方塊或者豎條是feature map,連接配接的線是layer的名字,全連接配接就是dense layer。最後的特征是1000個元素的feature map,丢給沒在圖上展現的softmax完成分類)來完成2010年的ImageNet分類比賽的任務(LVSRC-2010),将120萬張高分辨率的圖檔分為1000個類别。

特點在于:

  1. 收斂/訓練速度加快,用了非飽和神經元(ReLU,即線性矯正單元)。ReLU是一種非線性神經元(當輸入x大于0,則輸出為x;輸入x小于0,則輸出0)較之前的激活函數計算速度更快,收斂也更快;
  2. 模型并行。文中并沒寫是模型并行,但我看介紹認為是一種(有資料并行的)模型并行。訓練過程用了兩個GTX580 GPU 3GB(一個gpu無法cover住),除了第三層卷積、最後的兩個全連接配接層(兩個GPU将全連接配接拆成各自兩部分)和 Softmax(彙聚到單個GPU處理),其餘部分處理可以看作兩個GPU的資料并行(AlexNet架構圖的上部分和下部分對應兩個GPU各自的處理流程,單GPU的話,就是 group=2 這個可以看後文中通過netscope可視化出的網絡結構)。相比單GPU沒加快多少,同時某些層兩個GPU共享參數,可以減少顯存占用這樣以便在一次參數更新放更多的圖檔加入訓練。發現雙卡比單卡top1和top5的error有下降(我認為是作者忘了改SGD學習率導緻,先跑one-gpu net再跑two-gpu net);
  3. 正則化——資料擴增和dropout。關于資料增強,我的了解是:有變化才能找到規律。隻要保證資料多樣性的同時,同樣的圖像有些許的變化(不aug過頭),這樣網絡的權重才能學到在變化中,輸入到輸出的映射本質,學習出/建立起魯棒的網絡權重/參數。沒dropout模型訓練很快會過拟合,加上後double了收斂時間(iteration次數)。但這一項沒有做ablation study(或許做了文中沒說),感覺這個dropout不convincing;
  4. 性能state of art。top-1和top-5錯誤率分别為37.5%和17.5%。相比第二 top-5 錯分率是26.2%。我覺得除以上外,性能提升的一個重要因素是做了資料擴增(data augmentation),産生更diverse的資料來拟合模型。

我個人認為Alex的工作主要在提出了AlexNet這一個網絡架構和 cuda-convnet 這個庫,其實整體工作很偏工程的。比較有特色的是跨通道的LRN(Local Response Normalization 對 acc提升很有限)但之後的發展曆史上沒見過了(不過我覺得LRN跨通道特征 transform 的思想帶來了shuffleNet),論文中隻有一個兩行的帶了動量的SGD的權重更新公式,沒有數學證明和推倒,很像一篇實驗報告,最後定性分析了結果(第一層卷積核的可視化:不同group 上學到的 kernel 不同;網絡最後一層的學到的特征好,可以用于圖像搜尋前的原始特征提取)。沒什麼理論證明啥的,dropout,relu那些東西也是拿來主義。

我想了下,也不能這麼說,最大的工作就是AlexNet這個架構,除了不斷嘗試(超強的工程能力),有一定道理的分析和拍腦袋也是有學術思想在裡面的(不能說偏工程,畢竟工程是實作的前提)。AlexNet能提出還是很有想法的。當時沒有什麼深度學習架構,實作這個工程挺難的。

總結:學術和工程意義非凡!開創了現代CNN的先河!本篇對該文章進行了分析,具體目錄如下:

1. 任務背景
    1.1 Model Capacity/Depth
    1.2 ImageNet
2. Architecture
    2.2 多卡訓練
    2.3 Local Response Normalization
    2.4 Overlapping Pooling
3. Reducing Overfitting
    3.1 Data Augmentation
    3.2 Dropout
4. Details of learning
5. Feature Extractor
6. 參考
           

當然我有兩個問題沒有解決:

  1. Local Response Normalization 的 backprop 是怎麼做的(後文給出了caffe的代碼);
  2. data augmentation 的 Fancy PCA 怎麼算一張三通道圖像的特征值和特征向量的(後文給出了cuda-convnet代碼位置)?

下面我會逐一來詳細分析這幾個要點。不過首先來說一下任務背景。

1. 任務背景

1.1 Model Capacity/Depth

介紹部分有說到對 training se t做了 label-preserving 的 transformation,說白了就是一定程度的資料擴增(data augmentation),其實用更多的資料來訓練模型這點大家都懂,尤其是資料多樣性可以覆寫要訓練的目标類别,多樣性越豐富訓練出的模型效果越好(YOLO文章作者曾寫過一篇預測機器人物體抓取位置預測的文章,裡面資料擴增的比例是1:3000,當然結果也是好的驚人,其實我也有點納悶是模型好,還是資料擴增的作用做到了state of art)。

此外這篇文章說到 model capacity 要足夠大,這樣才能從上百萬圖檔中學到東西。作者提到前人的工作表示model capacity 可以了解為網絡的寬度和深度,相比前饋網絡(Feedforward Network,也就是多層感覺器,即MLP),前人的理論證明相同層數的多層感覺器和卷積網絡,卷積神經網絡雖然參數少易于訓練(更易收斂),但性能(準确率)上卷積網絡要稍微差一些(其實我不太懂這裡作者的邏輯,見下面)。

Thus, compared to standard feedforward neural networks with similarly-sized layers, CNNs have much fewer connections and parameters and so they are easier to train, while their theoretically-best performance is likely to be only slightly worse.

以上兩段分别說明了資料和模型大小的重要性。作者的表意就是:千類物體分類的大任務,解決如此大任務需要大模型(深度-層數、寬度-卷積核數要夠)來Cover,如此大模型就需要大量有些許差異的資料拟合(用變化去學習圖像中不變的本質)。不過在我以往的經驗上來看,越深(層數)的網絡不見得越好。在MINST資料集和其它的一些 Kaggle 資料集上跑過不同深度的卷積網絡和殘差網絡(18、34、50、101、152、200層),發現隻有深度不同的情況下,最好的性能不是深度最深的,

而是深度一般的(寬度這一因素我也做過類似的實驗,結果也差不多)。當然我考慮的是網絡這個東西本身就是牽一發而動全身,因素太多,可能把深度作為一個因變量的同時,其他一些未知的超參數也需要重新設定為跟深度match的值,測試才有效果?這我不得而知,反正就是深度不一定,當然也有可能是我做的實驗不夠細緻也不夠多。

Our final network contains five convolutional and three fully-connected layers, and this depth seems to be important: we found that removing any convolutional layer (each of which contains no more than 1% of the model’s parameters) resulted in inferior performance.

不過作者做了這麼一個實驗,把已經确定的網絡結構中的某些卷積層去掉,發現性能下降,然後得出的結論是:深度很重要。感覺這個實驗在對深度的處理來說并不 convincing,隻能說明目前的網絡結構(尤其是卷積層)很合理,卷積層在最大深度為 5 的的架構下比較好。關于深度的重要性,文章的conclusion部分也有論述:

our network’s performance degrades if a single convolutional layer is removed. For example, removing any of the middle layers results in a loss of about 2% for the top-1 performance of the network. So the depth really is important for achieving our results.

our results have improved as we have made our network larger and trained it longer.

另外剛說到模型 capacity 和資料量的關系,說到底,這些個監督學習是需要 label 的,模型的這些參數(或者說監督學習)都是在拟合資料的機率分布,既有圖像中标準化後RGB的機率分布,還有類别資訊的機率分布,還有每個類别中訓練資料的占比的機率分布等等機率分布。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

總是說“機率分布”、“拟合”,那網絡到底學了什麼。這裡給出一頁slide是CS231n這門課上關于線性回歸的介紹,其中下面的10張可視化出來的小圖就是這個線性分類器學到的權重矩陣 W 對每個類别的權重向量可視化的結果。這10張小圖從右往左數第三個是馬,但模模糊糊好像是一個雙頭馬,而且左邊的頭更明顯一些。這是因為訓練資料中馬的頭大多數是朝向左邊的,其實這個過程就是在拟合資料得到一個模闆,讓新來的圖像x和這個權重矩陣W(或者說是一個類别模闆)計算 f(x,W)=Wx+b f ( x , W ) = W x + b ,輸入圖像與各個類的權重向量(W整個權重矩陣是所有類的權重向量拼起來的)的模闆越比對,則算出來那一類的機率值就越大。雖然這個例子比較感性,但我想足已展現監督學習都是在拟合機率分布這個事實。

其實,監督模型拟合機率分布中的訓練資料每類别的樣本占比,這個還挺重要的。比方實際貓狗分類,需要考慮分類器的應用場景,若狗的訓練資料過多而貓很少(想象一個極端情況:總計10000張貓狗圖像,狗有10張剩下全是貓的圖像),用這樣類别極度不平衡的資料訓練處的分類器大機率會把物體識别為貓,因為分類器對狗學習得不夠多,不了解,反而對貓學習得過多很可能造成了嚴重的過拟合。如果實際的使用場景中狗作為異常點,比方一個貓貓樂園要統計貓的數量來确定每天需要的食物量確定不要把外來的狗計算在内(狗就被當做異常),即使資料傾斜得厲害(貓多狗少),這個分類器在實際中大機率也還是可以用的。還有一個例子是 Kaggle 小夥伴在一個二分類比賽中送出 test 類别完全一樣的submission,結合 evaluation 的公式來public board上的類别分布,進而估計private board上的類别分布,進而在訓練的時候按照這一的比例去訓練模型,最後拿到了不錯名次。

1.2 ImageNet

ImageNet包含22000個類别的1500萬張圖像(主要标注任務由項目負責人 FeiFei Li 組織完成,大部分标注任務使用了亞馬遜的衆包項目完成)。因為ImageNet這個項目和FeiFei Li等人的不懈努力,有一個名叫ILSVRC(ImageNet Large Scale Visual Recognition Challenge)的年度比賽,作為 Pascal Visual Object Challenge 的一部分。但ILSVRC隻用了ImageNet資料集中的一個子集資料:包含1000類,每類大概1000張圖像,訓練集有120萬張圖像、驗證集 5 萬張圖像以及 15 萬張用來測試的圖像。分類任務的模型性能習慣用top-1和top-5兩個錯誤率進行不同模型的比較。

因為本身ImageNet的圖像分辨率是不同的,而我們的分類系統一般而言輸入都是固定大小的。作者的做法是将圖像下采樣到一個固定的 256×256 分辨率。對于矩形圖像,将其短邊縮放到256像素,然後從中間部分crop out 256×256 大小的圖像patch。作者做的預處理操作隻有一個——将圖像中每個像素值減去整個訓練集所有圖像同一位置處的像素均值。相當于作者喂給網絡的是一個 centered raw RGB像素值。

2. Architecture

再一次回到正題,網絡結構一開始的時候已經放出了圖,兩個GPU分别跑前五個卷積層,從第一個到第三個全連接配接層共享參數,每個GPU分攤二分之一的參數,最後生成的1000個元素的feature map交給下面的GPU,再進行softmax計算。

這個圖注裡作者寫的是 neurons 數目,我看既不是 feature map 也不是網絡參數(CS231n在講Conv的博文中有一章節講到從brain view來看neuron,沒看懂)。經過和小夥伴們的讨論,我們确定第一個卷積層後的 feature map,也就是 253400 這個數作者算錯了,而應該是 55×55×48×2=290400,後面計算的feature map次元都正确。這裡簡單說一下feature map和網絡權重的計算方法。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

上圖每個立方體或者長條都是feature map(次元為 224x224x3 的輸入圖像其實也可以看成是一種feature map,需要注意的是某些 conv 層後有 pool 操作,Alex沒有畫出來,但是後面我貼出的netscope給出了pool具體的參數)。

  • 輸入層:作者說網絡輸入是 150528 次元,也就是輸入圖像的長、寬和通道數的乘積 (224×224×3=150528) ( 224 × 224 × 3 = 150528 ) 。接下來經過第一層卷積操作,第一層卷積操作的具體參數量為 11712;
  • 卷積層1:卷積核寬和高為11,即11×11,網絡架構圖上有上下兩路,兩路feature map通道數的總和96,等同于 output channel 的數量,這個結論我是根據CS231n中的下面這張圖得出的(可以看到有兩個卷積核 w0和 w1,而且每個卷積核的次元都是 3×3×3 的帶深度的三維結構,需要注意的是:每個卷積核處理上一層輸入的一張圖三個通道,那麼卷積核也是三維的,同時每個卷積核還有1個偏執單元,那麼 2 卷積核有2個bias,總參數量為: 3×3×3×2+2=56 3 × 3 × 3 × 2 + 2 = 56 ,那麼總共的卷積參數大小為: (11×11×3×48+48)×2=34944 ( 11 × 11 × 3 × 48 + 48 ) × 2 = 34944 。主要在于這裡是上下兩路,單路是 11×11×3×48+48=17472 11 × 11 × 3 × 48 + 48 = 17472 ;
    AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
  • 卷積層1得到的feature map:第一個卷積層後得到 feature map,由兩個48通道的 feature map合起來看,相當于算這個立方體的體積。總次元為 55×55×48×2=290400 55 × 55 × 48 × 2 = 290400 (作者圖注寫的是253400是錯的)。feature map的計算是根據上一層(輸入)的長寬減去filter的長寬,加上2倍的padding的和,除以stride 4,最後加1,即 (224–11+2×0)÷4+1=54.25 ( 224 – 11 + 2 × 0 ) ÷ 4 + 1 = 54.25 ,會發現這裡不是 55 且帶小數,CS231n課程中有下面一段說Alex當時可能是在 227 的圖上做的(但是下面這段話說Alex做了padding,我覺得如果224的圖要padding得到55的話,要這麼算: (224–11+2×1.5)÷4+1=55 ( 224 – 11 + 2 × 1.5 ) ÷ 4 + 1 = 55 ,也就是說padding=1.5),即 (227–11+2×0)÷4+1=55 ( 227 – 11 + 2 × 0 ) ÷ 4 + 1 = 55 ;

    As a fun aside, if you read the actual paper it claims that the input images were 224×224, which is surely incorrect

    because (224 – 11)/4 + 1 is quite clearly not an integer. This has confused many people in the history of ConvNets and little is known about what happened. My own best guess is that Alex used zero-padding of 3 extra pixels that he does not mention in the paper.

    from CS231n blog

  • norm1層:norm就是 local response normalization,基于局部小區域的像素值強度歸一化(後面介紹),對feature map大小沒有影響;
  • pool1層(pool:max;kernel size:3;stride:2):上一層卷積feature map次元(把兩個支路一起看)為: W=55,H=55,D=96 W = 55 , H = 55 , D = 96 , 分别表示寬度、高度、深度(channel數)。經過pool後的結果這麼算: W1=(W−F)/S+1;H1=(H–F)/S+1;D2=D1 W 1 = ( W − F ) / S + 1 ; H 1 = ( H – F ) / S + 1 ; D 2 = D 1 ,其中 F F 和 SS 分别是兩個pool的超參數池化空間擴充(spatial extent,相當于池化的kernel)和池化步幅(Stride)。那麼得到的新的 feature volume的長、寬、深(通道數)為 W1=27;H1=27;D1=96 W 1 = 27 ; H 1 = 27 ; D 1 = 96 ,整體規模為 [(55−3)/2+1]×[(55–3)/2+1]×96=69984 [ ( 55 − 3 ) / 2 + 1 ] × [ ( 55 – 3 ) / 2 + 1 ] × 96 = 69984 ;
  • 卷積層2:權重規模為(拆成兩路算): (5×5×48×128+128)×2=307456 ( 5 × 5 × 48 × 128 + 128 ) × 2 = 307456 。其實有個很基礎的公式(也帶計算bias): #inputchannel×#kernelsize2×#outputchannel+#outputchannel # i n p u t c h a n n e l × # k e r n e l s i z e 2 × # o u t p u t c h a n n e l + # o u t p u t c h a n n e l ,這裡是雙路GPU所有每一路算各自的,最後乘以2.
  • 卷積層2得到的feature map: 27×27×128×2=186624 27 × 27 × 128 × 2 = 186624 ;
  • norm2:略,186624;
  • pool2: [(27–3)/2+1]×[(27–3)/2+1]×128×2=43264 [ ( 27 – 3 ) / 2 + 1 ] × [ ( 27 – 3 ) / 2 + 1 ] × 128 × 2 = 43264 ;
  • 卷積層3:權重規模為: (3×3×128×192×2+192×2)×2=885504 ( 3 × 3 × 128 × 192 × 2 + 192 × 2 ) × 2 = 885504 ,但是我根據netscope的網絡結構(沒有group,注意沒有分支)圖算出來的權重規模為: 3×3×256×384+384=885120 3 × 3 × 256 × 384 + 384 = 885120 。後來發現這個885504(前一個)算的不對,因為文中說到:

    the kernels of layer 3 take input from all kernel maps in layer 2

    ,是以這裡就把兩條支路當做一條路,前一層上下兩路的feature map當做一起的,後面得到的上下兩路feature map 一當做一起的。前者多出來一個384,是因為多了一個GPU産生的bias數目。或許是以,two-gpu net 比 one-gpu net的性能好(我的猜測後文是作者又嘗試了非GTX580的大顯存卡吧,當然不排除作者減小 input shape 和模型中的 kernel size 來訓練 one-gpu net。這裡作者也沒講清);
  • 卷積層3得到的 feature map: 13×13×192×2=648,96 13 × 13 × 192 × 2 = 648 , 96 ;
  • 卷積層4:權重規模為: (3×3×192×192+192)×2=663,936 ( 3 × 3 × 192 × 192 + 192 ) × 2 = 663 , 936 ,與單GPU(後面netscope)的數值一緻;
  • 卷積層4得到的feature map: 13×13×192×2=64896 13 × 13 × 192 × 2 = 64896 ;
  • 卷積層5:權重規模為: (3×3×192×128+128)×2=442624 ( 3 × 3 × 192 × 128 + 128 ) × 2 = 442624 ,與單GPU(後面netscope)的數值一緻;
  • 卷積層5得到的feature map: 13×13×128×2=43264 13 × 13 × 128 × 2 = 43264 ;
  • pool5:見下;
  • 全連接配接層1:權重規模:可以單獨算, (13×13×128×(2048+2048)+13×13×128)×2=177,252,608 ( 13 × 13 × 128 × ( 2048 + 2048 ) + 13 × 13 × 128 ) × 2 = 177 , 252 , 608 ,或者兩路GPU的結果合并算, 13×13×128×2×2048×2+13×13×128×2=177,252,608 13 × 13 × 128 × 2 × 2048 × 2 + 13 × 13 × 128 × 2 = 177 , 252 , 608 。這麼算是沒錯的但是沒有考慮pooling5(pool:max;kernel size:3;stride:2),我們先計算經過pooling5後的次元: [(13–3)/2+1]×[(13–3)/2+1]×128=4608 [ ( 13 – 3 ) / 2 + 1 ] × [ ( 13 – 3 ) / 2 + 1 ] × 128 = 4608 ,當然這是一個支路pool5後的結果,兩個就是 9216。此時再計算全連接配接: 9216×4096+4096=37,752,832 9216 × 4096 + 4096 = 37 , 752 , 832 。相比沒有pool5的177,252,608,數量減少了一個多億的參數量(78%);
  • 全連接配接層1得到的feature map:4096;
  • 全連接配接層2:權重規模:4096×4096+4096=16,781,312;
  • 全連接配接層2得到的feature map:4096;
  • 全連接配接層3:權重規模:4096×1000+1000=4,097,000;
  • 全連接配接層3得到的feature map:1000;
  • softmax:1k-way。

這裡再把netscope和Alex畫的圖都扔出來,同時計算總的參數量:

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
Layer Name model size of one-gpu net model size of two-gpu net feature map
input 150,528
conv1 34,944、8(hyper-params) 34944、16 290,400
norm1 3(local size、alpha、beta) 6 290,400
pool1 2(kernel size、stride) 4 69,984
conv2 307,456、8 307,456、16 186,624
norm2 3 6 186,624
pool2 2(kernel size、stride) 4 43,264
conv3 885,120、8 885,504、16 648,96
conv4 663,936、8 663,936、16 648,96
conv5 442,624、8 442,624、16 43,264
pool5 2(kernel size、stride) 4 4,608
fc6 37,752,832、6(有dropout) 37,752,832(強調:每張卡攤一半)、12 4,096
fc7 16,781,312、6(有dropout) 16,781,312(同上)、12 4,096
fc8 4,097,000、5 4,097,000(強調:reduce到一張卡)、5 1,000
SUM 400,740,785 基本同左 1,404,680

最後算出來,one-gpu net 和 two-gpu net 的model參數規模基本一緻,量級都在4000萬。feature map的量級在140萬(feature map就是作者文中所說的neurons個數)。不過可以發現,和作者開頭所寫的模型參數6000萬,神經元數量(feature map)60萬是不吻合的。不過還是以我算的為準吧(作者又算錯了)。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

下面根據論文主要講架構的三個點:非線性激活函數ReLU、重疊池化(Overlapping Pooling)以及這篇文章比較有特色的多卡訓練和局部響應規範化(Local Response Normalization)。

2.1 ReLU

用這種分段函數比先前的S型函數(tanh、sigmoid)收斂快,作者拿了個四層的CNN分别用tanh和ReLU訓練,發現在train error rate曲線圖上,到達相同的train error rate,tanh比ReLU慢6倍(見下圖,當然這個tanh的曲線好像是作者手畫的還抖了幾下子)。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

2.2 多卡訓練

兩張小破卡GTX 580 3GB(查了下CUDA compute capacity為2.0,現在成本效益最高的1080是6.1)是要解決一張卡,顯存塞不下模型的問題,但其實我也有疑問,後文又說到 two-gpu net 比 one-gpu net 的 acc 要高,這個one-gpu net 如果說是用580的卡,豈不是沖突,一種可能是作者有顯存更大的卡,在保證其他都一樣的情況下,又訓練了一遍發現兩張卡的結果好;要麼就是縮小了模型或圖像分辨率等等,把模型和中間的結果得以塞進一張卡。後來我又想了下,有可能是作者先跑了one-gpu net的模型,之後跑two-gpu net的模型時,然後忘了改學習率,或者說bsize比較小。當然都有可能啦。

那麼具體來說一下兩張卡相比單張卡在模型訓練過程中的差異。在上下兩路中第2層、第4層、第五層的卷積會有參數共用,若是單GPU訓練,這裡其實就是 group=2。簡單來說,如果上一層的 feature map 的depth為256,在group=2的情況下,256會分為兩個128的feature map(即feature map A和feature map B),同時這一層的卷積kernel也會分為等數量的兩組比方C和D,那麼做conv的過程就是:用C這一組卷積核隻在A上做,用D這一組卷積核隻在B上做。那麼最後就是 A×C+B×D A × C + B × D 。而這裡巧妙之處在于為了節省顯存,作者用2個GPU實作了group=2這個概念,在當時可能還沒有group這個卷積分組的概念。

This means that, for example, the kernels of layer 3 take input from all kernel maps in layer 2. However, kernels in layer 4 take input only from those kernel maps in layer 3 which reside on the same GPU.

圖中第三個卷積層,第一個、第二個全連接配接層有參數共享,最後一個全連接配接的網絡參數都在第二張卡上(相當于第一張空了)做reduce。除此之外,網絡上下兩部分(即兩個GPU對應的兩個分支)都是相同的。但是這裡有個問題在于,我算第三個卷積層的時候,two-gpu net 比 one-gpu net多了384個參數(來自多出一個GPU所帶來的conv3 的 filter數量個bias),因為兩個 gpu 存在兩組filter,但其實還是對作者上面這句話不太了解:似乎是說兩張卡隻有一張卡存模型參數,如果是這樣,那麼參數量就多計算了384個。其實這裡忽略吧,畢竟現在即使是多張卡,也都是one-gpu net的資料并行,而不是模型并行。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

這種連接配接pattern作者是通過validation來選的,認為這樣比較好控制計算量和模型體積。說幾句題外話,現在似乎都不關心GPU的連接配接拓撲結構了,即插即用。Pascal等新架構有了NVLink(一個能夠在GPU-GPU以及GPU-CPU之間實作高速大帶寬直連通訊的快速互聯機制),理論帶寬性能可以達到80GB/s,當然是卡之間的,是傳統PCIE3.0的5~12倍率。如果機器是Power架構,Power CPU和GPU之間也可以通過NVLink連接配接,同時Power架構另一個優勢是超高的記憶體帶寬(CPU-記憶體),不過理論上雙路Power CPU帶寬為230GB/s,實測150GB/s,雙路至強是99GB/s左右(Power架構就以上兩個優勢吧,總體而言和X86架構在深度學習的訓練上比沒什麼)。

2.3 Local Response Normalization

作用是有助于泛化,有這個操作模型在Cifar10(四層CNN)和 ImageNet資料集(AlexNet)上準确率有提高。這是一種根據附近 n/2 n / 2 個通道在同一個位置的像素來做局部像素值規範化的方法,計算第 j j 個通道在 x,y 位置上局部響應規範化後的值bix,ybx,yi的計算公式如下:

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

其中, ajx,y a x , y j 是第 j j 個通道上 x,y 位置上的ReLU激活值。n,k,α,βn,k,α,β 都是超參數,根據一組validation set得到 n=5,k=2,α=10−4,β=0.75 n = 5 , k = 2 , α = 10 − 4 , β = 0.75 , N N 是這個feature map總的通道個數。

對值不大或過大的response在channel層級上有normalization的作用,被磨平(跨通道且帶有局部性質的非極大值抑制)了。缺點也比較明顯,因為是跨通道的,本身通道層級上的資訊交流(不同卷積核之間的資訊交流)相比如卷積這樣單通道的資訊交流有說服力(不過這裡有一種,ShuffleNet中channel級别的資訊crosstalk的感覺);此外local帶有随意性,和batch size内的順序有關(如果配置設定再科學一些就更好了)。這一方法在後來VGG那篇文章裡說到LRN并沒什麼用,在之後的發展曆史上也基本沒有出現過。

忽然想到一個問題那LRN的backprop怎麼算?問了好幾個人答案不盡相同,還是翻了Caffe的代碼:

template <typename Dtype>
void LRNLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  switch (this->layer_param_.lrn_param().norm_region()) {
  case LRNParameter_NormRegion_ACROSS_CHANNELS:
    CrossChannelBackward_cpu(top, propagate_down, bottom);
    break;
  case LRNParameter_NormRegion_WITHIN_CHANNEL:
    WithinChannelBackward(top, propagate_down, bottom);
    break;
  default:
    LOG(FATAL) << "Unknown normalization region.";
  }
}

template <typename Dtype>
void LRNLayer<Dtype>::CrossChannelBackward_cpu(
    const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,
    const vector<Blob<Dtype>*>& bottom) {
  const Dtype* top_diff = top[]->cpu_diff();
  const Dtype* top_data = top[]->cpu_data();
  const Dtype* bottom_data = bottom[]->cpu_data();
  const Dtype* scale_data = scale_.cpu_data();
  Dtype* bottom_diff = bottom[]->mutable_cpu_diff();
  Blob<Dtype> padded_ratio(, channels_ + size_ - , height_, width_);
  Blob<Dtype> accum_ratio(, , height_, width_);
  Dtype* padded_ratio_data = padded_ratio.mutable_cpu_data();
  Dtype* accum_ratio_data = accum_ratio.mutable_cpu_data();
  // We hack a little bit by using the diff() to store an additional result
  Dtype* accum_ratio_times_bottom = accum_ratio.mutable_cpu_diff();
  caffe_set(padded_ratio.count(), Dtype(), padded_ratio_data);
  Dtype cache_ratio_value =  * alpha_ * beta_ / size_;

  caffe_powx<Dtype>(scale_.count(), scale_data, -beta_, bottom_diff);
  caffe_mul<Dtype>(scale_.count(), top_diff, bottom_diff, bottom_diff);

  // go through individual data
  int inverse_pre_pad = size_ - (size_ + ) / ;
  for (int n = ; n < num_; ++n) {
    int block_offset = scale_.offset(n);
    // first, compute diff_i * y_i / s_i
    caffe_mul<Dtype>(channels_ * height_ * width_,
        top_diff + block_offset, top_data + block_offset,
        padded_ratio_data + padded_ratio.offset(, inverse_pre_pad));
    caffe_div<Dtype>(channels_ * height_ * width_,
        padded_ratio_data + padded_ratio.offset(, inverse_pre_pad),
        scale_data + block_offset,
        padded_ratio_data + padded_ratio.offset(, inverse_pre_pad));
    // Now, compute the accumulated ratios and the bottom diff
    caffe_set(accum_ratio.count(), Dtype(), accum_ratio_data);
    for (int c = ; c < size_ - ; ++c) {
      caffe_axpy<Dtype>(height_ * width_, ,
          padded_ratio_data + padded_ratio.offset(, c), accum_ratio_data);
    }
    for (int c = ; c < channels_; ++c) {
      caffe_axpy<Dtype>(height_ * width_, ,
          padded_ratio_data + padded_ratio.offset(, c + size_ - ),
          accum_ratio_data);
      // compute bottom diff
      caffe_mul<Dtype>(height_ * width_,
          bottom_data + top[]->offset(n, c),
          accum_ratio_data, accum_ratio_times_bottom);
      caffe_axpy<Dtype>(height_ * width_, -cache_ratio_value,
          accum_ratio_times_bottom, bottom_diff + top[]->offset(n, c));
      caffe_axpy<Dtype>(height_ * width_, -,
          padded_ratio_data + padded_ratio.offset(, c), accum_ratio_data);
    }
  }
}

template <typename Dtype>
void LRNLayer<Dtype>::WithinChannelBackward(
    const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,
    const vector<Blob<Dtype>*>& bottom) {
  if (propagate_down[]) {
    vector<bool> product_propagate_down(, true);
    product_layer_->Backward(top, product_propagate_down, product_bottom_vec_);
    power_layer_->Backward(power_top_vec_, propagate_down, pool_top_vec_);
    pool_layer_->Backward(pool_top_vec_, propagate_down, square_top_vec_);
    square_layer_->Backward(square_top_vec_, propagate_down,
                            square_bottom_vec_);
    split_layer_->Backward(split_top_vec_, propagate_down, bottom);
  }
}
           

LRN層 backprop 的計算根據 LRN 的 norm_region 的不同分兩種情況:一種是

ACROSS_CHANNELS

(會調用

CrossChannelBackward_cpu(top ,propagate_down, bottom))

,另一種是

WITHIN_CHANNEL

(會調用

WithinChannelBackward(top, propagate_down, bottom)

)。壓力實在很大,看不下去了,放棄!

作者的cuda-convnet代碼寫的也很冗長,這裡隻貼出這backprop部分的上層代碼:

void ResponseNormLayer::bpropActs(NVMatrix& v, int inpIdx, float scaleTargets, PASS_TYPE passType) {
    convResponseNormUndo(v, _denoms, _prev[]->getActs(), getActs(), _prev[]->getActsGrad(), _channels, _size, _scale, _pow, scaleTargets, );
}
           

2.4 Overlapping Pooling

傳統沒有重疊,有重疊發現更難過拟合,泛化能力提高(準确率有微弱提高)。具體流程是這麼做的:

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

因為上圖是興趣區域的pooling,與我們的一邊的pooling有一些差别,不過二者大體一緻。此外,我在文章發現這句話:

We generally observe during training that models with overlapping pooling find it slightly more difficult to overfit.

“有了重疊池化網絡不易過拟合”。多少有點反直覺,池化帶來資訊量減少,重疊形式帶來非極大值抑制的作用。直覺上覺得這樣會更快收斂,但準确性上不好說(如果作者可以進一步分析一下就更好了)。但實際這裡性能的提升相比不帶重疊的确實很微弱:top1和top5分别提升0.4%和0.3%。

AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks

這裡放出池化算法的具體步驟(摘錄自CS231n):

  • Accepts a volume of size W1×H1×D1W1×H1×D1
    • Requires two hyper-parameters:
      • their spatial extent F,
      • the stride S,
    • Produces a volume of size W2×H2×D2 W 2 × H 2 × D 2 where:
      • W2=(W1−F)/S+1 W 2 = ( W 1 − F ) / S + 1
      • H2=(H1−F)/S+1 H 2 = ( H 1 − F ) / S + 1
      • D2=D1 D 2 = D 1
    • Introduces zero parameters since it computes a fixed function of the input
    • Note that it is not common to use zero-padding for Pooling layers
    • 3. Reducing Overfitting

      Although the 1000 classes of ILSVRC make each training example impose 10 bits of constraint on the mapping from image to label, this turns out to be insufficient to learn so many parameters without considerable overfitting.
      把圖像映射到 10 bits( 210=1024≈1000 2 10 = 1024 ≈ 1000 )的類别資訊中,如果不過拟合,那麼大模型很難從1000類圖像中學到如此量級的參數。那麼如此說來,就很有必要再過拟合以及參數學習兩者之間trade off。文中有兩個方法來避免過拟合:
      • Data augmentation
      • Dropout

      3.1 Data Augmentation

      作者用到的資料擴增有水準翻轉和像素值加噪聲兩種方式。水準翻轉是對原始圖像統一resize到 256×256 大小,再crop出所有 224×224 的圖,進行水準翻轉擴大2倍,那麼訓練圖像就增加了 (256–224+1)×(256–224+1)×2=2178 ( 256 – 224 + 1 ) × ( 256 – 224 + 1 ) × 2 = 2178 倍,作者算的2048(32×32×2=2048)是錯的。

      第二種方式是改變圖像上RGB通道的像素強度值(加噪聲,帶來的優勢是top1錯誤率降低1%),大概流程是:計算原始圖像的協方差矩陣,根據協方差矩陣計算特征值和特征向量,在特征值前加入噪聲系數(服從均值為0,标準差為0.1的高斯分布,然後複原回原始圖像,得到加了噪聲的圖像)。有三個步驟:

      AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
      上面這幅圖來自魏秀參的神經網絡訓練 tricks 的 data augmentation 部分。但是我覺得不需要計算PCA,隻需要把圖像的特征值和特征向量拿到就好了吧。然後在特征值前加入均值為0标準差為0.1的高斯噪聲随機變量,再将資料還原回去即可。這裡不是很明白,還是需要看看代碼

      cuda-convnet/src/cudaconv2/img_acts.cu

      ,困死了,卒!不看了!摔!

      3.2 Dropout

      AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
      dropout解決模型過拟合的問題,但帶來的一個問題是 double了 iteration 次數才收斂。實作是通過設定一個dropout的機率

      dropOutFraction

      ,訓練過程中的forward和backward會随機使這一層的神經元以 p p <script type="math/tex" id="MathJax-Element-415">p</script> 的機率失活,在AlexNet中dropout在前兩個全連接配接層。deeplearn toolbox的實作如下:
      % dropout
      % forward stage in NN\nnff.m
      if(nn.dropoutFraction > )
          if(nn.testing)
              nn.a{i} = nn.a{i}.*( - nn.dropoutFraction);
          else
              nn.dropOutMask{i} = (rand(size(nn.a{i}))>nn.dropoutFraction);
              nn.a{i} = nn.a{i}.*nn.dropOutMask{i};
          end
      end
      % backward stage in NN\nnbp.m
      if(nn.dropoutFraction>)
          d{i} = d{i} .* [ones(size(d{i},),) nn.dropOutMask{i}];
      end
                 
      • forward:可以看到神經元失活是先随機初始化一個與目前層規模一樣的mask,然後根據mask中每個元素的随機值(介于0到1之間),将小于

        dropoutFraction

        的元素設定為0,其餘為1,得到

        dropOutMask

        。再将該層的所有激活值與

        dropOutMask

        進行點乘,得到結果。照 deeplearn toolbox的代碼顯示,每次forward随機的mask都是不同的;
      • backward:backprop回來的所在層的derivatives乘以對應層的

        dropOutMask

      其實我一開始還有點費解為什麼dropout的backprop要乘以mask:forward的時候已經按照dropOutFraction這個值dropout了,backprop就不需要再dropout了吧,結果代碼中卻對derivatives再乘以

      dropOutMask

      ,豈不是相當于做了兩次同樣fraction的dropout?

      後來想了想,就應該在backward的階段乘以

      dropOutMask

      ,因為forward的時候是mask的不完整模型進行的,那麼backprop必然也是針對這個不完整的模型進行update;

      test:所有神經元參與計算,但是每個元素值乘以(1-

      dropOutFraction

      ),作為沒有被dropout的比率被留下的值。

      原理上,我同樣不太明白:為什麼所有單元都參與運算的同時還需要乘以

      droputFraction

      ?我覺得所有單元參與本身訓練的時候就是不完整的,test的時候再來一次,豈不被閹割兩次?

      後來也想通了,dropout 每次 forward 的 mask 都是不同的,相當于從一個大模型中sample出一個小模型,那麼forward 和 test 階段要保證一緻,forward是讓

      dropOutFraction

      的神經元失活,那麼test階段全用(內建的大模型,不要看做認為是被閹割過的),就通過結果再乘以(1-

      dropOputFraction

      )來和forward階段保持一緻。

      4. Details of learning

      這部分彙總一下作者提到的訓練經驗:
      • 權重初始化:每層權重根據均值為0标準差為0.01的高斯分布初始化,第二、第四、第五層卷積和全連接配接層的bias初始化為常數1,剩下所有層的bias初始化為0。這種初始化有助于早期階段正樣本的學習的加速;
      • 優化算法:小批量梯度下降;
      AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
      • batch size:128。其實這個bsize感覺很大,兩個 GTX580 3GB能Cover住,尤其是每張圖是224大小的,我表示震驚,這裡沒具體分析;
      • learning rate:0.01,所有層使用一樣的初始學習率。訓練過程中如果val-error不降低,那麼就會對learning rate除以10。訓練總共跑了90個epochs,3次因為val-error不降低調整學習率;
      • momentum:0.9;
      • weight decay:0.0005。重要,不僅起正則(提高val-acc)作用,也提高train-acc;
      • epochs:90。跑了90遍(兩張GTX 580 3GB,跑了5、6天,估算下來230img/sec/2gpus,感覺很挺快的)120萬張圖像的資料集,前面說到僅僅crop+flip的data augmentation就做了2178倍的資料擴增,這裡需要說明的是,data augmentation不是說跑一張圖像會跑2178次,而是說每個epoch每張圖像都會有2178種随機變化的可能性,使得每次資料有一定的變化。

      5. Feature Extractor

      其實訓練好的網絡本身就可以當做特征提取器,比方下面圖右側,就是跑了網絡得出 softmax 前的feature map,然後對其他所有圖與給定的圖計算歐氏距離,得到最近的 6 張圖(這一方法在人證比對之類的場景用的比較多)。每一行第一個是給出的圖,這一行剩下的6個是其他feature map與第一張圖最近的6張。
      AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
      這點足以看出,pre-trained模型的特征提取能力。還有一個好玩的地方是文中說的 data-connect 層,說白了就是第一個卷積層,作者将第一個卷積層可視化,總共96個通道,可以明顯看到上下兩個GPU訓練得到的卷積核側重點不同,上面偏重對網絡線條、方向的學習,下面偏重對顔色和方向的學習。其實在模型架構那裡,我也拿出了CS231n課程上線性回歸那一節的截圖,也是對線性回歸的權重可視化是目标類别的模模糊糊的輪廓。
      AlexNet 閱讀了解 - ImageNet Classification with Deep Convolutional Neural Networks論文了解 - ImageNet Classification with Deep Convolutional Neural Networks
      我想如果将每層可視化的卷積權重進行逐層的疊加等處理,是否也會得到類似目标類别模糊輪廓的模闆呢?

      參考

      [1] ImageNet Classification with Deep Convolutional Neural Networks

      [2] CS231n Convolutional Neural Networks for Visual Recognition - http://cs231n.github.io/convolutional-networks/#pool - http://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture3.pdf

      [3] [IBM Deep Learning “專用” S822LC 伺服器深(fu)度(yan)測評 – HPCCHINA] (http://hpcchina.net/ibm-deep-learning-server-s822lc-review/)

      [4] GTC喧嚣後的思考 NVLink技術及影響解析(全文)NVIDIA GeForce GTX 780Ti顯示卡評測-中關村線上

      [5] Must Know Tips/Tricks in Deep Neural Networks

      [6] deepsense-ai/roi-pooling

繼續閱讀