Skip to content

MIT 6.7960 L06:現代 CNN 架構 —— ResNet、EfficientNet、ConvNeXt

2026年8月30日 1 分鐘
TL;DR ResNet 用殘差連接解決退化問題,讓網路能堆到 100+ 層;EfficientNet 複合縮放平衡深度/寬度/解析度;ConvNeXt 吸收 Transformer 設計重奪 CV 王座。
目錄
  1. TL;R
  2. 背景 / 課程定位
  3. 核心概念對比
  4. 系統 / 方法架構
    1. 1. ResNet 殘差連接
    2. 2. 深度可分離卷積 (Depthwise Separable)
    3. 3. EfficientNet 複合縮放
    4. 4. ConvNeXt 設計選擇
  5. 實作啟發
    1. PyTorch 實作:ResNet 殘差塊
    2. 深度可分離卷積實作
    3. 直接調用預訓練模型(實務推薦)
  6. 批判性反思
  7. 參考資料

🌏 English version

教材版本:基於 MIT 6.7960 Fall 2024 OCW。影片、投影片、作業全公開於 MIT OCW


TL;R

ResNet 殘差連接解決退化問題;EfficientNet 複合縮放;ConvNeXt 吸收 Transformer 設計重奪 CV 王座。


背景 / 課程定位

第 6 講(L06),對應官方課表 9 月 23 日(推測)。L05 建立基礎 CNN;本講進入現代架構,探討「如何把網路堆深、堆高效、堆準」—— 這直接影響你訓練自己的模型時選什麼骨幹。

影片Lecture 6: Modern CNN(建議從 2:30:00 開始) 投影片Lecture 6: Modern CNN


核心概念對比

架構年份核心創新解決什麼問題
VGG (L05)20143×3 堆疊證明深度有益,但梯度消失
ResNet2015殘差連接退化問題(深網反而更差)
MobileNet2017深度可分離卷積行動端輕量化
EfficientNet2019複合縮放系統化平衡深度/寬度/解析度
ConvNeXt2022吸收 Transformer 設計證明 CNN 仍可打贏 ViT

系統 / 方法架構

1. ResNet 殘差連接

x → [Conv-BN-ReLU] → [Conv-BN] → + (skip x) → ReLU

                          identity shortcut
  • 學習殘差 $F(x) = H(x) - x$,優化目標從「絕對映射」變「增量」
  • 退化問題:plain 網路加深後訓練誤差反而上升;ResNet 讓最壞情況退化成 identity(拷貝上一層)

2. 深度可分離卷積 (Depthwise Separable)

標準卷積:  C_in × C_out × k × k 參數
可分離:    (C_in × k × k) + (C_in × C_out × 1 × 1) 參數
           計算量減少 ~ k² × C_out / (1 + k²)
  • MobileNet: 3×3 depthwise + 1×1 pointwise
  • 例:輸入 64 通道、輸出 128、核 3×3
    • 標準:$64 \times 128 \times 9 = 73728$
    • 可分離:$64 \times 9 + 64 \times 128 = 8768$(省 8.4×)

3. EfficientNet 複合縮放

$$\text{depth}: d=\alpha^\phi, \quad \text{width}: w=\beta^\phi, \quad \text{resolution}: r=\gamma^\phi$$ 約束 $\alpha \cdot \beta^2 \cdot \gamma^2 \approx 2$(固定總計算量)

  • B0→B7:$\phi$ 從 0 到 7,系統化放大
  • 關鍵:單獨縮放任一度都次優;複合縮放最優

4. ConvNeXt 設計選擇

設計元素來源效果
7×7 大核 (depthwise)Swin Transformer增大感受野
反轉瓶頸 (inverted bottleneck)MobileNetV2類 Transformer FFN
無偏置、LayerNormTransformer訓練更穩定
GELU 激活Transformer略優於 ReLU

實作啟發

PyTorch 實作:ResNet 殘差塊

import torch
import torch.nn as nn
import torch.nn.functional as F

class ResidualBlock(nn.Module):
    def __init__(self, in_ch, out_ch, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride, 1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_ch)
        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, 1, 1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_ch)
        
        # shortcut: 1×1 卷積對齊維度(當 in_ch ≠ out_ch 或 stride>1)
        self.shortcut = nn.Sequential()
        if in_ch != out_ch or stride != 1:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_ch, out_ch, 1, stride, bias=False),
                nn.BatchNorm2d(out_ch)
            )
    
    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)   # 殘差連接
        return F.relu(out)

# 堆疊 ResNet-18 風格
class ResNet18(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.pool = nn.MaxPool2d(3, 2, 1)
        self.layer1 = self._make_layer(64, 64, 2)
        self.layer2 = self._make_layer(64, 128, 2, stride=2)
        self.layer3 = self._make_layer(128, 256, 2, stride=2)
        self.layer4 = self._make_layer(256, 512, 2, stride=2)
        self.gap = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(512, num_classes)
    
    def _make_layer(self, in_ch, out_ch, blocks, stride=1):
        layers = [ResidualBlock(in_ch, out_ch, stride)]
        for _ in range(1, blocks):
            layers.append(ResidualBlock(out_ch, out_ch))
        return nn.Sequential(*layers)
    
    def forward(self, x):
        x = self.pool(F.relu(self.bn1(self.conv1(x))))
        x = self.layer1(x); x = self.layer2(x)
        x = self.layer3(x); x = self.layer4(x)
        x = self.gap(x).flatten(1)
        return self.fc(x)

深度可分離卷積實作

class DepthwiseSeparableConv(nn.Module):
    def __init__(self, in_ch, out_ch, kernel=3):
        super().__init__()
        self.depthwise = nn.Conv2d(in_ch, in_ch, kernel, padding=kernel//2, groups=in_ch, bias=False)
        self.pointwise = nn.Conv2d(in_ch, out_ch, 1, bias=False)
        self.bn = nn.BatchNorm2d(out_ch)
    
    def forward(self, x):
        return F.relu(self.bn(self.pointwise(self.depthwise(x))))

直接調用預訓練模型(實務推薦)

import torchvision.models as models

# ResNet-50 預訓練(ImageNet 權重)
resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
# EfficientNet-B0
effnet = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1)
# ConvNeXt-Tiny
convnext = models.convnext_tiny(weights=models.ConvNeXt_Tiny_Weights.IMAGENET1K_V1)

批判性反思

架構優點局限 / 注意
ResNet解決退化、易訓練、成為骨幹標準仍受感受野限制;超大模型不如後續架構
MobileNet/EfficientNet輕量、移動端友好小模型準確率天花板低;需仔細調複合縮放
ConvNeXt證明 CNN 仍具競爭力、設計清晰仍不如 ViT 在超大數據表現;推理成本較高
Transformer (ViT)大數據 SOTA、可擴展需海量數據/預訓練;小數據不如 CNN

課程強調:選骨幹看三件事——(1) 數據量(小數據選 CNN/ConvNeXt,大數據可試 ViT);(2) 部署限制(行動端選 MobileNet/EfficientNet);(3) 準確率需求(追求極致選 EfficientNetV2/ConvNeXt-XXL)。不要盲目追新,要看約束條件


參考資料

  • Lecture 6: Modern CNN (MIT OCW 6.7960 Fall 2024) — 投影片 PDF
  • Lecture 6 Video (YouTube) — 關鍵段落:0:00–15:00 ResNet;15:00–30:00 MobileNet/EfficientNet;30:00–45:00 ConvNeXt
  • He et al., "Deep Residual Learning for Image Recognition" (CVPR 2016) — ResNet
  • Howard et al., "MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications" (2017)
  • Tan & Le, "EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks" (ICML 2019)
  • Liu et al., "A ConvNet for the 2020s" (CVPR 2022) — ConvNeXt
  • PyTorch 官方文檔:torchvision.models