Last chance! 50% off DataCamp Premium
Sale ends in
Il machine learning è complesso e ricco di matematica.
Se sei agli inizi, è quasi certo che farai fatica a imparare i meccanismi interni delle decine di modelli a tua disposizione. Il problema si amplifica se non hai un background matematico. Ma anche con un dottorato, potresti comunque trovare difficile spiegare i risultati del modello a una figura business. Di solito non condividono il tuo livello di entusiasmo per i dettagli tecnici, ma vogliono essere sicuri che i tuoi modelli prendano decisioni nel modo giusto.
Quindi, come colmare il divario?
Esiste un linguaggio universale parlato da tecnici e business: la visualizzazione dei dati. I grafici sono facili da capire e rendono più gestibile la spiegazione di argomenti complessi, a prescindere dal background.
In questo articolo ti insegnerò i concetti di base e avanzati per visualizzare i modelli di machine learning supervisionato. Se cerchi buoni materiali sull’apprendimento non supervisionato, il nostro corso Unsupervised Learning with Python fa al caso tuo.
Dopo la lettura, termini come coefficienti, residui, importanza delle variabili, SHAP, LIME e Grad-CAM non ti suoneranno più come una lingua straniera.
Buttiamoci!
Partirò con un po’ di teoria leggera sui tipi di visualizzazione dei modelli di machine learning più comuni. Serve per allinearci.
Se li conosci già, sentiti libero di saltare questa sezione.
Quando vuoi capire quali attributi del tuo dataset contribuiscono di più alle previsioni di un modello di machine learning, è qui che devi guardare.
Pensala come una torta: ingredienti come farina e zucchero sono essenziali, altri, come le codette, non contribuiscono poi così tanto. In altre parole, puoi avere una torta senza codette ma non senza farina e zucchero.
Se rimuovi una variabile con un punteggio di importanza elevato, le prestazioni del modello caleranno in modo significativo.
Un grafico di importanza delle variabili è spesso associato ai modelli basati su alberi (albero decisionale, random forest, gradient boosting). I valori reali diventano disponibili dopo l’addestramento del modello e visualizzarli richiede solo poche righe di codice.
Le metriche più comuni per valutare i modelli di machine learning (ad es. per la classificazione) sono la matrice di confusione, la curva ROC e la curva PR (precision-recall).
La prima mostra il numero di previsioni corrette per classe su una diagonale dall’alto a sinistra al basso a destra. Tutto ciò che sta fuori è considerato errore (falso positivo o falso negativo).
La seconda mostra il trade-off tra tasso di veri positivi e tasso di falsi positivi per un insieme di soglie di classificazione.
E la terza mostra l’equilibrio tra previsioni positive corrette (precision) e la capacità del modello di identificare tutte le istanze positive (recall). Sia le curve ROC che PR funzionano solo con dataset di classificazione binaria. In caso di problemi multiclasse, dovrai confrontare due classi alla volta.
Certo, esistono altre metriche di valutazione, ma queste tre sono le più comuni. Sono integrate in scikit-learn, la libreria che userai per addestrare i modelli di machine learning.
Quando lavori con modelli lineari (ad es. regressione lineare), di solito vorrai tracciare i residui.
Questi rappresentano la differenza tra i valori reali e quelli predetti. Quando li tracci, dovresti controllare eventuali pattern visibili. Idealmente, dovrebbero essere dispersi casualmente attorno allo zero (distribuiti normalmente). Se non è così, il modello fa previsioni distorte e c’è ancora lavoro da fare.
In generale, è un grafico semplicissimo da fare e può dirti molto sul tuo modello di regressione.
I modelli di machine learning sono spesso trattati come scatole nere, ma non dev’essere per forza così.
Tecniche avanzate di interpretazione come SHAP (SHapely Additive exPlanations) e LIME (Local Interpretable Model-agnostic Explanations) aiutano a rispondere alla domanda su come il modello è arrivato a una certa decisione.
SHAP assegna un valore di importanza a ogni variabile per una singola previsione (pensa: questa persona è fumatore, quindi l’importo dell’assicurazione sarà più alto), mentre LIME crea un modello più semplice che approssima la previsione di un modello complesso per una particolare istanza.
Più avanti nell’articolo ti mostrerò esattamente come funzionano.
Ma prima di entrare nel vivo, vediamo un paio di librerie Python che ti serviranno per seguire.
In questa breve sezione ti mostrerò come installare le dipendenze di sistema e le librerie Python necessarie per visualizzare i modelli di machine learning.
L’unica dipendenza di sistema di cui avrai bisogno per seguire è Graphviz. Lo userai più avanti per visualizzare un albero decisionale, e il codice non funzionerà senza Graphviz installato.
È un software open source per creare diagrammi, grafi astratti e reti. Non lo userai direttamente; solo tramite scikit-learn.
Questa libreria Python è ampiamente usata per i compiti di machine learning in Python.
In questo articolo la userai per addestrare modelli, dividere i dataset, scalare le variabili numeriche e visualizzare le prestazioni dei modelli. È imprescindibile, quindi installala con il seguente comando (in base al tuo ambiente Python):
pip install scikit-learn
conda install scikit-learn
Se sei completamente nuovo a scikit-learn, ti consigliamo di guardare il nostro popolare corso sul machine learning supervisionato.
La libreria SHAP in Python è uno strumento molto usato per spiegare le previsioni dei modelli di machine learning. Sfrutta concetti della teoria dei giochi (ad es. valori di Shapley) per misurare il contributo di ogni attributo alla previsione del modello.
Ancora meglio, è ricca di visualizzazioni utili che ti aiutano a capire il funzionamento interno dei tuoi modelli.
Installala con il seguente comando:
pip install shap
conda install -c conda-forge shap
Questa libreria Python è molto usata quando è cruciale spiegare la singola previsione di un modello. Funziona in modo diverso da SHAP: approssima localmente il modello originale con un modello interpretabile e più semplice. Poi mostra il contributo di ogni variabile del dataset alla previsione.
Tra un attimo vedrai come funziona LIME, ma prima installala:
pip install lime
conda install conda-forge::lime
Se stai costruendo modelli di reti neurali con TensorFlow, allora TensorBoard è un no-brainer.
È uno strumento di visualizzazione che ti aiuta a tracciare gli esperimenti di machine learning e monitorare le metriche di addestramento (ad es. loss e accuracy). Visualizza e aggiorna in tempo reale i grafi del modello e mostra come cambiano i parametri durante l’addestramento.
TensorBoard può essere usato con altri framework di deep learning come PyTorch, ma per questo articolo mi concentrerò su TensorFlow.
Installalo eseguendo il seguente comando:
pip install tensorboard
conda install -c conda-forge tensorboard
L’ultimo passo in questa fase di preparazione è occuparsi dei dati.
Oggi userò due dataset: ammissioni MBA per la classificazione e assicurazioni per la regressione. Entrambi sono gratuiti e disponibili per il download su Kaggle.
Per iniziare, importa queste librerie Python:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
Per quanto riguarda il dataset di classificazione, il preprocessing che ho fatto è minimo. Si riduce a:
Non è certo una pipeline di preprocessing completa. Se hai tempo, migliorala pure.
Ad ogni modo, copia questa funzione per mettere a posto il dataset di classificazione:
def load_classification_dataset() -> pd.DataFrame:
# https://www.kaggle.com/datasets/taweilo/mba-admission-dataset?resource=download
df = pd.read_csv("MBA.csv")
# Just an arbitrary ID
df = df.drop(["application_id"], axis=1)
# Fill unknown
df["race"] = df["race"].fillna("Unknown")
# Assume these are denied
df["admission"] = df["admission"].fillna("Deny")
# Convert boolean cols to 0/1
df["gender"] = df["gender"].replace({"Male": 0, "Female": 1})
df["international"] = df["international"].replace({False: 0, True: 1})
# Create dummy columns for categorical features
cols_for_dummy = ["major", "race", "work_industry"]
for col in cols_for_dummy:
dummies = pd.get_dummies(df[col], prefix=col)
df = pd.concat([df, dummies], axis=1)
# To drop
cols_to_drop = ["major", "race", "work_industry", "major_Humanities", "race_Unknown", "work_industry_Other"]
df = df.drop(cols_to_drop, axis=1)
return df
load_classification_dataset().sample(5)

Un campione del dataset MBA modificato. Immagine dell’autore.
Ora farò lo stesso per il dataset di regressione.
Il dataset di regressione scelto contiene l’importo dell’assicurazione ($) come variabile continua che il modello cercherà di predire sulla base di altri attributi.
Anche qui, il preprocessing che ho fatto è piuttosto minimale. Si riduce a:
Se hai tempo, aggiungi pure altri passaggi alla pipeline.
Copia la seguente funzione per caricare e preprocessare il dataset assicurativo:
def load_regression_dataset() -> pd.DataFrame:
# https://www.kaggle.com/datasets/mirichoi0218/insurance
df = pd.read_csv("MedicalCostPersonal.csv")
# Scale numerical features
cols_to_scale = ["age", "bmi", "children"]
scaler = StandardScaler()
df[cols_to_scale] = scaler.fit_transform(df[cols_to_scale])
# Binary features
df["sex"] = df["sex"].replace({"male": 0, "female": 1})
df["smoker"] = df["smoker"].replace({"no": 0, "yes": 1})
# Dummies
dummies_region = pd.get_dummies(df["region"], prefix="region", drop_first=True)
df = pd.concat([df, dummies_region], axis=1)
df = df.drop("region", axis=1)
return df
load_regression_dataset().sample(5)

Un campione del dataset assicurativo modificato. Immagine dell’autore.
E questo è tutto!
Nella sezione seguente ti mostrerò come iniziare a visualizzare i modelli di machine learning.
I modelli basati su alberi sono spesso usati per la classificazione, ma la maggior parte può gestire anche compiti di regressione.
In questa sezione ti mostrerò come visualizzare un albero decisionale, l’importanza delle variabili da un modello random forest e le spiegazioni delle previsioni con SHAP e LIME.
Tieni a mente che gli alberi decisionali e le random forest possono essere difficili da comprendere. Abbiamo un corso completo che copre le basi dei fondamentali dei modelli di machine learning basati su alberi in Python.
Per iniziare, carica il dataset di classificazione e dividilo in sottoinsiemi di training e test:
df = load_classification_dataset()
X = df.drop("admission", axis=1)
y = df["admission"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
Ora, visualizziamo un albero decisionale!
Pensa a un albero decisionale come a un insieme di istruzioni annidate if in cui le condizioni sono determinate da un modello di machine learning.
C’è di più, ma con questa analogia si capisce che visualizzare le decisioni dovrebbe essere un processo lineare. E lo è: la funzione plot_tree() di sklearn fa gran parte del lavoro.
Inizia addestrando un modello ad albero decisionale. Il parametro max_depth è opzionale ed è qui solo per scopi di visualizzazione. Senza, l’albero diventa troppo profondo, e rischi di perderti nell’enorme quantità di decisioni prese dal modello, soprattutto per dataset grandi.
Il seguente snippet addestra il modello di classificazione ad albero decisionale sul sottoinsieme di training:
from sklearn import tree
decision_tree = tree.DecisionTreeClassifier(random_state=42, max_depth=4)
decision_tree.fit(X_train, y_train)
E per la visualizzazione, copia semplicemente il seguente snippet. I parametri opzionali filled e feature_names rendono l’albero più facile da interpretare:
plt.figure(figsize=(12,8))
tree.plot_tree(decision_tree, filled=True, feature_names=X.columns, class_names=y.unique())
plt.title("Decision Tree Visualization", size=20, loc="left", y=1.04, weight="bold")
plt.show()

Albero decisionale profondo quattro livelli. Immagine dell’autore.
Nota che le decisioni prese dal modello non significano nulla se il modello non è accurato. Più avanti nell’articolo ti mostrerò come stimare l’accuratezza.
Ricordi l’analogia della torta? È il momento di metterla in pratica.
Ogni volta che addestri un modello ad albero con sklearn, hai accesso alla proprietà feature_importances_. Abbinata ai nomi delle variabili, hai tutti i dati necessari per vedere quali attributi contribuiscono di più alla previsione.
Vediamolo in azione! Per prima cosa, addestra un classificatore random forest sul sottoinsieme di training:
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(n_estimators=25, random_state=42)
random_forest.fit(X_train, y_train)
La visualizzazione ora si riduce a estrarre e ordinare le importanze e sostituire gli indici con i nomi delle variabili:
importances = random_forest.feature_importances_
indices = np.argsort(importances)[::-1]
plt.figure(figsize=(10,6))
bars = plt.bar(range(X.shape[1]), importances[indices], edgecolor="#008031", linewidth=1)
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width() / 2, height, f"{height:.2f}", ha="center", va="bottom", size=8)
plt.title("Feature Importances", size=20, loc="left", y=1.04, weight="bold")
plt.ylabel("Importance")
plt.xticks(range(X.shape[1]), np.array(X.columns)[indices], rotation=90, size=12)
plt.show()

Grafico di importanza delle variabili della random forest. Immagine dell’autore.
Sembra che la media dei voti su una scala 4.0 e il punteggio GMAT contribuiscano maggiormente all’ammissione a un programma MBA. Seguono l’esperienza lavorativa, che è un requisito per gli studi MBA. La specializzazione e il settore in cui si lavora sono molto meno rilevanti.
L’importanza delle variabili dà il quadro generale, ma se vuoi visualizzare i modelli di machine learning a livello di singola previsione?
Qui entrano in gioco SHAP e LIME. Parlerò prima di SHAP. Sai già cos’è, quindi salto la teoria.
Adatterò un modello di gradient boosting al nostro dataset di regressione per vedere l’impatto delle singole variabili sulle spese assicurative. Il seguente snippet mostra come adattare il modello e calcolare i valori SHAP da un modello shap.Explainer():
import shap
from xgboost import XGBRegressor
df = load_regression_dataset()
# No need for train-test splits
X = df.drop("charges", axis=1)
y = df["charges"]
model = XGBRegressor().fit(X, y)
# Shap explainer
explainer = shap.Explainer(model)
shap_values = explainer(X)
Con SHAP c’è un ventaglio di grafici che puoi realizzare.
Inizierò con waterfall() ed esaminerò i valori SHAP per la prima previsione:
shap.plots.waterfall(shap_values[0])

Spiegazioni della prima previsione. Immagine dell’autore.
In questo caso specifico, essere fumatore aumenta drasticamente le spese assicurative. Le variabili che hanno più impatto nel ridurre le spese sono BMI (collegato al peso) ed età. Altre variabili hanno un impatto minimo o nullo.
Puoi rappresentare il grafico precedente in un formato più compatto:
shap.plots.force(shap_values[0])

Spiegazioni concise della prima previsione. Immagine dell’autore.
Le informazioni sono le stesse: le variabili in rosso aumentano le spese, quelle in blu le riducono. Il punto in cui si incontrano mostra le spese assicurative per una singola istanza.
Il grafico beeswarm mostra quali variabili sono più importanti tracciando i valori SHAP di ogni variabile per ogni campione. Le variabili sono ordinate per la somma delle magnitudini dei valori SHAP su tutti i campioni. Il colore rappresenta il valore della variabile (rosso alto e blu basso):
shap.plots.beeswarm(shap_values)

Effetto riassuntivo di tutte le variabili. Immagine dell’autore.
In sintesi, essere giovani, non fumatori e con un BMI ragionevole riduce le spese assicurative.
L’ultima visualizzazione SHAP che voglio mostrarti è il grafico a barre della media del valore assoluto. Calcola il valore assoluto medio di tutti i valori SHAP per ciascuna variabile:
shap.plots.bar(shap_values)

Valore medio assoluto di tutti i valori SHAP per tutte le variabili. Immagine dell’autore.
In altre parole, è un modo sofisticato per calcolare l’importanza globale delle variabili: il grafico non è legato a una singola previsione.
E questo è tutto per SHAP. Ora sposto l’attenzione su LIME.
Proprio come SHAP, LIME riguarda l’interpretabilità dei modelli.
Non ha così tanti tipi di visualizzazione, ma fa bene una cosa—almeno con i dataset tabellari.
A scopo dimostrativo, caricherò il dataset di classificazione e lo convertirò ulteriormente in un compito di classificazione binaria sostituendo le voci in lista d’attesa con quelle rifiutate. Questo renderà più semplice comprendere l’output di LIME:
from lime import lime_tabular
df = load_classification_dataset()
# Convert to binary
df["admission"] = df["admission"].replace({"Waitlist": "Deny"})
X = df.drop("admission", axis=1)
y = df["admission"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
X_train.shape, y_test.shape
random_forest = RandomForestClassifier(n_estimators=25, random_state=42)
random_forest.fit(X_train, y_train)
La classe LimeTabularExplainer() ora riceve i dati di training, i nomi delle colonne, i nomi delle categorie nella variabile target e la modalità del modello di machine learning (classificazione o regressione):
explainer = lime_tabular.LimeTabularExplainer(
training_data=np.array(X_train),
feature_names=X_train.columns,
class_names=["Admit", "Deny"],
mode="classification"
)
Una volta fatto, puoi chiamare il metodo explain_instance() per interpretare una singola previsione sulla base delle probabilità di previsione delle classi:
exp = explainer.explain_instance(
data_row=X_test.iloc[0],
predict_fn=random_forest.predict_proba
)
exp.show_in_notebook(show_table=True)

Spiegazioni LIME (1). Immagine dell’autore.
Il modello LIME è sicuro al 96% che questa ammissione MBA sarà rifiutata. Variabili come gmat e gender hanno avuto il maggior impatto sulla decisione.
Ora facciamo lo stesso per un’istanza ammessa al programma MBA:
exp = explainer.explain_instance(
data_row=X_test.iloc[234],
predict_fn=random_forest.predict_proba
)
exp.show_in_notebook(show_table=True)

Spiegazioni LIME (2). Immagine dell’autore.
Le stesse variabili ora hanno avuto l’effetto opposto! Questa persona aveva un punteggio gmat elevato, che è il contributo maggiore all’ammissione al programma MBA.
E questo è tutto per i modelli ad albero! Ora imparerai come visualizzare i modelli lineari per compiti di regressione.
Se stai iniziando con il predictive modeling, non c’è nulla di più basilare della regressione lineare. È un modello semplice, facile da capire e che funziona bene, purché le relazioni nel tuo dataset siano lineari.
Esistono altri modelli lineari, ma in questa sezione lavorerò solo con la regressione lineare.
Inizia caricando il dataset di regressione, dividendolo in sottoinsiemi di training e test e adattando un modello di regressione lineare alla parte di training:
from sklearn.linear_model import LinearRegression
df = load_regression_dataset()
X = df.drop("charges", axis=1)
y = df["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
model = LinearRegression().fit(X_train, y_train)
Il primo tipo di visualizzazione che esplorerò sono i coefficienti del modello.
In parole povere, un modello di regressione lineare si riduce a una singola equazione. Forse ricordi y = mx+b dal liceo: l’idea è la stessa.
L’equazione di regressione si espande a y = w0 + w1_x1 + w2_x2 + … + wn_xn per gestire più parametri. L’obiettivo del modello è trovare la stima migliore dei pesi (w) dato l’insieme delle variabili in input (x).
Perché è importante?
Perché puoi accedere ai coefficienti (pesi) dopo l’addestramento e analizzarne il contributo: le variabili con un coefficiente più grande avranno un contributo maggiore nel predire la variabile target.
Puoi ottenere i coefficienti accedendo al parametro coef_ su un modello addestrato.
Il seguente snippet recupera i coefficienti e li traccia come un grafico a barre orizzontali:
features = X_train.columns
coefficients = model.coef_
plt.figure(figsize=(10, 4))
bars = plt.barh(y=features, width=coefficients, edgecolor="#008031", linewidth=1)
for bar in bars:
width = bar.get_width()
plt.text(width + 1, bar.get_y() + bar.get_height()/2, f"{width:.2f}",
va="center", ha="left")
plt.xlabel("Coefficient value")
plt.title("Linear Regression Model Coefficients", y=1.05)
plt.show()

Coefficienti del modello di regressione lineare. Immagine dell’autore.
Le variabili smoker, BMI ed age sono i contributori positivi più grandi alle spese assicurative—quando aumentano, aumenta anche l’importo. Ci sono anche un paio di coefficienti negativi, che riducono l’importo complessivo.
L’altro tipo comune di visualizzazione per la regressione è il grafico dei residui. In parole semplici, stai creando uno scatter plot con i valori predetti sull’asse X e i residui (valori reali - predetti) sull’asse Y.
Idealmente, non dovrebbero essere visibili pattern nei residui e dovrebbero essere centrati attorno a 0. In altre parole, dovrebbero essere distribuiti normalmente.
Usa questo snippet per visualizzare i residui di un modello di regressione lineare sul dataset assicurativo:
y_pred = model.predict(X_test)
residuals = y_test - y_pred
plt.figure(figsize=(10, 6))
plt.scatter(y_pred, residuals, color="#03EF62", alpha=0.6, edgecolors="#008031")
plt.axhline(0, color="red", linestyle="--")
plt.xlabel("Predicted Values")
plt.ylabel("Residuals")
plt.title("Residuals vs Predicted Values", y=1.05)
plt.grid(True)
plt.show()

Residui del modello di regressione lineare. Immagine dell’autore.
Non è il miglior grafico dei residui che abbia visto. Esteticamente è gradevole, ma i valori sono sparsi ovunque. Se come practitioner ottieni un grafico simile, hai parecchio lavoro davanti.
A seguire, ti mostrerò 3 modi per visualizzare i modelli di reti neurali.
Se c’è un’area del machine learning in cui visualizzazione e interpretazione contano di più, sono le reti neurali.
Sono complesse da comprendere, anche a livello fondamentale. Ci sono diversi tipi di layer, funzioni di attivazione e backpropagation, per citarne alcuni. Per questo motivo, le reti neurali sono spesso sinonimo di modelli a scatola nera.
Non dev’essere per forza così.
In questa sezione ti mostrerò tre modi per visualizzare le reti neurali: grafici dell’architettura, metriche di training in tempo reale e Grad-CAM.
La mia libreria di riferimento è TensorFlow. Se non ne hai mai sentito parlare, abbiamo un corso per principianti su TensorFlow che ti farà partire in fretta!
Quando visualizzi l’architettura del tuo modello di rete neurale, chiarisci una cosa—le shape.
In altre parole, vedrai come la matrice sottostante cambia dimensione attraversando i layer. Può essere un argomento difficile per i neofiti, quindi ogni visualizzazione è benvenuta.
Per dimostrare, userò TensorFlow per creare un modello base di rete neurale per la classificazione di cifre scritte a mano. Poi userò la funzione plot_model() per salvare l’immagine dell’architettura su un file locale.
Dai un’occhiata:
from tensorflow.keras import layers, models
from tensorflow.keras.utils import plot_model
model = models.Sequential()
model.add(layers.Input(shape=(28, 28, 1)))
model.add(layers.Conv2D(32, (3, 3), activation="relu"))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation="relu"))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation="relu"))
model.add(layers.Dense(10, activation="softmax"))
plot_model(model, to_file="model_architecture.png", show_shapes=True, show_layer_names=True)

Architettura del modello di rete neurale. Immagine dell’autore.
Utile, certo, ma questo tipo di visualizzazione ha un limite.
Sai come i dati si trasformano lungo il percorso, ma non hai idea di come una rete neurale arrivi alle sue conclusioni. È quello che vedremo ora.
Grad-CAM, o Gradient Class Activation Mapping, è una tecnica popolare per visualizzare i modelli di reti neurali convoluzionali.
Più precisamente, ti aiuta a capire quali parti di un’immagine in input contribuiscono maggiormente alle previsioni del modello. Pensala come un grafico di importanza delle variabili per un albero decisionale, ma potenziato.
È una tecnica avanzata di interpretabilità che funziona per tutti i modelli convoluzionali indipendentemente dall’architettura e ti aiuta a capire perché una rete neurale fa una certa previsione.
Ma c’è un problema: non è banale da implementare in Python. Ecco una panoramica ad alto livello dell’algoritmo:
È un processo piuttosto articolato e, per semplificare, userò un modello pre-addestrato ResNet50 che può già classificare 1000 tipi di immagini.
Ma prima, carica le librerie necessarie e l’immagine per cui vuoi vedere un Grad-CAM. Io uso una foto stock di un cane:
import cv2
import tensorflow as tf
from tensorflow.keras.applications.resnet50 import ResNet50
from tensorflow.keras.preprocessing.image import load_img
image = np.array(load_img("dog.jpg", target_size=(224, 224, 3)))
plt.grid(False)
plt.imshow(image)

Immagine di esempio del cane. Immagine dell’autore.
Ora inizia la parte divertente. Nel seguente snippet implemento il processo in quattro passaggi descritto prima. Troverai commenti sopra ogni riga di codice per facilitare la comprensione:
# Load the pre-trained ResNet50 model
model = ResNet50()
# Extract the output of the last convolutional layer
last_conv_layer = model.get_layer("conv5_block3_out")
# Create a model that outputs the last convolutional layer’s activations
last_conv_layer_model = tf.keras.Model(model.inputs, last_conv_layer.output)
# Prepare the classifier model using the layers after the last convolutional layer
classifier_input = tf.keras.Input(shape=last_conv_layer.output.shape[1:])
x = classifier_input
for layer_name in ["avg_pool", "predictions"]:
# Reuse the pooling and prediction layers from the ResNet50 model
x = model.get_layer(layer_name)(x)
# Create a new model that takes in the last conv layer output and returns predictions
classifier_model = tf.keras.Model(classifier_input, x)
# Use a GradientTape to record operations for automatic differentiation
with tf.GradientTape() as tape:
# Prepare the input image and get the activations from the last conv layer
inputs = image[np.newaxis, ...]
last_conv_layer_output = last_conv_layer_model(inputs)
tape.watch(last_conv_layer_output) # Watch the conv layer output
# Get predictions from the classifier model
preds = classifier_model(last_conv_layer_output)
# Get the index of the highest predicted class
top_pred_index = tf.argmax(preds[0])
# Focus on the prediction of the top class
top_class_channel = preds[:, top_pred_index]
# Compute the gradient of the top predicted class with respect to the conv layer output
grads = tape.gradient(top_class_channel, last_conv_layer_output)
# Average the gradients over the width and height dimensions
pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
# Multiply each channel in the conv layer output by its corresponding gradient
last_conv_layer_output = last_conv_layer_output.numpy()[0]
pooled_grads = pooled_grads.numpy()
for i in range(pooled_grads.shape[-1]):
last_conv_layer_output[:, :, i] *= pooled_grads[I]
# Compute the Grad-CAM by averaging the channels and apply a ReLU activation
gradcam = np.mean(last_conv_layer_output, axis=-1)
# Normalize the Grad-CAM to be between 0 and 1
gradcam = np.clip(gradcam, 0, np.max(gradcam)) / np.max(gradcam)
# Resize the Grad-CAM heatmap to the size of the original image (224x224)
gradcam = cv2.resize(gradcam, (224, 224))
È stato tanto, certo, ma ora puoi finalmente tracciare la heatmap prodotta dall’algoritmo Grad-CAM:
plt.grid(False)
plt.imshow(gradcam)

Heatmap Grad-CAM. Immagine dell’autore.
I punti più chiari indicano le aree in cui l’attivazione è stata più alta, ma la heatmap da sola non dice granché.
È molto meglio sovrapporla all’immagine originale, riducendo leggermente l’opacità:
plt.grid(False)
plt.imshow(image)
plt.imshow(gradcam, alpha=0.5)

Immagine del cane con overlay grad-CAM. Immagine dell’autore.
Interpretazione: il fattore che contribuisce maggiormente a classificare questa immagine come “golden retriever” è il muso, il che ha senso.
Puoi usare Grad-CAM per assicurarti che il tuo modello faccia previsioni nel modo giusto. In questo caso, immagina che la heatmap evidenzi altro, come la sedia sullo sfondo, come fattore principale. Ti fideresti di quel modello?
L’addestramento di un modello di rete neurale può richiedere molto tempo. La buona notizia è che non devi aspettare la fine per dare un’occhiata alle prestazioni. Librerie come TensorBoard possono mostrartelo in tempo reale.
TensorBoard è incluso in TensorFlow, quindi non devi installare nulla per seguire.
A scopo dimostrativo, allenerò un classificatore di cifre base per 25 epoche. La parte importante è il callback—qui specifichi il percorso e il formato dei log di training, che TensorBoard userà tra un attimo.
Ecco il codice per addestrare il modello e salvare i log del training:
import tensorflow as tf
from tensorflow.keras import layers, models
from datetime import datetime
# Load MNIST dataset
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# Normalize pixel values between 0 and 1
x_train, x_test = x_train / 255.0, x_test / 255.0
# Add a channels dimension (for the Convolutional layer)
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)
# Build a simple CNN model
model = models.Sequential([
layers.Conv2D(32, kernel_size=(3, 3), activation="relu", input_shape=(28, 28, 1)),
layers.MaxPooling2D(pool_size=(2, 2)),
layers.Flatten(),
layers.Dense(128, activation="relu"),
layers.Dense(10, activation="softmax")
])
# Compile the model
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"]
)
# Set up TensorBoard callback
log_dir = "logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
# Train the model with TensorBoard monitoring
model.fit(
x_train,
y_train,
epochs=25,
validation_data=(x_test, y_test),
callbacks=[tensorboard_callback]
)

Processo di training del modello. Immagine dell’autore.
Mentre il modello è in addestramento, esegui TensorBoard dal terminale specificando il percorso alla cartella dei log:
tensorboard --logdir=logs/fit
Per impostazione predefinita, TensorBoard gira sulla porta 6006:

Metriche TensorBoard (1). Immagine dell’autore.
L’immagine sopra mostra l’istogramma dei bias e l’accuratezza per epoca, sia per i set di training che di validazione. Si vede che l’accuratezza è molto alta in entrambi i casi, vicina al 100%—è solo la scala dell’asse Y ad essere troppo piccola.
Altre schede approfondiscono metriche più specifiche e ti permettono di regolare alcune cose, come vedi qui sotto:

Metriche TensorBoard (2). Immagine dell’autore.
In conclusione, TensorBoard è un ottimo strumento per visualizzare le prestazioni del modello e può aiutarti ad analizzarle mentre il modello si addestra.
In quest’ultima parte, voglio fare un passo indietro e approfondire metriche più generali per visualizzare le prestazioni del modello.
Ne vedrai tre: matrice di confusione, curva ROC e curva Precision-Recall.
Dato che sono legate ai problemi di classificazione, dovrai caricare il dataset MBA di classificazione. Per semplificare, l’ho anche convertito in un problema binario impostando le voci in lista d’attesa come rifiutate. Il resto del codice divide i dati in sottoinsiemi di training e test e addestra un modello di classificazione random forest:
from sklearn.ensemble import RandomForestClassifier
df = load_classification_dataset()
df["admission"] = df["admission"].replace({"Waitlist": "Deny"})
df["admission"] = df["admission"].replace({"Deny": 0, "Admit": 1})
df.rename(columns={"admission": "is_admitted"}, inplace=True)
X = df.drop("is_admitted", axis=1)
y = df["is_admitted"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
random_forest = RandomForestClassifier(n_estimators=25, random_state=42)
random_forest.fit(X_train, y_train)
Partiamo dalla prima metrica: la matrice di confusione.
Una matrice di confusione ti dice quanto bene performa il modello. In uno scenario ideale, i valori sulla diagonale dall’alto a sinistra al basso a destra sarebbero gli unici elementi non zero, a indicare che il modello non ha fatto previsioni errate.
Ma nel mondo reale succede raramente.
Usa le seguenti linee guida per interpretare una matrice di confusione (per classificazione binaria):
Non c’è una regola generale sul fatto che dovresti preoccuparti di più dei falsi positivi o dei falsi negativi. I primi sono più dolorosi nel caso delle ammissioni MBA, perché il modello classifica lo studente come ammesso, ma in realtà non lo è. In altri casi, come la diagnosi di cancro, è vitale minimizzare i falsi negativi, perché non vuoi dichiarare sano qualcuno che ha il cancro.
Qui entra in gioco la conoscenza del dominio.
Comunque, torniamo al codice. Il seguente snippet calcola la matrice di confusione del nostro modello random forest sul set di test e usa la classe ConfusionMatrixDisplay per creare una visualizzazione:
from sklearn.metrics import confusion_matrix
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
preds = random_forest.predict(X_test)
cm = confusion_matrix(y_true=y_test, y_pred=preds, labels=random_forest.classes_)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=random_forest.classes_)
disp.plot()
plt.grid(False)
plt.title("Confusion Matrix", y=1.04)
plt.show()

Grafico della matrice di confusione. Immagine dell’autore.
Le classi non sono bilanciate, ma il numero di previsioni errate è sorprendentemente alto.
Vediamo cosa dice la curva ROC.
ROC sta per Receiver Operating Characteristics, ed è una curva che mostra la valutazione delle prestazioni di un modello di classificazione binaria. Nel caso di classificazione multiclasse, dovrai confrontare due classi alla volta.
La curva ROC mostra il trade-off tra tasso di veri positivi (TPR, sensibilità, recall) e tasso di falsi positivi (FPR) attraverso diverse soglie di classificazione. Il TPR è sull’asse Y e l’FPR sull’asse X. Ogni punto rappresenta una soglia diversa per la decisione di classificazione (punteggio di probabilità usato per classificare un’istanza come positiva o negativa).
La curva è tipicamente tracciata contro una diagonale da (0, 0) a (1, 1), che rappresenta un classificatore casuale.
Se la curva è sopra la diagonale, significa che il modello è migliore di un classificatore casuale. Un singolo valore scalare riassume questo comportamento: si chiama AUC (Area Under the Curve) e va da 0 a 1, più è alto meglio è, 0.5 è casuale.
In breve, vuoi una curva il più possibile vicina all’angolo in alto a sinistra.
Usa il seguente snippet per calcolare ROC e AUC e tracciare la curva:
from sklearn.metrics import roc_curve, auc
# Get predicted probabilities for the positive class
y_probs = random_forest.predict_proba(X_test)[:, 1]
fpr, tpr, roc_thresholds = roc_curve(y_test, y_probs)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f"AUC = {roc_auc:.2f}")
plt.plot([0, 1], [0, 1], color="navy", linestyle="--")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve", y=1.04)
plt.legend(loc="lower right")
plt.show()

Grafico della curva ROC. Immagine dell’autore.
Interpretazione: il modello random forest è molto migliore di un classificatore casuale e ha un equilibrio ragionevole tra veri positivi e falsi positivi. C’è ancora una certa misura di misclassificazione, e dovresti puntare ad alzare la curva verso l’alto a sinistra ottimizzando i dati o scegliendo un altro modello.
Questa curva è simile alla ROC ma mostra il trade-off tra precision e recall per diverse soglie di classificazione. Mostra la precision sull’asse Y e il recall sull’asse X, ed è in genere preferita alla ROC quando le classi sono sbilanciate.
È il caso del dataset di ammissioni MBA, quindi una curva PR è un’ottima scelta!
È importante notare che le curve precision-recall si concentrano solo sulla classe minoritaria e offrono un quadro migliore di quanto bene il modello identifica le istanze che contano di più (studenti ammessi, tumori rilevati, ecc.).
Usa il seguente snippet per calcolare i valori di precision e recall e tracciarli su un grafico:
from sklearn.metrics import precision_recall_curve
# Get predicted probabilities for the positive class
y_probs = random_forest.predict_proba(X_test)[:, 1]
# Precision-Recall curve
precision, recall, pr_thresholds = precision_recall_curve(y_test, y_probs)
pr_auc = auc(recall, precision)
plt.plot(recall, precision, label=f"(AUC = {pr_auc:.2f})")
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.title("Precision-Recall Curve", y=1.04)
plt.legend(loc="lower left")
plt.show()

Grafico della curva precision-recall. Immagine dell’autore.
Interpretazione: il modello performa male, come indica l’AUC di 0,42. Quando il modello prova ad aumentare il recall, sacrifica troppa precision, causando molti falsi positivi. In breve, questo modello non è adatto a questo dataset, oppure il dataset non è preprocessato adeguatamente.
In conclusione, il campo del machine learning è complesso e spesso poco intuitivo. Se sei un principiante, farai fatica a interiorizzare le grandi idee. Se lavori con un cliente business, probabilmente non capirà il gergo tecnico.
La visualizzazione dei dati aiuta a colmare il divario in entrambi gli scenari.
Oggi hai visto i diversi tipi di grafici che puoi usare per visualizzare modelli di regressione e classificazione, oltre al processo decisionale delle reti neurali e l’interpretazione della singola previsione tramite SHAP e LIME. È tanta roba: sentiti libero di tornare su questo articolo più volte!
Se sei completamente nuovo all’argomento, ti consigliamo di guardare il nostro corso fondamentale di machine learning per iniziare. Dopo, un corso più applicato con Python è la scelta giusta.
Se hai un po’ di esperienza ma non capisci come tutto si incastri su scala più ampia, ti invitiamo a provare il nostro corso sul machine learning per la produzione.
La visualizzazione dei modelli aiuta a colmare il divario tra la tua competenza tecnica e la conoscenza di una figura business; di solito non capiscono il gergo e trarrebbero beneficio da spiegazioni semplificate. Inoltre, se stai iniziando con il machine learning, la visualizzazione può aiutarti a comprendere meglio i fondamentali.
Assolutamente no! È il principio del “write once, run anywhere”. Scriverai (o copierai il mio) codice di visualizzazione e lo userai in tutti i tuoi progetti, con zero o minime modifiche.
Di solito no. I modelli di regressione usano set di metriche di valutazione diversi (ad es. grafici dei coefficienti, grafici dei residui) rispetto ai modelli di classificazione (ad es. matrice di confusione, curve ROC/PR). Alcune metriche come l’accuratezza possono funzionare in entrambi gli scenari, ma in genere dovrai usare set diversi di metriche e visualizzazioni.
Sì, è l’obiettivo del machine learning spiegabile. Librerie Python come SHAP e LIME fanno gran parte del lavoro per te e riportano quanto influisce ciascuna variabile quando si effettua una singola previsione. Queste librerie sono imprescindibili se vuoi capire o spiegare a una figura business il processo decisionale.
Sì, una tecnica chiamata Grad-CAM viene usata per ottenere una heatmap dei punti decisionali del modello. Puoi quindi sovrapporre la heatmap all’immagine originale per vedere quali aree contribuiscono di più al processo decisionale del modello.
Approfondisci il machine learning con questi corsi!
Corso
Corso
Corso

blog
Abid Ali Awan
10 min

blog
Abid Ali Awan
15 min
blog
Tim Lu
12 min