Werkplaats / De werkplaats
In jouw eigen tempo
← Deep learning
MODULE 03 · LES 07

Hergebruik features en kies wat mag veranderen

Bevries een encoder, train een nieuwe head en maak fine-tuning een expliciete keuze per parametergroep.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt vaste feature-extractie van fine-tuning.
  • Je traint een nieuwe classifier en bewijst dat bevroren parameters niet veranderen.
  • Je behandelt trainingstoestand en dataverschuiving apart van de updatekeuze.

Je hebt een encoder die beeldpatronen herkent, maar voor een nieuwe taak weinig voorbeelden. Alles opnieuw leren kan bruikbare kennis weggooien. Bij transfer learning hergebruik je een representatie en leer je bijvoorbeeld alleen een nieuwe classifier erbovenop.

Zie de encoder als een meetinstrument en de classifierhead als een beslisregel op die metingen. Je kunt het instrument vastzetten of voorzichtig mee aanpassen, fine-tuning genoemd. Onze kleine arrays zijn gecontroleerde oefenwaarden, geen voorgetraind model; in een echt project kun je de feature-extractor uit je CNN hergebruiken.

1. Bereken features met een vaste encoder

De encoder krijgt invoermatrix X, gewichten E en bias c en levert features H. Elke rij blijft één voorbeeld. Voor deze oefening gebruiken we tanh als activatie; bevriezen betekent dat de encodergewichten niet worden bijgewerkt.

H=tanh(XE+c)

Neem X=[[0],[1]], E=[[atanh(0.5)]] en c=[0]. Atanh is hier de inverse van tanh: E≈0.549306, zodat de features precies [[0],[0.5]] worden. Je verandert de oorspronkelijke invoer en encoder niet om die nieuwe waarden te maken; de twee rijen zijn twee voorbeelden die straks ieder een label krijgen.

import numpy as np

x = np.array([[0.],[1.]])
e = np.array([[np.arctanh(.5)]])
c = np.array([0.])
features = np.tanh(x@e+c)
print(np.round(features,6).tolist())
# [[0.],[.5]]

Maak frozen_features(x,encoder) met encoder-sleutels w en b. Zet de rekenarrays vóór het matrixproduct om naar floating point, zodat gehele getallen niet ongemerkt overlopen; retourneer een nieuwe array en behoud x en encoder. Vaste features kun je hergebruiken als encoder, preprocessing en evaluatiemodus gelijk blijven; noteer daarom modelversie en voorbereiding bij een echte featurecache.

01
CHECKPOINTVaste gewichten maken herbruikbare features

2. Leer een nieuwe binaire head

De head vormt een logit z=Hw+b en een sigmoidkans p. Het label y is nul of één. Met gemiddelde binaire cross-entropy wordt het foutsignaal per voorbeeld (p−y)/n, waarbij n de batchgrootte is; zo blijft de stap niet afhangen van alleen het aantal kopieën van dezelfde data.

e=p−yndw=HTedb=∑e

Gebruik nu dezelfde features [[0],[0.5]], met labels [0,1] en nulgewichten voor de nieuwe head. Beide kansen zijn 0.5; de gemiddelde fouten zijn [0.25,−0.25], dus dw=0×0.25+0.5×(−0.25)=−0.125 en db=0. Bij leersnelheid 0.2 wordt w=0.025 en b=0; het verlies vóór die stap is log(2)≈0.693147.

h = features
y = np.array([0.,1.])
w = np.zeros(1); b = 0.
z = h@w+b
p = np.exp(-np.logaddexp(0,-z))
error = (p-y)/len(y)
dw = h.T@error
db = error.sum()
w = w-.2*dw; b = b-.2*db
out = np.round(w,6).tolist()
print(out,round(b,6))
# [0.025] 0.0

Bereken het verlies stabiel als het gemiddelde van logaddexp(0,z)−yz. Rechtstreeks log(sigmoid(z)) nemen kan bij extreme logits log(0) opleveren; een correcte hoge zekerheid mag geen rekenfout veroorzaken. Het volgende voorbeeld toont het verlies op dezelfde twee voorbeelden na de update, lager dan vóór de update; de encoder is nog steeds gelijk.

z = h@w+b
loss = np.mean(
    np.logaddexp(0,z)-y*z)
print(round(float(loss),6))
# 0.690032

Maak train_head(features,labels,epochs=300,eta=.2), start w en b op nul en retourneer w,b,history. Bewaar één werkelijk verlies vóór iedere update en behoud features en labels; bij ongebalanceerde labels leert ook de bias mee.

Met nul epochs voer je geen updates uit: w en b blijven nul en history is leeg. De oefenfixture controleert het leermechanisme; kies instellingen in een echte taak met validatie en evalueer daarna pas op aparte testdata.

02
CHECKPOINTLeer alleen een nieuwe binaire head

3. Kies updates per parametergroep

Bevriezen is een expliciete updatekeuze. Neem encodergewichten [1,2], headgewicht [3] en gradiënten [0.5,−0.5] en [2]. Met alleen een headstap van 0.1 wordt de head 2.8 en blijft de encoder [1,2]; met een encoderstap van 0.01 erbij wordt die [0.995,2.005].

encoder = np.array([1.,2.])
head = np.array([3.])
ge = np.array([.5,-.5])
gh = np.array([2.])
print((head-.1*gh).tolist())
print((encoder-.01*ge).tolist())
# [2.8]
# [.995,2.005]

Maak selective_step(params,grads,rates). Alleen namen in rates krijgen hun eigen stap; geef een nieuwe dictionary met nieuwe arrays, ook voor bevroren groepen. Weiger onbekende namen en negatieve leersnelheden met ValueError; een nulrate is een geldige keuze zonder verandering.

DE UPDATEKEUZE IS EXPLICIETBevries de encoder, train de head

Eén echt berekende sigmoid-classifier: invoer 1 → tanh-encoder → head. De beginwaarden zijn een leerfixture, geen voorgetraind model. Iedere stap gebruikt de gradiënt van de huidige binaire cross-entropy.

Encodergewicht0.8000Bevroren · η=0gradiënt 0.4327
Headgewicht1.2000Leert mee · η=0.2gradiënt 0.4283
Headbias-0.2000Leert mee · η=0.2gradiënt 0.6449
Feature0.6640Kans op label 10.6449Verlies1.0355

0 stappen uitgevoerd. Bij een bevroren encoder blijft de feature gelijk terwijl de head leert. Dit experiment heeft één trainingspunt en meet geen generalisatie.

Zet een stap met de encoder bevroren en vergelijk de feature vóór en na de stap. Ontgrendel daarna de encoder: nu verandert ook het meetinstrument. Een kleine encoderstap kan bestaande kennis minder abrupt wijzigen, maar een representatie kan informatie missen die de nieuwe taak nodig heeft; vergelijk daarom op validatie met een passende baseline.

Let ook op toestand buiten gewichten. BatchNorm kan in trainmodus lopende statistieken aanpassen en dropout blijft willekeurig; vaste feature-extractie vraagt daarom een passende evaluatiemodus. Onze NumPy-encoder heeft zulke verborgen toestand niet: gewichten bevriezen en evaluatiemodus zijn in echte frameworks verschillende keuzes.

03
CHECKPOINTMaak fine-tuning expliciet en controleer alle toestand

Wat neem je mee?

  • Vaste features hergebruiken vereist dezelfde encoder en preprocessing.
  • De nieuwe head leert uit gemiddelde fouten; history bevat echte verliezen.
  • Fine-tuning kiest per groep een update, terwijl je ook andere modeltoestand bewaakt.

Hierna onderzoek je hoe netwerken informatie door een reeks vasthouden.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPLaat een netwerk zijn vorige toestand meenemen
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
Vaste gewichten maken herbruikbare features

Schrijf frozen_features(x,encoder) met encoder-sleutels w en b. Retourneer een nieuwe NumPy-array.

Een hint

Gebruik np.tanh(np.asarray(x)@encoder["w"]+encoder["b"]). Er hoort geen update of in-place bewerking in deze functie.

02
Leer alleen een nieuwe binaire head

Schrijf train_head(features,labels,epochs=300,eta=.2) met w,b,history. Laat features en labels ongewijzigd.

Een hint

error=(probability-labels)/n; grad_w=features.T@error en grad_b=sum(error).

03
Maak fine-tuning expliciet en controleer alle toestand

Schrijf selective_step(params,grads,rates). Geef een volledig nieuwe dictionary met gekopieerde arrays. Weiger onbekende namen of negatieve leersnelheden met ValueError.

Een hint

Controleer rates eerst. Gebruik v-rates[name]*grads[name] voor geselecteerde namen en v.copy() voor de rest.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def frozen_features(x,encoder):
    return np.tanh(np.asarray(x,float)@np.asarray(encoder["w"],float)+np.asarray(encoder["b"],float))

def train_head(features,labels,epochs=300,eta=.2):
    features,labels=np.asarray(features,float),np.asarray(labels,float)
    w=np.zeros(features.shape[1]);b=0.
    history=[]
    for _ in range(epochs):
        z=features@w+b
        probability=np.exp(-np.logaddexp(0,-z))
        error=(probability-labels)/len(labels)
        history.append(float(np.mean(np.logaddexp(0,z)-labels*z)))
        w,b=w-eta*(features.T@error),b-eta*float(error.sum())
    return {'w':w,'b':b,'history':history}

def selective_step(params,grads,rates):
    if any(k not in params or k not in grads or rate<0 for k,rate in rates.items()):
        raise ValueError("Ongeldige updatekeuze")
    return {k:v-rates[k]*grads[k] if k in rates else v.copy() for k,v in params.items()}

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