- 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.
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.
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.
Iedere kaart toont invoer → uitvoer. Tijdens training worden behouden activaties door 1−p gedeeld. Het gemiddelde van één trekking mag afwijken.
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.
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.
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.
03Wat 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.