Decision Tree, Introduzione
Parte (1/3)
Potrebbe interessarti:
Il progetto Python, che comprende gli script mostrati in questo post, e quelli relativi alla creazione dei grafici Matplotlib e Plotly, è disponibile assieme al corso Introduzione al Machine Learning.
Cos’è un Decision Tree
Un Decision Tree (albero decisionale) è un algoritmo di apprendimento supervisionato, utilizzato per compiti di classificazione e regressione, che fornisce una stima su una data osservazione, imparando semplici regole decisionali dedotte dalle feature del dataset. Tramite queste regole, il modello è in grado di restringere i possibili valori di output, e di fare una previsione. L’ordine e il contenuto delle domande sono decisi dal modello stesso.
Per esempio, consideriamo il gioco da tavolo “Indovina Chi”. Nel gioco, il vostro avversario sceglie un determinato personaggio, e noi dobbiamo capire chi è stato scelto, ponendo una serie di domande sulle caratteristiche del personaggio, come ad esempio: “porta gli occhiali?”, “ha gli occhi azzurri?”, ecc. La caratteristica associata a una specifica domanda viene utilizzata per suddividere l’insieme dei personaggi a disposizione (gli esempi nel dataset) in modo che i gruppi risultanti siano il più possibile diversi l’uno dall’altro, e i membri di ciascun gruppo risultante siano il più possibile simili tra loro.
Semplificando, l’algoritmo di un Decision Tree è una serie di istruzioni if-then-else che possono essere utilizzate per prevedere un risultato, in base ai dati disponibili.
L’Algoritmo di Classificazione
I Decision Tree per la classificazione sono utilizzati per prevedere la classe di appartenenza di un dato esempio. Un Decision Tree è un classificatore strutturato ad albero, in cui i nodi interni rappresentano le caratteristiche di un insieme di dati, i rami rappresentano le regole decisionali, e ogni nodo foglia rappresenta il risultato previsto per una data osservazione. Ecco l’algoritmo per un classificatore binario:
Associamo il dataset originale al nodo root (il nodo principale dell’albero):

Figura 1: Nodo Root Partendo dal nodo root, troviamo la regola decisionale - un’espressione booleana che utilizza una feature e un valore della stessa (ad esempio: “feature $\le$ valore”, oppure “feature $\gt$ valore”), che separa meglio gli esempi del dataset con caratteristiche simili da altri esempi con caratteristiche diverse. L’obiettivo è dividere il dataset multiclasse iniziale in due sottoinsiemi con meno classi, minimizzando l’impurità - il numero di classi diverse tra loro - di ciascun sottoinsieme. L’impurità, in questo caso, è inversamente proporzionale alla qualità della suddivisione:

Figura 2: Prima Suddivisione Associamo i sottoinsiemi appena creati a un nuovo nodo figlio sinistro e destro, e per ogni nodo figlio applichiamo lo stesso processo ricorsivo di partizionamento. Man mano che si scende nella struttura ad albero, la suddivisione genera sottoinsiemi con un numero sempre minore di esempi:

- Quando il processo di divisione genera un dataset puro, che contiene solo esempi della stessa classe, o quando si verificano determinate condizione di uscita (ad esempio: il processo raggiunge il livello massimo di ricorsione consentito per l’albero, o il numero di esempi necessari per creare una divisione è insufficiente), interrompiamo la ricorsione per quel ramo, e generiamo un nodo foglia che contenga l’obiettivo di classificazione desiderato. Per obiettivo intendiamo la label della classe che compare più frequentemente nell’insieme di dati del nodo foglia:

La Funzione di Ipotesi del Decision Tree
Il percorso che va dal nodo root ai nodi foglia rappresenta l’insieme delle regole di classificazione utilizzate per prevedere la label di classe di un dato esempio. Tali regole rappresentano la funzione di ipotesi del classificatore, un insieme annidato di condizioni, che guidano il processo decisionale in base ai valori di soglia delle feature. Esse equivalgono ad una serie di istruzioni if-then-else, come ad esempio:
if (feature_1 <= v_1) {
if (feature_2 <= v_2) {
return "circle"
}
else {
return "rectangle"
}
}
else {
return "triangle"
}
Per questo motivo, gli alberi decisionali sono facili da interpretare, perché la loro struttura ad albero si traduce direttamente in regole decisionali if-then-else facilmente comprensibili.
Modello Non-Parametrico e Greedy
Un Decision Tree è un modello non parametrico, che non fa ipotesi relative alla distribuzione dei dati sottostanti. In altre parole, il modello non si basa su regole o vincoli predefiniti su come sono strutturati o distribuiti i dati. Il modello non pone attenzione neppure alla relazione tra le feature di input e la variabile target. Ad esempio, la Regressione Lineare presuppone che la relazione tra le feature di input $x_i$ e la variabile di output, $\hat{y}$, sia lineare:
$$ \begin{align} \hat{y} & = w_1x_1 + w_2x_2 + \dots + w_nx_n + b\\[6pt] \end{align} $$Invece, un’albero decisionale apprende la struttura e la complessità dei dati direttamente dal dataset di addestramento, tramite le regole decisionali che compongono la sua funzione di ipotesi.
Un Decision Tree utilizza un algoritmo di apprendimento “greedy” (“avido”). Ciò significa che per ogni nodo, l’albero effettua un’ottimizzazione locale, trovando la migliore suddivisione per quel nodo con i dati a disposizione, e poi procede nel processo di adattamento ai dati. In questo modo, l’algoritmo non prende in considerazione tutti i possibili alberi risultanti, generati da tutte le combinazioni di suddivisione, per tutti i nodi disponibili. Pertanto, l’algoritmo potrebbe non trovare il modello più performante. Una strategia greedy di solito non produce una soluzione ottimale, ma un’euristica greedy può produrre soluzioni localmente ottimali, che che si avvicinano a una soluzione ottimale globale, in un tempo ragionevole.
Un Esempio Pratico
Per capire meglio come viene costruito un Decision Tree per la classificazione, consideriamo questo dataset di $7$ esempi, con $3$ feature (“Loves Popcorn”, “Loves Soda”, “Age”), e la label target binaria associata, “Loves Pop Music”:
| id | f: Loves Popcorn | f: Loves Soda | f: Age | l: Loves Pop Music |
|---|---|---|---|---|
| 1 | Yes | Yes | 7 | No |
| 2 | Yes | No | 12 | No |
| 3 | No | Yes | 18 | Yes |
| 4 | No | Yes | 35 | Yes |
| 5 | Yes | Yes | 38 | Yes |
| 6 | Yes | No | 50 | No |
| 7 | No | No | 83 | No |
Partiamo dal nodo root del nostro ipotetico albero, e dividiamo il dataset originale utilizzando la regola decisionale che risulta nel maggior Information Gain (IG).
IG è una misura della purezza, cioè della riduzione dell’Entropia o della “sorpresa” presente nei dataset risultanti dalla procedura di suddivisione del dataset padre. L’Entropia è la misura della disomogeneità del dataset e indica quanto siano disordinati i dati. IG calcola la differenza tra l’Entropia prima e dopo la suddivisione, e specifica l’impurità degli elementi della classe in questi sottoinsiemi. In altre parole, essa misura quanto bene una determinata feature separi gli esempi del dataset di addestramento in base alla loro classificazione. Se i sottoinsiemi creati dalla divisione contengono meno classi rispetto al dataset padre, essi sono “più puri”, quindi massimizzano l’Information Gain, e minimizzano l’Entropia.
Per ora, facciamo “un atto di fede”, e diciamo che la regola decisionale che produce l’IG maggiore, e quindi nodi figli associati a dataset più puri, è l’espressione booleana “Loves Soda?” (i calcoli sono illustrati nel capitolo Gini Impurity). Ciò significa che, tra tutte le regole decisionali, “Loves Soda?” è quella che predice meglio chi “ama la musica pop” e chi no. Pertanto, il dataset originale viene diviso in due sottoinsiemi in base a questa domanda, sequendo questo processo:
visto che i valori associati alla feature “Loves Soda” sono booleani, si definisce come regola decisionale l’espressione booleana “Loves Soda == True” (oppure “Loves Soda == False”).
Tale regola viene usata per dividere il dataset originale in due subset: un subset sinistro, contenente le istanze di chi “ama la soda”, che viene associato ad un nuovo nodo figlio sinistro, e un subset destro, contenente le istanze di chi “non ama la soda”, che viene associato ad un nuovo nodo figlio destro. La struttura dati risultante è questa:

Figura 5: Loves Soda Il subset sinistro contiene 4 esempi che “amano la soda”; 3 sono associati ad una label di classe positiva - “Loves Pop Music” = “Yes”, ed uno è associato ad una label di classe negativa - “Loves Pop Music” = “No”. Il subset destro contiene 3 esempi che non “amano la soda”, tutti associati a una label di classe negativa - “Loves Pop Music” = “No”. Poiché tutti gli esempi sono associati alla stessa label di classe (“No”), questo nodo identifica un nodo foglia, ovvero un nodo “puro”.
In un processo iterativo, ripetiamo la procedura di suddivisione per ogni subset “impuro”, i cui esempi appartengono a classi diverse, fino a quando i nodi foglia generati diventano puri, o finché il processo non raggiunga il livello massimo di ricorsione consentito per l’albero.
Considerando il dataset generato dalla precedente suddivisione, “Loves Soda?”, diciamo che, questa volta, la regola decisionale con il miglior Information Gain è “Age $\le$ 15” (oppure “Age $\gt$ 15”). Quindi, applichiamo questa nuova regola al sottoinsieme sinistro impuro generato dalla prima suddivisione, ed eseguiamo un’altra suddivisione. La struttura dati risultante è questa:

Ora, entrambi i sottoinsiemi risultanti sono puri, quindi possiamo interrompere il processo di suddivisione. Anche in questo caso, ogni subset è legato a un nuovo nodo figlio destro e sinistro. Per completare la struttura ad albero, in ogni nodo foglia impostiamo un valore di classificazione target, calcolato scegliendo l’etichetta della classe che compare più frequentemente nel sottoinsieme di esempi correlati:

Quando utilizziamo una regola decisionale per dividere un dataset associato a un dato nodo, memorizziamo anche le informazioni della regola decisionale (la feature e il valore della feature usato per dividere il dataset) all’interno di quel nodo. Una volta che abbiamo un albero decisionale addestrato, la generazione di previsioni su dati mai visti prima è semplice. Ogni esempio viene fatto passare attraverso l’albero, partendo dal nodo root, e viene successivamente fatto passare al nodo figlio sinistro o destro, a seconda di come le feature dell’esempio soddisfino le regole decisionali legate a ciascun nodo. Navighiamo attraverso l’albero in questo modo fino a raggiungere un nodo foglia. A questo punto, la label di classe associata a tale nodo foglia viene fornita come valore di predizione di output.
L’ordine di queste regole decisionali definisce quindi la funzione di ipotesi associata al modello. Nell’esempio precedente, abbiamo la seguente struttura if-then-else:
if (Loves Soda) {
if (Age <= 15) {
return "Class 0" # Does Not Love Pop Music
}
else {
return "Class 1" # Loves Pop Music
}
}
else {
return "Class 0" # Does Not Love Pop Music
}
Massimizzazione dell’Information Gain
L’Entropia è una misura in bit della complessità o disomogeneità del dataset, e indica quanto sono disordinati i dati. Maggiore è l’Entropia, maggiore è l’informazione ad essa associata, e più bit sono necessari per comunicare tale informazione. I dataset impuri hanno più Entropia e contengono più informazioni, mentre un dataset puro (dove tutte le osservazioni appartengono alla stessa classe) non ha Entropia, e non contiene informazioni.
L’Information Gain (IG) calcola la differenza tra l’Entropia prima e dopo la suddivisione del dataset padre, e indica l’impurità delle classi dei dataset risultanti. In altre parole, l’IG indica quanto bene una determinata feature separi le osservazioni del dataset di addestramento, in riferimento alle relative classi target. Se i dataset figli, creati dalla divisione del dataset padre, contengono meno classi rispetto al padre, essi sono “più puri”, e minimizzano l’Entropia.
Negli alberi decisionali, per massimizzare l’IG ad ogni suddivisione del dataset, è necessario definire una funzione obiettivo1 che si vuole massimizzare:
$$ \begin{align} \text{IG}(D_p, f) & = I(D_p) - \sum_{j=1}^{m}\left( \frac{N_j}{N_p} I\left(D_j\right) \right)\\[6pt] \end{align} $$dove:
- $D_p$ è il dataset del nodo padre.
- $f$ è la feature usata per eseguire la suddivisione del dataset.
- $N_j$ è il numero totale di esempi di training associati al $j$-esimo nodo figlio.
- $N_p$ è il numero totale di esempi di training associati al nodo padre.
- $\frac{N_j}{N_p}$ è il peso associato al $j$-esimo nodo figlio.
- $I$ è la misura dell’impurità, o criterio di divisione. Quelle più usate sono Entropy $\left(I_H\right)$ e Gini Impurity $\left(I_G\right)$.
- $D_j$ è il dataset del $j$-mo nodo figlio.
Pertanto, IG indica la differenza tra l’impurità del nodo padre e la somma delle impurità dei nodi figli. Più basse sono le impurità dei nodi figli, maggiore è il guadagno di informazione. Per semplicità, la maggior parte delle librerie di machine learning, compresa Scikit-Learn, implementano Decison Tree binari. Ciò significa che ogni nodo genitore è diviso in due nodi figli, $D_{\text{left}}$ e $D_{\text{right}}$, e l’IG viene calcolato come:
$$ \begin{align} \text{IG}(D_p, f) & = I(D_p) - \left(\frac{N_\text{left}}{N_p} I \left(D_{\text{left}}\right) + \frac{N_\text{right}}{N_p} I\left(D_{\text{right}}\right)\right)\\[6pt] & = I(D_p) - \frac{N_\text{left}}{N_p} I \left(D_{\text{left}}\right) - \frac{N_\text{right}}{N_p} I\left(D_{\text{right}}\right)\\[6pt] \end{align} $$Vediamo ora due misure di impurità: Entropy ($I_H$) e Gini Impurity ($I_G$).
Entropy
Entropy (Entropia) calcola la sorpresa o l’incertezza media di una fonte di messaggi. Essa indica quante informazioni otteniamo, ogni volta che riceviamo un messaggio da quella fonte. Quanto più un messaggio è inaspettato, o casuale, tante più informazioni contiene. Di contro, più il messaggio è ovvio, meno informazione restituisce. L’Entropia è definita dalla seguente formula:
$$ \begin{align} H\left(t\right) & = -\sum_{i=1}^{I} p\left(x_i\right)\log_2\left(p\left(x_i\right)\right)\\[6pt] \end{align} $$dove:
- $I$ è il numero di messaggi ottenuti dalla fonte.
- $x_i$ è l’$i$-esimo messaggio.
- $p\left(x_i\right)$ è la probabilità dell’$i$-esimo messaggio.
In particolare, il termine $\log_2\left(p\left(x_i\right)\right)$ indica quante informazioni (in bit) riceviamo quando appare il messaggio $x_i$:

Relativamente ad un classificatore Decision Tree, l’Entropia definisce l’impurità o l’incertezza di un dataset per tutte le classi non vuote (le classi che sono associate ad almeno un’osservazione), ed è definita come:
$$ \begin{align} I_{H}\left(t\right) & = -\sum_{c=1}^{C} p\left(c|t\right)\log_2\left(p\left(c|t\right)\right)\\[6pt] \end{align} $$dove:
- $I_{H}\left(t\right)$ è l’Entropia del $t$-mo nodo dell’albero decisionale, e misura il livello di impurità delle classi di tale nodo.
- $C$ è il numero totale di classi non vuote.
- $p(c|t)$ è la proporzione di esempi della classe $c$, che appartengono al nodo $t$, dove $p(c|t) \ne 0$
Abbiamo detto che l’Entropia è $0$ se tutti gli esempi di un nodo appartengono alla stessa classe. Se la proporzione di esempi che appartengono alla Classe “$1$” è $1$, ovvero: $p(c=1|t) = 1$, abbiamo:
$$ \begin{align} I_{H}\left(t\right) & = -p\left(1|t\right)\log_2\left(p\left(1|t\right)\right)\\[6pt] & = -1\log_2(1)\\[6pt] & = -1 \cdot 0\\[6pt] & = 0 \text{ bit}\\[6pt] \end{align} $$Al contrario, l’Entropia è massima se abbiamo una distribuzione uniforme di classi, cioè una distribuzione di probabilità in cui ogni classe $c$ ha la stessa probabilità di verificarsi. Ad esempio, in un contesto di classificazione binaria, se $p(c=1|t) = p(c=0|t) = 0.5$, abbiamo:
$$ \begin{align} I_{H}\left(t\right) & = -p\left(1|t\right)\log_2\left(p\left(1|t\right)\right) + \\[6pt] & ~~~~ -p\left(0|t\right)\log_2\left(p\left(0|t\right)\right)\\[6pt] & = -0.5\log_2(0.5) + \\[6pt] & ~~~~ -0.5\log_2(0.5)\\[6pt] & = 0.5 + 0.5\\[6pt] & = 1 \text{ bit}\\[6pt] \end{align} $$Con una distribuzione uniforme di $10$ classi, con $p(c|t) = 0.1$, dove $1 \le c \le 10$, abbiamo invece:
$$ \begin{align} I_{H}\left(t\right) & = -\sum_{c=1}^{10} p\left(c|t\right)\log_2\left(p\left(c|t\right)\right)\\[6pt] & = -0.1\log_2(0.1) \times 10\\[6pt] & = 3.32 \text{ bit}\\[6pt] \end{align} $$Relativamente ad un problema di classificazione binaria, il grafico seguente mostra il valore dell’Entropia, in funzione della proporzione di esempi della Classe Positiva, $p(c=1)$. Si noti come l’Entropia è massima quando $p(c=1) = 0.5$, ovvero quando il classificatore ha la stessa probabilità di ritornare la Classe Positiva e quella Negativa:

Gini Impurity
Gini Impurity (Impurità di Gini) misura la probabilità che un’esempio scelto casualmente da un dataset venga classificato in modo errato, se etichettato in base alla proporzione delle classi di quel dataset. In altre parole, essa indica la probabilità di sbagliare la classificazione di un esempio. Relativamente ad un classificatore Decision Tree, l’Impurità di Gini è usata per valutare quanto un nodo sia “puro” o “impuro”. Per un nodo $t$ con $C$ classi, l’Impurità di Gini è definita come:
$$ \begin{align} I_{G}\left(t\right) & = -\sum_{c=1}^{C} \left\{ p\left(c|t\right)\left[1 - p\left(c|t\right)\right] \right\}\\[6pt] & = -\left[p\left(1|t\right) - p\left(1|t\right)^2 + p\left(2|t\right) - p\left(2|t\right)^2 + \cdots + p\left(C|t\right) - p\left(C|t\right)^2\right]\\[6pt] & = 1 - \sum_{c=1}^{C} p\left(c|t\right)^2\\[6pt] \end{align} $$dove:
- $I_{G}\left(t\right)$ è l’Entropia del $t$-mo nodo dell’albero decisionale, e misura il livello di impurità delle classi di tale nodo.
- $C$ è il numero totale di classi non vuote.
- $p(c|t)$ è la proporzione di esempi che appartengono alla classe $c$, per un particolare nodo $t$. Ovvero il numero di esempi della classe $c$, diviso il numero totale di esempi nel nodo.
- $p(1|t) + \cdots + p(C|t) = 1$.
Notiamo che $\sum {p\left(c|t\right)^2}$ è la probabilità che, se si estraggono due esempi dal nodo (con reinserimento), entrambi appartengano alla stessa classe $c$. Possiamo pensare alla prima estrazione come alla classe reale della nostra osservazione, e alla seconda estrazione come alla classe predetta. Potremmo ottenere due classi $c_1 \rightarrow p\left(c_1|t\right)^2$, oppure due classi $c_2 \rightarrow p\left(c_2|t\right)^2$, oppure due classi $c_3 \rightarrow p\left(c_3|t\right)^2$, e così via. Visto che questi sono abbinamenti reciprocamente esclusivi, sommiamo le loro probabilità. Di contro, $1 - \sum {p\left(c|t\right)^2}$ è la probabilità che tali esempi appartengano a classi diverse. Dire che la “probabilità che due estrazioni da tale nodo ricadano in classi diverse” è equivalente a dire che “un esempio scelto casualmente da un dataset è classificato in modo errato, se etichettato in base alla proporzione delle classi di quel dataset”.
Analogamente ad Entropy, Gini Impurity è pari a $0$ se tutti gli esempi di un nodo appartengono alla stessa classe. In un’impostazione a classe singola, $p(c = 1|t) = 1$, abbiamo:
$$ \begin{align} I_{G}\left(t\right) & = 1 - p\left(c=1|t\right)^2\\[6pt] & = 1 - 1^2\\[6pt] & = 0\\[6pt] \end{align} $$Al contrario, Gini Impurity è massima (0.5) se le classi sono perfettamente miste. Ad esempio, in un contesto di classificazione binaria, dove $p(c = 1|t) = p(c = 0|t) = 0.5$, abbiamo:
$$ \begin{align} I_{G}\left(t\right) & = 1 - \left[p(1|t)^2 + p(0|t)^2\right]\\[6pt] & = 1 - 0.5^2 - 0.5^2\\[6pt] & = 0.5\\[6pt] \end{align} $$Relativamente ad un problema di classificazione binaria, il grafico seguente mostra il valore dell’Impurità di Gini, in funzione della proporzione di esempi della Classe Positiva, $p(c=1)$. Si noti come l’Entropia è massima quando $p(c=1) = 0.5$, ovvero quando il classificatore ha la stessa probabilità di ritornare la Classe Positiva e quella Negativa:

Considerando il dataset “Love Pop Music”, i valori Gini Impurity calcolati per ogni feature sono:
“Loves Popcorn”
La feature booleana “Loves Popcorn” suddivide il dataset di partenza in questo modo:

Nel subset di sinistra abbiamo 4 osservazioni, dove 1 è associata alla label positiva, e 3 a quella negativa. In quello di destra abbiamo 3 osservazioni, dove 2 sono associate alla label positiva, e 1 a quella negativa. Il valore di Gini Impurity totale associato all’espressione “Loves Popcorn == True” (oppure Loves Popcorn == False) è calcolato in questo modo:
$$ \begin{align} I_{G}\left(t\right) & = 1 - p(c=1|t)^2 - p(c=0|t)^2\\[6pt] I_{G}\left(D_\text{left}\right) & = 1 - \left(\frac{1}{4}\right)^2 - \left(\frac{3}{4}\right)^2 && \text{// 4 esempi: 1 yes; 3 no}\\[6pt] & = 1 - \frac{1}{16} - \frac{9}{16} = \frac{6}{16}\\[6pt] & = 0.375\\[6pt] I_{G}\left(D_\text{right}\right) & = 1 - \left(\frac{2}{3}\right)^2 - \left(\frac{1}{3}\right)^2 && \text{// 3 esempi: 2 yes; 1 no}\\[6pt] & = 1 - \frac{4}{9} - \frac{1}{9} = \frac{4}{9}\\[6pt] & = 0.444\\[6pt] I_{G_\text{total}}(D_\text{left}, D_\text{right}) & = \frac{N_\text{left}}{N_p} \cdot I_{G}\left(D_\text{left}\right) + \frac{N_\text{right}}{N_p} \cdot I_{G}\left(D_\text{right}\right)\\[6pt] & = \frac{4}{7} \cdot 0.375 + \frac{3}{7} \cdot 0.444\\[6pt] & = 0.405\\[6pt] \end{align} $$dove $I_{G_\text{total}}(D_\text{left}, D_\text{right})$ è la somma pesata di $I_{G}\left(D_\text{left}\right)$ e $I_{G}\left(D_\text{right}\right)$, considerando un totale di $7$ esempi.
“Loves Soda”
La feature booleana “Loves Soda” suddivide il dataset di partenza in questo modo:

Nel subset di sinistra abbiamo 4 osservazioni, dove 3 sono associate alla label positiva, e 1 a quella negativa. In quello di destra abbiamo 3 osservazioni, tutte associate alla label negativa (questo identifica un dataset puro). Il valore di Gini Impurity totale associato all’espressione “Loves Soda == True” (oppure “Loves Soda == False”) è calcolato in questo modo:
$$ \begin{align} I_{G}\left(D_\text{left}\right) & = 1 - \left(\frac{3}{4}\right)^2 - \left(\frac{1}{4}\right)^2 && \text{// 4 esempi: 1 yes; 3 no}\\[6pt] & = 0.375\\[6pt] I_{G}\left(D_\text{right}\right) & = 1 - \left(\frac{0}{3}\right)^2 - \left(\frac{3}{3}\right)^2 && \text{// 3 esempi: 0 yes; 3 no}\\[6pt] & = 0\\[6pt] I_{G_\text{total}}(D_\text{left}, D_\text{right}) & = \frac{4}{7} \cdot 0.375 + \frac{3}{7} \cdot 0 = 0.214\\[6pt] \end{align} $$“Age”
Poiché la feature “Age” contiene valori numerici, Il calcolo di Gini Impurity è un po’ più complesso del calcolo delle feature booleane. In questo caso, dobbiamo:
ordinare i valori “Age” in ordine crescente:
$7, 12, 18, 35, 38, 50, 83$per ogni coppia di valori “Age” $(\text{Age}i, \text{Age}{i+1})$:
calcolare il valore mediano correlato:
- $\text{median}(07, 12) = 9.5$
- $\text{median}(12, 18) = 15$
- $\text{median}(18, 35) = 26.5$
- $\text{median}(35, 38) = 36.5$
- $\text{median}(38, 50) = 44$
- $\text{median}(50, 83) = 66.5$
usare ogni valore mediano calcolato al punto 2.1 per dividere il dataset in due nodi, e calcolare il valore Gini Impurity totale:
- $\text{Age Median}$ $09.5$ $\rightarrow$ $I_{G_\text{total}} = 0.429$
- $\text{Age Median}$ $15.0$ $\rightarrow$ $I_{G_\text{total}} = 0.343$ (valore più basso)
- $\text{Age Median}$ $26.5$ $\rightarrow$ $I_{G_\text{total}} = 0.476$
- $\text{Age Median}$ $36.5$ $\rightarrow$ $I_{G_\text{total}} = 0.476$
- $\text{Age Median}$ $44.0$ $\rightarrow$ $I_{G_\text{total}} = 0.343$ (valore più basso)
- $\text{Age Median}$ $66.5$ $\rightarrow$ $I_{G_\text{total}} = 0.429$
selezionare il valore mediano di “Age” associato al valore più basso di Gini Impurity totale. In questo caso, il valore di Gini più basso è $0,343$, associato a due valori mediani di età: $15$ e $44$. Selezioniamo $15$ (oppure $44$). Pertanto, la regola decisionale utilizzata per suddividere il dataset diventa “Age $\gt$ 15” (oppure “Age $\le$ 15”), e il valore Gini Impurity totale associato è $0,343$.
Riassumendo, le regole decisionali del dataset, ordinate in ordine crescente in base al valore Gini Impurity totale, sono:
- “Loves Soda == True” $(0.214)$
- “Age $\le$ 15” $(0.343)$
- “Loves Popcorn == True” $(0.405)$
Di seguito possiamo vedere il codice usato per calcolare i valori $I_{G}\left(D_\text{left}\right)$, $I_{G}\left(D_\text{right}\right)$ e $I_{G_\text{total}}$ di cui sopra:
def _total_gini_impurity(X, y, dec_rule_left, dec_rule_right):
"""Calculate the Total Gini Impurity of a group of observations based on the ratio of examples
(probabilities) belonging to class 0 and class 1."""
# Dataset left
# =========================================
D_left_ids = np.argwhere(dec_rule_left).ravel()
p_c0, p_c1 = _proba(y[D_left_ids])
Ig_left = _gini_impurity(p_c0, p_c1)
# Dataset right
# =========================================
D_right_ids = np.argwhere(dec_rule_right).ravel()
p_c0, p_c1 = _proba(y[D_right_ids])
Ig_right = _gini_impurity(p_c0, p_c1)
# Total Gini Impurity
# =========================================
m = len(X)
w_left = len(D_left_ids) / m
w_right = len(D_right_ids) / m
Ig_total = w_left * Ig_left + w_right * Ig_right
return Ig_total, Ig_left, Ig_right
def _proba(y):
"""Calculate the ratio of examples (probabilities) belonging to class 0 and class 1."""
m = len(y)
p_c0 = np.count_nonzero(y == 0) / m
p_c1 = np.count_nonzero(y == 1) / m
return p_c0, p_c1
def _gini_impurity(p_c0, p_c1):
"""Calculate the Gini Impurity of a group of boolean observations based on the ratio of examples
(probabilities) belonging to class 0 and class 1."""
# I_G(t) = 1 - ∑(c=1,C) { p(c|t)^2 }
return 1 - p_c0**2 - p_c1**2
def total_gini_impurity_bool(X, y):
"""Calculate the Total Gini Impurity of a group of boolean observations
based on the ratio of examples (probabilities) belonging to class 0 and class 1."""
return _total_gini_impurity(X, y, X == 1, X == 0)
def total_gini_impurity_float(X, y):
# sort unique values in ascending order. Then, for each pair of age values (age_i, age_i+1),
# compute the related median value. Then use that value to split the dataset into two nodes,
# to compute the Total Gini Impurity
X_u = np.unique(X)
values = []
for i in range(len(X_u) - 1):
median = np.median((X_u[i], X_u[i + 1]))
Ig_total, _, _ = _total_gini_impurity(X, y, X <= median, X > median)
values.append((median, Ig_total))
return values
def print_gini_impurity(X, y, feature_idx, feature_label):
Ig_total, Ig_left, Ig_right = total_gini_impurity_bool(X[:, feature_idx], y)
print(f"Total Gini Impurity for '{feature_label}': {Ig_total:.3f} "
f"(left: {Ig_left:.3f}, right: {Ig_right:.3f})")
# "Loves Pop Music" Dataset.
# Features are: "Loves Popcorn", "Loves Soda", "Age"
X = np.array([[True, True, 7],
[True, False, 12],
[False, True, 18],
[False, True, 35],
[True, True, 38],
[True, False, 50],
[False, False, 83]])
# "Loves Pop Music" Label
y = np.array([0, 0, 1, 1, 1, 0, 0])
print_gini_impurity(X, y, 0, "Loves Popcorn")
print_gini_impurity(X, y, 1, "Loves Soda")
values = total_gini_impurity_float(X[:, 2], y)
print("Total Gini Impurity values for 'Age' Median:")
for value in values:
print(f" 'Age' Median: {value[0]} -> Ig_total: {value[1]:.3f}")
print("\nBest Total Gini Impurity value, and associated 'Age' Median:")
id = np.argmin(np.array(values)[:, 1])
value = values[id]
print(f" Ig_total: {value[1]:.3f} -> 'Age' Median: {value[0]}")
una funzione obiettivo è una funzione matematica che rappresenta il criterio da ottimizzare (massimizzare o minimizzare) all’interno di un problema. La funzione obiettivo è espressa generalmente come $f(\textbf{x})$, dove $\textbf{x}$ rappresenta un insieme di variabili decisionali. Queste variabili possono essere soggette a vincoli che limitano l’insieme delle soluzioni ammissibili; l’obiettivo è trovare i valori di $\textbf{x}$ che ottimizzano questa funzione. ↩︎