Werkplaats / De werkplaats
In jouw eigen tempo
← Neurale netwerken
MODULE 04 · LES 14

Pas de stapgrootte per parameter aan

Implementeer Adams momentenschattingen, biascorrectie en volledige update met blijvende optimizerstate.

90 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt het eerste moment en het ongecentreerde tweede moment van gradiënten.
  • Je bouwt Adam met een correcte stappenteller, biascorrectie en epsilon buiten de wortel.

Een parameter kan steeds grote gradiënten krijgen, terwijl een andere vooral kleine waarden ziet. Adam bewaart daarom zowel een gemiddelde richting als een gemiddelde kwadratische grootte. Daarmee schaalt hij de update voor iedere parameter afzonderlijk.

Je bouwt drie onderdelen: twee momentenschattingen, hun correctie voor de nulstart en de uiteindelijke stap. Bewaar die toestand tussen updates. Adam kan het afstellen van training helpen, maar garandeert geen beter model of globale convergentie voor ieder probleem.

1. Bewaar richting en grootte apart

De visualisatie laat zien hoe een gemiddelde vanuit nul op gang komt. Verhoog de stappenteller en keer daarna de gradiënt om. De gecorrigeerde oranje lijn verhelpt de nulstart, maar volgt een nieuwe richting nog steeds met vertraging.

DE STARTBIAS ZICHTBAAR GEMAAKTEen gemiddelde dat bij nul begint

Gradiënt g=2, eventueel −2 vanaf stap 11. Groen is m, oranje is het gecorrigeerde m̂. De grijze lijn is g.

-2-10121102030
m0.200m̂2.000v̂4.000

β₂=0.999. v̂ blijft hier vier: g² verandert niet bij een tekenwissel. Biascorrectie verwijdert de nulstartbias, niet de vertraging bij een veranderend signaal.

Het eerste moment m middelt de gradiënt g; het tweede moment v middelt g², elementgewijs. De letter v benoemt hier het tweede moment, niet de snelheid uit de momentumles. Beta1 en beta2 bepalen hoeveel van de vorige schatting blijft staan; beide zijn hier minstens nul en strikt kleiner dan één.

mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)gt2

Begin met m=v=0 en kies voor het handvoorbeeld beide beta's 0.5. Bij g=2 krijg je m=1 en v=2; bij een tweede component g=−1 worden die −0.5 en 0.5. Het kwadraat is altijd niet-negatief en wordt genomen vóór het middelen.

import numpy as np
def adam_momenten(m,v,g,beta1=.9,
                  beta2=.999):
    m = np.asarray(m,dtype=float)
    v = np.asarray(v,dtype=float)
    g = np.asarray(g,dtype=float)
    return (beta1*m+(1-beta1)*g,
            beta2*v+(1-beta2)*g*g)

m, v = adam_momenten([0,0],[0,0],
                     [2,-1],.5,.5)
print(m.tolist(), v.tolist())
# [1.0, -0.5] [2.0, 0.5]

Maak adam_momenten als tuple met twee nieuwe NumPy-arrays van dezelfde vorm als de invoer, zonder m, v of g te veranderen. Anders dan bij onze momentumconventie krijgt de nieuwe gradiënt hier wel factor 1−beta1. V is geen gecentreerde variantie: er wordt geen kwadraat van het gemiddelde afgetrokken.

01
CHECKPOINTOnthoud richting en kwadratische grootte

2. Compenseer het begin bij nul

Bij de eerste stap heeft m maar een deel van g ontvangen. De nulstart trekt de schatting dus naar nul. Deel daarom door één min beta tot de macht t, met t als het totale aantal uitgevoerde updates, vanaf één.

m^t=mt1−β1tv^t=vt1−β2t

Met beta 0.5 en t=1 deel je onze schattingen door 0.5. Je krijgt m-hat=[2,−1] en v-hat=[4,1], precies de oorspronkelijke gradiënten en hun kwadraten. De standaardbeta's in code zijn 0.9 en 0.999; de visualisatie houdt beta2 op 0.999.

def corrigeer_momenten(m,v,t,beta1=.9,
                       beta2=.999):
    if t < 1:
        raise ValueError('t begint bij 1')
    mh = np.asarray(m)/(1-beta1**t)
    vh = np.asarray(v)/(1-beta2**t)
    return mh, vh

mh, vh = corrigeer_momenten(m,v,1,.5,.5)
print(mh.tolist(), vh.tolist())
# [2.0, -1.0] [4.0, 1.0]

Maak deze functie met twee NumPy-arrays als resultaat; weiger een t kleiner dan 1 met ValueError en bewaar de invoer. Bij een constante gradiënt herstelt de correctie de constante waarden. Zij verwijdert de nulstartbias, niet alle vertraging wanneer het signaal verandert.

02
CHECKPOINTCompenseer de start bij nul

3. Bewaar ruwe momenten voor de volgende stap

De state is een dictionary met m, v en t. Verhoog t, werk beide ruwe momenten bij, corrigeer ze en bereken dan de nieuwe gewichten. Gebruik de gecorrigeerde arrays alleen voor de stap; bewaar de ruwe momenten in de nieuwe state.

wt=wt−1−ηm^tv^t+ε

Eta is de leersnelheid en epsilon een kleine positieve stabilisator, hier standaard 10⁻⁸. Zij staat buiten de wortel. De bewerkingen zijn elementgewijs, zodat iedere parameter zijn eigen noemer krijgt.

def adamstap(w,g,state,eta=.001,
             beta1=.9,beta2=.999,
             epsilon=1e-8):
    t = state['t'] + 1
    m,v = adam_momenten(state['m'],
                        state['v'],g,
                        beta1,beta2)
    mh,vh = corrigeer_momenten(
        m,v,t,beta1,beta2)
    stap = eta*mh/(np.sqrt(vh)+epsilon)
    new_w = np.asarray(w)-stap
    return new_w,dict(m=m,v=v,t=t)

w = np.array([1.])
state = dict(m=np.zeros(1),
             v=np.zeros(1),t=0)
for g in [[2.],[-1.],[-1.]]:
    w,state = adamstap(w,g,state,.1,.5,.5)
    print(round(w[0],6), state['t'])
# 0.9 1
# 0.9 2
# 0.947809 3

Bij de tweede gradiënt −1 worden de ruwe m=0 en v=1.5. Na delen door 0.75 blijft m-hat nul, dus de tweede gewichtsupdate is nul. Een reset van t of het bewaren van gecorrigeerde momenten zou de volgende stap veranderen.

Maak adamstap als (new_w,new_state), met een nieuwe NumPy-gewichtsarray en nieuwe dictionary. Bewaar w, g en alle oude statevelden intact. Eén state hoort bij één doorlopende optimizer; begin een nieuwe state wanneer je een nieuw trainingsexperiment start.

4. Wanneer is de eerste stap ongeveer eta?

Bij nulstate vereenvoudigt de af te trekken stap s tot eta×g/(|g|+epsilon). Alleen als |g| veel groter is dan epsilon, is de grootte ongeveer eta. Voor een heel kleine gradiënt kan epsilon juist overheersen.

s=ηg|g|+ε
g = np.array([2., 1e-12])
stap = .1*g/(np.abs(g)+1e-8)
print(np.round(stap,8).tolist())
# [0.1, 1e-05]

De tweede component beweegt hier ongeveer tienduizendmaal minder dan eta. Ook latere stappen hangen van eerdere gradiënten af. “Adam doet altijd dezelfde stap” is dus een verkeerde conclusie uit het eenvoudige eerste voorbeeld.

03
CHECKPOINTBewaar state en zet de adaptieve stap

Wat neem je mee?

  • Adam bewaart richting en ongecentreerde kwadratische grootte per parameter.
  • Correctie gebruikt de totale stappenteller; bewaar ruwe momenten voor de volgende update.
  • Epsilon staat buiten de wortel en kan de stap bij kleine gradiënten sterk beïnvloeden.

Hierna onderzoek je hoe normalisatie, dropout en leerschema's het trainingsgedrag verder veranderen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPKies welke waarden samen een schaal krijgen
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Onthoud richting en kwadratische grootte

Schrijf adam_momenten(m,v,g,beta1=.9,beta2=.999) als (new_m,new_v).

Een hint

Gebruik NumPy-arrays en g*g; werk de oude state niet in-place bij.

02
Compenseer de start bij nul

Schrijf corrigeer_momenten(m,v,t,beta1=.9,beta2=.999); werp ValueError voor t<1.

Een hint

De exponent is het totale aantal updates, niet een parameterindex.

03
Bewaar state en zet de adaptieve stap

Schrijf adamstap(w,g,state,eta=.001,beta1=.9,beta2=.999,epsilon=1e-8) als (new_w,new_state), met state-sleutels m,v,t.

Een hint

Bewaar m en v uit adam_momenten; gebruik alleen hun gecorrigeerde versies voor de parameterupdate.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def adam_momenten(m,v,g,beta1=.9,beta2=.999):
    m,v,g=[np.asarray(a,dtype=float) for a in (m,v,g)]
    return beta1*m+(1-beta1)*g,beta2*v+(1-beta2)*g*g

def corrigeer_momenten(m,v,t,beta1=.9,beta2=.999):
    if t<1:
        raise ValueError("Stappenteller begint bij een")
    return np.asarray(m)/(1-beta1**t),np.asarray(v)/(1-beta2**t)

def adamstap(w,g,state,eta=.001,beta1=.9,beta2=.999,epsilon=1e-8):
    t=state["t"]+1
    m,v=adam_momenten(state["m"],state["v"],g,beta1,beta2)
    mh,vh=corrigeer_momenten(m,v,t,beta1,beta2)
    updated=np.asarray(w,dtype=float)-eta*mh/(np.sqrt(vh)+epsilon)
    return updated,{"m":m,"v":v,"t":t}

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.