Faza 04 · lecția 05

Învățare prin transfer și ajustare fină

Scopul lecției: Altcineva a cheltuit un milion de ore-GPU învățând o rețea cum arată marginile, texturile și părțile obiectelor. Împrumutați acele caracteristici înainte de a o antrena pe a dumneavoastră.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
Viziune computerizată
Lectură
18 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. Extragerea de caracteristici vs. ajustarea fină
  5. De ce funcționează înghețarea
  6. Rate de învățare discriminatorii
  7. Problema BatchNorm
  8. Proiectarea capului
  9. Decăderea ratei de învățare pe straturi
  10. Ce trebuie evaluat
  11. Construiți-l
  12. Pasul 1: Încărcați un backbone preantrenat și inspectați-l
  13. Pasul 2: Extragerea de caracteristici — înghețați totul, înlocuiți capul
  14. Pasul 3: Ajustare fină discriminatorie
  15. Pasul 4: Tratarea BatchNorm
  16. Pasul 5: O buclă minimală de ajustare fină de la un capăt la altul
  17. Pasul 6: Dezghețarea progresivă
  18. Utilizați-l
  19. Livrați-l
  20. Exerciții
  21. Termeni-cheie
  22. Lecturi suplimentare

Altcineva a cheltuit un milion de ore-GPU învățând o rețea cum arată marginile, texturile și părțile obiectelor. Împrumutați acele caracteristici înainte de a o antrena pe a dumneavoastră.

Tip: Construcție Limbaje: Python Cerințe preliminare: Faza 4, Lecția 03 (CNN-uri), Faza 4, Lecția 04 (Clasificarea imaginilor) Timp: ~75 de minute

Obiective de învățare

  • Deosebiți extragerea de caracteristici de ajustarea fină și alegeți-o pe cea potrivită în funcție de dimensiunea setului de date, distanța dintre domenii și bugetul de calcul
  • Încărcați un backbone preantrenat, înlocuiți-i capul de clasificare și antrenați numai capul pentru un reper funcțional în mai puțin de 20 de linii
  • Dezghețați progresiv straturile cu rate de învățare discriminatorii, astfel încât caracteristicile generice timpurii să primească actualizări mai mici decât cele specifice sarcinii, din final
  • Diagnosticați cele trei eșecuri frecvente: devierea caracteristicilor cauzată de o rată de învățare prea mare pe blocurile dezghețate, colapsul statisticilor BN pe seturi de date mici și uitarea catastrofică

Problema

Antrenarea unui ResNet-50 pe ImageNet costă aproximativ 2.000 de ore-GPU. Foarte puține echipe au acest buget pentru fiecare sarcină pe care o livrează. Aproape fiecare echipă livrează, de fapt, un backbone preantrenat cu un cap nou, antrenat pe câteva sute sau câteva mii de imagini specifice sarcinii.

Aceasta nu este o scurtătură. Primul bloc convoluțional al oricărei CNN antrenate pe ImageNet învață filtre asemănătoare marginilor și filtrelor Gabor. Următoarele câteva blocuri învață texturi și motive simple. Blocurile de mijloc învață părți ale obiectelor. Blocurile finale învață combinații care încep să semene cu cele 1.000 de categorii ImageNet. Primele 90% din această ierarhie se transferă aproape neschimbate la imagistica medicală, inspecția industrială, datele satelitare și orice altă sarcină de viziune — deoarece natura are un vocabular limitat de margini și texturi. Ultimele 10% sunt ceea ce antrenați efectiv.

Notă tehnică a traducerii: Nu există o proporție universală de „90%” a caracteristicilor transferabile. Transferabilitatea depinde de strat, sarcină și diferența dintre domeniile sursă și țintă; caracteristicile timpurii tind să fie mai generale, dar adaptarea poate fi necesară și pentru ele. Consultați Yosinski și colab..

Realizarea corectă a transferului are trei erori care vă așteaptă: distrugerea caracteristicilor preantrenate printr-o rată de învățare prea mare, înfometarea modelului de informație prin înghețarea unei porțiuni prea mari și lăsarea statisticilor curente ale BatchNorm să derive către un set de date mic, pe care restul rețelei nu l-a învățat niciodată. Această lecție le parcurge intenționat pe fiecare.

Conceptul

Extragerea de caracteristici vs. ajustarea fină

Două regimuri, alese după câtă încredere aveți în caracteristicile preantrenate și după câte date aveți.

Диаграмма к уроку «Învățare prin transfer și ajustare fină»

Reguli orientative:

Dimensiunea setului de date Distanța dintre domenii Rețetă
< 1k imagini apropiat de ImageNet Înghețați backbone-ul, antrenați numai capul
1k–10k apropiat Înghețați primele 2–3 etape, ajustați fin restul
10k–100k oricare Ajustați fin de la un capăt la altul cu LR discriminatorii
100k+ îndepărtat Ajustați fin totul; luați în calcul antrenarea de la zero dacă domeniul este suficient de îndepărtat

Notă tehnică a traducerii: Pragurile din tabel sunt euristici de pornire, nu limite demonstrate. Efectul preantrenării și alegerea dintre înghețare, ajustare fină ori antrenare de la zero depind de sarcină, arhitectură, mărimea datelor și protocol; Raghu și colab. și He, Girshick & Dollár arată contexte în care beneficiul preantrenării ImageNet nu este universal.

„Apropiat de ImageNet” înseamnă, în linii mari, fotografii RGB naturale cu conținut asemănător obiectelor. Scanările CT medicale, imaginile satelitare de sus și microscopia sunt domenii îndepărtate — caracteristicile continuă să ajute, însă va trebui să permiteți adaptarea mai multor straturi.

De ce funcționează înghețarea

Caracteristicile ImageNet învățate de o CNN nu sunt specializate pentru cele 1.000 de categorii. Sunt specializate pentru statisticile imaginilor naturale: margini la orientări specifice, texturi, tipare de contrast, primitive de formă. Aceste statistici sunt stabile în aproape orice domeniu vizual pe care îl poate numi un om. De aceea, un model antrenat pe ImageNet și evaluat „zero-shot” pe CIFAR-10 doar cu un cap liniar nou (fără ajustarea fină a backbone-ului) ajunge la o acuratețe de peste 80%. Capul învață ce caracteristici deja învățate să pondereze pentru această sarcină.

Notă tehnică a traducerii: Antrenarea unui cap liniar nou peste un backbone înghețat nu este evaluare zero-shot; este evaluare prin sondă liniară (linear probe). Pragul de 80% depinde de backbone, ponderi, preprocesare și protocol. Kornblith și colab. discută relația dintre modelele ImageNet și transfer.

Rate de învățare discriminatorii

Când dezghețați straturi, cele timpurii trebuie antrenate mai lent decât cele târzii. Straturile timpurii codifică caracteristici generice pe care doriți să le păstrați; straturile târzii codifică structura specifică sarcinii, pe care trebuie să o modificați semnificativ.

Rețetă tipică:

  etapa 0 (stem + primul grup): lr = base_lr / 100    (în mare parte fixă)
  etapa 1:                      lr = base_lr / 10
  etapa 2:                      lr = base_lr / 3
  etapa 3 (ultimul grup din backbone): lr = base_lr
  cap:                          lr = base_lr  (sau puțin mai mare)

În PyTorch, aceasta este doar o listă de grupuri de parametri transmisă optimizatorului. Un model, cinci rate de învățare, zero cod suplimentar.

Problema BatchNorm

Straturile BN păstrează bufferele running_mean și running_var care au fost calculate pe ImageNet. Dacă sarcina are o distribuție diferită a pixelilor — iluminare diferită, senzor diferit, spațiu de culoare diferit — aceste buffere sunt greșite. Trei opțiuni, în ordinea preferinței:

  1. Ajustați fin cu BN în modul train. Lăsați BN să își actualizeze statisticile curente împreună cu toate celelalte componente. Este alegerea implicită când setul de date al sarcinii are dimensiune medie (>= 5k exemple).
  2. Înghețați BN în modul eval. Păstrați statisticile ImageNet și antrenați doar ponderile. Este corect când setul de date este suficient de mic încât media mobilă a BN ar fi zgomotoasă.
  3. Înlocuiți BN cu GroupNorm. Aceasta elimină în întregime problema mediei mobile. Este folosită în backbone-uri pentru detecție și segmentare, unde dimensiunea lotului pe GPU este foarte mică.

Alegerea greșită reduce pe tăcute acuratețea cu 5–15%.

Notă tehnică a traducerii: Pragul de 5k exemple și intervalul de 5–15% sunt euristici, nu reguli universale. Comportamentul depinde de dimensiunea efectivă a lotului, distribuție și arhitectură; BatchNorm folosește statistici de lot în antrenare și statistici curente la evaluare, iar GroupNorm nu depinde de lot. Consultați Batch Normalization și Group Normalization.

Proiectarea capului

Capul clasificatorului are 1–3 straturi liniare, plus un dropout opțional. Fiecare backbone torchvision este livrat cu un cap implicit pe care îl înlocuiți:

backbone.fc = nn.Linear(backbone.fc.in_features, num_classes)          # ResNet
backbone.classifier[1] = nn.Linear(..., num_classes)                    # EfficientNet, MobileNet
backbone.heads.head = nn.Linear(..., num_classes)                       # torchvision ViT

Pentru seturi de date mici, un singur strat liniar este de obicei suficient. Adăugarea unui strat ascuns (Linear -> ReLU -> Dropout -> Linear) ajută când distribuția sarcinii este mai îndepărtată de distribuția de antrenare a backbone-ului.

Decăderea ratei de învățare pe straturi

O versiune mai netedă a LR discriminatorii, folosită în ajustarea fină modernă (BEiT, DINOv2, ajustări fine ViT-B). În loc să grupați straturile în etape, acordați fiecărui strat o LR puțin mai mică decât cea a stratului de deasupra:

lr_layer_k = base_lr * decay^(L - k)

Cu decay = 0.75 și L = 12 blocuri Transformer, primul bloc se antrenează la 0.75^11 ≈ 0.04x din LR a capului. Contează mai mult pentru ajustările fine Transformer decât pentru CNN-uri, pentru care LR grupate pe etape sunt de regulă suficiente.

Ce trebuie evaluat

Rulările de învățare prin transfer necesită două numere pe care nu le-ați urmări într-o rulare de la zero:

  • Acuratețea numai cu preantrenare — acuratețea capului cu backbone-ul înghețat. Aceasta este limita inferioară.
  • Acuratețea după ajustarea fină — același model după antrenarea de la un capăt la altul. Aceasta este limita superioară.

Dacă rezultatul după ajustarea fină este mai mic decât cel numai cu preantrenare, aveți o eroare a ratei de învățare sau a BN. Afișați-le întotdeauna pe ambele.

Notă tehnică a traducerii: Acuratețea după ajustarea fină nu este o limită superioară garantată. Un rezultat sub baseline-ul cu backbone înghețat este un semnal de diagnostic; pe lângă LR și BN, verificați preprocesarea, împărțirea datelor, protocolul de evaluare, regularizarea și scheduler-ul. He și colab. ilustrează cât de dependente de protocol sunt rezultatele clasificării imaginilor.

Construiți-l

Pasul 1: Încărcați un backbone preantrenat și inspectați-l

import torch
import torch.nn as nn
from torchvision.models import resnet18, ResNet18_Weights

backbone = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
print(backbone)
print()
print("classifier head:", backbone.fc)
print("feature dim:", backbone.fc.in_features)

ResNet18 are patru etape (layer1..layer4), plus un stem și un cap fc. Fiecare backbone torchvision de clasificare are o structură analogă.

Pasul 2: Extragerea de caracteristici — înghețați totul, înlocuiți capul

def make_feature_extractor(num_classes=10):
    model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
    for p in model.parameters():
        p.requires_grad = False
    model.fc = nn.Linear(model.fc.in_features, num_classes)
    return model

model = make_feature_extractor(num_classes=10)
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
frozen = sum(p.numel() for p in model.parameters() if not p.requires_grad)
print(f"trainable: {trainable:>10,}")
print(f"frozen:    {frozen:>10,}")

Numai model.fc poate fi antrenat. Backbone-ul este un extractor de caracteristici înghețat.

Pasul 3: Ajustare fină discriminatorie

O utilitate care construiește grupuri de parametri cu rate de învățare specifice etapei.

def discriminative_param_groups(model, base_lr=1e-3, decay=0.3):
    stages = [
        ["conv1", "bn1"],
        ["layer1"],
        ["layer2"],
        ["layer3"],
        ["layer4"],
        ["fc"],
    ]
    groups = []
    for i, names in enumerate(stages):
        lr = base_lr * (decay ** (len(stages) - 1 - i))
        params = [p for n, p in model.named_parameters()
                  if any(n.startswith(k) for k in names)]
        if params:
            groups.append({"params": params, "lr": lr, "name": "_".join(names)})
    return groups

model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10)
for p in model.parameters():
    p.requires_grad = True

groups = discriminative_param_groups(model)
for g in groups:
    print(f"{g['name']:>10s}  lr={g['lr']:.2e}  params={sum(p.numel() for p in g['params']):>8,}")

decay=0.3 înseamnă că fiecare etapă se antrenează la 30% din rata următoarei etape. fc primește base_lr, layer4 primește 0.3 * base_lr, iar conv1 primește 0.3^5 * base_lr ≈ 0.00243 * base_lr. Sună extrem; empiric, funcționează.

Pasul 4: Tratarea BatchNorm

O funcție auxiliară pentru înghețarea statisticilor curente BN fără înghețarea ponderilor sale.

def freeze_bn_stats(model):
    for m in model.modules():
        if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d)):
            m.eval()
            for p in m.parameters():
                p.requires_grad = False
    return model

Apelați-o după ce setați model.train() la începutul fiecărei epoci. model.train() comută totul în modul de antrenare; aceasta inversează setarea numai pentru straturile BN.

Notă tehnică a traducerii: Contrar propoziției introductive, funcția de mai sus îngheață și parametrii afini ai BatchNorm (weight și bias), nu doar statisticile curente. Pentru a îngheța numai statisticile, păstrați m.eval() și omiteți bucla care setează requires_grad = False; vedeți documentația PyTorch BatchNorm2d.

Pasul 5: O buclă minimală de ajustare fină de la un capăt la altul

from torch.optim import SGD
from torch.utils.data import DataLoader
from torch.optim.lr_scheduler import CosineAnnealingLR
import torch.nn.functional as F

def fine_tune(model, train_loader, val_loader, device, epochs=5, base_lr=1e-3, freeze_bn=False):
    model = model.to(device)
    groups = discriminative_param_groups(model, base_lr=base_lr)
    optimizer = SGD(groups, momentum=0.9, weight_decay=1e-4, nesterov=True)
    scheduler = CosineAnnealingLR(optimizer, T_max=epochs)

    for epoch in range(epochs):
        model.train()
        if freeze_bn:
            freeze_bn_stats(model)
        tr_loss, tr_correct, tr_total = 0.0, 0, 0
        for x, y in train_loader:
            x, y = x.to(device), y.to(device)
            logits = model(x)
            loss = F.cross_entropy(logits, y, label_smoothing=0.1)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            tr_loss += loss.item() * x.size(0)
            tr_total += x.size(0)
            tr_correct += (logits.argmax(-1) == y).sum().item()
        scheduler.step()

        model.eval()
        va_total, va_correct = 0, 0
        with torch.no_grad():
            for x, y in val_loader:
                x, y = x.to(device), y.to(device)
                pred = model(x).argmax(-1)
                va_total += x.size(0)
                va_correct += (pred == y).sum().item()
        print(f"epoch {epoch}  train {tr_loss/tr_total:.3f}/{tr_correct/tr_total:.3f}  "
              f"val {va_correct/va_total:.3f}")
    return model

Cinci epoci cu rețeta de mai sus pe CIFAR-10 duc ResNet18-IMAGENET1K_V1 de la aproximativ 70% acuratețe zero-shot cu sondă liniară la aproximativ 93% acuratețe după ajustarea fină. Numai capul ar atinge un platou în jurul a 86%, fără să atingă backbone-ul.

Notă tehnică a traducerii: Aceste procente sunt rezultate orientative ale unei rețete concrete, nu o garanție de reproductibilitate. Ele depind de împărțirea datelor, augmentări, preprocesare, numărul epocilor și versiunea ponderilor; consultați documentația torchvision pentru resnet18 pentru ponderi și transformările asociate.

Pasul 6: Dezghețarea progresivă

Un program care dezgheață o etapă pe epocă, de la final către început. Atenuează devierea caracteristicilor cu prețul unor epoci suplimentare.

def progressive_unfreeze_schedule(model):
    stages = ["layer4", "layer3", "layer2", "layer1"]
    yielded = set()

    def start():
        for p in model.parameters():
            p.requires_grad = False
        for p in model.fc.parameters():
            p.requires_grad = True

    def unfreeze(epoch):
        if epoch < len(stages):
            name = stages[epoch]
            yielded.add(name)
            for n, p in model.named_parameters():
                if n.startswith(name):
                    p.requires_grad = True
            return name
        return None

    return start, unfreeze

Apelați start() o dată înaintea primei epoci. Apelați unfreeze(epoch) la începutul fiecărei epoci. Reconstruiți optimizatorul ori de câte ori se schimbă setul parametrilor antrenabili; altfel, parametrii înghețați păstrează momente memorate care îl încurcă.

Notă tehnică a traducerii: Nu este întotdeauna necesară reconstruirea optimizatorului la dezghețare. Dacă parametrul se află deja într-un grup al optimizatorului, activarea lui prin requires_grad = True este suficientă; dacă a fost omis din grupuri, adăugați-l prin optimizer.add_param_group(...). Reconstruirea resetează starea curentă, inclusiv momentele parametrilor deja activi; consultați PyTorch Optimizer.add_param_group.

Utilizați-l

Pentru majoritatea sarcinilor reale, torchvision.models și trei linii sunt suficiente. Mecanismele mai grele de mai sus contează când întâlniți probleme pe care valorile implicite ale bibliotecii nu le pot rezolva.

from torchvision.models import resnet50, ResNet50_Weights

model = resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
model.fc = nn.Linear(model.fc.in_features, num_classes)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)

Alte două valori implicite de nivel de producție:

  • timm oferă aproximativ 800 de backbone-uri de viziune preantrenate, cu un API consecvent (timm.create_model("resnet50", pretrained=True, num_classes=10)). Pentru orice ajustare fină dincolo de colecția torchvision, este standardul.
  • Pentru Transformere, transformers.AutoModelForImageClassification.from_pretrained(name, num_labels=N) vă oferă ViT / BEiT / DeiT cu aceeași semantică de încărcare ca modelele de text.

Notă tehnică a traducerii: „Aproximativ 800” este un număr dependent de versiune și se poate învechi. Consultați documentația timm pentru catalogul curent și compatibilitatea modelelor.

Livrați-l

Această lecție produce:

  • outputs/prompt-fine-tune-planner.md — un prompt care alege între extragerea de caracteristici, ajustare fină progresivă și ajustare fină de la un capăt la altul în funcție de dimensiunea setului de date, distanța dintre domenii și bugetul de calcul.
  • outputs/skill-freeze-inspector.md — o abilitate care, pentru un model PyTorch dat, raportează ce parametri pot fi antrenați, ce straturi BatchNorm sunt în modul eval și dacă optimizatorul primește efectiv parametrii antrenabili.

Exerciții

  1. (Ușor) Antrenați un ResNet18 ca sondă liniară (backbone înghețat) și cu ajustare fină completă pe același set CIFAR sintetic. Raportați ambele acurateți alăturat. Explicați ce diferență arată că caracteristicile se transferă bine și ce diferență arată că nu se transferă bine.
  2. (Mediu) Introduceți intenționat o eroare: setați base_lr = 1e-1 pe etapa backbone-ului în locul capului. Arătați explozia pierderii de antrenare, apoi recuperați prin aplicarea funcției auxiliare discriminative_param_groups. Notați LR la care fiecare etapă începe să divergă.
  3. (Dificil) Alegeți un set de date de imagistică medicală (de exemplu, CheXpert-small, PatchCamelyon sau HAM10000) și comparați trei regimuri: (a) backbone ImageNet preantrenat înghețat + cap liniar; (b) ajustare fină de la un capăt la altul a modelului preantrenat pe ImageNet; (c) antrenare de la zero. Raportați acuratețea și costul de calcul pentru fiecare. La ce dimensiune a setului de date devine competitivă antrenarea de la zero?

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
Extragerea de caracteristici „Înghețați și antrenați capul” Parametrii backbone-ului sunt înghețați; numai capul nou de clasificare primește gradient
Ajustare fină „Reantrenați de la un capăt la altul” Toți parametrii pot fi antrenați, de obicei cu o LR mult mai mică decât la antrenarea de la zero
LR discriminatorie „LR mai mică pentru straturile timpurii” Grupuri de parametri ale optimizatorului în care LR a etapelor timpurii este o fracțiune din LR a etapelor târzii
Decăderea LR pe straturi „Gradient LR neted” LR pe fiecare strat, înmulțită cu decay^(L - k); frecventă în ajustările fine Transformer
Uitare catastrofică „Modelul a pierdut ImageNet” O LR prea mare suprascrie caracteristicile preantrenate înainte ca semnalul noii sarcini să fie învățat
Deriva statisticilor BN „Media curentă este greșită” running_mean/running_var BatchNorm sunt calculate pe o distribuție diferită de sarcina curentă și reduc pe tăcute acuratețea
Sondă liniară „Backbone înghețat + cap liniar” Evaluarea caracteristicilor preantrenate — acuratețea celui mai bun clasificator liniar peste reprezentarea înghețată
Colaps catastrofic „Totul prezice o singură clasă” Apare la ajustarea fină cu o LR suficient de mare încât să distrugă caracteristicile înainte ca gradienții capului să se poată stabiliza

Lecturi suplimentare

Notă tehnică a traducerii: Adresa 1805.08974 corespunde lucrării Do Better ImageNet Models Transfer Better? de Kornblith și colaboratorii, nu titlului afișat în lista originală; ea rămâne relevantă pentru evaluarea transferului prin sondă liniară.

Sursă: Originalul în limba engleză

Navigare: ← Lecția 04.04 — Clasificarea imaginilor · Faza 4 — Viziune computerizată · Catalog complet