Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Arbres de decisió

Introducció

Farem servir un exemple senzill. Gestiones un centre d’adopció de gats i vols un classificador que, a partir d’unes quantes característiques d’un animal, decideixi ràpidament si és un gat o no.

Tens 10 exemples d’entrenament (5 gats i 5 gossos). Per a cada animal saps:

  • Forma de les orelles (punxegudes o caigudes)
  • Forma de la cara (rodona o no rodona)
  • Bigotis (presents o absents)
  • Etiqueta: és gat (1) o no és gat (0)

Per exemple, el primer animal té orelles punxegudes, cara rodona i bigotis, i és un gat. El segon té orelles caigudes, cara no rodona i bigotis, i també és un gat.

Com que l’etiqueta només pot ser 0 o 1, és un problema de classificació binària. De moment, cada característica només pren dos valors. Més endavant veurem què fer amb característiques contínues, com el pes.

Què és un arbre de decisió?

Un arbre de decisió és un model que, un cop entrenat, té forma d’arbre:

  • Cada node de decisió (els ovals) fa una pregunta sobre una característica.
  • El node arrel, a dalt de tot, és el punt de partida.
  • Les fulles (les caixes de baix) donen la predicció final.

Per classificar un animal nou amb orelles punxegudes, cara rodona i bigotis:

  1. Comencem a l’arrel, que pregunta: “Quina forma tenen les orelles?”
  2. Segons la resposta, anem a l’esquerra o a la dreta.
  3. El node següent potser pregunta: “La cara és rodona?”
  4. Continuem baixant fins a una fulla, que diu: “És un gat”.

Les arrels a dalt i les fulles a baix semblen estranyes, però pensa-hi com en una planta penjant.

Amb les mateixes dades es poden construir molts arbres diferents: un que primer mira les orelles i després els bigotis, un altre que comença per la cara… L’algoritme d’aprenentatge ha de trobar un arbre que generalitzi bé, és a dir, que funcioni també amb animals que no ha vist.

Construcció d’un arbre

En aquest arbre s’hi ha afegit, entre parèntesis, quants animals arriben a cada node:

  1. Arrel. Escollim la característica de l’arrel: la forma de les orelles. Els 5 animals amb orelles punxegudes van a l’esquerra, i els 5 amb orelles caigudes a la dreta.
  2. Branca esquerra (4 gats i 1 gos). Dividim per la forma de la cara. Els 4 de cara rodona són tots gats: es converteix en una fulla “gat”. L’únic de cara no rodona és un gos: fulla “no gat”.
  3. Branca dreta (1 gat i 4 gossos). Dividim pels bigotis. Amb bigotis queda el gat, sense bigotis els 4 gossos. Tots dos nodes són purs (només tenen una classe) i es converteixen en fulles.

Tot el procés es redueix a dues preguntes, que l’algoritme es fa a cada node:

  1. Quina característica faig servir per dividir? La que deixa els grups més purs, amb menys barreja de gats i gossos.
  2. Quan deixo de dividir? Quan el node ja és pur, quan s’arriba a una profunditat màxima, quan queden molt pocs exemples o quan la millora és massa petita.

L’algoritme és recursiu: aplica el mateix procés a cada branca.

funció construir_arbre(exemples):
    si cal parar (node pur, profunditat màxima, pocs exemples...):
        retorna fulla amb la classe majoritària

    característica = la que deixa els grups més purs
    dividir exemples segons característica

    retorna node(
        esquerra = construir_arbre(exemples_esquerra),
        dreta = construir_arbre(exemples_dreta)
    )

Com escollir la millor pregunta?

Imagina una bossa amb gats i gossos barrejats. Una bona pregunta és la que, en respondre-la, et deixa dues bosses on els animals s’assemblen més entre ells. L’ideal seria tots els gats a una banda i tots els gossos a l’altra.

Mesurar la barreja: l’entropia

Per mesurar com de barrejat està un grup fem servir l’entropia:

  • Grup pur (tots gats o tots gossos): entropia 0
  • Grup 50%-50%: entropia 1, la màxima confusió

El que importa és la intuïció: com més barrejat, més entropia.

La fórmula

\[ H(p) = -p \log_2(p) - (1-p)\log_2(1-p) \]

  • p: la proporció d’una de les dues classes al grup

En paraules senzilles: val 0 si p és 0 o 1 (grup pur) i 1 si p és 0.5 (meitat i meitat).

Exemple: quina característica triem?

Tornem als 10 animals. Si provem les tres característiques:

CaracterísticaEsquerraDretaResultat
Orelles4 gats, 1 gos1 gat, 4 gossosBona separació
Cara4 gats, 3 gossos1 gat, 2 gossosEncara barrejats
Bigotis3 gats, 1 gos2 gats, 4 gossosRegular

“Orelles” deixa els grups més purs. Això es mesura amb el guany d’informació: quanta entropia eliminem amb la divisió.

  • Orelles: 0.28, el guany més alt
  • Bigotis: 0.12
  • Cara: 0.03
Com es calcula el guany

El guany d’informació és l’entropia abans de dividir menys la mitjana de les entropies després de dividir, ponderada per la mida de cada grup:

\[ \text{Guany} = H(\text{arrel}) - \left( \frac{m_{\text{esq}}}{m} H(\text{esq}) + \frac{m_{\text{dre}}}{m} H(\text{dre}) \right) \]

  • H: l’entropia de cada grup
  • m: exemples al node, m_esq i m_dre: exemples que van a cada banda

Per a “orelles”: a l’arrel hi ha 5 gats de 10 (p = 0.5), i \( H = 1 \). A cada banda hi ha 5 animals, amb una proporció de gats de 4/5 i 1/5, i \( H(0.8) = H(0.2) \approx 0.72 \). El guany és \( 1 - (0.5 \cdot 0.72 + 0.5 \cdot 0.72) = 0.28 \).

En paraules senzilles: el guany mesura quanta barreja hem tret amb la pregunta. Els grups grans pesen més, perquè una divisió que deixa pur un grup de 2 animals no ajuda tant com una que deixa pur un grup de 8.

Triem sempre la característica amb el guany més alt. El guany també ens diu quan parar: si és molt petit, dividir més només fa l’arbre més complex.

Característiques contínues

Què passa amb una característica contínua, com el pes de l’animal? De mitjana els gats pesen menys que els gossos, així que és una característica útil.

La pregunta ja no és “té orelles punxegudes?” sinó “pesa menys de X quilos?”, i l’algoritme ha de trobar el millor valor de tall.

LlindarEsquerra (≤)Dreta (>)Qualitat
pes ≤ 82 gats3 gats, 5 gossosDolenta (dreta molt barrejada)
pes ≤ 94 gats1 gat, 5 gossosBona (grups més purs)
pes ≤ 134 gats, 2 gossos1 gat, 3 gossosRegular

El llindar pes ≤ 9 és el millor. Per trobar-lo, l’algoritme ordena els exemples per pes, prova cada punt mitjà entre dos exemples consecutius i es queda amb el que dona més guany. Amb 10 exemples, prova 9 llindars.

Hiperparàmetres: controlant la complexitat

Els hiperparàmetres són els “botons” que fixem abans d’entrenar i que decideixen com de complex serà l’arbre:

HiperparàmetreQuè controlaSi és altSi és baix
max_depthProfunditat màximaArbre complex, risc de sobreajustamentArbre simple, risc de subajustament
min_samples_splitMínim d’exemples per dividir un nodeDivisions més prudentsMés divisions, arbre més complex
min_samples_leafMínim d’exemples a cada fullaFulles més fiablesFulles amb pocs exemples

Profunditat i sobreajustament

Un arbre sense límit de profunditat pot créixer fins que cada fulla tingui un sol exemple. Tindrà un 100% d’encerts amb les dades d’entrenament, perquè les ha memoritzat, i fallarà amb dades noves.

ProfunditatEncerts (entrenament)Encerts (validació)Diagnòstic
275%74%Biaix alt: massa simple
592%88%Bon equilibri
20100%72%Variància alta: memoritza

És el mateix biaix i variància que vam veure amb els polinomis: el grau del polinomi ara és la profunditat de l’arbre.

Validació creuada

Per triar la profunditat provem diversos valors i mirem quin dona millors resultats amb dades de validació. Però amb una sola divisió el resultat depèn de la sort: de quins exemples han caigut a cada banda.

La validació creuada (cross-validation) ho resol. Dividim les dades d’entrenament en K parts (típicament 5), entrenem K vegades deixant cada vegada una part diferent per validar, i fem la mitjana dels K resultats. Cada exemple serveix tant per entrenar com per validar, i el resultat és més estable. Ho expliquem amb un gràfic a Validació creuada.

GridSearchCV prova totes les combinacions d’hiperparàmetres amb validació creuada i es queda amb la millor. Ho provem amb el dataset breast_cancer de scikit-learn: 30 mesures de 569 tumors, i com a objectiu si el tumor és maligne.

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.tree import DecisionTreeClassifier

data = load_breast_cancer()
X, y = data.data, 1 - data.target  # 1 = maligne
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

# Valors a provar
param_grid = {
    'max_depth': [3, 5, 10, 20, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# cv=5: validació creuada amb 5 parts
tree = DecisionTreeClassifier(random_state=42)
search = GridSearchCV(tree, param_grid, cv=5, scoring='accuracy')
search.fit(X_train, y_train)

print(f"Millors paràmetres: {search.best_params_}")
print(f"Millor resultat: {search.best_score_:.3f}")

# El test només es mira al final, amb el model ja triat
print(f"Test: {search.score(X_test, y_test):.3f}")
Millors paràmetres: {'max_depth': 10, 'min_samples_leaf': 1, 'min_samples_split': 5}
Millor resultat: 0.938
Test: 0.939

La validació creuada estimava un 93.8% d’encerts i el test en dona un 93.9%: l’estimació era fiable. GridSearchCV ha provat 45 combinacions (5 × 3 × 3), cadascuna 5 vegades, i al final reentrena el model guanyador amb tot el conjunt d’entrenament.

Consells pràctics:

  • Comença simple: max_depth=5 és un bon punt de partida.
  • Compara entrenament i validació: si l’entrenament és molt millor, l’arbre és massa complex.
  • Amb pocs exemples, limita més la profunditat.

Què fer si el model falla

Amb l’error d’entrenament i el de validació ja podem decidir què provar:

SímptomaDiagnòsticQuè provar
Entrenament i validació dolentsBiaix altArbre més profund, característiques noves, un ensemble
Entrenament molt bo, validació dolentaVariància altaLimitar la profunditat, min_samples_leaf més gran, més dades
Els dos bons i semblantsCorrecteAvaluar una vegada amb el test

La taula completa de remeis, vàlida per a qualsevol model, és a Què fer si el model falla.

Conjunts d’arbres (ensembles)

Un ensemble fa treballar molts models junts. En lloc de confiar en un sol arbre, entrenem molts arbres i combinem les seves prediccions.

Per què un sol arbre no és suficient?

Un arbre és molt sensible a petits canvis en les dades: si canviem un sol exemple, la característica de l’arrel pot canviar i l’arbre resultant pot ser totalment diferent. Combinant molts arbres, cap d’ells té massa influència.

La combinació més senzilla és una votació:

ArbrePredicció
Arbre 1gat
Arbre 2gos
Arbre 3gat

Resultat: 2 vots a 1, guanya gat.

Random Forest

Si entrenem tots els arbres amb les mateixes dades, sortiran idèntics. El Random Forest fa que cada arbre sigui diferent amb dues tècniques:

  1. Mostreig amb reposició (bagging): cada arbre s’entrena amb una mostra diferent de les dades. Traiem exemples a l’atzar i els tornem a posar a la bossa, de manera que uns es repeteixen i d’altres no hi apareixen.

    Dades originalsMostra 1Mostra 2Mostra 3
    A, B, C, D, EA, A, C, D, EB, C, C, D, EA, B, D, D, E
  2. Característiques a l’atzar: a cada node, l’arbre només pot triar entre un subconjunt aleatori de les característiques. Això el força a explorar camins diferents.

Per predir, cada arbre vota i guanya la majoria.

from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
rf.fit(X_train, y_train)
print(f"Test: {rf.score(X_test, y_test):.3f}")

Gradient boosting

El gradient boosting segueix una estratègia diferent: en lloc d’arbres independents que voten, construeix els arbres un darrere l’altre, i cada arbre nou s’entrena per corregir l’error que encara cometen els anteriors. La predicció final és la suma de tots.

És com la pràctica deliberada al piano: en lloc de tocar la peça sencera una vegada i una altra, et concentres en els compassos que encara no et surten.

PasQuè fa
1Un primer arbre petit fa una predicció aproximada
2Calculem quant s’equivoca amb cada exemple (l’error que queda)
3Un arbre nou aprèn a predir aquest error, i el sumem al model
4Repetim (centenars d’arbres petits) fins que l’error de validació deixa de millorar

Les implementacions més utilitzades són XGBoost, LightGBM i, dins de scikit-learn, HistGradientBoostingClassifier.

from sklearn.ensemble import HistGradientBoostingClassifier

gb = HistGradientBoostingClassifier(max_iter=200, random_state=42)
gb.fit(X_train, y_train)
print(f"Test: {gb.score(X_test, y_test):.3f}")

# Alternativa: from xgboost import XGBClassifier
# Per a regressió: HistGradientBoostingRegressor o XGBRegressor

Amb les dades de tumors, el millor arbre sol que ha trobat GridSearchCV encerta el 93.9% del test. El Random Forest i el gradient boosting arriben al 97.4%, sense ajustar res: és la millora típica d’un ensemble sobre un arbre sol.

Random ForestGradient boosting
IdeaArbres independents que votenArbres que corregeixen els errors dels anteriors
EntrenamentEn paral·lelSeqüencial
AjustFunciona bé gairebé sense tocar resCal ajustar més hiperparàmetres (nombre d’arbres, taxa d’aprenentatge)
Quan usar-loUn bon punt de partidaQuan vols treure el màxim rendiment

Amb dades en forma de taula, el gradient boosting continua sent l’opció per defecte en molts problemes reals, tot i que no sempre és el guanyador.

Quan usar arbres i quan xarxes neuronals?

Arbres (gradient boosting)Xarxes neuronals
Dades tabulars (fulls de càlcul, bases de dades)RecomanatFunciona
Imatges, àudio, textNo adequatRecomanat
Velocitat d’entrenamentRàpidLent
InterpretabilitatAlta (arbres petits)Baixa
Models preentrenatsNoSí
Dataset petitFunciona béNecessita més dades
  • Dades en taules (preus, clients, transaccions): comença amb gradient boosting.
  • Imatges, vídeo, àudio, text: fes servir xarxes neuronals, normalment a partir d’un model preentrenat.
  • Si dubtes amb dades tabulars, prova tots dos i compara’ls amb validació.

Més detalls

Criteri de Gini

El criteri de Gini (o impuresa de Gini) és una alternativa a l’entropia per mesurar la barreja. La intuïció: si agafes dos animals a l’atzar del grup, quina probabilitat hi ha que siguin de classes diferents?

MesuraRangMàxim aQuè mesura
Entropia0 a 150%-50%“Sorpresa” o incertesa
Gini0 a 0.550%-50%Probabilitat d’error aleatori

Totes dues valen 0 quan el grup és pur i són màximes quan està més barrejat, i a la pràctica donen resultats molt semblants. scikit-learn fa servir Gini per defecte perquè és una mica més ràpid de calcular (no necessita logaritmes):

from sklearn.tree import DecisionTreeClassifier

model = DecisionTreeClassifier(max_depth=3, random_state=42)  # Gini per defecte
# model = DecisionTreeClassifier(max_depth=3, criterion='entropy')
model.fit(X_train, y_train)

Característiques amb més de dos valors

Si les orelles poden ser punxegudes, caigudes o ovalades, creem una columna per a cada valor:

AnimalOrelles (original)PunxegudesCaigudesOvalades
Gat 1punxegudes100
Gat 2ovalades001
Gos 1caigudes010

Això s’anomena codificació one-hot, perquè a cada fila només una columna està “encesa”. També la necessiten els models que fan operacions matemàtiques amb les dades (regressió, xarxes neuronals), perquè no es pot multiplicar “punxegudes” per un pes.

import pandas as pd

df = pd.DataFrame({'orelles': ['punxegudes', 'ovalades', 'caigudes']})
encoded = pd.get_dummies(df, columns=['orelles'])
print(encoded)
#    orelles_caigudes  orelles_ovalades  orelles_punxegudes
# 0             False             False                True
# 1             False              True               False
# 2              True             False               False

Arbres per a regressió

Els arbres també poden predir números, com el pes d’un animal. La mecànica és la mateixa amb dos canvis:

ClassificacióRegressió
Predicció a la fullaLa classe més freqüentLa mitjana dels valors
Criteri de divisióReduir l’entropia (barreja)Reduir la variància (dispersió)

Si una fulla conté animals de 7.2, 7.6, 10.2 i 8.4 kg, la predicció és la mitjana: 8.35 kg. Un bon tall agrupa animals amb pesos semblants (7, 8 i 9 kg), on la mitjana és representativa, i evita grups dispersos (5, 12 i 20 kg), on la mitjana no representa ningú.

Importància de les característiques

Un arbre ens diu quines característiques li han estat més útils. La importància (feature importance) mesura quanta barreja elimina cada característica a tot l’arbre. Una característica que s’usa a prop de l’arrel afecta molts exemples i és molt important. Una que no s’usa mai té importància 0.

CaracterísticaImportància
Forma orelles0.52
Bigotis0.31
Forma cara0.17

Serveix per entendre què mira el model, per explicar-ne les decisions i per detectar problemes: si una característica inesperada és molt important, potser hi ha un error a les dades.

import pandas as pd
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt

importances = pd.DataFrame({
    'característica': data.feature_names,
    'importància': model.feature_importances_
}).sort_values('importància', ascending=False)
print(importances)

# Dibuixar l'arbre (només és llegible amb arbres petits, max_depth <= 5)
plt.figure(figsize=(15, 8))
plot_tree(model, feature_names=data.feature_names, class_names=['benigne', 'maligne'], filled=True, rounded=True)
plt.show()

Poda

Una alternativa als hiperparàmetres és la poda posterior (post-pruning): deixar créixer l’arbre sencer i després tallar les branques que no aporten. A la pràctica, limitar la complexitat amb hiperparàmetres és més habitual i sol ser suficient. scikit-learn ofereix la poda amb el paràmetre ccp_alpha.

Last change: , commit: cc6333d