Hoofdstuk 2
Het leren
Nu de vorm vastligt, kijken we onder de motorkap. Een getal stroomt door het netwerk (voorwaarts), we meten hoe fout het antwoord was, en dat verschil stroomt terug om elk gewicht een tikje bij te sturen (achterwaarts). Doe dat duizenden keren en het netwerk leert optellen.
Leren = gewichten bijstellen tot de fout klein is
Het hele netwerk is niets meer dan een grote berg getallen: de gewichten en biassen. “Leren” betekent simpelweg: die getallen stukje bij beetje aanpassen, zodat de antwoorden steeds minder fout worden. Eén leerstap bestaat altijd uit dezelfde drie fasen, die we nu één voor één bekijken.
Fase 1 — de voorwaartse doorgang
We sturen de 10 invoergetallen door het netwerk. Eerst de verborgen laag: elk verborgen neuron j telt alle invoeren op, elk met zijn eigen gewicht, plus een bias, en past dan ReLU toe.
h[j] = max(0, z₁[j]) ← ReLU
Daarna de uitvoerlaag: elk uitvoer-neuron k (voor som k) telt de 16 verborgen waarden op met zijn gewichten. Dat geeft 9 ruwe scores, de logits.
Hier betaalt de one-hot codering zich uit. Omdat in elke helft van x maar één getal een 1 is, valt bijna alles in die eerste som weg: W₁·x komt neer op het optellen van twee kolommen uit W₁ — de kolom van getal a en de kolom van getal b. Het netwerk “zoekt” dus voor elk getal een rijtje kenmerken op en telt die samen. Toepasselijk, voor een optelnetwerk.
Softmax
De 9 scores zijn nog willekeurige getallen, groot of klein, positief of negatief. We willen er kansen van maken: allemaal tussen 0 en 1, en samen precies 1. Dat doet softmax. Het neemt van elke score de exponentiële waarde (ez, altijd positief) en deelt door de som van allemaal:
De exponentiële functie versterkt verschillen: een score die wat hoger ligt, krijgt een flink grotere kans. Een klein rekenvoorbeeld met vier scores:
Het verlies: kruisentropie
We moeten de fout in één getal vatten, zodat het netwerk weet hoe goed het bezig is. Bij classificatie gebruiken we de kruisentropie. Ze kijkt enkel naar de kans die het netwerk gaf aan het juiste antwoord, en neemt daar de negatieve logaritme van:
De logica: gaf het netwerk de juiste som een hoge kans, dan is −ln daarvan klein (weinig straf). Gaf het een lage kans, dan schiet de straf omhoog. Het systeem wordt dus vooral afgestraft als het met overtuiging fout zit.
Stel het juiste antwoord kreeg kans 0.69. Dan is het verlies −ln(0.69) = 0.37 — klein. Maar kreeg datzelfde juiste antwoord maar 0.10, dan is het verlies −ln(0.10) = 2.30 — ruim zes keer zo groot. Naarmate de kans naar 1 kruipt, zakt het verlies naar 0.
Fase 2 — backpropagatie
Nu het interessante deel: hoe weten we voor élk van de 304 gewichten en 25 biassen welke kant het op moet? Daarvoor sturen we het foutsignaal terug door het netwerk. Dat heet backpropagatie, en het is in de kern gewoon de kettingregel uit de wiskunde — maar je hoeft hier vooral de vorm te zien, niet de afleiding.
Bij de uitvoer: verrassend eenvoudig
Voor de combinatie softmax + kruisentropie valt het foutsignaal bij de scores schitterend simpel uit. Het is gewoon “kans min gewenst”:
Hierbij is t de gewenste one-hot uitvoer (een 1 bij de juiste som, anders 0). Staat het netwerk te hoog op een fout antwoord, dan is dat verschil positief (“omlaag”); te laag op het juiste antwoord, dan negatief (“omhoog”). Elegant: het foutsignaal is letterlijk hoe ver elke kans van zijn doel zit.
Gradient voor de uitvoergewichten
Uit dat foutsignaal volgt meteen hoeveel elk uitvoergewicht moet veranderen. Een gewicht dat aan een sterk verborgen signaal hing, krijgt een grotere correctie:
Terug naar de verborgen laag
We sturen het foutsignaal verder terug naar de verborgen neuronen. Elk verborgen neuron krijgt de schuld toebedeeld via de gewichten waarmee het naar de uitvoer ging:
Maar een verborgen neuron telt alleen mee als het “aan” stond. ReLU zette negatieve waarden immers op 0, en wat 0 was, had geen invloed. De afgeleide van ReLU is dus een simpele poort: 1 als het neuron actief was, anders 0.
Gradient voor de invoergewichten
En net als bij de uitvoer volgt hieruit de correctie voor de gewichten van de verborgen laag:
Daarmee hebben we voor élk gewicht een richting: de gradient.
Fase 3 — gradient descent
De gradient wijst de richting waarin de fout stijgt. We willen ze laten dalen, dus zetten we elk gewicht een klein stapje in de tegengestelde richting. De stapgrootte is de leersnelheid η (hier 0.1):
Te grote stappen en het netwerk schiet door en wordt onstabiel; te kleine en het leert tergend traag. 0.1 is voor dit probleem een rustige, betrouwbare keuze.
Eén subtiliteit: we bekijken telkens álle 25 voorbeelden, tellen hun gradiënten op, en sturen pas daarna bij met het gemiddelde. Dat heet een volledige batch en geeft een gladde, stabiele daling. (Het alternatief — na elk los voorbeeld bijsturen — werkt ook, maar schommelt meer.)
De trainingslus
Dat is het volledige recept. Eén “epoch” is één keer alle voorbeelden doorlopen. We herhalen dat een paar duizend keer. In pseudocode:
herhaal voor elke epoch:
zet alle gradient-sommen op nul
voor elk voorbeeld (x, t):
# --- voorwaarts ---
z1 = W1 · x + b1
h = ReLU(z1)
z2 = W2 · h + b2
y = softmax(z2)
# --- achterwaarts ---
dz2 = y − t
tel dW2, db2 op (uit dz2 en h)
dh = som van W2[k][j] · dz2[k]
dz1 = dh, maar 0 waar z1 ≤ 0
tel dW1, db1 op (uit dz1 en x)
# --- bijsturen met de gemiddelde gradient ---
W ← W − η · (gradient / aantal voorbeelden)
b ← b − η · (gradient / aantal voorbeelden)
Bij het begin staan de gewichten willekeurig en zit het netwerk er meestal naast. Maar elke epoch wordt het verlies een tikje kleiner en de juistheid een tikje hoger — tot het alle 25 sommen juist heeft. In het volgende hoofdstuk gieten we dit recept letterlijk in Java, en zie je het echt gebeuren.