Werkplaats / De werkplaats
In jouw eigen tempo
← Neurale netwerken
MODULE 05 · LES 16

Beperk afhankelijkheid van één toevallige fit

Bouw een L2-straf en inverted dropout, inclusief het juiste masker voor de terugweg en een schone evaluatiemodus.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je berekent L2-verlies en gradiënten met een expliciete selectie van gewichten.
  • Je past dropout alleen tijdens training toe en hergebruikt het voorwaartse masker bij backward.

Een model kan de trainingsdata steeds beter passen terwijl de validatiefout stijgt. Regularisatie beïnvloedt welke oplossingen het leren verkiest. Je onderzoekt een straf op grote gewichten en het tijdelijk wegvallen van activaties tijdens training.

Deze twee ingrepen werken verschillend. Kies hun sterkte op validatiegegevens en houd de testset buiten die keuze. Regularisatie vervangt geen goede datasplit en garandeert niet dat ieder model beter wordt.

1. Straf alleen de gekozen gewichten

Een L2-straf maakt grote gewichten duurder. We kiezen expliciet welke parameterarrays meetellen; biases blijven in deze oefening buiten de selectie. Noem de sterkte lambda en de geselecteerde gewichtswaarden w.

R=λ2∑gekozen ww2∂R∂w=λw

Neem w1=[3,4], w2=[−2] en bias=[10], met lambda=0.1. De geselecteerde kwadraten tellen op tot 29, dus R=0.1×29/2=1.45. De extra gradiënten zijn [0.3,0.4], [−0.2] en voor de uitgesloten bias [0].

import numpy as np
def l2_straf(params,weight_names,lam):
    gradients = {
        k:np.zeros_like(v,dtype=float)
        for k,v in params.items()}
    penalty = 0.
    for name in weight_names:
        w = np.asarray(params[name],
                       dtype=float)
        penalty += lam/2*float((w*w).sum())
        gradients[name] = lam*w
    return penalty, gradients

params = dict(w1=np.array([3.,4.]),
              w2=np.array([-2.]),
              b=np.array([10.]))
r,g = l2_straf(params,['w1','w2'],.1)
print(round(r,4), g['b'].tolist())
# 1.45 [0.0]
print(np.round(g['w1'],4).tolist())
# [0.3, 0.4]
print(g['w2'].tolist())  # [-0.2]

Maak deze functie als (penalty,gradients): een getal en een nieuwe dictionary met NumPy-arrays voor álle parameters. Gebruik unieke bestaande namen in weight_names en een niet-negatieve lambda. Verander de oorspronkelijke parameterarrays niet; een lege selectie geeft alleen nulgradiënten.

De extra gradient tel je op bij die van de datafout. Bij gewone SGD lijkt dit op gewichten vermenigvuldigen met 1−eta×lambda. Bij Adam wordt de loss-gradiënt adaptief geschaald; een L2-straf is daardoor niet algemeen hetzelfde als een los toegepaste verkleining van de gewichten.

01
CHECKPOINTLeg vast welke parameters je bestraft

2. Laat activaties tijdelijk wegvallen

Door tijdens training activaties weg te laten, kan het netwerk minder op één vaste combinatie daarvan leunen.

Bekijk de kaarten in de visualisatie. Vergelijk train- en evaluatiemodus en trek een nieuw masker: welke waarden worden nul en welke worden groter? Het weergegeven gemiddelde hoeft bij één trekking niet gelijk te blijven.

TOEVAL BIJ TRAINEN, IDENTITEIT BIJ EVALUERENLaat activaties tijdelijk wegvallen

Iedere kaart toont invoer → uitvoer. Tijdens training worden behouden activaties door 1−p gedeeld. Het gemiddelde van één trekking mag afwijken.

activatie 10.5 →0.00lokale afgeleide 0.00
activatie 21 →0.00lokale afgeleide 0.00
activatie 31.5 →3.00lokale afgeleide 2.00
activatie 42 →0.00lokale afgeleide 0.00
activatie 50.25 →0.00lokale afgeleide 0.00
activatie 60.75 →0.00lokale afgeleide 0.00
activatie 71.25 →0.00lokale afgeleide 0.00
activatie 81.75 →0.00lokale afgeleide 0.00
Behouden1 / 8Uitvoergemiddelde0.375

Invoergemiddelde en verwachte trainingsuitvoer: 1.125. De maskerreeks is reproduceerbaar; een nieuw masker verandert de demonstratie.

Bij inverted dropout valt iedere activatie onafhankelijk weg met kans p. Een behouden waarde wordt door 1−p gedeeld. Het booleaanse masker m bevat True voor behouden en False voor uitgeschakeld, dus rekent als één of nul.

Di=mixi1−pE[Di]=xi

E betekent hier het gemiddelde over herhaalde maskertrekkingen. Bij p=0.5 wordt een waarde x met kans 0.5 verdubbeld en anders nul: gemiddeld blijft x over. Dit geldt per activatie; na verdere niet-lineaire lagen is het geen garantie voor dezelfde gemiddelde netwerkvoorspelling.

Neem als handvoorbeeld x=[1,2,3,4] en masker [True,False,True,False]. De uitvoer wordt [2,0,6,0]. Het invoergemiddelde is 2.5 en het uitvoergemiddelde van deze ene trekking 2, ondanks het verwachtingsbehoud.

x = np.array([1.,2.,3.,4.])
mask = np.array([True,False,True,False])
out = x*mask/(1-.5)
print(out.tolist())  # [2.0, 0.0, 6.0, 0.0]
print(x.mean(),out.mean())  # 2.5 2.0

Maak dropout(x,p,seed=7,training=True) als (out,mask), met een nieuwe NumPy-array en een booleaans masker van dezelfde vorm. Gebruik een lokale np.random.default_rng(seed) en behoud waar haar uniforme trekking minstens p is. Zo blijven seedkeuze en globale toevalsreeks gescheiden.

def dropout(x,p,seed=7,training=True):
    x = np.asarray(x,dtype=float)
    if not 0 <= p < 1:
        raise ValueError('p buiten [0,1)')
    if not training:
        return x.copy(),np.ones(
            x.shape,dtype=bool)
    rng = np.random.default_rng(seed)
    mask = rng.random(x.shape) >= p
    return x*mask/(1-p),mask

out, mask = dropout(x,.5,seed=7)
print(mask.tolist())
# [True, True, True, False]
print(out.tolist())  # [2.0, 4.0, 6.0, 0.0]

P ligt van nul tot strikt onder één; weiger ongeldige kansen met ValueError, ook in evaluatiemodus. Tijdens evaluatie geef je x als nieuwe kopie door, met een masker vol True. Deel dan niet nogmaals door de behoudkans.

02
CHECKPOINTBewaar de verwachting met inverted dropout

3. Gebruik hetzelfde masker op de terugweg

Voor het vastgelegde masker is de lokale afgeleide mask/(1−p). Vermenigvuldig het binnenkomende foutsignaal ermee. Een nieuw willekeurig masker zou bij een andere voorwaartse berekening horen en dus een verkeerde gradiënt geven.

Bij upstream=[0.1,0.2,0.3,0.4], ons handmasker [True,False,True,False] en p=0.5 krijg je [0.2,0,0.6,0]. Tijdens evaluatie is de operatie identiteit; het foutsignaal blijft dan gelijk. Geef ook dan een nieuwe array, zodat latere wijzigingen de oorspronkelijke upstream niet raken.

def dropout_terug(upstream,mask,p,
                  training=True):
    upstream = np.asarray(upstream,
                          dtype=float)
    if not training:
        return upstream.copy()
    return upstream*np.asarray(mask)/(1-p)

mask = [True,False,True,False]
back = dropout_terug([.1,.2,.3,.4],mask,.5)
print(back.tolist())  # [0.2, 0.0, 0.6, 0.0]

Maak deze functie met het meegegeven voorwaartse masker, de bijbehorende geldige p en dezelfde modus. Laat upstream en masker intact. Een vaste seed is handig om te controleren; tijdens echte training wil je opeenvolgende trekkingen laten variëren.

03
CHECKPOINTGebruik hetzelfde masker op de terugweg

Wat neem je mee?

  • L2 bestraft expliciet gekozen gewichten en voegt lambda maal het gewicht aan de gradiënt toe.
  • Inverted dropout bewaart de activatieverwachting over trekkingen, niet ieder afzonderlijk gemiddelde.
  • Bewaar masker en modus voor backward; evaluatie geeft activaties en gradiënten als nieuwe kopieën door.

Hierna stuur je het leertempo bij, begrens je grote gradiënten en bewaar je de beste validatieversie.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPStuur het leertempo en bewaar de beste stap
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
Leg vast welke parameters je bestraft

Schrijf l2_straf(params,weight_names,lam) als (penalty,gradients).

Een hint

Maak eerst nularrays voor alle parameters, tel alleen de geselecteerde kwadraten en zet daar lam*w als gradiënt.

02
Bewaar de verwachting met inverted dropout

Schrijf dropout(x,p,seed=7,training=True) als (out,mask), met een lokale NumPy-generator.

Een hint

Maak het masker met rng.random(x.shape)>=p. In evaluatiemodus is het masker overal True en out een kopie van x.

03
Gebruik hetzelfde masker op de terugweg

Schrijf dropout_terug(upstream,mask,p,training=True) als nieuwe array.

Een hint

Gebruik het meegegeven masker; roep geen randomgenerator aan in backward.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def l2_straf(params,weight_names,lam):
    gradients={k:np.zeros_like(v,dtype=float) for k,v in params.items()}
    penalty=0.0
    for name in weight_names:
        w=np.asarray(params[name],dtype=float)
        penalty+=lam/2*float((w*w).sum())
        gradients[name]=lam*w
    return penalty,gradients

def dropout(x,p,seed=7,training=True):
    x=np.asarray(x,dtype=float)
    if not 0<=p<1:
        raise ValueError("p moet in [0,1) liggen")
    if not training:
        return x.copy(),np.ones(x.shape,dtype=bool)
    mask=np.random.default_rng(seed).random(x.shape)>=p
    return x*mask/(1-p),mask

def dropout_terug(upstream,mask,p,training=True):
    upstream=np.asarray(upstream,dtype=float)
    return upstream*np.asarray(mask)/(1-p) if training else upstream.copy()

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