Machine Learning Academy · Lezione

Costruire un albero: suddivisioni, nodi e foglie

Seguirà il modo in cui un albero decisionale suddivide ricorsivamente i dati a ogni nodo, dalla radice alla foglia, e farà previsioni seguendo i rami.

Lezione 1 di 413 passaggi

Costruire un albero: suddivisioni, nodi e foglie è una lezione Machine Learning Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Machine Learning Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Machine Learning Academy include 4 lezioni in totale.

Che cos'è un albero decisionale?

Un albero decisionale è una struttura simile a un diagramma di flusso, in cui ogni nodo interno pone una domanda sì/no su una caratteristica, ogni ramo rappresenta una risposta e ogni nodo foglia contiene una previsione. Per classificare un nuovo campione, si parte dalla radice, si seguono i rami in base ai valori delle caratteristiche e si arriva a una foglia, la cui etichetta costituisce la previsione. Gli alberi decisionali sono interpretabili per progettazione: è possibile ricostruire esattamente il motivo di qualsiasi previsione leggendo la sequenza delle domande a cui è stata data risposta, caratteristica che li rende popolari in settori regolamentati come quello finanziario e sanitario.

# Conceptual tree for predicting loan default:
#
# Is income > 50000?
# |--- Yes: Is credit_score > 700?
# |         |--- Yes: APPROVE (leaf)
# |         |--- No:  Is debt_ratio < 0.4?
# |                   |--- Yes: APPROVE (leaf)
# |                   |--- No:  REJECT (leaf)
# |--- No: REJECT (leaf)

print('Decision tree makes predictions by asking questions')
print('Each path from root to leaf = one decision rule')

Nodi, rami e foglie

Un albero decisionale ha tre tipi di componenti: nodo radice (la prima domanda posta, ovvero la suddivisione più informativa dell'intero dataset), nodi interni (domande intermedie che suddividono ulteriormente sottoinsiemi dei dati) e nodi foglia (nodi terminali in cui vengono memorizzate le predizioni). Ogni nodo interno suddivide i dati in due o più sottoinsiemi in base a una soglia di una feature. La profondità di un albero è la lunghezza del percorso più lungo dalla radice a una foglia qualsiasi. Gli alberi più profondi possono rappresentare pattern più complessi, ma sono più soggetti all'overfitting.

# Tree anatomy example
print('Root node: first split on most informative feature')
print('Internal nodes: further splits on subsets')
print('Leaf nodes: final predictions')
print()
print('Depth=1 tree (stump): one question, two leaves')
print('Depth=2 tree: up to three questions, four leaves')
print('Depth=d tree: up to 2^d leaves')
print()
print('More depth = more flexible but higher overfitting risk')

Partizionamento ricorsivo: come l'algoritmo suddivide i dati

La costruzione di un albero decisionale è un algoritmo greedy e ricorsivo. A ogni nodo, valuta ogni possibile suddivisione per ogni feature e soglia, sceglie la suddivisione che separa meglio le classi (misurata tramite impurità di Gini o guadagno informativo), la applica e poi ripete ricorsivamente il processo su ciascun sottoinsieme risultante. Il processo continua finché non viene raggiunto un criterio di arresto: profondità massima raggiunta, numero minimo di campioni per nodo oppure assenza di ulteriori suddivisioni utili. Greedy significa che a ogni passaggio viene scelta la suddivisione localmente migliore, senza backtracking: questo può far perdere suddivisioni ottimali a livello globale, ma rende l'algoritmo trattabile.

# Pseudocode for recursive tree building
def build_tree(X, y, depth=0, max_depth=3):
    # Stopping conditions
    if len(set(y)) == 1:      # All same class
        return {'leaf': True, 'prediction': y[0]}
    if depth >= max_depth:    # Max depth reached
        from collections import Counter
        return {'leaf': True, 'prediction': Counter(y).most_common(1)[0][0]}
    
    # Find best split
    best_feature, best_threshold = find_best_split(X, y)
    
    # Partition data
    left_mask  = X[:, best_feature] <= best_threshold
    right_mask = ~left_mask
    
    return {
        'leaf': False,
        'feature': best_feature,
        'threshold': best_threshold,
        'left':  build_tree(X[left_mask],  y[left_mask],  depth+1, max_depth),
        'right': build_tree(X[right_mask], y[right_mask], depth+1, max_depth)
    }

Suddivisioni allineate agli assi: soglie su singole feature

Gli alberi decisionali in scikit-learn utilizzano sempre suddivisioni allineate agli assi (ortogonali): ogni domanda verifica se una feature è al di sopra o al di sotto di una soglia (ad esempio, age <= 35?). Questo crea regioni decisionali rettangolari nello spazio delle feature 2D. Sebbene questo approccio sia semplice e interpretabile, non può rappresentare in modo efficiente confini decisionali diagonali: ad esempio, separare due classi lungo una linea a 45 gradi richiede molte suddivisioni. Gli ensemble di alberi (Random Forests) superano questo limite combinando molti alberi, ciascuno con suddivisioni allineate agli assi diverse, che insieme approssimano confini di qualsiasi forma.

import numpy as np

# Simulate finding a split on one feature
feature_values = np.array([10, 20, 30, 40, 50])
labels = np.array([0, 0, 0, 1, 1])

# For each possible threshold between consecutive values:
for threshold in [15, 25, 35, 45]:
    left_labels  = labels[feature_values <= threshold]
    right_labels = labels[feature_values > threshold]
    print(f'Threshold {threshold}: left={list(left_labels)}, right={list(right_labels)}')
# Threshold 35 gives perfect separation [0,0,0] vs [1,1]

Addestramento di un albero decisionale con scikit-learn

DecisionTreeClassifier di scikit-learn viene addestrato con una singola chiamata a fit(). I parametri principali includono max_depth (profondità massima dell'albero, fondamentale per controllare l'overfitting), criterion (misura della qualità della suddivisione: 'gini' o 'entropy') e min_samples_split (numero minimo di campioni necessari per suddividere un nodo, che impedisce di suddividere gruppi troppo piccoli). Dopo il fitting, l'albero è immediatamente pronto per effettuare predizioni. A differenza di KNN, la predizione ha complessità O(log N): è sufficiente seguire i rami appresi, rendendo gli alberi decisionali veloci in fase di inferenza.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

tree = DecisionTreeClassifier(
    max_depth=3,
    criterion='gini',
    random_state=42
)
tree.fit(X_train, y_train)

print('Train accuracy:', tree.score(X_train, y_train).round(3))
print('Test  accuracy:', tree.score(X_test, y_test).round(3))
print('Tree depth:', tree.get_depth())
print('Number of leaves:', tree.get_n_leaves())

Seguire il percorso di una predizione

Il punto di forza degli alberi decisionali è che è possibile seguire ogni passaggio della predizione. Il metodo decision_path() restituisce una matrice sparsa che indica i nodi attraversati da ciascun campione. Il metodo apply() restituisce l'indice del nodo foglia per ciascun campione. Questi strumenti consentono di spiegare a un utente esattamente quali domande sono state poste e quali risposte hanno portato alla predizione: un aspetto essenziale per la conformità normativa, il debugging e la creazione di fiducia con gli stakeholder non tecnici.

from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

# Print human-readable decision rules
rules = export_text(tree, feature_names=load_iris().feature_names)
print(rules[:500])  # First 500 chars of the rule printout

# Which leaf does sample 0 land in?
leaf = tree.apply(X[[0]])
print('Sample 0 lands in leaf node:', leaf)

Predizioni dell'albero nei nodi foglia

Ogni nodo foglia memorizza una distribuzione delle classi ottenuta dai campioni di addestramento che lo hanno raggiunto. Nella classificazione, la classe predetta è la classe maggioritaria nella foglia. Per la stima delle probabilità, predict_proba() restituisce la frazione di ciascuna classe nella foglia. Una foglia contenente 10 campioni, di cui 9 della classe A e 1 della classe B, predice la classe A con probabilità 0.9. Gli alberi con meno campioni per foglia producono stime di probabilità meno affidabili; per questo i parametri di regolarizzazione come min_samples_leaf sono importanti per ottenere probabilità calibrate.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np

X, y = load_iris(return_X_y=True)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

# Predicted class and probabilities for first three samples
preds = tree.predict(X[:3])
probas = tree.predict_proba(X[:3])

for i in range(3):
    print(f'Sample {i}: class={preds[i]}, probabilities={probas[i].round(3)}')

Albero decisionale per la regressione

DecisionTreeRegressor funziona come il classificatore, ma predice il valore medio del target dei campioni di addestramento presenti in ogni foglia. Cambia il criterio di suddivisione: invece dell'impurità di Gini, minimizza l'errore quadratico medio (o l'errore assoluto medio) all'interno di ciascun nodo figlio risultante. Gli alberi di regressione producono predizioni a gradino, ovvero valori costanti all'interno di regioni rettangolari. Con una profondità sufficiente, possono adattarsi perfettamente a tutti i dati di addestramento, ma questo porta a un grave overfitting. Controlli la profondità e min_samples_leaf per regolarizzare l'albero di regressione.

from sklearn.tree import DecisionTreeRegressor
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
X = np.sort(5 * np.random.rand(80, 1), axis=0)
y = np.sin(X).ravel() + np.random.randn(80) * 0.3

for depth in [1, 3, 10]:
    reg = DecisionTreeRegressor(max_depth=depth)
    reg.fit(X, y)
    mse = np.mean((reg.predict(X) - y)**2)
    print(f'max_depth={depth}: train MSE={mse:.4f}')
# depth=10 nearly zero MSE (memorised training data)

Importanza delle feature negli alberi decisionali

Dopo il fitting, tree.feature_importances_ fornisce una misura del contributo di ciascuna feature alle suddivisioni. L'importanza di una feature viene calcolata come la riduzione totale dell'impurità (Gini o entropia) attribuita a quella feature, ponderata in base alla frazione di campioni che raggiunge ciascuna suddivisione. I valori hanno somma pari a 1.0. La feature più importante ottiene il punteggio più alto. Questo offre un metodo rapido e interpretabile per individuare quali input influenzano maggiormente le predizioni, utile per la selezione delle feature, l'analisi aziendale e l'individuazione di potenziali problemi nei dati.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import pandas as pd

X, y = load_iris(return_X_y=True)
feature_names = load_iris().feature_names

tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

importances = pd.Series(tree.feature_importances_, index=feature_names)
print('Feature Importances:')
print(importances.sort_values(ascending=False))

Invarianza alla scala: gli alberi non richiedono lo scaling

Un importante vantaggio pratico degli alberi decisionali è che sono completamente invarianti alla scala. Una suddivisione su income <= 50000 e una suddivisione su income_thousands <= 50 producono strutture dell'albero identiche. Aggiungere 100 a tutti i valori di una feature o moltiplicarli per 1000 non cambia le suddivisioni scelte. Non è mai necessario applicare StandardScaler o MinMaxScaler prima di un albero decisionale. Questo significa anche che gli alberi decisionali gestiscono feature con scale molto diverse senza alcun preprocessing, semplificando le pipeline.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
import numpy as np

X, y = load_iris(return_X_y=True)

# Without scaling
tree1 = DecisionTreeClassifier(random_state=42)
tree1.fit(X, y)

# With scaling (same result expected)
X_scaled = StandardScaler().fit_transform(X)
tree2 = DecisionTreeClassifier(random_state=42)
tree2.fit(X_scaled, y)

print('Without scaling accuracy:', tree1.score(X, y).round(3))
print('With scaling accuracy:   ', tree2.score(X_scaled, y).round(3))
# Identical -- scaling has no effect on tree splits

Gestione dei valori mancanti negli alberi

Gli alberi decisionali gestiscono i valori mancanti in modo più efficace rispetto a molti algoritmi. Il DecisionTreeClassifier di scikit-learn supporta nativamente i valori mancanti quando splitter='best': i campioni con valori mancanti nella feature usata per la suddivisione vengono inviati al nodo figlio che minimizza l'impurità calcolata sui dati non mancanti. In alternativa, è possibile usare suddivisioni surrogate: quando la feature della suddivisione principale è mancante per un campione, viene utilizzata al suo posto una feature correlata. Questa robustezza rispetto ai dati mancanti è uno dei vantaggi pratici dei modelli basati su alberi rispetto ai metodi basati sulla distanza, come KNN, che richiedono vettori di feature completi.

from sklearn.tree import DecisionTreeClassifier
import numpy as np

# Tree can handle NaN values with missing_values support
# In scikit-learn >= 1.0, DecisionTreeClassifier accepts NaN
X = np.array([
    [1, 2], [np.nan, 3], [3, np.nan], [4, 5]
])
y = np.array([0, 1, 0, 1])

tree = DecisionTreeClassifier(random_state=42)
tree.fit(X, y)
preds = tree.predict(X)
print('Predictions with NaN features:', preds)
# Tree routes NaN samples gracefully

Verifica rapida

Verifichi la Sua comprensione dei concetti di Machine Learning con Python trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato come gli alberi decisionali suddividono ricorsivamente i dati usando suddivisioni allineate agli assi, che ogni foglia memorizza la distribuzione delle classi per effettuare predizioni tramite voto di maggioranza e che gli alberi decisionali sono invarianti alla scala e non richiedono lo scaling delle feature. Ora esamineremo l'impurità di Gini e il guadagno informativo, i criteri che determinano quale suddivisione scegliere in ogni nodo.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Costruire un albero: suddivisioni, nodi e foglie» è gratuita?

Sì — il testo completo di «Costruire un albero: suddivisioni, nodi e foglie» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Machine Learning Academy, passa a CoddyKit PRO. Il corso Machine Learning Academy include 4 lezioni in totale.

Cosa imparerò in «Costruire un albero: suddivisioni, nodi e foglie»?

Seguirà il modo in cui un albero decisionale suddivide ricorsivamente i dati a ogni nodo, dalla radice alla foglia, e farà previsioni seguendo i rami. Eserciti Machine Learning Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Machine Learning Academy?

Non è richiesta alcuna esperienza precedente. Machine Learning Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Costruire un albero: suddivisioni, nodi e foglie»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Machine Learning Academy?

Sì. Ogni lezione Machine Learning Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Costruire un albero: suddivisioni, nodi e foglie
  2. Impurità di Gini e guadagno informativo
  3. Controllare la profondità dell'albero per prevenire l'overfitting
  4. Visualizzare e interpretare gli alberi decisionali
← Torna a Machine Learning Academy