Werkplaats / De werkplaats
In jouw eigen tempo
← Data
MODULE 03 · LES 09

Een grote dataset kan toch eenzijdig zijn

Meet klassenverhoudingen, bereken gewichten en ontdek ontbrekende groepen.

50 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je meet class imbalance en berekent gebalanceerde gewichten.
  • Je onderscheidt een gewogen verlies van ontbrekende dekking in de dataset.

Van honderd gemeten planten bloeien er negentig rood en tien blauw. Een voorspeller die altijd rood zegt, heeft dan negentig antwoorden goed zonder ooit blauw te herkennen. Eén totaalscore kan dus verbergen voor wie een model slecht werkt.

Klassenonbalans (class imbalance) betekent dat de aantallen per antwoordklasse verschillen. Selectiebias (selection bias) gaat over welke situaties überhaupt in je gegevens terechtkomen. We gaan eerst tellen, daarna een mogelijke weging berekenen en ten slotte ontbrekende dekking zichtbaar maken.

1. Geef ieder label zijn eigen teller

Een klasse is hier een mogelijk antwoordlabel, bijvoorbeeld A of B. Neem acht oefenlabels: zes A en twee B. Hun aantallendictionary moet {"A":6, "B":2} worden; de oorspronkelijke labels blijven de sleutels.

Denk aan twee bakjes waarin je per waarneming één fiche legt. Bij een nog onbekend label maak je eerst een leeg bakje. Daarna tel je precies één op, ook bij de eerste waarneming van die klasse.

def klasse_aantallen(labels):
    counts = {}
    for i in range(len(labels)):
        label = labels[i]
        if label not in counts:
            counts[label] = 0
        counts[label] = counts[label] + 1
    return counts

labels = ["A", "A", "A", "A",
          "A", "A", "B", "B"]
counts = klasse_aantallen(labels)
print(counts)   # {'A': 6, 'B': 2}

Je kent not in bij dictionaries: het controleert hun sleutels. Maak klasse_aantallen(labels) met deze opbouw; een lege lijst geeft {}. Het label zelf kan tekst of een geheel getal zijn, maar blijft steeds dezelfde identiteit houden.

01
CHECKPOINTTel voordat je corrigeert

Deze telling beschrijft de verzamelde gegevens, niet automatisch de wereld buiten je dataset. Een zeldzame klasse hoeft niet kunstmatig even vaak voor te komen in het echte gebruik. Daarom volgt uit ongelijke aantallen nog niet welke correctie wenselijk is.

2. Bereken een bewuste foutweging

Een model krijgt tijdens het leren een verlies (loss): een getal dat aangeeft hoe slecht een voorspelling is. Je kunt de foutstraf per voorbeeld met een klassegewicht vermenigvuldigen. Dat verandert hoeveel invloed fouten uit een klasse op de totale straf hebben.

Een veelgebruikte keuze geeft iedere aanwezige klasse evenveel totaalgewicht. Met zes A en twee B wil je dat één B zwaarder telt dan één A. De volgende regel bereikt dat zonder records te kopiëren:

wc=NK×nc

N is het totale aantal voorbeelden, K het aantal aanwezige klassen en n met index c het aantal voorbeelden in klasse c. w met index c is het gewicht voor één voorbeeld uit die klasse. Bij ons zijn N=8 en K=2.

Voor A is het gewicht 8 gedeeld door 12, dus twee derde. Voor B is het 8 gedeeld door 4, dus 2. Zes keer twee derde en twee keer 2 geven beide totaalgewicht 4; we wegen voorbeelden, niet de klassenlabels zelf.

N = len(labels)
K = len(counts)
klassen = list(counts)
gewichten = {}
for i in range(len(klassen)):
    c = klassen[i]
    gewichten[c] = N / (K * counts[c])
print(gewichten["A"])  # 0.6666666666666666
print(gewichten["B"])  # 2.0
print(6 * gewichten["A"]) # 4.0
print(2 * gewichten["B"]) # 4.0

list(counts) maakt een lijst van dictionarysleutels, zodat de lus iedere aanwezige klasse eenmaal bezoekt. Maak gebalanceerde_gewichten(labels) met je telfunctie. Geef bij lege invoer {} vóór je deelt: dan bestaan er geen klassegewichten om te berekenen.

02
CHECKPOINTGeef aanwezige klassen evenveel totaalgewicht

Veelgemaakte fout: altijd K=2 gebruiken omdat het voorbeeld twee klassen heeft. Met alleen A is K=1 en krijgt ieder voorbeeld gewicht 1; met drie aanwezige klassen is K=3. Bereken K daarom uit de telling, niet uit een vooraf vast getal.

Gelijk totaalgewicht is een ontwerpkeuze, geen garantie op betere voorspellingen of eerlijke behandeling. Verkeerde antwoorden kunnen in je toepassing verschillende gevolgen hebben. Beoordeel daarom ook fouten per klasse; concrete meetmaten volgen in de ML-track.

3. Maak zichtbaar welke groepen je niet hebt gemeten

Stel dat je wilt voorspellen voor noord en zuid, maar de twee gemeten groepen zijn ["noord", "west"]. Noord heeft één van de twee metingen, dus fractie 0.5. Zuid heeft geen metingen, dus fractie 0; west blijft wel meetellen in de noemer.

groups = ["noord", "west"]
doelgroepen = ["noord", "zuid"]
counts = klasse_aantallen(groups)
fracties = {}
for i in range(len(doelgroepen)):
    g = doelgroepen[i]
    aantal = 0
    if g in counts:
        aantal = counts[g]
    fracties[g] = aantal / len(groups)
print(fracties) # {'noord':0.5, 'zuid':0.0}

Maak dekking(groups, doelgroepen) met een fractie voor iedere opgegeven doelgroep. Als groups leeg is, geef je overal 0.0 zonder deling. Bij geen doelgroepen is het resultaat een lege dictionary.

03
CHECKPOINTVergelijk dekking met een expliciet doel

De fracties hoeven niet tot één op te tellen: je rapporteert alleen de gevraagde groepen, terwijl alle waarnemingen in de noemer staan. Ze zeggen evenmin welke verdeling wenselijk zou zijn. Je hebt daarvoor kennis nodig van de toepassing en hoe de metingen zijn verzameld.

Gewichten voor aanwezige klassen maken geen voorbeelden van een ontbrekende groep. Bij zuid=0 moet je onderzoeken of je toepassing daar wel verantwoord beoordeeld kan worden. Meer kopieën van noord leveren die ontbrekende informatie niet.

Wat neem je mee?

  • Tel labels vóór je weegt; een goede totaalscore kan fouten bij een kleine klasse verbergen.
  • Klassegewichten verdelen foutstraf over aanwezige klassen, zonder nieuwe informatie te maken.
  • Rapporteer dekking ten opzichte van een expliciet doel en tel alle metingen in de noemer mee.

In het bouwproject combineer je de eerdere voorbereidingsstappen tot één pipeline en controleer je de uitvoer met een grafiek.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPBouw een herbruikbare data-pipeline
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
Tel voordat je corrigeert

Schrijf klasse_aantallen(labels), met een lege dictionary bij geen labels.

Een hint

Begin bij nul voor ieder nieuw label en verhoog per voorbeeld.

02
Geef aanwezige klassen evenveel totaalgewicht

Schrijf gebalanceerde_gewichten(labels); geef {} bij lege invoer.

Een hint

Bereken counts, N en K. Controleer dat count*gewicht voor iedere klasse N/K is.

03
Vergelijk dekking met een expliciet doel

Schrijf dekking(groups,doelgroepen) als dictionary van geobserveerde fracties; leeg betekent overal nul.

Een hint

Tel alle gemeten groepen; deel door het totale aantal metingen, niet alleen door de genoemde groepen.

Vastgelopen? Bekijk de voorbeeldoplossing
def klasse_aantallen(labels):
    counts = {}
    for i in range(len(labels)):
        label = labels[i]
        if label not in counts:
            counts[label] = 0
        counts[label] = counts[label] + 1
    return counts


def gebalanceerde_gewichten(labels):
    counts = klasse_aantallen(labels)
    if len(labels) == 0:
        return {}
    N = len(labels)
    K = len(counts)
    klassen = list(counts)
    gewichten = {}
    for i in range(len(klassen)):
        c = klassen[i]
        gewichten[c] = N / (K * counts[c])
    return gewichten


def dekking(groups, doelgroepen):
    counts = klasse_aantallen(groups)
    fracties = {}
    for i in range(len(doelgroepen)):
        g = doelgroepen[i]
        fractie = 0.0
        if len(groups) > 0:
            aantal = 0
            if g in counts:
                aantal = counts[g]
            fractie = aantal / len(groups)
        fracties[g] = fractie
    return fracties

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