網(wǎng)絡CNN:從原理到工程部署的實戰(zhàn)總結)
1. 從一條卷積計算開始CNN到底在算什么先說個我自己的體驗。當年第一次接觸卷積神經(jīng)網(wǎng)絡時找了一堆資料來看結果越看越糊涂。網(wǎng)上教程大多從“圖像分類任務”講起上來就是“把圖片輸入網(wǎng)絡輸出類別概率”中間那一大段黑箱過程基本靠想象。后來我把一張圖的完整處理流程在紙上一步步畫出來才真正明白CNN不是玄學它就是在做一件很樸素的事用可學習的濾波器去像素矩陣里反復提取有結構的特征。那段時間我還在某項目里做工業(yè)質檢拍出來的零件圖有劃痕、有臟污傳統(tǒng)圖像處理方法寫了幾百行規(guī)則換個光照就崩。后來換成CNN檢測效果一下子穩(wěn)定了很多。但真正讓我頭疼的不是網(wǎng)絡本身的準確率而是怎么理解它、怎么調它、怎么讓它跑得快。這篇文章就是把我這幾年從理論到實際部署過程中關于CNN的全部心得整理出來不寫教科書式的內容只講我驗證過、踩過坑的東西。先從最核心的計算開始。你有一張灰度圖就是一個二維矩陣比如 256×256。卷積操作就是拿一個小矩陣——通常叫卷積核或濾波器——在這個大矩陣上按步長滑動每停一個位置就把對應位置的元素逐項相乘再求和得到一個數(shù)值。所有位置滑完就生成一張新的、尺寸更小的矩陣叫特征圖。舉個例子假設輸入是一個 5×5 的矩陣卷積核是 3×3步長stride為 1沒有填充padding那么輸出尺寸是 3×3。計算公式很簡單[ \text{輸出邊長} \frac{\text{輸入邊長} - \text{核大小}}{\text{步長}} 1 (5 - 3) / 1 1 3 ]如果加padding把輸入矩陣周圍補一圈0那么5×5的輸入在padding1后變成7×7輸出就能保持5×5。這一點在搭建深層網(wǎng)絡時至關重要——一旦層數(shù)多了不加padding特征圖尺寸會迅速縮水網(wǎng)絡沒法建深。而一張彩色圖通常是三通道RGB也就是三層矩陣疊在一起。這時卷積核就不再是二維的而是和輸入通道數(shù)一致的三維核比如 3×3×3。每一層和對應的輸入通道做卷積三個通道的結果相加再加上偏置得到輸出特征圖的一個通道。到這里你需要記住第一個要點卷積層的參數(shù)數(shù)量只和卷積核尺寸、輸入通道數(shù)、輸出通道數(shù)有關和輸入圖片的寬高完全無關。比如輸入是任意分辨率的RGB圖一個 3×3 卷積核從3通道變到64通道參數(shù)數(shù)就是 3×3×3×64 64 1792。不管你喂進去的是 224×224 還是 512×512參數(shù)都一樣。這一點直接決定了卷積網(wǎng)絡可以處理任意尺寸輸入也為后面講部署優(yōu)化埋下伏筆。1.1 卷積核它到底在提取什么很多人問卷積核里面的數(shù)值是怎么來的是人為設計的嗎早期確實有人工設計特征提取器比如邊緣檢測里的Sobel算子[ \begin{bmatrix} -1 0 1 \ -2 0 2 \ -1 0 1 \end{bmatrix} ]這個矩陣是固定的圖片經(jīng)過它之后橫向邊緣信息就凸顯出來了。但CNN里的卷積核初始值是隨機初始化然后通過反向傳播在訓練過程中不斷調整最終網(wǎng)絡自己學會了類似邊緣、紋理、形狀等特征。換句話說我們連“讓網(wǎng)絡提取什么特征”都不用指定只要告訴它最終任務是什么它自己組織出一整套由低層到高層的特征提取流水線。低層卷積核學到的是邊緣、顏色塊這類基礎紋理中層學到的是更有結構的局部模式比如某個弧線組合、某個紋理單元高層則對應整個物體部件甚至整體語義。這個特點我在實際項目里驗證過很多次當訓練樣本不足時網(wǎng)絡往往會停在低層特征上輸出的特征圖看不出什么語義信息訓練充分后高層特征圖對目標位置的響應會非常明確。1.2 步長與填充特征的“采樣粒度”步長設置也很關鍵。很多初學者習慣把步長固定為1認為卷積就該逐像素滑動。但實際工程中步長為2的卷積經(jīng)常被用來替代池化做下采樣好處是可以在降維的同時學習采樣方式而不是用固定規(guī)則硬性丟掉信息。每個卷積層輸入尺寸、輸出尺寸、核大小、填充、步長共五個參數(shù)配置它們要滿足兩個約束一是輸出尺寸不能變成非整數(shù)即上式必須整除二是感受野覆蓋范圍要合理別讓邊緣信息在第一層就丟太多。填充的選擇也要分情況。如果你做的是圖像分類通常paddingsame保持空間尺寸不變讓深層的特征圖不至于縮得太快如果你做的是像素級任務比如語義分割更要注意padding對邊界的影響因為填充0會在邊緣引入無效信息可能讓模型對邊緣區(qū)域的預測噪聲更大。我曾經(jīng)在分割模型里反復調整padding策略最終在訓練時給輸入圖做小幅隨機裁剪讓邊緣目標不那么頻繁地出現(xiàn)在邊緣位置效果比單純換padding好很多。2. 激活函數(shù)與池化讓網(wǎng)絡具備非線性與尺度魯棒性卷積本身是線性操作——它不過是一組乘加運算。如果沒有非線性變換你再堆多少層卷積整個網(wǎng)絡等效于一個線性變換表達能力非常有限。所以每個卷積層后面幾乎都會接一個激活函數(shù)逐元素地對特征圖做非線性映射。早期網(wǎng)絡多用sigmoid但它在深層網(wǎng)絡中會帶來一個很致命的問題梯度消失。我們公司曾經(jīng)做過一個實驗把一個小型網(wǎng)絡用sigmoid激活函數(shù)反復加深到二十幾層結果是訓練loss幾乎不下降。后來換成ReLU同樣結構、同樣超參數(shù)幾十個epoch后收斂明顯。ReLU的定義太簡單了[ f(x) \max(0, x) ]正區(qū)間導數(shù)為1負區(qū)間直接置0。這讓梯度在正向傳播路徑上能順暢回傳緩解了梯度消失問題。但它的問題在于“死神經(jīng)元”——大量負值輸入被置0后對應神經(jīng)元可能再也得不到有效梯度更新永遠輸出0。我在實際訓練中觀察過統(tǒng)計模型剛初始化時ReLU死神經(jīng)元比例通常在10%以下隨著訓練推進某些通道的響應逐漸變成全0占比可以達到20%甚至更高使用Leaky ReLU后這個比例顯著下降但訓練速度略有下降。所以現(xiàn)在更推薦在中等規(guī)模網(wǎng)絡里優(yōu)先試Leaky ReLU或GELU而不是一把梭用ReLU。2.1 池化解決“位置微變但類別不變”的問題池化層做的事情概括起來就是兩個字降采樣。最常用的是最大池化——取一個小窗口內的最大值作為輸出。這么做有兩個直觀的動機一是降低特征圖分辨率減少后續(xù)計算量二是引入一定平移不變性圖像里的目標稍微移動幾個像素池化結果仍然穩(wěn)定。但池化有個容易被人忽略的問題它會讓位置信息變得粗糙。如果你做的任務是圖像分類目標位置偏移幾個像素本來就不影響語義粗一點沒關系但如果是目標檢測或關鍵點定位隨隨便便下采樣四倍定位精度就會大打折扣。所以現(xiàn)代檢測模型里主干網(wǎng)絡的下采樣比例往往會被刻意限制比如輸出特征圖的步長一般控制在32或16個別對小目標敏感的任務甚至會降到8。池化的具體操作不復雜但選擇哪種池化是有講究的池化類型計算方式適用場景最大池化取窗口最大值分類任務、紋理顯著目標保留強響應平均池化取窗口均值全局信息整合避免極端值干擾全局平均池化對整個特征圖取均值分類頭前最后一層直接替代全連接層自適應池化由PyTorch按輸出尺寸反推窗口輸入尺寸不固定時統(tǒng)一特征維度在實際項目中我常常把分類網(wǎng)絡末尾的全連接層刪掉換成全局平均池化加一個1×1卷積輸出類別數(shù)。這樣做的理由是全連接層參數(shù)量巨大而且一旦輸入尺寸變了維度就對不上全局平均池化則對輸入尺寸完全免疫并且天然匯總了每張?zhí)卣鲌D的全局語義。這個調整對控制過擬合也有幫助——參數(shù)量小了需要擬合的空間就小了。順便說一嘴現(xiàn)在很多新網(wǎng)絡已經(jīng)不用傳統(tǒng)池化。比如用步長為2的卷積做下采樣或者用空洞卷積在保持分辨率的前提下擴大感受野。但池化這個概念并沒有過時理解池化的本質有助于你理解為什么特征圖尺寸會一層層縮小以及怎么控制網(wǎng)絡的空間信息流。3. 特征圖的形狀變化書寫維度勝過看十遍論文我見過很多新手從GitHub上拉了一個模型下來代碼運行通了但一旦要改輸入尺寸或者改網(wǎng)絡深度就卡住了。問題的根子在于沒有建立對特征圖形狀變化的直覺。下面我用一個具體例子完整走一遍。假設輸入圖像是 224×224×3網(wǎng)絡結構如下卷積層164個3×3卷積核padding1步長1。輸入224×224×3輸出224×224×64。參數(shù)量3×3×3×64641792。池化層1最大池化窗口2×2步長2。輸出112×112×64。卷積層2128個3×3卷積核padding1步長1。輸入112×112×64輸出112×112×128。參數(shù)量3×3×64×12812873856。池化層2窗口2×2步長2。輸出56×56×128。卷積層3256個3×3卷積核padding1步長1。輸出56×56×256。參數(shù)量3×3×128×256256295168。池化層3輸出28×28×256。全局平均池化輸出1×1×256。全連接層或者1×1卷積輸出類別數(shù)。這個流程中可以清晰看到空間尺寸從224→112→56→28通道數(shù)從3→64→128→256??臻g尺寸下降意味著網(wǎng)絡在更大范圍內匯總信息通道數(shù)上升意味著在更多維度上描述特征。這兩者總是交織在一起這也是CNN比較優(yōu)雅的地方。從計算量角度看主流框架PyTorch、TensorFlow的FLOPs計算工具都能幫你自動統(tǒng)計但你自己要能快速估算每層的計算量。單個卷積層的計算量大概是[ \text{FLOPs} \approx \text{輸出元素數(shù)} \times \text{核元素數(shù)} \times \text{輸入通道數(shù)} ]以剛才第一層為例輸出是224×224×64核元素數(shù)是9輸入通道是3所以 FLOPs ≈ 224×224×64×9×3 8.67 億次乘加接近9億??雌饋砗艽蟮秃竺鎸颖绕饋磉@只是毛毛雨。在實際部署時模型推理時間往往不是按參數(shù)數(shù)量算的而是按計算量FLOPs和訪存量算的。這兩個指標在CPU和GPU上的權重完全不同后面我會專門展開講。3.1 感受野每個點看著多大范圍的輸入感受野Receptive Field是CNN里最容易被忽視的概念它決定了最后一層特征圖上的一個點在原圖上對應多大范圍。簡單記公式初始感受野為1每經(jīng)過一個卷積核尺寸為k、步長為s的層感受野按如下方式增大[ RF_{\text{out}} RF_{\text{in}} (k - 1) \times \text{stride累積} ]我舉個例子你就能秒懂。一個兩層3×3卷積堆疊中間沒有池化每層步長1。第一層輸出上每個點看到的是原圖3×3區(qū)域第二層輸出的每個點看到的是第一層特征圖上3×3區(qū)域而第一層上3×3區(qū)域對應原圖5×5范圍。所以兩層3×3卷積的感受野是5×5。三層3×3卷積的感受野是7×7。這一點在實際工程里的意義在于感受野如果太小模型看不到足夠的上下文識別大目標時容易漏檢太大又可能引入過多無關區(qū)域導致局部細節(jié)被淹沒。比如檢測小目標高分辨率特征圖上的感受野本身就不大反而沒太大問題而檢測大目標通常依賴低分辨率、大感受野的高層特征。所以有些任務會刻意用空洞卷積來快速擴大感受野??斩绰蕿?的3×3卷積實際覆蓋范圍相當于5×5但參數(shù)仍然是9個。我在一個語義分割項目里就靠空洞卷積把感受野擴大了一倍多又不增加什么參數(shù)量內存占用和速度幾乎沒有損失。4. 經(jīng)典網(wǎng)絡架構的演進邏輯每個改動背后都有原因CNN發(fā)展到現(xiàn)在主干結構已經(jīng)相當成熟。但不了解來龍去脈你去讀那些新模型的代碼會非常吃力。我梳理了一下我認為最關鍵的幾次架構演進它們每一個都回答了某個具體痛點。AlexNet2012用ReLU解決了深層網(wǎng)絡訓練慢的問題用Dropout緩解了過擬合用重疊池化小幅提升精度。這個網(wǎng)絡第一次讓大家認識到GPU并行訓練深度網(wǎng)絡的威力。VGG2014核心思想就是“小核堆疊”。用三個3×3卷積堆疊替代一個7×7卷積感受野相同但參數(shù)量從49C2變成27C2還引入了更多非線性。VGG結構極其規(guī)整是很多工程項目的默認backbone選擇之一。GoogLeNet2014提出了Inception結構每個block里并行多個尺寸的卷積核最后拼接通道。好處是在同一層內捕捉多尺度特征但結構復雜工程落地不太友好。ResNet2015核心是殘差連接把輸入直接加到輸出上。為什么有效因為網(wǎng)絡只需要學習殘差部分梯度可以從后層直接跳回前層訓練幾十上百層不再是問題。我試過把ResNet的殘差連接去掉在CIFAR-10上同樣的深度準確率掉了接近10個百分點。這不是玄學是優(yōu)化難度實實在在降低了。DenseNet2016每一層都跟前面所有層連接特征復用能力極強參數(shù)效率也很高。但其顯存占用通常較大因為要保存大量中間特征。EfficientNet2019用復合縮放方法同時調整深度、寬度、分辨率。這個思路很實用——你不需要手動試很多組合按固定比例一起放大經(jīng)常能得到明顯更優(yōu)的效果。不同任務對主干架構的需求差異很大。我做工業(yè)質檢的時候小樣本場景居多輕量網(wǎng)絡反而更穩(wěn)在另一個需要高精度的識別項目里用了預訓練的ResNet系列做遷移學習效果比從零訓練好非常多。4.1 從零訓練還是微調這是一個工程決策很多初學者以為模型總要從頭訓練但實際上在絕大多數(shù)實際項目里遷移學習才是正確起點。預訓練模型在ImageNet等大規(guī)模數(shù)據(jù)集上訓練過的已經(jīng)在低中層的特征提取上非常成熟了你只需要在其基礎上針對自己的數(shù)據(jù)微調高層。我記得有一個直觀對比同樣只有5000張工業(yè)圖片的數(shù)據(jù)集從零訓練的ResNet50驗證集準確率大約在87%而使用ImageNet預訓練權重微調的版本能達到95%以上。這個差距在數(shù)據(jù)量越少時越明顯。不過要注意一個坑預訓練權重本身是用自然圖像訓出來的如果你的數(shù)據(jù)和自然圖像差異極大——比如是X光片、光譜圖、顯微圖像——那預訓練對低層特征的增益就沒那么明顯了這時候可以考慮凍結前面幾層甚至全部從頭訓練。微調時學習率要設得比從頭訓練小很多通常建議設為主訓練學習率的10%甚至更小。否則第一輪迭代就可能劇烈破壞原有權重。我自己常用的策略是先用1e-4的學習率微調全部層訓練幾個epoch后再解凍所有層用小學習率繼續(xù)訓練。數(shù)據(jù)增強在微調階段也不能省哪怕你的任務是工業(yè)圖我也會做隨機翻轉、平移、亮度擾動把有限數(shù)據(jù)的利用率提上去。5. 訓練CNN你真正需要盯住的幾個信號很多人以為訓練CNN就是把數(shù)據(jù)丟進去、等loss下降就完事。實際上訓練過程中的很多信號非常重要我在多個項目里靠這些信號救回了必失敗的訓練。第一個信號是初始loss。分類任務如果是C類初始loss應該在ln(C)附近。如果你的初始loss偏差很大大概率是權重初始化不對、輸入沒有歸一化或者標簽有錯。我在一個多標簽分類任務里初始loss一直比理論值高很多排查了一天發(fā)現(xiàn)是損失函數(shù)里標簽平滑參數(shù)寫錯了把不該平滑的維度也消掉了。養(yǎng)成在跑訓練前先算一遍期望初始loss的習慣能幫你省下大量排查時間。第二個信號是梯度范數(shù)。每個epoch記錄梯度的L2范數(shù)如果訓練初期梯度范數(shù)突然爆炸到1e3以上說明要么是學習率太大要么是數(shù)據(jù)里有異常值。反之如果梯度范數(shù)一直在1e-7量級幾乎不動那大概率梯度已經(jīng)消失需要檢查激活函數(shù)和網(wǎng)絡深度。第三個信號是訓練集和驗證集的gap。gap小說明模型沒在過擬合還有提升空間gap大說明泛化能力不夠優(yōu)先考慮增加數(shù)據(jù)增強、正則化或者降低模型容量。但很多人忽略的是驗證集loss有時會因為數(shù)據(jù)增強強度太大而長期不降這時候不代表模型沒學好只是驗證集和你喂給模型的數(shù)據(jù)分布不一致了。這種時候最好單獨跑一組無增強的驗證數(shù)據(jù)做對照我靠這招排掉過好幾個假象。5.1 數(shù)據(jù)增強是治過擬合的特效藥但不是萬能藥數(shù)據(jù)增強在CNN訓練里的地位非常高尤其當數(shù)據(jù)量不夠時。常用的增強手段有隨機水平翻轉、小角度旋轉、隨機裁剪顏色擾動亮度、對比度、飽和度Cutout / Random Erasing隨機遮掉一個小區(qū)域Mixup / CutMix按比例混合兩張圖及標簽這些方法各有適用場景。水平翻轉對多數(shù)目標識別任務都安全但對文字識別、車牌識別這類有方向性的任務反而不安全。Cutout類增強能提升模型對遮擋的魯棒性但對小目標檢測隨機遮掉一個區(qū)域可能恰好把目標蓋住了造成標簽與內容不匹配訓出來的模型反而變差。我在一個產(chǎn)品表面缺陷檢測項目里的經(jīng)驗是增強強度需要根據(jù)數(shù)據(jù)量和任務復雜度調到一個合適的甜點區(qū)。增強太弱模型記憶數(shù)據(jù)增強太強模型學不到真實的判別細節(jié)。這里沒有萬能公式只能靠實驗。你可以固定一個基準增強配置然后每次只改一個維度看驗證集變化。改動一次、驗證一次這聽起來慢但比同時改五個變量最后不知道是哪個起作用要快得多。5.2 Batch Size、學習率與訓練策略Batch Size 的選擇對訓練結果影響很大。太小的 batch比如2、4梯度噪聲大訓練不穩(wěn)定太大的 batch比如512、1024雖然梯度穩(wěn)定但容易收斂到尖銳極小值泛化能力可能變差。學術界在“大batch與小batch”上的討論很多我的工程經(jīng)驗是中等batch32到128通常普適性最好尤其在顯存不夠時先用小batch確認代碼邏輯能跑通再逐步往上加。學習率策略上比較推薦的做法是先用學習率熱身warmup從很小的值線性上升到目標值避免初期劇烈震蕩訓練中段用余弦退火或階梯下降把學習率逐步減小最后1-2個epoch可以降到極小值讓loss在底部多磨一會兒。我自己常用的是OneCycle策略學習率先升后降配合適當?shù)淖畲髮W習率常常能在更短的epoch里達到更好的精度。但有前提——你對數(shù)據(jù)量、batch和網(wǎng)絡結構已經(jīng)有比較充分的把握否則直接硬套OneCycle反而容易跑飛。6. 從訓練到部署讓CNN真正跑起來的工程考量模型在GPU上驗證準確率不錯不等于就能順利上線。真正落地部署時你會遇到另一套問題推理延遲、顯存占用、精度損失、平臺兼容性。先明確一個基本事實訓練時看重吞吐量throughput推理時看重延遲latency和資源占用memory/FLOPs。訓練時可以隨便用大的batch、混合精度、多卡并行但推理時通常要求在單樣本或小batch下盡可能快。更關鍵的是硬件差異。GPU擅長并行稠密矩陣運算CPU對這類運算的支持遠不如GPU但如果網(wǎng)絡里有大量小卷積、拼接、切分操作反而可能受限于訪存和調度在CPU上的表現(xiàn)差異很大。所以在實際項目里必須先搞清楚最終跑在什么設備上再決定優(yōu)化方向——如果跑在服務器GPU上浮點精度和顯存占用優(yōu)先如果跑在邊緣設備或手機上優(yōu)先考慮量化、剪枝、算子融合。6.1 通道剪枝、量化與知識蒸餾落地時最常用的三個手段是剪枝、量化、蒸餾但它們的收益和代價大相徑庭。通道剪枝是去掉網(wǎng)絡中對最終輸出貢獻不大的通道。常見做法是觀察每個通道的權重范數(shù)或者激活統(tǒng)計把范數(shù)小或激活接近零的通道刪掉再微調恢復精度。剪枝比例很關鍵剪太狠精度崩得快剪太少收益又低。我在一個輕量分類網(wǎng)絡里把通道數(shù)從128剪到96精度只掉0.3%FLOPs卻少了近30%這比例相當劃算。量化是把你訓練的32位浮點模型壓縮成8位整數(shù)甚至更低精度。一般有三種方式訓練后動態(tài)量化、訓練后靜態(tài)量化、量化感知訓練。動態(tài)量化實現(xiàn)簡單但只對部分算子有效靜態(tài)量化需要校準數(shù)據(jù)效果更好但操作更繁瑣。量化感知訓練是在訓練過程中模擬量化噪聲讓模型提前適應低精度通常精度損失最小。對于CPU部署8位量化是性價比最高的優(yōu)化沒有之一。知識蒸餾是訓練一個輕量學生模型去學大模型的行為。做法通常是讓學生模型同時去擬合真實標簽和教師模型的軟輸出。軟輸出里包含了類別間相似度信息比如“貓”和“狗”的概率分布其實蘊含了大量結構知識這比單純的one-hot標簽信息量豐富得多。我的一個落地項目里教師模型是ResNet50學生模型是只有原來1/4計算量的輕量網(wǎng)絡蒸餾之后學生模型準確率比從零訓練高了約4個百分點。實踐中這三招經(jīng)常配合使用先剪枝、再量化、必要時蒸餾輔助每一步都要在驗證集上確認精度變化不能盲目相信某個預設壓縮率。6.2 框架選擇與推理引擎在訓練側PyTorch和TensorFlow是目前的主流選擇。如果項目以研究驗證為主PyTorch生態(tài)更靈活在過去的幾年內新論文模型基本都優(yōu)先提供PyTorch版本如果團隊已有成熟的TensorFlow生產(chǎn)鏈路沿用TensorFlow也完全合理畢竟工程穩(wěn)定性和圖優(yōu)化集成更成熟。模型訓練完成后的推理環(huán)節(jié)則可以考慮專門推理引擎比如TensorRTNVIDIA GPU、OpenVINOIntel CPU或ONNX Runtime跨平臺通用它們會把計算圖做算子融合、層間緩沖復用等優(yōu)化加速效果通常非常顯著。一個容易被忽略的點是推理引擎支持的算子集合和訓練框架不完全一致。有些新算子或者自定義算子在ONNX轉換時可能直接失敗或者轉換后跑得非常慢。所以從一開始訓練時就要考慮部署端兼容性。如果目標平臺不支持某個算子網(wǎng)絡設計階段就換掉別等到訓練完再折騰。我在一個項目里就因為自定義池化算子無法導出到ONNX最后繞了一個大彎改網(wǎng)絡結構損失了三天時間。另外輸入尺寸在部署時最好固定下來。動態(tài)輸入尺寸雖然靈活但很多推理引擎對動態(tài)shape支持不好會退化成低效的通用路徑。預先設定好幾個常用分辨率讓推理引擎把圖的shape和內存布局都靜態(tài)化性能和穩(wěn)定性都會好很多。7. CNN的邊界與變體什么時候它是最好的選擇CNN很強但它不是所有問題的唯一答案。在圖像類任務里CNN至今依然是默認選擇但在處理序列、點云、圖結構數(shù)據(jù)時像Transformer、GNN這些架構可能更合適。甚至在某些圖像任務上Vision TransformerViT已經(jīng)在大型數(shù)據(jù)集上超越CNN但ViT往往需要海量數(shù)據(jù)和更強的正則化才能訓練好在小樣本場景下未必比得上有先驗偏置的CNN。所謂“先驗偏置”就是CNN天然具備的特性局部連接、權值共享、平移等變。你不需要告訴它“相鄰像素通常相關”網(wǎng)絡結構本身就把這個歸納偏置內置進去了。在數(shù)據(jù)量中等或偏少的場景中這個偏置是巨大優(yōu)勢——模型不需要重新摸索像素間的空間關系。但這幾年有一種趨勢是用卷積注意力的混合結構來取長補短。比如在淺層用卷積抓局部紋理在深層用注意力機制捕捉全局依賴。我在一個遙感影像分類項目里試過這種混合結構全局檢測效果比純卷積高不少尤其在需要跨區(qū)域上下文時優(yōu)勢明顯。如果你手頭任務的特征同時有強局部性和強全局性比如大尺寸醫(yī)學影像或遙感圖值得優(yōu)先考慮混合架構。不過需要提醒的是不要盲目追求最新架構。對一個特定業(yè)務問題穩(wěn)定性、可復現(xiàn)性、部署易用性往往比刷那零點幾個百分點更重要。我在和不少同行交流時發(fā)現(xiàn)最穩(wěn)妥的路徑往往是先用一個成熟且更簡單的CNN基線跑通全鏈路再通過消融實驗逐步替換模塊每一步都驗證收益和成本。8. 我踩過的最有價值的幾個坑這部分挑三個印象最深的坑來寫可能比前面所有章節(jié)都更有實際價值。第一個坑是把輸入歸一化忘在訓練前處理里但推理時沒有對應處理。一個分類模型的輸入歸一化方式在訓練和推理時必須完全一致。有一陣我部署一個模型時訓練代碼里對圖像先做減均值除以方差但導出的模型沒有把這些歸一化操作包進去推理端直接把原始像素喂進去結果準確率降了十多個百分點。后來我在導出時把歸一化作為一個預處理節(jié)點嵌入計算圖或者確保外部預處理和訓練時完全相同問題才徹底解決。第二個坑是用數(shù)據(jù)增強后的分布去評估模型。最初做一項檢測任務時我沿用分類任務的模式對驗證集也加了較強的增強結果驗證集指標一直在95%左右沒什么變化我就以為是訓練飽和了。后來發(fā)現(xiàn)增強的隨機性掩蓋了真實困難樣本的表現(xiàn)。改成固定翻轉、固定裁剪的評估方式后模型的薄弱點立刻暴露出來針對性補了數(shù)據(jù)之后最終泛化性能大幅提升。第三個坑是推理引擎默認選擇難以預料很慢的路徑。某個算子在某些推理引擎上需要跨步內存拷貝性能一下子掉好幾倍。有一次我測量一個全卷積檢測網(wǎng)絡的推理延遲TensorRT下只有8毫秒改用CPU端的ONNX Runtime卻跑了近200毫秒。后來定位到是某個步長為2的轉置卷積算子沒有好的實現(xiàn)路徑。這種情況下最簡單的辦法就是改網(wǎng)絡結構用最近鄰上采樣加普通卷積代替轉置卷積代價極小但延遲下降了非常多。這些坑說出來都特別簡單但每一個都是實際項目中耗費了大量時間才換來的教訓。如果你以后遇到類似“怎么數(shù)據(jù)越訓越差”“怎么部署后精度和訓練時對不上”的問題可以先按上面的清單排查一遍。對我個人來說CNN早已不是模型結構那么簡單。它是理解深度學習中“表示學習”的最佳教材——從像素到特征、從特征到語義、從語義到?jīng)Q策每一層的變換都有清晰的幾何與統(tǒng)計含義。無論未來Transformer類架構怎么流行這份對特征提取與空間建模的理解放到任何架構上都不會過時。如果你正在學習和使用CNN我建議你不要只跑通現(xiàn)成代碼而是親手把每層的輸入輸出尺寸算一遍把中間特征圖可視化出來把loss曲線的每一個異常都追究到底。這個過程熬過去了你對深度學習的整體認知會上一個臺階。