- Je leert preprocessing uitsluitend van de trainingsset.
- Je bewijst dat veranderende testdata de aangeleerde toestand niet beïnvloeden.
Je hebt planten keurig over training en test verdeeld. Toch gebruik je alvast alle hoogtes om een gemiddelde te berekenen waarmee je de gegevens voorbereidt. De test heeft dan invloed voordat het model zijn eerste voorspelling maakt.
Dit heet datalekkage (data leakage): informatie gebruiken die je tijdens het leren niet mocht kennen. Het kan een toekomstig antwoord zijn, maar ook een instelling die ongemerkt uit de testgegevens komt. We bouwen een kleine bewerking waarin je die invloed precies kunt aanwijzen.
1. Kies het nulpunt uitsluitend uit training
Stel je een meetlat voor waarop je een nieuw nulpunt markeert. Centreren (centering) betekent dat je van iedere waarde hetzelfde gemiddelde aftrekt. Je bewaart zo verschillen tussen waarden, maar beschrijft ze als afwijkingen van een gekozen referentie.
Onze trainingshoogtes zijn 2, 4 en 6. Hun som is 12 en hun aantal is 3, dus het gemiddelde is 4. De fit-stap, het leren van een instelling, slaat dat getal op voor later gebruik.
m noemt ons nieuwe nulpunt. Zoals bij de invulwaarde in de vorige module mag alleen training dit getal bepalen. De dictionary state, Engels voor toestand, bewaart de geleerde waarde onder sleutel "mean".
def fit_centrum(train):
if len(train) == 0:
raise ValueError("Lege training")
totaal = 0
for i in range(len(train)):
totaal = totaal + train[i]
return {"mean": totaal / len(train)}
train = [2, 4, 6]
state = fit_centrum(train)
print(state) # {'mean': 4.0}
Maak fit_centrum(train) met deze berekening. We oefenen met kleine eindige getallen; bij een lege trainingslijst geef je ValueError vóór de deling. Zonder waarnemingen is geen gemiddelde geleerd: nul teruggeven zou een verzonnen instelling opleveren.
De functie heeft bewust alleen train als argument. Zo kun je aan haar invoer al zien welke gegevens deze leerstap mogen beïnvloeden. Bewaar de oorspronkelijke lijst; de toestand bevat alleen het resultaat van de berekening.
2. Gebruik overal dezelfde meetlat
De transform-stap past de eerder geleerde instelling toe. Training 2, 4 en 6 wordt dus −2, 0 en 2. Een testplant met hoogte 100 wordt 96: ver boven het trainingsgemiddelde, precies wat de bewerking zichtbaar moet laten.
x is de oorspronkelijke waarde, m het bewaarde trainingsgemiddelde en z de gecentreerde waarde. Er komt hier geen nieuwe fit aan te pas. Je trekt simpelweg hetzelfde getal van iedere invoerwaarde af.
def centreer(xs, state):
nieuw = []
for i in range(len(xs)):
waarde = xs[i] - state["mean"]
nieuw.append(waarde)
return nieuw
test = [100]
print(centreer(train, state))
# [-2.0, 0.0, 2.0]
print(centreer(test, state)) # [96.0]
Maak centreer(xs, state) met een nieuwe uitvoerlijst. Verander xs en state niet; een lege lijst geeft weer een lege lijst. Of xs training, validatie of test bevat, verandert niets aan de gebruikte instelling.
Veelgemaakte fout: iedere verzameling rond haar eigen gemiddelde centreren. Voor de testlijst [100] zou je dan 100 aftrekken en nul krijgen. Dat verbergt het verschil met training én geeft de test een andere meetlat.
De volgende visualisatie houdt training vast op 2, 4 en 6. Zet Testwaarde op 100: de correcte fit blijft 4, maar fit op alle vier de waarden samen geeft 28. De correcte testuitvoer blijft 96.
gelekt = (2 + 4 + 6 + 100) / 4
print(gelekt) # 28.0
Training: 2, 4, 6. Verander alleen het onbekende testvoorbeeld. Groen leert uit training; oranje kijkt ook naar test.
Zet de schuif daarna op 20. De oranje fit verandert naar 8 terwijl de groene fit 4 blijft; alleen die groene instelling is onafhankelijk van test. Dit toont beïnvloeding van de voorbereiding, niet hoeveel een modelscore zou veranderen: we hebben hier nog geen model beoordeeld.
3. Bouw een controleerbare keten
Een pipeline is een vaste keten van bewerkingen. Onze keten leert één toestand op training en gebruikt haar daarna tweemaal: voor training en voor test. De drie resultaten gaan samen terug als tuple, zodat ook de geleerde instelling inspecteerbaar blijft.
Dit voorbeeld gebruikt de twee functies die je zojuist hebt gemaakt. De namen links bewaren hun resultaten; iedere aanroep voert één stap uit:
def centreer_pipeline(train, test):
state = fit_centrum(train)
a = centreer(train, state)
b = centreer(test, state)
return (a, b, state)
print(centreer_pipeline([2, 4, 6], [100]))
# ([-2.0, 0.0, 2.0], [96.0], {'mean': 4.0})
Voor training [2,4,6] en test [100] verwacht je ([-2,0,2], [96], {"mean":4}). Maak centreer_pipeline(train, test) en controleer ook een andere testlijst: met [10] wordt alleen de testuitvoer [6]. De trainingsuitvoer en toestand blijven precies gelijk.
Dit is een bruikbare controle-eigenschap: verander wat onbekend moest blijven en kijk of de geleerde toestand toch verandert. Ook bij het eerder behandelde imputeren hoort de invulwaarde deze controle te doorstaan. Een pipeline helpt alleen als je haar stappen daadwerkelijk de juiste gegevens geeft.
Wat neem je mee?
- Splits vóór je instellingen uit gegevens leert; ook voorbereiding kan informatie lekken.
- Fit leert het nulpunt uit training, transform gebruikt datzelfde nulpunt voor iedere verzameling.
- Een andere testlijst mag de training en geleerde toestand niet veranderen.
Hierna onderzoek je welke records samen moeten blijven als planten vaker gemeten worden of de tijd een rol speelt.