Hyperparametrar för djupa nät
Kunna prioritera vilka hyperparametrar som spelar roll och sätta upp ett svep.
Förkunskaper
- EHyperparametersökningkrävs
- ETräningsdiagnostikkrävs
Intuition
Det finns tiotals hyperparametrar. De flesta spelar nästan ingen roll, och att söka över alla är slöseri.
Prioritetsordning, med ungefärlig effektstorlek:
| Prioritet | Parameter | Effekt |
|---|---|---|
| 1 | Lärhastighet | enorm — kan avgöra om modellen lär sig alls |
| 2 | Batchstorlek (och lr tillsammans med den) | stor |
| 3 | Weight decay | måttlig |
| 4 | Lärhastighetsschema | måttlig |
| 5 | Modellstorlek och djup | stor, men dyr att söka |
| 6 | Dropout och augmentering | måttlig, störst vid lite data |
| 7 | Optimerarens β-värden | liten — låt vara |
| 8 | Initialisering | liten med moderna standardval |
Regel: sök över 1–4 och lämna resten på standardvärden. Att söka brett över allt ger sämre resultat på samma budget än att söka noggrant över det som betyder något.
Och kom ihåg: bättre data slår nästan alltid bättre hyperparametrar. En timme på datakvalitet ger oftare mer än en dag på svep.
Formellt
Slumpsökning slår rutnätssökning. Bergstra & Bengio (2012) visade varför: med ett rutnät över två parametrar där bara den ena betyder något provar du samma få värden av den viktiga parametern om och om igen. Slumpsökning provar ett nytt värde varje gång.
Rutnät 3×3 (9 körningar) Slump (9 körningar)
viktig param: 3 unika värden viktig param: 9 unika värden
Sök i rätt skala. Lärhastighet och weight decay ska samplas log-uniformt, inte uniformt: skillnaden mellan 1e-5 och 1e-4 är lika betydelsefull som mellan 1e-3 och 1e-2.
| Parameter | Skala | Typiskt intervall |
|---|---|---|
| Lärhastighet | log | 1e-5 – 1e-2 |
| Weight decay | log | 1e-6 – 1e-1 |
| Dropout | linjär | 0 – 0,5 |
| Batchstorlek | log₂ | 16 – 512 |
| Antal lager | heltal | 2 – 12 |
Batchstorlek och lärhastighet hänger ihop. Ökar du batchen behöver du oftast höja lr. Två tumregler cirkulerar: linjär skalning (, bra för SGD på bildnät) och kvadratrotsskalning (, ofta bättre för Adam). Behandla dem som utgångspunkter, inte lagar — men sök inte de två oberoende av varandra, det slösar budget.
Bättre än ren slumpsökning, när budgeten är knapp:
| Metod | Idé |
|---|---|
| Successive halving / Hyperband | starta många, döda de sämsta tidigt, ge resten mer budget |
| Bayesiansk optimering | modellera resultatet som funktion av parametrarna och sampla där det ser lovande ut |
| Population based training | låt körningar kopiera och mutera varandras parametrar under tiden |
Hyperband är oftast den bästa avvägningen mellan enkelhet och effekt.
Fällan: att överanpassa valideringsmängden. Provar du 500 konfigurationer och väljer den bästa på valideringen har du i praktiken tränat på den. Skillnaden mellan bästa och femte bästa är då ofta ren slump. Motmedel: håll en separat testmängd som bara används en gång, och rapportera spridningen över några frön — inte bara den bästa siffran.
Kod
import numpy as np, itertools, json
from pathlib import Path
RUM = {
"lr": ("log", 1e-5, 1e-2),
"weight_decay": ("log", 1e-6, 1e-1),
"dropout": ("lin", 0.0, 0.5),
"batch": ("log2", 4, 9), # 16 … 512
}
def sampla(rng):
ut = {}
for namn, (skala, lo, hi) in RUM.items():
if skala == "log":
ut[namn] = float(10 ** rng.uniform(np.log10(lo), np.log10(hi)))
elif skala == "log2":
ut[namn] = int(2 ** rng.integers(lo, hi + 1))
else:
ut[namn] = float(rng.uniform(lo, hi))
return ut
def hyperband(trana, budget_min=1, budget_max=27, eta=3, frö=0):
"""Starta många korta körningar, behåll de bästa, ge dem mer budget."""
rng = np.random.default_rng(frö)
n = int(eta ** np.floor(np.log(budget_max / budget_min) / np.log(eta)))
kandidater = [sampla(rng) for _ in range(n)]
budget = budget_min
while len(kandidater) > 1:
resultat = [(trana(k, epoker=budget), k) for k in kandidater]
resultat.sort(key=lambda r: r[0]) # lägre förlust är bättre
behall = max(1, len(kandidater) // eta)
print(f" budget {budget:>3} epoker: {len(kandidater)} → {behall} kandidater, "
f"bäst {resultat[0][0]:.4f}")
kandidater = [k for _, k in resultat[:behall]]
budget *= eta
return kandidater[0]
# Rapportera spridning, inte bara bästa siffran
def med_spridning(trana, params, fron=(0, 1, 2)):
v = [trana(params, frö=f) for f in fron]
return {"medel": round(float(np.mean(v)), 4), "std": round(float(np.std(v)), 4),
"alla": [round(float(x), 4) for x in v]}
# Logga allt — annars går sökningen inte att återanvända
def logga(kor, params, resultat, fil="svep.jsonl"):
with Path(fil).open("a", encoding="utf-8") as f:
f.write(json.dumps({"kor": kor, **params, **resultat}, ensure_ascii=False) + "\n")
med_spridning är det som skiljer ett användbart svep från ett vilseledande. Är standardavvikelsen mellan frön 0,01 och skillnaden mellan bästa och femte bästa konfiguration 0,005, har du inte hittat en bättre konfiguration — du har hittat ett lyckosamt frö.
Behärskning innebär
- Prioriterar hyperparametrar efter förväntad effekt
- Sätter upp ett svep med rätt söktyp och skala
- Undviker att överanpassa valideringsmängden
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- Bergstra & Bengio — Random Search for Hyper-Parameter Optimization (JMLR 2012) — öppen tillgång
- arXiv — Hyperband: A Novel Bandit-Based Approach to Hyperparameter Optimization — arXiv (öppen åtkomst; licens per artikel)
- Google — Deep Learning Tuning Playbook — CC BY 4.0