Hoppa till innehållet
AI-grafen
E· Universitetdatorseende· ca 60 min· utvecklande· verifierad 2026-09-20

CNN-arkitekturer: LeNet till ResNet

Kunna förklara hur arkitekturerna utvecklats och varför ResNet blev standard.

Förkunskaper

Intuition

CNN-arkitekturernas historia är en serie svar på konkreta problem.

ÅrArkitekturNyhetProblem den löste
1998LeNet-5faltning + pooling, 7 lagerhandskrivna siffror
2012AlexNetReLU, dropout, GPU, augmenteringImageNet — halverade felet
2014VGGbara 3×3-kärnor, 16–19 lagerenkelhet och djup
2014Inceptionparallella kärnstorlekar, 1×1beräkningseffektivitet
2015ResNetresidualkopplingar, 152 lagerdjupet självt
2017MobileNetdjupseparerbar faltningmobil hårdvara
2019EfficientNetbalanserad skalning av djup/bredd/upplösningbästa kvalitet per FLOP
2022ConvNeXtCNN med transformer-receptkonkurrera med ViT

Två insikter återkommer:

  1. Små kärnor staplade slår stora. Två 3×3-lager har samma receptiva fält som ett 5×5 men färre parametrar (18 mot 25 per kanalpar) och två olinjäriteter i stället för en.
  2. 1×1-faltning är gratis dimensionsreduktion. Den blandar kanaler utan att röra rumsliga dimensioner, och används för att krympa kanalantalet före dyra operationer.

Formellt

Varför ResNet blev vändpunkten. Före 2015 var djupet ett problem: ett 56-lagers nät presterade sämre än ett 20-lagers, och inte på grund av överanpassning utan optimering. Residualkopplingen y=x+F(x)y = x + F(x) gjorde identitetsavbildningen trivial att lära och gav gradienten en oskalad väg genom nätet.

Effekten var omedelbar: 152 lager, bättre än allt tidigare, och principen spred sig till i princip varje djup arkitektur som byggts sedan dess — inklusive transformern.

Beräkningskostnad per lager:

FLOPs≈Hut⋅But⋅Cin⋅Cut⋅k2\text{FLOPs} \approx H_{ut} \cdot B_{ut} \cdot C_{in} \cdot C_{ut} \cdot k^2

Det förklarar tre designval:

ValEffekt på formeln
Nedsampla tidigtH⋅BH\cdot B faller fyrfaldigt per steg
1×1 före 3×3 (flaskhals)CinC_{in} i den dyra termen minskar
Djupseparerbar faltningCin⋅Cut⋅k2C_{in}\cdot C_{ut}\cdot k^2 blir Cin⋅k2+Cin⋅CutC_{in}\cdot k^2 + C_{in}\cdot C_{ut}

Den sista är MobileNets idé och ger 8–9× färre beräkningar för 3×3-faltning med många kanaler.

Läget i dag:

BehovVal
Standardval för bildförtränad ResNet eller ConvNeXt, finjusterad
Litet datasetförtränad modell — träna aldrig från grunden
Mobil eller inbäddadMobileNet, EfficientNet-Lite
Mycket stor dataViT eller hybrid
Segmentering, detektionryggrad + uppgiftsspecifikt huvud

Den viktigaste praktiska slutsatsen är att arkitekturvalet sällan är det som avgör. Förträning, dataaugmentering och datakvalitet ger nästan alltid mer än att byta från en modern arkitektur till en annan — skillnaden mellan ResNet-50 och ConvNeXt-T på ett typiskt finjusteringsproblem är ofta mindre än skillnaden mellan bra och dålig augmentering.

Kod

import torch, torch.nn as nn

# 1. Två 3×3 slår ett 5×5: samma receptiva fält, färre parametrar, fler olinjäriteter
C = 64
en_5x5 = nn.Conv2d(C, C, 5, padding=2, bias=False)
tva_3x3 = nn.Sequential(nn.Conv2d(C, C, 3, padding=1, bias=False), nn.ReLU(),
                        nn.Conv2d(C, C, 3, padding=1, bias=False))
for namn, m in (("ett 5×5", en_5x5), ("två 3×3", tva_3x3)):
    print(f"{namn:<9} {sum(p.numel() for p in m.parameters()):>8,} parametrar")
# ett 5×5    102,400 parametrar
# två 3×3     73,728 parametrar

# 2. Flaskhalsblocket: 1×1 ner, 3×3, 1×1 upp
class Flaskhals(nn.Module):
    def __init__(self, kanaler, krymp=4):
        super().__init__()
        m = kanaler // krymp
        self.f = nn.Sequential(
            nn.Conv2d(kanaler, m, 1, bias=False), nn.BatchNorm2d(m), nn.ReLU(),
            nn.Conv2d(m, m, 3, padding=1, bias=False), nn.BatchNorm2d(m), nn.ReLU(),
            nn.Conv2d(m, kanaler, 1, bias=False), nn.BatchNorm2d(kanaler),
        )
        nn.init.zeros_(self.f[-1].weight)          # startar som identitet

    def forward(self, x):
        return torch.relu(x + self.f(x))

class Enkel(nn.Module):
    def __init__(self, kanaler):
        super().__init__()
        self.f = nn.Sequential(
            nn.Conv2d(kanaler, kanaler, 3, padding=1, bias=False), nn.BatchNorm2d(kanaler),
            nn.ReLU(),
            nn.Conv2d(kanaler, kanaler, 3, padding=1, bias=False), nn.BatchNorm2d(kanaler),
        )

    def forward(self, x):
        return torch.relu(x + self.f(x))

for namn, m in (("enkelt block", Enkel(256)), ("flaskhals", Flaskhals(256))):
    print(f"{namn:<14} {sum(p.numel() for p in m.parameters()):>9,} parametrar")
# enkelt block     1,180,672 parametrar
# flaskhals          70,400 parametrar     ← 17× färre, samma djup

# 3. Djupseparerbar faltning (MobileNet)
def separerbar(cin, cut, k=3):
    return nn.Sequential(
        nn.Conv2d(cin, cin, k, padding=k // 2, groups=cin, bias=False),   # per kanal
        nn.BatchNorm2d(cin), nn.ReLU(),
        nn.Conv2d(cin, cut, 1, bias=False),                               # blanda kanaler
        nn.BatchNorm2d(cut), nn.ReLU(),
    )

vanlig = nn.Conv2d(128, 256, 3, padding=1, bias=False)
sep = separerbar(128, 256)
print(f"vanlig 3×3:   {sum(p.numel() for p in vanlig.parameters()):>8,}")
print(f"separerbar:   {sum(p.numel() for p in sep.parameters()):>8,}")
# vanlig 3×3:    294,912
# separerbar:     34,688     ← ~8× färre

# 4. I praktiken: använd en förtränad modell
from torchvision.models import resnet50, ResNet50_Weights
m = resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
m.fc = nn.Linear(m.fc.in_features, 10)      # byt huvudet mot dina klasser

Behärskning innebär

  • Beskriver utvecklingen från LeNet till ResNet
  • Förklarar varför varje steg var en förbättring
  • Vet vad som gäller i dag

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser