Träningsdiagnostik
Kunna läsa loss-kurvor, gradientnormer och lärhastighetssvep och avgöra vad som är fel.
Förkunskaper
Intuition
När träningen inte fungerar finns det en handfull orsaker, och de ser olika ut i kurvorna. Att kunna avläsa dem sparar dagar.
| Mönster | Diagnos | Åtgärd |
|---|---|---|
| Båda kurvorna faller och planar ut ihop | sunt | klart |
| Träning faller, validering vänder uppåt | överanpassning | tidigt stopp, mer data, regularisering |
| Båda planar ut högt | underanpassning | större modell, längre träning, bättre features |
| Kurvan hoppar vilt | för hög lärhastighet eller för liten batch | sänk lr, öka batch |
| Kurvan är helt platt från start | inget lär sig alls | se nedan |
| Kurvan går till NaN | explosion eller division med noll | gradientklippning, lägre lr, kontrollera data |
Den platta kurvan är den mest förvirrande. Den beror nästan alltid på något trivialt: glömd optimizer.step(), requires_grad=False någonstans, lärhastighet på 0, eller att förlusten beräknas på fel tensor.
Regel nummer ett i all felsökning: få modellen att överanpassa 10 exempel först. Klarar den inte att memorera tio datapunkter är felet i koden, inte i hyperparametrarna.
Formellt
Storheter att logga från början, inte när det redan gått fel:
| Storhet | Sund nivå | Vad avvikelsen betyder |
|---|---|---|
| Träningsförlust | faller | platt = buggen sitter i koden |
| Valideringsförlust | faller, sedan planar | vänder = överanpassning |
| Gradientnorm | 0,1–10 | → 0: försvinnande; > 100: explosion |
| Andel aktiva ReLU | 0,2–0,7 | nära 0: döda neuroner |
| Vikternas norm | växer långsamt | rusar: för svag weight decay |
| Förhållandet uppdatering/vikt | ~1e-3 | > 1e-2: lr för hög; < 1e-4: för låg |
Den sista raden är det mest underskattade måttet. Logga per lager: den säger direkt om lärhastigheten är rimlig, oberoende av modellens skala.
Lärhastighetssvep (LR range test) hittar rätt storleksordning på några minuter:
- Börja på en mycket liten lr (1e-7).
- Öka exponentiellt varje batch upp till ~1.
- Rita förlusten mot lr i log-skala.
- Välj en lr strax före minimum — typiskt en tiondel av den lr där förlusten är som lägst, eftersom kurvan redan är på väg att bli instabil där.
Felsökningsordningen, i tur och ordning:
| # | Kontroll |
|---|---|
| 1 | Kan modellen överanpassa 10 exempel till nära noll förlust? |
| 2 | Är förlusten vid start rimlig? (ln(antal klasser) för korsentropi) |
| 3 | Är etiketterna rätt kopplade till indata? |
| 4 | Är normaliseringen densamma i träning och utvärdering? |
| 5 | Kör LR-svepet |
| 6 | Först nu: övriga hyperparametrar |
Kontroll 2 är gratis och avslöjar mycket. Med 10 klasser ska den första förlusten vara ungefär . Är den 7 är något fel med initialiseringen eller etiketterna; är den 0,1 läcker facit in i modellen.
Kod
import math, torch, torch.nn as nn
# 1. Kan modellen överanpassa 10 exempel? Gör detta ALLTID först.
def kan_overanpassa(modell, x, y, steg=300, lr=1e-3):
x, y = x[:10], y[:10]
opt = torch.optim.Adam(modell.parameters(), lr=lr)
for _ in range(steg):
loss = nn.functional.cross_entropy(modell(x), y)
opt.zero_grad(); loss.backward(); opt.step()
return float(loss) # ska bli < 0.01; annars sitter felet i koden
# 2. Är startförlusten rimlig?
def kontrollera_start(modell, x, y, n_klasser):
with torch.no_grad():
f = float(nn.functional.cross_entropy(modell(x), y))
vantat = math.log(n_klasser)
print(f"start {f:.3f}, väntat ~{vantat:.3f}")
if abs(f - vantat) > 0.5:
print(" ⚠ kontrollera initialisering och etiketter")
# 3. Logga det som avslöjar problem
def diagnostik(modell, lr):
normer = {n: float(p.grad.norm()) for n, p in modell.named_parameters()
if p.grad is not None}
total = sum(v**2 for v in normer.values()) ** 0.5
forhallande = {n: lr * float(p.grad.norm()) / (float(p.norm()) + 1e-12)
for n, p in modell.named_parameters() if p.grad is not None}
return {"grad_norm": round(total, 4),
"minsta_lager": min(normer, key=normer.get),
"uppdatering_per_vikt": round(sum(forhallande.values()) / len(forhallande), 6)}
# 4. Lärhastighetssvep
def lr_svep(modell, dataloader, lr_min=1e-7, lr_max=1.0, steg=100):
opt = torch.optim.Adam(modell.parameters(), lr=lr_min)
faktor = (lr_max / lr_min) ** (1 / steg)
lrs, losses = [], []
it = iter(dataloader)
for i in range(steg):
try:
x, y = next(it)
except StopIteration:
it = iter(dataloader); x, y = next(it)
loss = nn.functional.cross_entropy(modell(x), y)
opt.zero_grad(); loss.backward(); opt.step()
lrs.append(opt.param_groups[0]["lr"]); losses.append(float(loss))
if float(loss) > 4 * losses[0]: # spårat ur, avbryt
break
for g in opt.param_groups:
g["lr"] *= faktor
bast = lrs[int(min(range(len(losses)), key=lambda i: losses[i]))]
return {"lr_vid_minimum": bast, "rekommenderad": bast / 10}
rekommenderad = bast / 10 är tumregeln från Smiths LR range test: vid den lr där förlusten är som lägst är träningen redan på väg att bli instabil, så man tar en storleksordning under.
Behärskning innebär
- Diagnosticerar ur loss-kurvan
- Loggar och tolkar gradientnormer
- Kör ett lärhastighetssvep
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- PyTorch — tutorials (BSD-3) — BSD-3-Clause
- arXiv — Cyclical Learning Rates for Training Neural Networks — arXiv (öppen åtkomst; licens per artikel)
- Dive into Deep Learning (CC BY-SA 4.0) — CC BY-SA 4.0