標準卷積一次做兩件事:在空間上看鄰居(k×k 的小窗),同時把所有輸入通道混在一起。成本是 H·W·k²·Cin·Cout 次乘加。拆法有兩種:group conv 把通道分成 g 組,每組只跟自己組混;depthwise separable(MobileNet)把兩件事分開做,先每個通道各自看鄰居(depthwise),再用 1×1 把通道混起來(pointwise)。拉下面的滑桿,看三種做法各要多少。
Pruning vs 架構設計:講義的第一個結論是,一開始就設計一個有效率的網路,效果比把大網路剪小(pruning)更好;代價是小模型比較難訓練。兩條路可以一起走:講義的總結把 pruning 看成一種架構搜尋,預期能省 3 到 10 倍;量化(32 bit 變 8 bit,省 4 倍)是最先該試的。
從壓縮到加速:講義把執行時間拆成兩塊。
Model 大小 = Cin × Cout × k² × bits。壓縮(pruning、量化)主要縮小 model,decomposition(拆卷積)則同時改變計算量和要搬的資料量。SEC.02 會看到後者常常才是瓶頸。
拆卷積的幾種方向(講義的整理,都可以套在現有的卷積層上):k×k 拆成 k×1 加 1×k(asymmetric);通道分組(g < Cout 是 group conv,g = Cout 是 depthwise);分組後用 1×1 或 shuffle 把組融回來(MobileNet、ShuffleNet);inverted residual(MobileNet v2);用 1×1 先減通道;通道分區,每區用不同 kernel(SqueezeNet)或只讓一部分通道進主路(CSPNet)。
SqueezeNet(fire module):目標是壓縮 AlexNet。三個策略:大部分 3×3 換成 1×1(參數少 9 倍);先用 squeeze 層(1×1)減少進入 3×3 的通道數;降採樣盡量放在網路後面,大的特徵圖準確率較高。fire module = squeeze(1×1)接 expand(1×1 和 3×3 並排)。標題說「參數少 50 倍、小於 0.5MB」,但講義特別提醒:0.5MB 是再加上 Deep Compression 才達到的;而且它的乘加約 861M,和 AlexNet 差不多。參數省了,計算沒省。
CSPNet(channel partition,YOLOv4 用的):把一個 stage 的通道切成兩半,一半走 dense block,另一半直接繞過去,最後才合併。目的:增加梯度路徑的組合、讓每層計算量平均、減少記憶體流量。講義的 ablation:合併放在 transition 之後(late fusion)計算量少 21%、準確率只掉 0.1%;放在之前(early fusion)少 26% 但掉 1.5%;CSPPeleeNet 少 13% 還多 0.2%。
Group conv 與 ResNeXt:參數從 C²k² 變成 (C/g)²·g·k² = C²k²/g。最早出現在 AlexNet,當時是為了切到兩張 GPU 上跑。ResNeXt 把 ResNet bottleneck 中間的 128 通道切成 32 組 × 4 通道,提出 cardinality(組數)這個新維度:模型大小不變,表示能力更強。g 等於 Cout、kernel 等於整張特徵圖時叫 global depthwise conv(MobileFaceNet)。
MobileNet v1(depthwise separable):2D 的空間混合(depthwise)之後接所有通道的混合(1×1 pointwise)。講義的數字:3×3 時約 1/7 參數、1/9 乘加。除了第一層,全部用 separable;不用 pooling 降採樣,用 stride 2。
量化友善的 separable conv:MobileNet 直接量化到 8 bit 時準確率掉很多,講義引用一篇專門改 separable 結構的論文,讓它量化後仍穩定(筆記裡提到的其中一招是加 L2 regularization 壓住權重範圍)。
ShuffleNet v1(channel shuffle):MobileNet 的問題是 1×1 太貴,ShuffleNet 把 1×1 也改成 group conv。副作用是組和組之間不交流,所以在兩層之間做 channel shuffle:把每組的通道再分小份,發給下一層的不同組。講義也提醒:shuffle 打亂了資料在記憶體裡的連續性,對 CPU 的 SIMD 不友善。
Filter decomposition / asymmetric:5×5 有 25 個權重;兩個 3×3 疊起來看到一樣大的範圍,只要 18 個;1×5 加 5×1 只要 10 個。ADSCNet 把 asymmetric 和 depthwise separable 合在一起做即時語意分割。
Depthwise 的變體:MixNet 問「kernel 越大越準嗎」:單一 kernel 大小在 9×9 時準確率最高,再大反而下降,所以在同一層混用 3×3、5×5、7×7。Shift 用「把通道整片平移一格」取代 depthwise,零 FLOPs、零參數,約 10% 的通道需要移;硬體效率高、表達力弱,模型要變大。GhostNet 觀察到特徵圖裡有很多「長得很像的一對」,所以只算一半,另一半用便宜的運算(例如 depthwise 3×3)從前一半變出來;講義引用的實驗在 CIFAR-10 上參數和 FLOPs 都約減半、準確率差不多。ShiftAddNet 把乘法換成位移和加法(距離量測從內積換成 L1)。另外還有依 kernel 大小分組(Big-Little Net)、依輸入解析度分組(Octave conv)、依數值精度分組。
FLOPs(乘加次數)是間接指標,真正要的是延遲(latency)這個直接指標。ShuffleNet v2 量過:FLOPs 差不多的網路,在 GPU 和手機 ARM 上速度可以差很遠。漏掉的是兩件事:記憶體存取成本(MAC),也就是讀寫特徵圖和權重要搬多少資料;以及平行度,硬體能不能一次把事情做完。它整理出四條守則,下面每一條都可以拉。
B = hw·c₁·c₂/g、MAC = hw(c₁+c₂) + c₁c₂/g,每次改形狀都把 c₁、c₂ 重新調到 B 不變。延遲是示意:用「計算時間 + 搬移時間 + 每個 kernel 的啟動成本」估,三種硬體的算力和頻寬是為了說明而設的數字。試試看:在手機加速器上把 G1 拉到 1:16,FLOPs 一樣,延遲變成將近兩倍;換到手機 CPU,同樣的動作只慢一成左右,因為它卡在算力不是頻寬。G3 在 GPU 上最痛:每多一條支路就多一次啟動和同步,算力閒著。G4 的 ReLU 和相加幾乎不加 FLOPs,卻要把整張特徵圖多讀寫幾次。G1 推導:1×1 卷積,B = hw·c₁c₂,MAC = hw(c₁+c₂) + c₁c₂。由算幾平均 c₁+c₂ ≥ 2√(c₁c₂),代入得 MAC ≥ 2√(hwB) + B/hw,等號在 c₁ = c₂。G2:MAC = hw·c₁ + Bg/c₁ + B/hw,其他固定時隨 g 增加。G3:網路的支路(fragment)越多,越不利平行計算,同 FLOPs 下單一卷積最快。G4:element-wise 運算(ReLU、shortcut 相加、加 bias)FLOPs 很低但 MAC 高,不能忽略;depthwise 也被看成這一類:FLOPs 低、MAC 高。
其他被 FLOPs 漏掉的事:cuDNN 對 3×3 特別最佳化,所以不能以為 3×3 一定比 1×1 慢 9 倍。group conv 這種低複雜度層裡,MAC 占的比例很大;在 GPU 這種算力強的裝置上,MAC 更是瓶頸。筆記也提到,訓練過 ResNeXt 的人都有感覺:同樣層數,它大約只有 ResNet 一半快。
拿守則檢查前人:ShuffleNet v1 大量用 group conv(違反 G2)、用 bottleneck 結構(違反 G1);MobileNet v2 的 inverted bottleneck 違反 G1,在很厚的特徵圖上做 depthwise 和 ReLU 違反 G4;NASNet 的碎片結構違反 G3。ShuffleNet v2 的改法:開頭做 channel split(一半通道直接過)、1×1 拿掉 group、相加改成 concat。
MobileNet v2:ResNet 的 bottleneck 是「壓縮 → 卷積 → 擴張」,v2 反過來做 inverted residual:「擴張 → depthwise 卷積 → 壓縮」,先用 1×1 把通道撐開,才有足夠的特徵給 depthwise 處理。最後那個 1×1 壓縮後面不接 ReLU,改成線性(linear bottleneck),因為在低維的地方 ReLU 會破壞特徵。
MobileNet v3:用 platform-aware NAS 搜整體架構,再手工修。加入 Squeeze-and-Excitation(SE):先 global average pooling 得到每個通道的整體資訊,再學一組通道權重,等於通道注意力;SE 的大小固定為擴張層通道數的 1/4。把 swish 換成 hard swish(用 ReLU6 拼出來),量化時誤差小、跑得快。Redesign expensive layers:v2 最後在 7×7 上做 1×1 很貴,v3 先 pooling 到 1×1 再做分類前的運算;並使用 5×5 depthwise。
MobileNet v4:目標是一個模型在手機 CPU、GPU、DSP、加速器上都有效率,組成是 UIB + Mobile MQA + NAS。UIB(Universal Inverted Bottleneck)= inverted bottleneck 加兩個可選的 depthwise,可以變成 IB、ConvNext 式、ExtraDW、FFN 四種,便宜地調整深度和感受野。Roofline 模型:運算強度 = 乘加數 ÷ 搬移的資料量;硬體的 ridge point = 峰值算力 ÷ 峰值頻寬。ridge point 低(CPU)時省乘加有用,高(加速器)時要省搬移。Mobile MQA:所有 head 共用 key 和 value,大幅減少記憶體存取;講義引用的數字是推理快 39% 以上,參數和 MACs 也減少 25% 以上。
重參數化:訓練時多分支、推論時一條路。多分支(例如 ResNet 的 shortcut)好訓練,但推論時多讀寫、不利平行;講義提到 ResNet-50 的殘差連接約占特徵圖記憶體的 40%。RepVGG 訓練時每層有 3×3、1×1、identity 三條支路,訓練完把三條線性地合成一個 3×3,推論時就是像 VGG 的直筒網路;缺點是訓練時要更多顯存和時間。RepOptVGG 改成把這個結構先驗放進 optimizer:用 CSLA(Constant-Scale Linear Addition,每條支路一個可訓練參數乘上常數縮放)推出梯度的乘數,訓練時就只有一條路,batch 可以更大、對量化也友善;縮放值先在小資料集上帶 BN 訓練找出來。RMNet 用 reserving and merging 直接把殘差拿掉:在第一個卷積加入 Dirac 初始化的 filter 保留輸入、讓 BN 變 identity、ReLU 改 PReLU 參數設 1,再把通道併進第二個卷積;它解決 RepVGG 在很深時退化的問題。MobileOne = MobileNet + RepVGG + 訓練技巧,針對 iPhone 12 最佳化,ImageNet 推理約 1ms;講義引用的觀察是 FLOPs 和參數量與手機上的延遲相關性很弱(iPhone 12 上 Spearman 相關小於 0.5)。
有了一個好的小網路,資源多一點時怎麼放大?可以加深(depth d)、加寬(width w,通道變多)、或吃更大的圖(resolution r)。任何一個方向都會變準,但講義的觀察是:只放大一個方向,報酬很快遞減。EfficientNet 的做法是三個一起按固定比例放大。成本也不對稱:卷積的 FLOPs ∝ d·w²·r²,寬度或解析度翻倍,FLOPs 變 4 倍。
EfficientNet:先固定 φ = 1(假設多兩倍資源),在基準網路上做小規模格點搜尋,得到 α = 1.2、β = 1.1、γ = 1.15(EfficientNet-B0)。之後固定這三個數,改 φ 得到 B1 到 B7。講義的數字:最大的版本 ImageNet top-1 84.4%、top-5 97.1%,比當時最好的 CNN 小 8.4 倍、推理快 6.1 倍;遷移到 CIFAR-100 約 91%、Flowers 98.8%。
EfficientFormerV2(輕量 ViT):在 FFN 裡加 depthwise 3×3,用 FFN 取代原本的 local token mixer;只在 1/32 的低解析度做 attention,因為 attention 的成本和解析度是平方關係;MHSA 也改良:對 value 加 depthwise 3×3 注入局部資訊、在 head 之間加一層全連接(talking head)讓 head 互通;再加上雙路徑的 attention。
TinyML / MCUNet v2:在微控制器(MCU)上,瓶頸是晶片上的那一點記憶體,而且記憶體用量很不平均:以 224×224 輸入的 MobileNetV2 為例,前面幾層的特徵圖最大。MCUNet v2 的做法是在記憶體最密集的那段改成一塊一塊(patch-by-patch)算,其他地方照常一層一層算;patch 會有重疊造成重複計算,所以再重新分配感受野:縮小 patch 階段的感受野、加大後面的,補回準確率。
動態執行(依輸入決定算多少):不是每張輸入都一樣難。SBNet 先算一張計算遮罩(例如 LiDAR 偵測時,路面以外的區域不用算),轉成 tile 清單,把要算的 tile 沿 batch 維度堆起來(gather)用一般的密集卷積算,再寫回去(scatter)。Dynamic network slimming:一個預測器看輸入,輸出一組門檻,當場把模型剪成適合這張圖的大小。Input-dependent depth:語音辨識的 Transformer 依輸入決定要走幾層。講義強調動態執行和其他方法正交,可以疊著用。
小模型難訓練,一個原因是標準答案太「瘦」:一張貓的圖,標籤只說「是貓」(hard target)。訓練好的大模型(老師)知道更多:它會說「九成是貓,有一點像狗,完全不像卡車」。用老師的機率分佈當學生的目標(soft target),學生就多學到「類別之間誰像誰」。問題是老師的 softmax 太尖,第二名以後幾乎都是 0。解法是在 softmax 裡除以一個溫度 T:pi = ezi/T / Σ ezj/T。
做法:學生(小網路)和老師(大網路)看同一張圖,都用同一個溫度 T 算 softmax,學生最小化和老師分佈之間的 cross-entropy(soft target);通常再加上一項和真實標籤的 cross-entropy(hard target)。推論時學生用回 T = 1。原始論文(2015)提出 softmax temperature 就是為了解決「老師太有自信、soft target 太尖」這件事。
為什麼有用:soft target 用機率分佈,而不只是對或錯,提供更多隱含資訊:哪些類別彼此相似、哪些差很遠,等於在訓練中多加了很多判斷條件。講義也提到 ensemble:把 N 個網路的輸出平均後當老師。
老師模型裡的幾種知識(講義引用的 knowledge distillation survey 的分法):response-based,學老師最後的輸出(就是這一節的 soft target);feature-based,學老師中間層的特徵圖;relation-based,學不同層之間、或不同樣本之間的關係。
和其他方法的關係:講義把蒸餾列為「恢復準確率」的工具之一,和放大輸入、depth、width,混用 kernel 大小,SE attention 並列。講義的總結也說:先訓練大模型再壓縮是一條路,而 fine-tuning 是恢復準確率最好的朋友。
這個模組最常被講錯的幾句話。講義的結論第一句就是:模型大小和 FLOPs 不等於執行時間。