- Je kiest een splitsing op basis van gewogen Gini-onzuiverheid.
- Je bouwt en gebruikt een echte boom met een begrensde diepte.
Een tuinder wil planten indelen met hun hoogte: “lager dan deze grens?” Een beslisboom (decision tree) stelt zulke vragen achter elkaar. Het antwoord stuurt een plant naar een volgende vraag of naar een blad met een klasse.
Je bouwt hier een echte boom voor één numerieke feature en integerlabels. Eerst zoek je een nuttige vraag; daarna herhaal je hetzelfde werk in kleinere groepen. Met meerdere features zou een boom ook moeten kiezen over welke feature hij vraagt.
1. Tel hoe gemengd een groep is
Een groep met alleen gezonde planten vraagt geen verdere splitsing. Een groep met half gezonde en half zieke planten is minder duidelijk. Gini-onzuiverheid vat die menging samen: trek de som van gekwadrateerde klasseaandelen af van één.
c loopt over de aanwezige klassen; p is hun aandeel. Bij labels [0,0,0,1] zijn de aandelen 3/4 en 1/4: G = 1−9/16−1/16 = 6/16 = 0.375. Alleen klasse 0 geeft 1−1² = 0.
labels = [0, 0, 0, 1]
telling = {}
for label in labels:
n = telling.get(label, 0)
telling[label] = n + 1
kwadraten = 0
for n in telling.values():
kwadraten = kwadraten + n * n
g = 1 - kwadraten / len(labels) ** 2
print(g) # 0.375
Een dictionary bewaart aantallen per label: .get(label,0) geeft de bestaande telling of nul als dit label nog ontbreekt; .values() geeft alle aantallen. De code telt eerst gekwadrateerde aantallen en deelt daarna door het gekwadrateerde totaal: dat is dezelfde formule, met minder afrondingsstappen. Maak gini(labels) en gebruik voor de lege groep de rekenconventie 0.
Dit criterium helpt trainingsgroepen vormen; het zegt nog niets over prestaties op onbekende planten.
012. Weeg de kinderen naar hun grootte
Neem hoogtes [1,2,3,4] met labels [0,0,1,1]. Een grens t = 2.5 maakt twee zuivere groepen: links gaat x kleiner dan t, rechts gaat de rest. De gewogen onzuiverheid is nul.
Bij t = 1.5 bevat links één label 0 en rechts [0,1,1]. Rechts is G = 4/9, maar die groep vertegenwoordigt drie van vier planten. De splitscore is daarom 1/4 × 0 + 3/4 × 4/9 = 1/3.
N is het totale aantal; n geeft het aantal links of rechts. Veelgemaakte fout: beide Gini-waarden ongewogen middelen. Dan krijgt een klein kind ten onrechte evenveel invloed als een groot kind.
links = 0.0
rechts = 1 - (1/3)**2 - (2/3)**2
score = (1 * links + 3 * rechts) / 4
print(score) # ongeveer 0.333333
Maak split_gini(xs,ys,t): verzamel links en rechts de labels met een lus, roep gini aan en weeg. Weiger lege of ongelijk lange invoer en een split met een leeg kind. Het diagram heeft acht planten; vergelijk daar grenzen 3.5 en 4.5 en verklaar het verschil.
Cirkels zijn klasse 0, vierkanten klasse 1. Links gaat x<t; rechts x≥t. De ouder heeft Gini 0.5.
3. Vergelijk alleen grenzen die iets veranderen
Bij de vier hoogtes zijn 1.5, 2.5 en 3.5 voldoende kandidaten. Binnen een opening tussen twee hoogtes verandert de groepsindeling niet. Dubbele hoogtes mogen niet over de kinderen verdeeld worden.
import math
xs = [1, 2, 3, 4]
waarden = sorted(set(xs))
grenzen = []
for i in range(len(waarden) - 1):
a, b = waarden[i], waarden[i+1]
t = a / 2 + b / 2
if t <= a:
t = b
grenzen.append(t)
print(grenzen) # [1.5, 2.5, 3.5]
scores = [1/3, 0, 1/3]
paren = list(zip(scores, grenzen))
beste = paren[0]
for kandidaat in paren[1:]:
score, t = kandidaat
gelijk = math.isclose(
score, beste[0],
rel_tol=1e-12, abs_tol=1e-12)
if score < beste[0] and not gelijk:
beste = kandidaat
print(beste[1]) # 2.5
set verwijdert dubbele waarden; sorted sorteert de rest. zip koppelt scores en grenzen. Maak beste_grens(xs,ys) met werkelijk berekende scores en geef None terug wanneer alle hoogtes gelijk zijn.
We behandelen scores als gelijk als math.isclose met relatieve én absolute tolerantie 1e−12 dat aangeeft. Zo kan een minuscuul afrondingsverschil de tie-regel niet omdraaien: de grenzen staan oplopend, dus bewaar bij gelijke scores de eerste. Alleen een duidelijk lagere score vervangt de beste kandidaat.
De invoer moet niet-leeg en even lang zijn; gebruik eindige x-waarden. Bij bijna gelijke floats kan het midden naar a afronden: t = b scheidt de twee waarden dan alsnog met dezelfde x-kleiner-dan-t-regel. Deze gretige keuze optimaliseert alleen de volgende vraag, niet alle mogelijke volledige bomen tegelijk.
034. Herhaal dezelfde vraag binnen elk kind
Een boom kun je bewaren als een dictionary met t, left en right; een blad bevat alleen label. Voor ons voorbeeld is één vraag genoeg. Bij een nieuwe hoogte 2.5 volg je rechts, want de regel links is strikt kleiner.
boom = {"t": 2.5,
"left": {"label": 0},
"right": {"label": 1}}
def boom_voorspel(boom, x):
if "label" in boom:
return boom["label"]
if x < boom["t"]:
kind = boom["left"]
else:
kind = boom["right"]
return boom_voorspel(kind, x)
print(boom_voorspel(boom, 2.5)) # 1
Een functie die zichzelf aanroept gebruikt recursie. Iedere aanroep krijgt een kleiner onderdeel; het blad beëindigt het voorspellen. Bij het bouwen krijgen beide kinderen één diepteniveau minder.
Met labels [0,1,1,0] zijn twee vragen nodig. Hoogte 4 gaat bij 1.5 naar rechts, daarna bij 3.5 opnieuw naar rechts en eindigt bij label 0:
boom2 = {"t": 1.5,
"left": {"label": 0},
"right": {"t": 3.5,
"left": {"label": 1},
"right": {"label": 0}}}
print(boom_voorspel(boom2, 4)) # 0
Maak bouw_boom(xs,ys,diepte): tel labels, kies hun meerderheid en stop bij diepte nul, één klasse of geen bruikbare grens. Anders verdeel je de x- en y-lijsten en bouw je ieder kind opnieuw. Bij gelijke aantallen wint het kleinste label; diepte moet een niet-negatief geheel getal zijn en ongeldige paren geven ValueError.
Een diepe boom kan ruis onthouden. Kies zijn diepte op validatie, ook als hij alle trainingslabels goed voorspelt.
04Wat neem je mee?
- Gini meet menging; een splitscore weegt beide groepen naar hun grootte.
- Middelpunten geven zinvolle kandidaten en een vaste tie-regel maakt de keuze reproduceerbaar.
- Recursie herhaalt dezelfde bouwstap; stopregels begrenzen de boom.
Hierna laat je verschillende bomen samenwerken om minder gevoelig te worden voor toevallige trainingsrijen.