:從全連接到卷積的架構躍遷)
先說一個我自己復習時反復確認過很多次的結論卷積神經網絡CNN真正厲害的地方不是它有多少層、用了多少參數而是它在一開始就從架構層面繞開了全連接網絡在圖像任務上的兩大死穴——參數爆炸和空間結構丟失。2017年CS231n第五講花了大量篇幅把這件事拆開揉碎地講我當時沒有完全聽進去后來自己復現了幾個經典結構才徹底想明白。這篇內容就是我對這一講筆記的完整整理把卷積層、池化層、全連接層、激活函數、訓練技巧串起來講適合正在學深度視覺、準備復現模型、或者剛被各種卷積術語繞暈的同學。先說好這不是課程視頻的逐句翻譯而是我自己消化之后重新組織的版本。重點放在“為什么這么做”和“動手時要注意什么”上公式會有但每個公式我都會想辦法解釋到你能自己推出來。1. 從全連接層到卷積層為什么非改不可1.1 一張圖片被全連接層“拉直”之后丟掉了什么假設輸入是一張224×224的RGB圖片數據形狀是224×224×3。傳統(tǒng)全連接層的做法很直接把整個三維數組展平成一維向量長度是224×224×3150528然后接入一個擁有若干神經元的隱層。聽起來沒什么大不了但如果你讓這個隱層有512個神經元權重矩陣的規(guī)模就是150528×512約7700萬個參數這還只是一層。更關鍵的問題不是參數量而是結構。圖像是三維的像素之間存在天然的空間鄰域關系相鄰像素往往顏色相近、紋理連續(xù)貓的眼睛總是出現在臉的局部區(qū)域。展平成一維向量之后這種鄰域關系被徹底打亂網絡看到的只是“一長串數字”每個位置之間的關系完全失去了空間含義。你等于把一張照片撕成碎片再硬塞給一個只會數順序的模型。課程里反復強調一個類比對圖像識別來說位置相近的像素比距離遠的像素關系大得多這是最基本的先驗。全連接層沒有利用這個先驗他默認所有輸入特征是全連接的、地位對等的這在圖像任務上是一種巨大的浪費。1.2 參數共享和局部連接是怎么救場的卷積層就是針對這兩個問題設計的。第一是局部連接。每個卷積核不再連接整張輸入圖而是只連接到輸入的一個局部區(qū)域這個區(qū)域的大小就是卷積核的尺寸比如3×3或5×5。這和人的視覺系統(tǒng)很像看東西的時候也是一小塊一小塊地看局部紋理先被檢測出來再由后面更深的層組合成更大的語義概念。第二是參數共享。同一個卷積核會游走整張輸入圖也就是說在左上角檢測“橫向邊緣”的那組權重也會被用在右下角。為什么合理因為圖像中同一個特征可能出現在任意位置一只貓在圖片左上角和右下角本質上都是“貓”對應的邊緣紋理并沒有變。既然特征本身一樣檢測特征的那組參數也沒必要區(qū)分位置。這一下子把參數量從幾千萬壓到幾百。你可以拿全連接和卷積做個對比直觀感受一下差距結構處理224×224×3圖像的典型參數量是否保留空間關系全連接層隱層512節(jié)點約7700萬否展平成一維卷積層6個3×3卷積核6×(3×3×31)168是按局部窗口掃描168對7700萬差了約46萬倍。課程里常說的“卷積讓深度學習在視覺上變得可行”說的就是這個差距。參數少了過擬合風險隨之降低訓練需要的樣本量和計算資源也友好很多。1.3 平移等變性換個位置特征依然能被抓住參數共享還帶來一個隱藏的好處卷積天然具備平移等變性。意思是輸入圖像里的一個目標平移一段距離卷積層輸出的特征圖也會跟著平移但特征本身仍然會被檢測出來。這對圖像任務太重要了因為我們不愿意模型因為貓從左邊挪到右邊就從“貓”變成“未知物體”。不過我后來想明白一件事只有卷積層給的是“等變”而不是“不變”。真正讓你對平移不敏感的是后面的池化層它把局部區(qū)域內的響應做聚合才給了分類器一定的“無論出現在哪都能認出來”的寬容度。所以看這章的時候最好把卷積和池化當成一對搭檔來理解單獨拎出來看任何一個都解釋不了完整的網絡行為。2. 卷積層的輸出尺寸、超參數與感受野親手算一次就忘不掉2.1 卷積核到底在算什么一個卷積核本質上是一個小的權重張量。以輸入RGB三通道圖為例一個3×3卷積核的形狀是3×3×3空間大小3×3通道數必須與輸入一致。這個卷積核覆蓋在輸入的某個位置上和覆蓋區(qū)域內的27個數值做逐元素乘法再求和加上一個偏置得到輸出特征圖上的一個數。這個數代表什么直觀理解就是“這個局部區(qū)域與卷積核的匹配程度”。如果卷積核學到的是“橫向漸變邊緣”那么它在圖像邊緣區(qū)域就會輸出較大的響應在平滑區(qū)域輸出接近0。多個卷積核并存時每個核相當于一個特征檢測器有多少個卷積核就有多少個輸出特征圖也就是輸出通道數。課程把輸出通道數叫深度這個叫法容易和網絡層數混淆我在筆記里習慣叫“特征圖數量”。2.2 三個超參數決定輸出有多大多密卷積層有三個核心超參數深度卷積核個數、步長stride和零填充zero-padding。步長決定了卷積核每次滑動幾步。步長為1時卷積核挨個移動步長為2時輸出特征圖尺寸直接減半左右。填充則是在輸入邊緣補一圈0目的有兩個一是防止卷積把特征圖越卷越小信息快速縮水二是讓邊緣像素也有機會被卷積核中心覆蓋到畢竟邊緣元素在無填充時參與的卷積次數會比中間元素少信息地位不對等。輸出尺寸公式在課程里寫得很清楚輸出邊長 (N - F 2P) / S 1。其中N是輸入邊長F是卷積核空間尺寸P是填充量S是步長。我在紙上算了三年才真正理解這個公式每個項的意圖N - F 是“卷積核中心還能走多遠”加上2P是因為填充讓輸入邊長變長除以S是看按步長能取幾個位置最后加1是因為初始位置也要算一次。我自己寫了個小函數每次搭網絡都過一遍def conv_out_size(N, F, S, P0): return (N - F 2 * P) // S 1 # 輸入32x325x5卷積核步長1無填充 print(conv_out_size(32, 5, 1, 0)) # 28 # 輸入32x325x5卷積核步長1填充2 print(conv_out_size(32, 5, 1, 2)) # 32 # 輸入7x73x3卷積核步長2無填充 print(conv_out_size(7, 3, 2, 0)) # 3搭網絡時最怕的就是這里算錯。常見配置是卷積核3×3、步長1、填充1輸出尺寸保持不變或者卷積核5×5、步長1、填充2輸出尺寸也保持不變。這樣在設計串聯(lián)結構時你可以清晰控制哪些層改變空間尺寸哪些層只做特征變換。2.3 感受野與堆疊小卷積核的哲學感受野是另一個繞不開的概念意思是輸出特征圖上一個像素點對應回輸入圖像上的多大區(qū)域。這個理解到后面看網絡結構會非常有用因為你很快就發(fā)現現代網絡很少用大的卷積核了。原因很簡單堆疊兩個3×3卷積核換算成感受野是一個5×5因為第一個3×3讓特征圖每個點看到輸入3×3區(qū)域再接一個3×3時看到的是上一層3×3區(qū)域對應的輸入區(qū)域合起來是5×5范圍。但參數量呢兩個3×3卷積的參數量是2×918按單通道看而一個5×5卷積是25。參數更少感受野卻一樣大而且中間多了一次ReLU激活給網絡增加了非線性表達能力。課程里明確指出這就是后來很多經典結構偏好使用小卷積核堆疊的根本原因。1×1卷積也值得單獨說。它不改變空間尺寸但會改變通道數相當于在每個像素位置上對通道做一次線性組合可以用很低的成本控制特征圖厚度。課程里把1×1卷積看成“跨通道的池化”或者說“通道層面的重組合”在GoogLeNet這類結構里被大量用來降維先把通道壓下來再做大卷積核計算。3. 池化層和全連接層縮小特征圖與匯總決策3.1 池化層不是“必須”但它的動機很樸素池化層負責的是特征圖的降采樣。最常見的是最大池化用一個2×2窗口以步長2掃過特征圖每個窗口內取最大值輸出寬高直接減半通道數不變。為什么需要這一步首先卷積層雖然已經控制了參數規(guī)模但特征圖本身依然很大如果一路保持高分辨率后面的層計算量會一直維持在很高的水平。其次最大池化給了網絡對微小位移的容忍度物體偏移一兩個像素時池化窗口內的最大值往往變化不大后續(xù)層看到的仍是接近相同的特征。用課程里的說法池化是在逐步抽象位置細節(jié)慢慢讓位于語義內容。這里特別想說池化并不改變通道數量。它只在寬和高兩個維度上做壓縮通道維度保持不動。常見配置就是2×2窗口加步長2效果是特征圖每個維度減半相當于對特征圖做了空間上的“濃縮”。3.2 最大池化和平均池化的取舍講師在課上對比過兩種池化。最大池化取窗口內的最強響應實際經驗里效果更好因為它保留的是“這個區(qū)域里到底有沒有某種特征”而不是被區(qū)域內大量低響應值拉低平均。平均池化沒那么激進把窗口內所有值等權匯總信息更平滑但更容易把微弱特征稀釋掉。不過平均池化有一個用法一直活到了今天全局平均池化也就是把整張?zhí)卣鲌D池化成1×1。它一般出現在網絡的末端用來替代全連接層做最終分類前的特征匯總好處是參數量幾乎為零并且天然輸出一個與類別數一致的特征向量。課程在講經典網絡的時候提到這種設計我當時沒太在意后來自己動手做小模型時試了一次才發(fā)現這種結尾方式既省參數又不容易過擬合。3.3 全連接層把二維特征圖變成分類分數卷積層和池化層把圖像逐步抽象成了“長寬更小、通道更多”的特征圖但分類任務最終要輸出一個向量比如10個類別的得分。全連接層做的就是最后這一步把特征圖展平成一維然后通過一個權重矩陣映射到類別維度上。全連接層的問題和開頭講的一樣參數量很大。假設最后一個卷積層輸出是7×7×512展平后長度是25088如果再接一個1024節(jié)點的全連接層權重就是25088×1024約2570萬。這也是為什么很多現代結構要么不用全連接層要么只保留最后一層。課程里提過一個技巧全連接層本質上可以被看成一種特殊卷積當輸入特征圖空間尺寸為1×1時全連接就是1×1卷積。理解這個對應關系對改造網絡結構很有幫助比如你想把某個分類網絡改成全卷積的版本只需要把全連接層替換成對應的1×1卷積。到這里一個經典網絡的完整骨架就清楚了輸入圖像經過多輪“卷積-激活-池化”把空間尺寸逐步壓低、通道逐步加深最后展平接全連接層再接一個Softmax把得分變成概率。整個流程可以概括成特征提取在前分類決策在后。4. 激活函數與經典網絡骨架從CONV-RELU-POOL到現代變體4.1 為什么Sigmoid在深度網絡里基本被放棄了2017年課程講激活函數時仍然是從Sigmoid講起的因為它歷史上重要但隨后很快就指出它的致命缺陷飽和。Sigmoid把輸入壓到0到1之間當輸入絕對值較大時曲線變得非常平緩梯度接近0。多個層連起來的時候這些接近0的梯度一乘就會讓靠近輸入層的參數幾乎得不到更新信號這就是梯度消失。Tanh比Sigmoid好一點輸出是零中心的但同樣存在飽和區(qū)的問題。ReLU就樸素多了函數是max(0,x)正半軸梯度恒為1負半軸直接置0。優(yōu)勢很明顯計算只要一次比較收斂速度比Sigmoid快很多而且正的梯度能順利往后傳。課程里給出的建議非常直接默認用ReLU如果遇到神經元大量死亡嘗試Leaky ReLU或帶參數的變體。ReLU有個著名的問題叫“死亡ReLU”如果某個神經元在訓練中被負區(qū)間壓住它的梯度永遠是0權重再也無法更新這個神經元就永久退出了。通常原因有兩點一是初始化不合適二是學習率設置太大。課程里也提到合理的初始化和學習率能大幅減少這種現象所以別一見到死亡神經元就急著換激活函數先把訓練配置調對。4.2 經典層布局CONV-RELU-[(POOL)]的節(jié)奏普通卷積網絡的布局有一個非常清晰的節(jié)奏卷積層負責特征提取激活函數引入非線性池化層做空間降維然后重復。典型的一個階段是“卷積-ReLU-池化”或者更細致一點“若干個卷積-ReLU-池化”。為什么卷積和池化的順序固定成這個樣子因為激活函數必須緊跟卷積否則多個卷積層疊起來還是線性變換堆再多層也等效于一個卷積。池化則可以稍微延后讓卷積層先在同一分辨率下充分提取特征再用池化降低冗余。課程講到最后的經典結構演進時其實講的都是這個節(jié)奏的變化早期5層網絡是“卷積-池化-全連接”的簡單拼接8層的AlexNet把卷積層增加到5層同時用ReLU和Dropout讓網絡第一次在大規(guī)模圖像數據上取得明顯優(yōu)勢16層風格的VGG網絡證明了“小卷積核深網絡”的組合它完全堆疊3×3卷積結構極度規(guī)整22層的GoogLeNet則嘗試在同一個層上并行使用不同尺寸的卷積核讓網絡自己選擇合適尺度的特征而50層以上能訓練起來的關鍵是殘差連接讓梯度能繞過一個層直接傳到前面。我自己復習到這一段時最大的感受是每一代結構都不是憑空想出來的都是在解決上一代網絡訓練或表達的某個具體瓶頸。經典結構按課程演進順序核心改動參數量量級早期5層網絡卷積池化全連接的基本拼接百萬級8層結構加深網絡、引入ReLU與隨機丟棄約6000萬16層純卷積結構大量堆疊3×3小卷積核結構規(guī)整約1.38億22層多分支結構同層多種尺度卷積并行組合特征約700萬50層殘差結構增加跨層直連解決深度網絡的梯度問題約2560萬4.3 為什么2017年講的內容到今天仍然適用雖然這是2017年的課很多具體結構和數據都已經不是最前沿但第五講的核心思想一點沒過時卷積層的局部連接和參數共享、池化層的空間降采樣、激活函數的非線性、殘差結構對梯度傳播的改善這些仍是今天絕大多數視覺模型的基石。后面的注意力機制、大規(guī)模預訓練、多模態(tài)模型都是在這些基礎構件之上生長出來的。所以我現在看這篇筆記更多的不是看技術有多新而是看它如何把若干基本的視覺先驗清晰地注入到架構里這個底層思路是穩(wěn)定的。5. 訓練卷積神經網絡的現實問題初始化、正則化、數據增強5.1 輸入預處理和權重初始化兩臺發(fā)動機的預熱課程在訓練細節(jié)部分很早就強調數據預處理。圖像數據最基礎的處理是零中心化對每個像素位置或每個通道減去均值讓輸入分布在0附近。這樣做的原因是Sigmoid和Tanh這類激活函數在0附近梯度大、不飽和雖然ReLU對中心化不那么敏感但中心化仍然能讓優(yōu)化過程更穩(wěn)定尤其是配合Batch Normalization時。權重初始化也是訓練成敗的隱形關卡。全零初始化不可行因為所有神經元會同步更新永遠無法分化出不同特征。用小隨機數初始化是可接受的基礎做法但更關鍵是要配合激活函數選擇方差尺度ReLU家族網絡更推薦He初始化它根據輸入維度調整權重方差保證前向傳播時信號的方差基本穩(wěn)定不會逐層放大或衰減。實測里初始化錯誤不會讓網絡立即報錯而是表現為loss在某個值附近震蕩或者訓練很慢不下降這種問題排查起來比顯式報錯痛苦得多所以寧可在一開始就按規(guī)則初始化。5.2 Batch Normalization和Dropout兩個作用完全不同的工具Batch Normalization簡稱BN在2017年已經非常常用。它的核心操作是讓每一層卷積后的特征在通道維度上做一次標準化減均值再除以標準差然后用可學習的縮放和平移參數恢復表達能力。效果是網絡對初始化敏感度降低收斂速度明顯提升。放到卷積層時BN的作用位置是在卷積/全連接之后、激活函數之前這一點我在復現時經常記反結果效果打了折扣。Dropout則是另一個維度的事它屬于正則化目標是抑制過擬合。做法非常簡單粗暴訓練時按概率隨機把一部分神經元輸出置0讓網絡不能過度依賴某幾個節(jié)點迫使信息分散到更多連接中。這里最容易翻車的是忘記在驗證和測試階段關閉Dropout或者把訓練用的縮放邏輯搞錯。我見過不少人的驗證效果比訓練差一大截排查半天發(fā)現是驗證代碼里忘了關Dropout屬于新手區(qū)高頻失誤。5.3 數據增強不要錢的訓練集擴張數據增強在圖像任務里性價比極高。最簡單的幾招隨機水平翻轉、隨機裁剪、小幅顏色抖動、隨機旋轉和縮放。這些變換不改變圖片的語義內容但讓模型看到更多變體從而學到更魯棒的特征。課程里明確把它當作一種正則化手段和Dropout、權值衰減并列。權值衰減L2正則化也值得提一句它給大權重加懲罰迫使模型使用分布更平緩的參數。通常系數設在10的負四次方到負五次方這個范圍。配上前面的增強和Dropout三者合起來能讓驗證準確率提升幾個點是非常劃算的組合拳。5.4 調學習率的過程比調結構更影響最終結果課程里反復強調學習率可能是最需要重點調的超參數。太大容易震蕩甚至發(fā)散太小則收斂極其緩慢。常見做法是階梯式衰減比如每訓練若干輪學習率乘0.1或者在驗證loss不再下降時手動降低學習率。我在復現過程中體會最深的一件事是與其盯著網絡結構改來改去不如先跑一個少量數據的小實驗把學習率、批大小這些訓練參數調到合理范圍再投入全部數據。這個順序節(jié)省下來的時間和算力非??捎^。還有一條被說了無數遍但依然有效的經驗在做大實驗之前先用極少量數據比如一個batch或幾十張圖把網絡跑到過擬合。如果連過擬合都做不到說明代碼里有bug如果能過擬合到接近100%才說明正向流程是通的。這個“先過擬合驗證代碼”的思路是課程和后來各種工程實踐都一致認可的起步動作。6. 復習完這一講之后我想留下的幾條實操經驗先說最容易卡住新手的“尺寸問題”。每次搭網絡前我建議把每一層的輸入輸出尺寸、通道數、參數量都手寫在一個草稿表里。不要只依賴框架自動計算因為你真正調試的時候發(fā)現尺寸不匹配會很難受。我自己吃過一次虧兩個分支結構里的卷積padding參數寫錯導致特征圖尺寸對不上拼接時直接報錯排查了一個多小時才找到問題。這類問題提前算一遍基本可以規(guī)避。然后是關于“復現經典結構”的路徑推薦。如果你正在入門不建議一上來就啃幾十層的復雜結構。先做一個小型卷積網絡跑到CIFAR級別的小數據集上看清卷積、池化、全連接之間怎么配合然后照著8層風格的網絡復現一遍體會什么叫“深度增加帶來的表達力提升”最后再去碰殘差結構這時你會真正理解為什么跨層連接能穩(wěn)住在深層網絡的訓練。每走一步都把前面講的尺寸計算、初始化和正則化流程走一遍這些基本功才會變成肌肉記憶。我最后一次完整復習這講內容時也順帶整理了一個適合自己的檢查清單數據有沒有做零中心化BN的位置是不是在激活之前Dropout在驗證時有沒有關閉卷積輸出尺寸有沒有算錯初始化是不是按激活函數選了對應的方案。如果這五項都對訓練一般不會出現“完全學不動”的詭異情況。遇到loss不降時我第一反應不是改網絡結構而是回頭核對這個清單。最后分享一個觀念上的轉變。最開始看這類筆記總會忍不住去記每個經典網絡的層數、參數量、準確率但這些數字過幾個月就會模糊。真正留在腦子里的是卷積為什么比全連接適合圖像、感受野是怎么被小卷積核擴大的、網絡加深為什么需要殘差連接兜底。理解了這些動機之后再去看任何新模型都不會覺得是一堆陌生名詞的堆砌。這大概是第五講對我來說最大的價值。