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

Geef de zoekrichting een geheugen

Bouw momentum, controleer de gekozen updateconventie en vergelijk echte trajecten in een smal verliesdal.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je bewaart een snelheidsvector tussen updates en legt de gebruikte conventie vast.
  • Je onderzoekt wanneer momentum een dal versnelt of juist overschrijdt.

Een minibatch kan je model de ene keer naar links duwen en de volgende keer naar rechts. Een optimizer met momentum kijkt daarom ook naar eerdere zoekrichtingen. Consequente beweging kan optellen, terwijl afwisselende componenten elkaar deels opheffen.

Denk aan een duw waarvan een deel nog doorwerkt bij de volgende stap. De extra toestand heet hier de snelheid v. Dit geheugen kan helpen, maar kan je ook voorbij het laagste punt voeren; meer momentum is geen algemene verbetering.

1. Tel de huidige duw bij het geheugen

Bekijk eerst het smalle dal. Groen gebruikt alleen de huidige gradiënt; oranje bewaart momentum. Zet het aantal updates op één en daarna op twee: waarom kan een tweede stap weer hoger uitkomen, terwijl de eerste stap de fout verkleinde?

ÉÉN DAL, TWEE ZOEKTOCHTENVergelijk SGD en momentum

L=½(w₀²+20w₁²), start (3,1.5). Groen: SGD. Oranje: momentum. Beide krijgen dezelfde leersnelheid en hetzelfde aantal updates.

w₀w₁
SGD-verlies1.613Momentumverlies0.807

We gebruiken een vaste conventie: vermenigvuldig de oude snelheid met mu, tel de huidige gradiënt g erbij op en trek eta maal de nieuwe snelheid van w af. Mu ligt hier tussen nul en één; eta is de leersnelheid. De snelheid bevat zelf nog geen leersnelheid.

vt=μvt−1+gtwt=wt−1−ηvt

De index t telt updates. Begin bij w=1, v=0, g=2, mu=0.5 en eta=0.1. De nieuwe snelheid is 2 en het gewicht wordt 0.8. Bij mu=0 verdwijnt het geheugen en krijg je gewone gradient descent.

import numpy as np
def momentumstap(w,v,g,eta,mu):
    w = np.asarray(w,dtype=float)
    v = np.asarray(v,dtype=float)
    g = np.asarray(g,dtype=float)
    new_v = mu*v + g
    return w-eta*new_v, new_v

w, v = momentumstap([1.],[0.],[2.],.1,.5)
print(w.tolist(), v.tolist())  # [0.8] [2.0]

Maak momentumstap als (new_w,new_v), met twee NumPy-arrays van dezelfde vorm als de invoer. Verander w, v en g niet in-place. Andere bronnen kunnen eta al in de snelheid verwerken of de nieuwe gradiënt met 1−mu wegen; meng zulke conventies niet met deze opdracht.

01
CHECKPOINTKies één heldere conventie

2. Een tekenwissel wordt anders ontvangen

Laat de gradiënt eerst nogmaals 2 zijn en daarna −2. Vanuit nul snelheid geeft dat [2,3,−0.5]. Bij de derde stap weegt de vorige snelheid nog voor 1.5 mee; de negatieve nieuwe duw maakt de som −0.5, dus het gewicht beweegt weer omhoog.

w, v = np.array([1.]), np.array([0.])
for g in [2., 2., -2.]:
    w, v = momentumstap(w,v,[g],.1,.5)
    print(round(v[0],3), round(w[0],3))
# 2.0 0.8
# 3.0 0.5
# -0.5 0.55

Maak snelheden(gradients,mu) voor een lijst scalar-gradiënten, met beginwaarde v=0. Geef de nieuwe snelheid na iedere update als lijst terug. De hele opgegeven reeks telt mee, ook als de richting verandert.

Voor een constante gradiënt g komt iedere vorige duw met een extra factor mu terug: g, mu×g, mu²×g, enzovoort. De som van die steeds kleiner wordende bijdragen is een meetkundige reeks. Bij nul startsnelheid kun je de recurrentie onafhankelijk controleren met deze formule.

vt=g1−μt1−μvlimiet=g1−μvoor 0 ≤ μ < 1

Bij g=2 en mu=0.5 krijg je achtereenvolgens 2,3,3.5 en 3.75; de limiet is 4. Dezelfde eta kan dus later een grotere verplaatsing geven dan zonder geheugen. Vergelijk optimizers daarom niet alleen op de naam van hun leersnelheid.

def snelheden(gradients,mu):
    v, result = 0., []
    for g in gradients:
        v = mu*v + g
        result.append(v)
    return result

print(snelheden([2.]*4,.5))
# [2.0, 3.0, 3.5, 3.75]
02
CHECKPOINTEen constante duw stapelt op

3. Bereken het hele traject

Het getekende dal heeft verlies één helft van w₀²+20w₁² en gradiënt [w₀,20w₁]. De tweede richting is twintig keer zo steil. Vanuit [3,1.5] geeft eta=0.05 eerst [2.85,0]; bij mu=0.8 wordt de tweede positie [2.5875,−1.2].

L(w)=12(w02+20w12)∇L(w)=(w0,20w1)

De beginloss is 27 en na de eerste stap ongeveer 4.06125. De tweede stap met momentum geeft ongeveer 17.74758: geheugen heeft de steile richting voorbij nul geduwd. Het traject hoeft dus niet bij iedere stap te dalen.

Maak momentumtraject(start,eta,mu,steps) als NumPy-array van vorm (steps+1,2). Begin met nul snelheid en bewaar zowel de start als iedere nieuwe positie. Gebruik de eigen momentumstap met de actuele dalgradiënt.

def momentumtraject(start,eta,mu,steps):
    w = np.array(start,dtype=float)
    v = np.zeros_like(w)
    history = [w.copy()]
    for _ in range(steps):
        g = np.array([w[0],20*w[1]])
        w, v = momentumstap(w,v,g,eta,mu)
        history.append(w.copy())
    return np.array(history)

pad = momentumtraject([3,1.5],.05,.8,2)
print(np.round(pad,4))
# [[ 3.      1.5   ]
#  [ 2.85    0.    ]
#  [ 2.5875 -1.2   ]]
loss = .5*pad[:,0]**2 + 10*pad[:,1]**2
print(np.round(loss,5))
# [27.       4.06125 17.74758]

Bewaar kopieën van posities en verander de startarray niet. Als alle geschiedenismomenten naar dezelfde gewijzigde array wijzen, zie je achteraf alleen de eindpositie herhaald. We gebruiken hier niet-negatieve gehele aantallen stappen; nul stappen geeft alleen de start terug.

03
CHECKPOINTMeet een traject in twee richtingen

Wat neem je mee?

  • Momentum bewaart een gewogen spoor van eerdere gradiënten, met een expliciete updateconventie.
  • Constante duwen kunnen optellen; een tekenwissel en overshoot vragen aandacht.
  • Meet het echte traject en bewaar eerdere posities zonder ze mee te veranderen.

Hierna gebruikt Adam ook de grootte van eerdere gradiënten om iedere parameter anders te schalen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPPas de stapgrootte per parameter aan
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
Kies één heldere conventie

Schrijf momentumstap(w,v,g,eta,mu) als (new_w,new_v), beide NumPy-arrays met de invoervorm. Bewaar w,v,g onveranderd.

Een hint

Bereken eerst new_v uit de oude snelheid en huidige gradiënt, dan new_w.

02
Een constante duw stapelt op

Schrijf snelheden(gradients,mu) voor een lijst scalar-gradiënten, startend met v=0.

Een hint

Voeg na iedere update mu*v+g de nieuwe snelheid aan de lijst toe.

03
Meet een traject in twee richtingen

Schrijf momentumtraject(start,eta,mu,steps) als NumPy-array met steps+1 rijen.

Een hint

Gebruik de eigen momentumstap en bewaar kopieën van w zodat eerdere rijen niet mee veranderen.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def momentumstap(w,v,g,eta,mu):
    w,v,g=[np.asarray(a,dtype=float) for a in (w,v,g)]
    new_v=mu*v+g
    return w-eta*new_v,new_v

def snelheden(gradients,mu):
    v=0.0
    result=[]
    for g in gradients:
        v=mu*v+g
        result.append(v)
    return result

def momentumtraject(start,eta,mu,steps):
    w=np.asarray(start,dtype=float).copy()
    v=np.zeros_like(w)
    history=[w.copy()]
    for _ in range(steps):
        w,v=momentumstap(w,v,np.array([w[0],20*w[1]]),eta,mu)
        history.append(w.copy())
    return np.array(history)

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