Werkplaats / De werkplaats
In jouw eigen tempo
← Data
MODULE 02 · LES 04

Oefenen, kiezen en pas dan toetsen

Maak reproduceerbare train-, validatie- en testdelen zonder overlap.

45 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt leren van parameters, modelkeuze en uiteindelijke evaluatie.
  • Je maakt reproduceerbare, gescheiden datasets zonder de oorspronkelijke volgorde te veranderen.

Je wilt een model kiezen dat de bloei van nieuwe planten voorspelt. Als je steeds de toetsvragen bekijkt en daarna je aanpak aanpast, oefen je uiteindelijk op de toets. Een mooie score vertelt dan minder over planten die je echt nog niet hebt gezien.

We geven gegevens daarom drie verschillende rollen. Training leert de getallen van het model, validatie (validation) helpt een aanpak kiezen en test beoordeelt de gekozen aanpak pas aan het einde. In deze les bouw je de verdeling; het model zelf volgt later.

1. Schud herhaalbaar, zonder je bron te veranderen

Stel dat je wilt kiezen tussen een eenvoudige en een uitgebreidere voorspelregel. Beide leren op training; hun fouten op validatie helpen je kiezen. Daarna leg je de gekozen aanpak vast en meet je één keer op test, zonder die uitslag alsnog voor modelkeuze te gebruiken.

Voor losse, onafhankelijke records kun je eerst schudden. Denk aan kaartjes met record-ID's: je verandert de volgorde, niet welke kaartjes bestaan. Een seed is een startinstelling waarmee een toevalsgenerator dezelfde reeks keuzes kan herhalen.

Je kent import math; import random maakt nu de bibliotheek voor toevalskeuzes beschikbaar. random.Random(7) maakt een eigen generator met seed 7. Daarmee verander je niet de gedeelde generator die andere code misschien gebruikt.

import random
items = [1, 2, 3, 4, 5, 6]
kopie = list(items)
generator = random.Random(7)
generator.shuffle(kopie)
nogmaals = list(items)
random.Random(7).shuffle(nogmaals)
print(kopie == nogmaals)   # True
print(sorted(kopie))   # [1, 2, 3, 4, 5, 6]
print(items)           # [1, 2, 3, 4, 5, 6]

list(items) maakt een nieuwe lijst. .shuffle(kopie) herschikt die lijst zelf; gebruik daarom daarna kopie als resultaat, niet de terugkeerwaarde van shuffle. Voor dezelfde uitvoer maak je opnieuw een generator met dezelfde seed en geef je dezelfde oorspronkelijke lijst mee.

Maak geschud(items, seed) met deze stappen en geef de nieuwe lijst terug. De bronlijst blijft in haar oude volgorde. Gebruik een vaste seed om een experiment te herhalen, niet om steeds andere verdelingen te proberen totdat de testscore mooi uitvalt.

01
CHECKPOINTMaak willekeur reproduceerbaar

Veelgemaakte fout: reproduceerbaar verwarren met eerlijk. Een vaste seed bewijst niet dat je planten representatief zijn. Herhaalde metingen van dezelfde plant en tijdreeksen vragen bovendien een andere verdeling; die komen in de derde les.

2. Geef ieder kaartje precies één plek

Neem nu als aparte oefening de al geschudde lijst [4, 1, 6, 2, 5, 3]. We kiezen drie trainingsrecords en één validatierecord; er blijven twee testrecords over. Deze aantallen laten het mechanisme zien, maar zijn veel te klein voor een serieuze prestatiemeting.

6=3+1+2

Een slice kiest een stuk uit een Pythonlijst. In items[start:stop] telt start wel mee en stop niet; een weggelaten start betekent het begin, een weggelaten stop het einde. Posities beginnen bij nul, dus de eerste drie posities zijn 0, 1 en 2.

items = [4, 1, 6, 2, 5, 3]
n_train = 3
n_val = 1
grens = n_train + n_val
train = items[:n_train]
val = items[n_train:grens]
test = items[grens:]
print(train)   # [4, 1, 6]
print(val)     # [2]
print(test)    # [5, 3]

De drie stukken sluiten aan: positie 3 begint validatie, positie 4 begint test. Slices maken nieuwe lijsten, zodat deze verdeling de invoer niet herschikt. Maak split3(items, n_train, n_val) en geef het tuple (train, val, test) terug.

De aantallen zijn gehele getallen. Weiger met ValueError een negatief aantal of een som groter dan de lijstlengte; nul is toegestaan in deze mechanische oefening. Voor een echte beoordeling heb je uiteraard bruikbare gegevens in iedere benodigde rol nodig.

02
CHECKPOINTVerdeel zonder een rij kwijt te raken

3. Controleer identiteit over alle delen

Een correcte lengte sluit overlap niet uit. Training [1, 2], validatie [2] en test [3] bevatten vier plekken maar slechts drie unieke records: ID 2 staat tweemaal in de verdeling. Ook [1, 1] binnen training is een dubbel record.

Gebruik de set uit de vorige les om alle geziene ID's te onthouden. Loop eerst langs de drie lijsten en daarbinnen langs hun ID's. Een al geziene ID betekent een fout, ongeacht of hij uit dezelfde lijst of een ander deel kwam.

delen = [[1, 2], [2], [3]]
gezien = set()
goed = True
for i in range(len(delen)):
    for j in range(len(delen[i])):
        id = delen[i][j]
        if id in gezien:
            goed = False
        gezien.add(id)
print(goed)   # False, ID 2 komt terug

Maak gescheiden(train, val, test): geef bij de eerste herhaling False, anders na alle controles True. De invoer bestaat uit lijsten met tekst-ID's of gehele getallen, geen dictionaries met volledige records. Zulke ID's kunnen in een set worden opgeslagen.

03
CHECKPOINTControleer de identiteit, niet alleen de aantallen

Deze controle ontdekt identieke ID's, geen kopieën met een nieuwe naam. Daarom blijven het datacontract en de eerdere controle op dubbele metingen nodig.

Wat neem je mee?

  • Training leert, validatie helpt kiezen en test blijft apart tot de aanpak vastligt.
  • Schud een kopie met een eigen generator; een vaste seed maakt dezelfde proef herhaalbaar.
  • Aansluitende slices verdelen posities; een setcontrole bewaakt unieke record-ID's.

De volgende les laat zien hoe de test toch kan meekijken, zelfs wanneer alle ID's netjes gescheiden zijn.

DE VOLGENDE STAPOok je voorbereiding kan het antwoord lekken
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
Maak willekeur reproduceerbaar

Maak geschud(items, seed): geef een nieuwe, herhaalbaar geschudde lijst terug. Behoud de bronlijst en gebruik een eigen randomgenerator.

Een hint

Maak list(items), maak random.Random(seed), roep shuffle op de kopie aan en geef die kopie terug.

02
Verdeel zonder een rij kwijt te raken

Maak split3(items, n_train, n_val) voor gehele aantallen en geef (train, val, test). Weiger negatieve aantallen of meer gevraagde records dan beschikbaar.

Een hint

Controleer eerst beide aantallen en hun som. Gebruik daarna de drie aansluitende slices uit het voorbeeld.

03
Controleer de identiteit, niet alleen de aantallen

Maak gescheiden(train, val, test) voor lijsten met tekst-ID's of gehele getallen. Geef False bij interne dubbelen of overlap tussen delen, anders True.

Een hint

Loop langs de drie lijsten met één gedeelde set. Een al geziene ID betekent direct False; geef pas na alle controles True.

Vastgelopen? Bekijk de voorbeeldoplossing
def geschud(items, seed):
    import random
    kopie = list(items)
    generator = random.Random(seed)
    generator.shuffle(kopie)
    return kopie


def split3(items, n_train, n_val):
    if n_train < 0:
        raise ValueError("Negatieve trainingsgrootte")
    if n_val < 0:
        raise ValueError("Negatieve validatiegrootte")
    grens = n_train + n_val
    if grens > len(items):
        raise ValueError("Meer gevraagd dan beschikbaar")
    train = items[:n_train]
    val = items[n_train:grens]
    test = items[grens:]
    return (train, val, test)


def gescheiden(train, val, test):
    delen = [train, val, test]
    gezien = set()
    for i in range(len(delen)):
        for j in range(len(delen[i])):
            id = delen[i][j]
            if id in gezien:
                return False
            gezien.add(id)
    return True

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