Werkplaats / De werkplaats
In jouw eigen tempo
← Klassieke machine learning
MODULE 02 · LES 07

Welke fouten maakt je classifier?

Lees een confusion matrix, bereken precision en recall en verschuif de beslisdrempel.

55 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt false positives en false negatives en berekent passende scores.
  • Je ziet hoe een drempel de foutverdeling verandert zonder het kansmodel opnieuw te trainen.

Een spamfilter kan echte mail blokkeren of spam doorlaten. Die fouten hebben verschillende gevolgen, ook als het totale aantal fouten gelijk is. Een verwarringsmatrix (confusion matrix) houdt bij welke verkeerde keuze het model maakte.

We noemen spam klasse 1, de positieve klasse. Positief betekent hier een gekozen label, niet iets wenselijks. Stel je vier vakken voor: de werkelijke klasse bepaalt de rij, de voorspelde klasse de kolom.

1. Geef elke beslissing precies één vak

Een true positive (TP) is terecht als spam aangemerkt. Een false positive (FP) is gewone mail die het model toch blokkeert; een false negative (FN) is gemiste spam. Bij een true negative (TN) blijven werkelijkheid en voorspelling beide 0.

Neem zes berichten met kansen [0.10,0.25,0.40,0.55,0.70,0.90] en werkelijke labels [0,1,0,1,0,1]. Kies voorlopig drempel 0.30: kansen minstens zo hoog worden klasse 1. Loop eerst zelf door de tabel en tel de vier uitkomsten.

KansEchtKeuzeUitkomst
0.1000TN
0.2510FN
0.4001FP
0.5511TP
0.7001FP
0.9011TP

Je krijgt TP = 2, FP = 2, FN = 1 en TN = 1. Samen zijn dat weer zes berichten. De volgende code kiest per paar precies één sleutel in een dictionary.

ys = [0, 1, 0, 1, 0, 1]
pred = [0, 0, 1, 1, 1, 1]
c = {"tp": 0, "fp": 0, "fn": 0, "tn": 0}
for i in range(len(ys)):
    if ys[i] == 1:
        if pred[i] == 1:
            vak = "tp"
        else:
            vak = "fn"
    else:
        if pred[i] == 1:
            vak = "fp"
        else:
            vak = "tn"
    c[vak] = c[vak] + 1
print(c)  # tp2, fp2, fn1, tn1

Maak verwarring(ys,pred) en geef de dictionary terug. Controleer eerst gelijke lengtes en alleen labels 0/1, anders ValueError. Twee lege lijsten geven vier nullen; niets waarnemen is geen bewijs van een perfect model.

01
CHECKPOINTTel de vier combinaties

2. Kies de vraag die bij de gevolgen past

Precisie (precision) vraagt: hoeveel van de als spam gekozen berichten zijn werkelijk spam? In ons voorbeeld zijn twee van de vier blokkades terecht, dus 1/2. Recall, ook gevoeligheid genoemd, vraagt hoeveel echte spam je vindt: twee van de drie, dus 2/3.

precision=TPTP+FPrecall=TPTP+FN

De letters verwijzen naar de vier tellingen, niet naar kansen. De twee noemers kiezen verschillende groepen: alle positieve voorspellingen tegenover alle werkelijke positieven. Precision en recall zijn daardoor niet uitwisselbaar.

F1 combineert beide via 2TP/(2TP+FP+FN), hier 4/7, ongeveer 0.571. Accuracy telt alle juiste keuzes, (TP+TN)/N; N is het totale aantal voorbeelden. Hier is accuracy 3/6 = 0.5.

F1=2TP2TP+FP+FNaccuracy=TP+TNN
def deel(teller, noemer):
    if noemer == 0:
        return None
    return teller / noemer

tp, fp = c["tp"], c["fp"]
fn, tn = c["fn"], c["tn"]
print(deel(tp, tp + fp))        # 0.5
print(deel(tp, tp + fn))        # 0.666...
print(deel(2 * tp, 2 * tp + fp + fn))
# ongeveer 0.571
print(deel(tp + tn, tp + fp + fn + tn))
# 0.5

Maak klasse_scores(c) met sleutels accuracy, precision, recall, f1. Ga uit van vier geldige niet-negatieve tellingen. Bij een noemer nul is de betreffende score None: zonder positieve voorspellingen kun je hun precision niet meten; zonder voorbeelden zijn alle vier scores ongedefinieerd.

Veelgemaakte fout: alleen accuracy melden bij zeldzame spam. Een filter dat alles doorlaat kan veel gewone mail juist behandelen, terwijl het iedere spam mist. F1 negeert juist de true negatives; geen van beide vervangt het bekijken van alle fouttypen.

02
CHECKPOINTStel verschillende vragen aan dezelfde tabel

3. Houd de kansen vast en verander de keuze

Bij drempel 0.50 worden de voorspellingen [0,0,0,1,1,1]. Nu is TP = 2, FP = 1, FN = 1 en TN = 2: je blokkeert één gewone mail minder. Precision stijgt van 1/2 naar 2/3, terwijl recall 2/3 blijft.

probs = [0.10, 0.25, 0.40, 0.55, 0.70, 0.90]
t = 0.50
pred = []
for i in range(len(probs)):
    keuze = 0
    if probs[i] >= t:
        keuze = 1
    pred.append(keuze)
print(pred)  # [0, 0, 0, 1, 1, 1]

Maak bij_drempel(ys,probs,t) door eerst deze keuzen te maken en dan verwarring te gebruiken. Kansen en drempel moeten in [0,1] liggen; anders ValueError. Een kans precies op de drempel telt als positief.

ZELFDE KANSEN, ANDERE KEUZESWelke fout laat je liever toe?

Zes fictieve spamkansen met vaste labels. Verhoog de drempel om minder berichten als spam aan te merken.

Rijen: werkelijk label · kolommen: voorspelling
WerkelijkheidGeen spamSpam
Geen spamTN 2FP 1
SpamFN 1TP 2
0.10echt: 0keuze: 0
0.25echt: 1keuze: 0
0.40echt: 0keuze: 0
0.55echt: 1keuze: 1
0.70echt: 0keuze: 1
0.90echt: 1keuze: 1
Precision0.667Recall0.667F10.667

Zet de schuif eerst op 0.30 en daarna op 0.50 en vergelijk met je berekening. Voorspel vervolgens 0.80: alleen het laatste bericht wordt spam, dus TP = 1, FN = 2, FP = 0 en TN = 3. Precision wordt 1, maar recall daalt naar 1/3.

Een hogere drempel geeft nooit méér positieve keuzes op dezelfde kansen. Precision hoeft echter niet bij iedere stap te stijgen: het hangt af van welke berichten verdwijnen. Kies je drempel op validatiedata met passende foutkosten, en houd de eindtest apart.

03
CHECKPOINTVerander de actie, niet de voorspelde kans

Wat neem je mee?

  • De matrix onderscheidt gemiste positieven van onterechte positieve keuzes.
  • Iedere score stelt een andere vraag; let op haar noemer en op ontbrekende waarnemingen.
  • Een andere drempel verandert beslissingen zonder het kansmodel opnieuw te trainen.

Hierna beoordeel je de kansen zelf, vóór je ze omzet naar ja of nee.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPBetekent 80% ook ongeveer acht van tien?
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 de vier combinaties

Schrijf verwarring(ys,pred) als dictionary met vier aantallen.

Een hint

Loop over alle paren en verhoog precies één van de vier tellers.

02
Stel verschillende vragen aan dezelfde tabel

Schrijf klasse_scores(c), met sleutels accuracy,precision,recall,f1.

Een hint

Gebruik een helper die None teruggeeft bij noemer nul.

03
Verander de actie, niet de voorspelde kans

Schrijf bij_drempel(ys,probs,t) als confusion dictionary voor de gegeven drempel.

Een hint

Maak met een lus label 1 als p minstens t is, anders 0; geef de labels aan verwarring. Controleer het bereik voordat je kiest.

Vastgelopen? Bekijk de voorbeeldoplossing
def verwarring(ys, pred):
    if len(ys) != len(pred):
        raise ValueError("Ongelijke aantallen labels")
    c = {"tp": 0, "fp": 0, "fn": 0, "tn": 0}
    for i in range(len(ys)):
        if ys[i] not in (0, 1) or pred[i] not in (0, 1):
            raise ValueError("Alleen binaire labels")
        if ys[i] == 1:
            if pred[i] == 1:
                vak = "tp"
            else:
                vak = "fn"
        else:
            if pred[i] == 1:
                vak = "fp"
            else:
                vak = "tn"
        c[vak] = c[vak] + 1
    return c


def deel(teller, noemer):
    if noemer == 0:
        return None
    return teller / noemer


def klasse_scores(c):
    tp, fp = c["tp"], c["fp"]
    fn, tn = c["fn"], c["tn"]
    return {"accuracy": deel(tp + tn, tp + fp + fn + tn),
            "precision": deel(tp, tp + fp),
            "recall": deel(tp, tp + fn),
            "f1": deel(2 * tp, 2 * tp + fp + fn)}


def bij_drempel(ys, probs, t):
    if not 0 <= t <= 1:
        raise ValueError("Drempel buiten bereik")
    pred = []
    for i in range(len(probs)):
        if not 0 <= probs[i] <= 1:
            raise ValueError("Kans buiten bereik")
        keuze = 0
        if probs[i] >= t:
            keuze = 1
        pred.append(keuze)
    return verwarring(ys, pred)

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