Machine Learning Academy · Lektion

Featureudtræk: Frysning af backbone

De lærende fryser alle lag undtagen det sidste klassifikationshoved, træner kun nye lag på et lille tilpasset datasæt og bekræfter en markant reduceret træningstid.

Lektion 2 af 412 trin

Featureudtræk: Frysning af backbone er en gratis Machine Learning Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Machine Learning Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Machine Learning Academy-kurset indeholder 4 lektioner i alt.

Feature-ekstraktion vs. finjustering

Transfer learning har to hovedstrategier. Ved feature-ekstraktion er den forudtrænede backbone-model fuldstændig frosset — dens vægte ændres ikke under træningen. Kun det nye klassifikationshoved, som du tilføjer ovenpå, lærer fra dine data. Ved finjustering opdateres hele netværket eller i det mindste nogle af backbone-lagene også.

Feature-ekstraktion er det rigtige valg, når dit datasæt er lille (mindre end nogle få tusinde billeder), eller når dine billeder er lignende ImageNet (naturlige fotografier af hverdagsobjekter). Det er meget hurtigere, eftersom gradienter ikke flyder gennem backbone-modellen, og det undgår at ødelægge omhyggeligt indlærte egenskaber med støjende opdateringer fra for få data.

Frysning af parametre i PyTorch

I PyTorch har hver parametertensor en requires_grad-attribut. Hvis du sætter den til False, forhindres gradientberegning for denne tensor, så den reelt fryses. Den enkleste måde at fryse alle backbone-parametre på er at gennemløbe model.parameters() og sætte requires_grad = False og derefter erstatte klassifikationshovedet (som starter med nye tilfældige vægte, så requires_grad=True som standard).

Dette er effektivt: PyTorchs autograd springer frosne parametre over under det baglæns gennemløb, hvilket reducerer hukommelsesforbruget og gør træningen betydeligt hurtigere sammenlignet med finjustering af hele netværket.

import torchvision.models as models
import torch.nn as nn

model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)

# Freeze ALL backbone parameters
for param in model.parameters():
    param.requires_grad = False

# Replace the classification head (creates new trainable parameters)
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)
# model.fc.parameters() have requires_grad=True by default

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f'Trainable: {trainable:,} / Total: {total:,} ({trainable/total:.1%})')

Hvorfor frosne features fungerer godt

ImageNet-forudtrænede modeller lærer et hierarki af features: Tidlige lag registrerer kanter og farver på lavt niveau, mellemste lag registrerer teksturer og dele, og senere lag registrerer abstrakte objekter. Disse features er generelle visuelle features, som kan overføres bredt på tværs af billeddomæner.

Når din opgave omfatter naturlige billeder — blomster, dyr, medicinske scanninger eller satellitfotos — er disse forudlærte features langt mere informative end noget, et tilfældigt initialiseret netværk kunne udtrække fra et lille datasæt. Feature-ekstraktion udnytter dette ved at behandle den frosne backbone-model som en fast featuretransformer og kun træne en lille lineær klassifikator oven på de udtrukne features.

import torchvision.models as models
import torch

# Pre-extract features for all images (faster than forward-passing every epoch)
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
model.fc = torch.nn.Identity()  # Remove classification head
model.eval()

# Extract 2048-dim features for all training images once
all_features, all_labels = [], []
with torch.no_grad():
    for images, labels in train_loader:
        features = model(images)  # Shape: (batch, 2048)
        all_features.append(features)
        all_labels.append(labels)

X_train = torch.cat(all_features)  # (N, 2048)
y_train = torch.cat(all_labels)    # (N,)

Træning af kun klassifikationshovedet

Når funktionerne er fastfrosset, går træningen hurtigt. Optimeringsalgoritmen opdaterer kun vægtene i det nye hoved. Du kan enten træne et simpelt nn.Linear-lag i PyTorch eller endda sende de allerede udtrukne funktionsvektorer til scikit-learns LogisticRegression eller SVC — begge tilgange fungerer godt til lineær klassifikation oven på omfattende fortrænede funktioner.

Brug af et enkelt lineært lag svarer til at træne en logistisk regression på de udtrukne funktioner. Til mere komplekse opgaver, eller når dine klasser kræver ikke-lineære beslutningsgrænser, kan du bruge et lille flerlags-hoved med ReLU-aktiveringer og dropout mellem det fastfrosne grundnetværk og outputtet.

import torch.nn as nn
import torch.optim as optim

# Option 1: Simple linear head (logistic regression on features)
classifier = nn.Linear(2048, num_classes)
optimizer = optim.Adam(classifier.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

# Option 2: Small MLP head for more complex tasks
mlp_head = nn.Sequential(
    nn.Linear(2048, 512),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(512, num_classes)
)

# scikit-learn option (useful for small datasets)
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(max_iter=1000, C=1.0)
clf.fit(X_train.numpy(), y_train.numpy())

Opsætning af DataLoader og transformationer

Under funktionsudtrækningen skal billedforbehandlingen svare til de transformationer, der blev brugt ved træningen af den oprindelige model. For fortrænede ImageNet-modeller betyder det: tilpas størrelsen til 256, beskær centreret til 224×224, konvertér til tensor, og normalisér med ImageNets middelværdi og standardafvigelse.

Når du bruger weights.transforms(), leverer PyTorch automatisk den korrekte forbehandlingspipeline, der er knyttet til netop disse vægte. Det eliminerer en almindelig kilde til svære fejl, hvor du ved et uheld bruger forkerte normaliseringskonstanter, hvilket kan reducere nøjagtigheden ved overførselslæring med flere procentpoint.

from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import torchvision.models as models

weights = models.ResNet50_Weights.IMAGENET1K_V1
preprocess = weights.transforms()  # Includes correct resize, crop, normalize

train_dataset = datasets.ImageFolder('data/train', transform=preprocess)
val_dataset   = datasets.ImageFolder('data/val',   transform=preprocess)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)
val_loader   = DataLoader(val_dataset,   batch_size=32, shuffle=False, num_workers=4)

print('Classes:', train_dataset.classes)
print('Training samples:', len(train_dataset))

Sammenligning af træningshastighed

Funktionsudtrækning er markant hurtigere end fuld finjustering. Når alle parametre i grundnetværket har requires_grad=False, beregner PyTorch ikke gradienter gennem dem under loss.backward(), hvilket sparer hukommelse og beregningstid proportionalt med antallet af fastfrosne lag.

For ResNet-50 (25M parametre, 2048 fastfrosne funktioner og kun omkring 2K parametre i hovedet) gennemføres en træningsepoke på et datasæt med 5000 billeder på sekunder på CPU, sammenlignet med minutter ved fuld finjustering. Det går endnu hurtigere at udtrække funktionerne offline på forhånd og derefter kun træne hovedet som en almindelig sklearn-klassifikator, eftersom grundnetværkets fremadrettede beregning kun køres én gang pr. billede.

import time

# Measuring training time difference
# With frozen backbone (feature extraction):
start = time.time()
for batch in train_loader:
    images, labels = batch
    with torch.no_grad():
        features = backbone(images)  # Fast: no grad tracking
    loss = criterion(classifier(features), labels)
    loss.backward()  # Gradients only through tiny classifier
    optimizer.step()
    optimizer.zero_grad()
print(f'Feature extraction epoch: {time.time()-start:.1f}s')

# Full fine-tuning trains 25M params instead of ~2K

Tilpasning af EfficientNet til funktionsudtrækning

Tilgangen er den samme for EfficientNet: fastfrys alle parametre, og erstat derefter det endelige klassifikationslag. I EfficientNet er klassifikatoren en Sequential-blok, der tilgås via model.classifier, ikke model.fc. Kontrollér altid modelarkitekturen for at finde det korrekte attributnavn.

EfficientNet-B0's klassifikator forventer inputfunktioner med 1280 dimensioner. Når du skriver kode, der fungerer med flere arkitekturer, kan du undersøge det endelige lag med in_features i stedet for at fastlåse dimensionen, så koden bliver mere genanvendelig på tværs af forskellige valg af grundnetværk.

import torchvision.models as models
import torch.nn as nn

model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1)

# Freeze backbone
for param in model.parameters():
    param.requires_grad = False

# EfficientNet uses model.classifier, not model.fc
print('Old classifier:', model.classifier)
in_features = model.classifier[1].in_features  # Access Linear inside Sequential
model.classifier = nn.Linear(in_features, num_classes)

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f'Trainable parameters: {trainable:,}')

Tilpasning af ViT til funktionsudtrækning

I ViT tilgås klassifikationshovedet via model.heads. Funktionsdimensionen fra ViT-B/16's grundnetværk er 768 (transformerens indlejringsdimension). Når du har fastfrosset alle parametre, skal du erstatte model.heads med et nyt nn.Linear(768, num_classes).

ViT har mindre fordel af funktionsudtrækning end CNN'er, fordi dets opmærksomhedslag er mere opgavespecifikke — de lærer at rette opmærksomheden mod andre billedområder end dem, din opgave kræver. Hvis du bruger et ViT-grundnetværk, bør du som minimum frigive de sidste få transformer-koderblokke for at opnå den bedste ydeevne. Til et hurtigt eksperiment er fuld funktionsudtrækning dog stadig et gyldigt udgangspunkt.

import torchvision.models as models
import torch.nn as nn

vit = models.vit_b_16(weights=models.ViT_B_16_Weights.IMAGENET1K_V1)

# Freeze all parameters
for param in vit.parameters():
    param.requires_grad = False

# Replace the classification head
print('Old head:', vit.heads)  # Sequential with Linear(768, 1000)
vit.heads = nn.Linear(768, num_classes)

print('ViT trainable params:', sum(p.numel() for p in vit.parameters() if p.requires_grad))

Evaluering af resultaterne for funktionsudtrækning

Efter træning med fastfrosne funktioner skal du evaluere på valideringssættet ved hjælp af standardmetrikker for klassifikation. En velindstillet funktionsudtrækker opnår ofte over 90 % nøjagtighed på små tilpassede datasæt (500-5000 billeder pr. klasse), hvor træning fra bunden ville føre til fuldstændig overtilpasning.

Hvis nøjagtigheden er lavere end forventet, kan du overveje: (1) at tilføje dataforøgelse under træningen, (2) at bruge et dropout-lag før det lineære hoved, (3) at bruge et lidt mere udtryksfuldt hoved (to lineære lag med ReLU) eller (4) at gå over til delvis finjustering ved at frigive det sidste residualblok. Overvåg både trænings- og valideringsnøjagtigheden for at skelne mellem overtilpasning og undertilpasning.

from sklearn.metrics import classification_report
import torch

def evaluate(model, loader, device):
    model.eval()
    all_preds, all_labels = [], []
    with torch.no_grad():
        for images, labels in loader:
            images = images.to(device)
            logits = model(images)
            preds = logits.argmax(dim=1).cpu()
            all_preds.extend(preds.numpy())
            all_labels.extend(labels.numpy())
    print(classification_report(all_labels, all_preds,
                                target_names=class_names))

evaluate(model, val_loader, device)

Når funktionsudtrækning ikke er nok

Funktionsudtrækning fungerer bedst, når kildedomænet (ImageNet: naturlige fotografier) og måldomænet ligner hinanden. Når dine data er meget anderledes — røntgenbilleder, satellitbilleder, mikroskopi eller infrarøde termiske billeder — overføres funktioner på lavt niveau stadig (kantdetektorer er universelle), men ImageNets funktioner på højere niveau er måske mindre nyttige.

Tegn på, at funktionsudtrækningen klarer sig dårligt: Valideringsnøjagtigheden flader ud langt under forventningen, eller træningsnøjagtigheden er meget højere end valideringsnøjagtigheden (hvilket viser, at hovedet overtilpasser for få funktioner). I disse tilfælde skal du gå over til finjustering: frigiv nogle lag i grundnetværket, og træn med en meget lavere indlæringshastighed for gradvist at tilpasse de forudlærte funktioner til dit domæne.

Hurtigt tjek

Test din forståelse af funktionsudtrækning med fastfrosne grundnetværk fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at funktionsudtrækning fastfryser alle parametre i grundnetværket og kun træner et nyt klassifikationshoved, hvilket giver hurtig træning og gode resultater, når dine data ligner ImageNet; at fastfrysning udføres ved at indstille requires_grad=False på parametrene i grundnetværket, før det endelige lag udskiftes; og at offline-udtrækning af funktioner på forhånd går endnu hurtigere ved at køre grundnetværket én gang og gemme funktionsvektorerne. Næste gang lærer du om finjustering — hvordan du forsigtigt frigiver lag i grundnetværket med lave indlæringshastigheder for at opnå endnu bedre nøjagtighed.

Gratis at komme i gang

Lær Python med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Featureudtræk: Frysning af backbone” gratis?

Ja — hele teksten til “Featureudtræk: Frysning af backbone” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Machine Learning Academy-kurset, skal du opgradere til CoddyKit PRO. Machine Learning Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Featureudtræk: Frysning af backbone”?

De lærende fryser alle lag undtagen det sidste klassifikationshoved, træner kun nye lag på et lille tilpasset datasæt og bekræfter en markant reduceret træningstid. Du øver dig i Machine Learning Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Machine Learning Academy?

Der kræves ingen tidligere erfaring. Machine Learning Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Featureudtræk: Frysning af backbone”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Machine Learning Academy-lektion?

Ja. Alle Machine Learning Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Prætrænede modeller i torchvision: ResNet, EfficientNet og ViT
  2. Featureudtræk: Frysning af backbone
  3. Finjustering: Ophæv frysning og brug lave learning rates
  4. Domænetilpasning: Medicinsk billedbehandling med få labels
← Tilbage til Machine Learning Academy