Werkplaats / De werkplaats
In jouw eigen tempo
← Neurale netwerken
MODULE 02 · LES 06

Van meerdere scores naar een stabiel verlies

Bouw softmax, cross-entropy uit logits en hun gecombineerde gradiënt zonder numerieke overflow.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je berekent multiclass-kansen stabiel, ook bij zeer grote logits.
  • Je leidt het foutsignaal p−y naar de scores terug en bewaakt de batchmiddeling.

Een berichtenfilter kiest nu uit drie klassen: normaal, reclame en phishing. Het netwerk geeft drie ruwe scores; we willen daar een verdeling en één trainingsfout van maken. Een heel hoge score mag de berekening niet laten overlopen.

Je kent de logistische kans voor twee klassen en het logaritmische verlies. Hier breid je die ideeën uit naar meerdere elkaar uitsluitende klassen. Per bericht is precies één label juist; dit is geen taak waarbij meerdere labels tegelijk waar kunnen zijn.

1. Scores worden een verdeling

Een logit is een onbegrensde score, geen kans. Softmax geeft een hogere score een groter aandeel: neem van iedere score de exponent en deel door de som van alle exponenten uit dezelfde rij.

DRIE SCORES, ÉÉN KANSVERDELINGBekijk de fout per klasse

Verander logits of het juiste label. De gradiënt is p−y voor dit ene voorbeeld. Een gemeenschappelijke verschuiving verandert de kansen niet.

00.250.50.7510.665klasse 0g=0.6650.245klasse 1g=0.2450.090klasse 2 ✓g=-0.910
Cross-entropy2.408Som kansen1.000

Werkelijke logits: [2, 1, 0]

pj=ezj∑k=0C−1ezk

Hier zijn z de scores, p de kansen en C het aantal klassen. De indices j en k beginnen bij nul. Verhoog in de visualisatie één score: de andere kansen dalen mee, ook wanneer hun scores gelijk blijven.

Neem scores [0,ln(2),0]. De exponenten zijn [1,2,1], hun som is 4 en de kansen zijn [¼,½,¼]. De tweede klasse krijgt dus de helft, maar zo'n softmax-uitkomst bewijst nog geen goed gekalibreerde zekerheid.

import numpy as np
z = np.array([[0., np.log(2.), 0.]])
e = np.exp(z)
p = e / e.sum(axis=1, keepdims=True)
print(p.tolist())  # [[0.25, 0.5, 0.25]]

Een batch heeft vorm (N,C): N voorbeelden als rijen, C klassen als kolommen. axis=1 rekent over de klassen binnen iedere rij; keepdims=True houdt de som als kolom (N,1), zodat NumPy iedere rij door haar eigen som deelt.

2. Dezelfde kansen, kleinere getallen

Bij scores rond 1000 kan np.exp(z) oneindig worden. Dezelfde constante van alle scores aftrekken verandert hun verhoudingen niet: de gemeenschappelijke factor in teller en noemer valt weg. Trek daarom per rij haar maximum af; de grootste exponent wordt dan één.

def softmax(logits):
    z = np.asarray(logits, dtype=float)
    s = z - z.max(axis=1, keepdims=True)
    e = np.exp(s)
    return e / e.sum(axis=1, keepdims=True)

groot = np.array([[1000., 1001., 1000.]])
print(np.round(softmax(groot), 6))
# [[0.211942 0.576117 0.211942]]

Maak softmax(logits) met een nieuwe NumPy-array als resultaat. De invoer blijft intact; schrijf dus niet met -= in een mogelijk gedeelde invoerarray. We geven hier geldige, niet-lege matrices met minstens twee klassen en eindige scores.

01
CHECKPOINTTrek eerst de grootste score af

3. Straf voor het juiste label

Cross-entropy neemt min de natuurlijke logaritme van de kans op het juiste label y. In ons handvoorbeeld is y = 1 en het verlies −ln(½) = ln(2), ongeveer 0.693147. Een lagere kans voor het juiste antwoord geeft een grotere straf.

Een uiterst kleine softmax-kans kan naar nul afronden. Bereken daarom het verlies rechtstreeks uit de verschoven scores s, zonder eerst de logaritme van die afgeronde kans te nemen.

ℓ=ln(∑kesk)−sy

Voeg een tweede bericht met gelijke scores toe, met label 0. Dat bericht krijgt verlies ln(3); de gemiddelde batchfout wordt (ln(2)+ln(3))/2, ongeveer 0.895880.

z = np.array([[0., np.log(2.), 0.],
              [0., 0., 0.]])
y = np.array([1, 0])
s = z - z.max(axis=1, keepdims=True)
rijen = np.arange(len(z))
juiste = s[rijen, y]
verliezen = np.log(np.exp(s).sum(axis=1))
verliezen = verliezen - juiste
print(round(float(verliezen.mean()), 6))
# 0.89588

rijen bevat [0,1]; samen met y selecteert dit precies de twee juiste posities. Maak cross_entropy(logits,labels) als gemiddelde Python-float, ook bij bijvoorbeeld scores [1000,−1000]. Scores en gehele klasseindices blijven onveranderd.

02
CHECKPOINTVermijd de logaritme van een afgeronde nul

4. Welke score moet veranderen?

De afgeleide van de log-som-exponent is p; de afgeleide van de afgetrokken juiste score is één op de labelpositie en nul elders. Het verschil geeft het foutsignaal naar de logits: bij het handvoorbeeld [¼,−½,¼]. Gradient descent verhoogt daardoor de juiste score en verlaagt de andere twee.

∂L∂zij=pij−YijN

L is het batchgemiddelde; Y is de one-hot-matrix met één op de juiste klasse en nul elders. Maak die matrix niet verplicht helemaal aan: trek één af op de labelposities en deel door N.

g = softmax(z)
g[np.arange(len(g)), y] -= 1
g = g / len(g)
print(np.round(g, 6))
# [[ 0.125    -0.25      0.125   ]
#  [-0.333333  0.166667  0.166667]]
print(np.allclose(g.sum(axis=1), 0))
# True

Maak logit_gradient(logits,labels) met dezelfde vorm als logits. Een veelgemaakte fout is later nogmaals door N delen: dan wordt de stap onbedoeld kleiner. Elke gradiëntrij telt hier tot nul op, passend bij de onveranderde kansen na een gelijke scoreverschuiving.

03
CHECKPOINTKansen min het gewenste antwoord

Wat neem je mee?

  • Softmax normaliseert per rij; een maximale score aftrekken voorkomt exponent-overflow.
  • Bereken cross-entropy uit logits, zodat een afgeronde nulkans het verlies niet breekt.
  • Het teruggaande signaal is kansen min one-hot-labels, met één batchmiddeling.

Hierna stuur je dit signaal door een verborgen laag terug naar alle netwerkgewichten.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPStuur het foutsignaal terug door een heel netwerk
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
Trek eerst de grootste score af

Schrijf softmax(logits) voor een tweedimensionale array, met normalisatie per rij.

Een hint

Gebruik max(axis=1,keepdims=True) en sum(axis=1,keepdims=True).

02
Vermijd de logaritme van een afgeronde nul

Schrijf cross_entropy(logits,labels) als gemiddeld verlies uit de logits, zonder eerst log(softmax) te berekenen.

Een hint

Selecteer per rij de juiste verschoven score met np.arange(len(z)) en de labelvector.

03
Kansen min het gewenste antwoord

Schrijf logit_gradient(logits,labels) als matrix met dezelfde vorm als logits.

Een hint

Begin met de nieuwe array van softmax, trek één af op de labelposities en deel door de batchgrootte.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def softmax(logits):
    z=np.asarray(logits,dtype=float)
    shifted=z-z.max(axis=1,keepdims=True)
    e=np.exp(shifted)
    return e/e.sum(axis=1,keepdims=True)

def cross_entropy(logits,labels):
    z=np.asarray(logits,dtype=float)
    labels=np.asarray(labels,dtype=int)
    s=z-z.max(axis=1,keepdims=True)
    return float(np.mean(np.log(np.exp(s).sum(axis=1))-s[np.arange(len(s)),labels]))

def logit_gradient(logits,labels):
    p=softmax(logits)
    p[np.arange(len(p)),np.asarray(labels,dtype=int)]-=1
    return p/len(p)

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