Last chance! 50% off DataCamp Premium
Sale ends in
Machine learning is complex en zit vol met wiskunde.
Als beginner is de kans groot dat je worstelt met het doorgronden van de werking van tientallen verschillende modellen die je tot je beschikking hebt. Dat wordt alleen maar lastiger als je geen wiskundige achtergrond hebt. Maar zelfs met een PhD blijft het moeilijk om de modelresultaten aan een businesscollega uit te leggen. Die delen doorgaans niet jouw enthousiasme voor technische details, maar willen wel zeker weten dat je modellen beslissingen nemen op de juiste manier.
Hoe overbrug je die kloof?
Er is een universele taal die zowel techprofessionals als businessmensen spreken: datavisualisatie. Grafieken zijn makkelijk te begrijpen en maken het uitleggen van complexe onderwerpen behapbaarder, ongeacht je achtergrond.
In dit artikel leer ik je de basis- en gevorderde concepten van het visualiseren van supervised machinelearningmodellen. Als je op zoek bent naar goed materiaal over unsupervised learning, dan zit je goed met onze Unsupervised Learning with Python-cursus.
Na het lezen klinken termen als coëfficiënten, residuen, feature-importance, SHAP, LIME en Grad-CAM niet meer als een vreemde taal.
Aan de slag!
Ik begin met een korte theoretische sectie over de meestgebruikte visualisaties voor machinelearningmodellen. Zo zitten we allemaal op één lijn.
Ben je hier al mee bekend, sla dit onderdeel dan gerust over.
Wanneer je wilt begrijpen welke kenmerken van je dataset het meest bijdragen aan de voorspellingen van een machinelearningmodel, is dit wat je nodig hebt.
Denk aan een taart bakken: ingrediënten zoals bloem en suiker zijn essentieel, en andere, zoals sprinkels, dragen minder bij. Met andere woorden: je kunt een taart hebben zonder sprinkels, maar niet zonder bloem en suiker.
Als je een feature met een hoge belangrijkheidsscore verwijdert, zal de performance van je model aanzienlijk dalen.
Een feature-importance-plot is vaak gekoppeld aan boomgebaseerde modellen (beslisboom, random forest, gradient boosting). De daadwerkelijke waarden zijn beschikbaar na het trainen van het model en het visualiseren ervan kost maar een paar regels code.
De meestgebruikte metrics om machinelearningmodellen te evalueren (bijv. voor classificatie) zijn de confusionmatrix, de ROC-curve en de PR-curve (precision-recall).
De eerste toont het aantal correcte voorspellingen per klasse op de diagonaal van linksboven naar rechtsonder. Alles daarbuiten geldt als een fout (ofwel false positive of false negative).
De tweede laat de afruil zien tussen true positive rates en false positive rates voor een reeks classificatiedrempels.
En de derde toont de balans tussen correcte positieve voorspellingen (precision) en het vermogen van het model om alle positieve gevallen te identificeren (recall). Zowel ROC- als PR-curves werken alleen met binaire classificatiedatasets. Bij multiclass-problemen moet je twee klassen tegelijk vergelijken.
Er zijn zeker andere evaluatiemetrics beschikbaar, maar deze drie komen het meest voor. Ze zijn ingebouwd in scikit-learn, de library die je gebruikt om machinelearningmodellen te trainen.
Bij werken met lineaire modellen (bijv. lineaire regressie) wil je doorgaans de residuen plotten.
Die vertegenwoordigen het verschil tussen de werkelijke en voorspelde waarden. Als je ze plot, let dan op zichtbare patronen. Idealiter zijn ze willekeurig verspreid rond nul (normaal verdeeld). Is dat niet zo, dan maakt het model bevooroordeelde voorspellingen en heb je nog werk te doen.
Kortom, het is een heel eenvoudige plot om te maken en hij vertelt je veel over je regressiemodel.
Machinelearningmodellen worden vaak gezien als black boxes, maar dat hoeft niet.
Geavanceerde interpretatietechnieken zoals SHAP (SHapely Additive exPlanations) en LIME (Local Interpretable Model-agnostic Explanations) helpen de vraag te beantwoorden hoe het model tot een bepaalde beslissing is gekomen.
SHAP kent voor een individuele voorspelling een belangrijkheidswaarde toe aan elke feature (denk: deze persoon rookt, dus hun verzekeringsbedrag zal hoger zijn), terwijl LIME een eenvoudiger model maakt dat de voorspelling van een complex model voor een specifiek geval benadert.
Ik laat je later in het artikel precies zien hoe ze werken.
Maar voordat we in de leuke dingen duiken, bespreken we een paar Python-libraries die je nodig hebt om mee te kunnen doen.
In dit korte onderdeel laat ik je zien hoe je systeemafhankelijken en Python-libraries installeert die je nodig hebt om machinelearningmodellen te visualiseren.
De enige systeembrede dependency die je nodig hebt om mee te kunnen doen is Graphviz. Je gebruikt het later om een beslisboom te visualiseren, en de code werkt niet zonder geïnstalleerde Graphviz.
Het is open-sourcesoftware die wordt gebruikt om diagrammen, abstracte grafen en netwerken te maken. Je gebruikt het niet rechtstreeks; alleen via scikit-learn.
Deze Python-library wordt veel gebruikt voor machinelearningtaken in Python.
In dit artikel gebruik je het om machinelearningmodellen te trainen, datasets te splitsen, numerieke kenmerken te schalen en modelprestaties te visualiseren. Het is onmisbaar, dus installeer het met het volgende commando (afhankelijk van je Python-omgeving):
pip install scikit-learn
conda install scikit-learn
Als je helemaal nieuw bent met scikit-learn, raden we onze populaire cursus over supervised machine learning aan.
De SHAP-library in Python is een populaire tool om de voorspellingen van machinelearningmodellen uit te leggen. Het maakt gebruik van speltheoretische concepten (bijv. Shapley-waarden) om de bijdrage van elk attribuut aan de voorspelling van het model te meten.
Nog beter: de library zit vol met handige visualisaties die je helpen de binnenkant van je modellen te begrijpen.
Installeer het met het volgende commando:
pip install shap
conda install -c conda-forge shap
Deze Python-library wordt veel gebruikt wanneer het verklaren van één enkele modelvoorspelling cruciaal is. Het werkt anders dan SHAP. Het benadert het oorspronkelijke model lokaal met een interpreteerbaar, eenvoudiger model. Vervolgens toont het de bijdrage van elk datasetkenmerk aan de voorspelling.
Je ziet zo hoe LIME werkt, maar installeer het eerst:
pip install lime
conda install conda-forge::lime
Als je neurale netwerkmodellen bouwt met TensorFlow, dan is TensorBoard een no-brainer.
Het is een visualisatietool die je helpt machinelearningexperimenten te volgen en trainingsmetrics te monitoren (bijv. verlies en nauwkeurigheid). Het visualiseert en actualiseert modelgrafen in realtime en laat zien hoe modelparameters tijdens de training veranderen.
TensorBoard kan met andere deep-learningframeworks zoals PyTorch worden gebruikt, maar ik focus me in dit artikel op TensorFlow.
Installeer het met dit commando:
pip install tensorboard
conda install -c conda-forge tensorboard
De laatste stap in deze voorbereidingsfase is het verzorgen van de data.
Ik gebruik vandaag twee datasets: MBA-toelatingen voor classificatie en Insurance voor regressie. Beide zijn gratis te gebruiken en te downloaden op Kaggle.
Begin met het importeren van deze Python-libraries:
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
Voor de classificatiedataset is de datapreprocessing die ik heb gedaan minimaal. Het komt neer op:
Het is geenszins een uitgebreide datapreprocessing-pijplijn. Als je tijd hebt, verbeter hem gerust.
Kopieer in ieder geval deze functie om de classificatiedataset op orde te krijgen:
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)

Een steekproef van de aangepaste MBA-dataset. Afbeelding door de auteur.
Ik ga nu hetzelfde doen voor de regressiedataset.
De gekozen regressiedataset bevat een verzekeringsbedrag ($) als continue variabele die het machinelearningmodel probeert te voorspellen op basis van andere attributen.
Ook hier is de datapreprocessing die ik heb gedaan vrij minimaal. Het komt neer op:
Als je tijd hebt, voeg dan gerust meer stappen toe aan de pijplijn.
Kopieer de volgende functie om de verzekeringsdataset te laden en te preprocessen:
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)

Een steekproef van de aangepaste verzekeringsdataset. Afbeelding door de auteur.
En dat was het!
In het volgende onderdeel laat ik je zien hoe je begint met het visualiseren van machinelearningmodellen.
Boomgebaseerde modellen worden vaak gebruikt voor classificatie, maar de meeste kunnen ook regressietaken aan.
In dit onderdeel laat ik je zien hoe je een beslisboom visualiseert, feature-importance uit een random-forestmodel en voorspellingsuitleg met SHAP en LIME.
Onthoud dat beslisbomen en random forests lastig te doorgronden kunnen zijn. We hebben een volledige cursus die de fundamentals van boomgebaseerde machinelearningmodellen in Python behandelt.
Om te beginnen laad je de classificatiedataset en splits je die in trainings- en testsubsets:
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)
Laten we nu een beslisboom visualiseren!
Zie een beslisboom als een set geneste if-statements waarbij de voorwaarden worden bepaald door een machinelearningmodel.
Er is meer over te zeggen, maar met deze analogie zie je dat het visualiseren van beslissingen een eenvoudig proces zou moeten zijn. En dat is het ook: de functie plot_tree() uit sklearn doet het meeste zware werk.
Begin met het trainen van een beslisboommodel. De parameter max_depth is optioneel en staat hier alleen voor visualisatiedoelen. Zonder deze parameter wordt de boom te diep, en verdwaal je in de enorme hoeveelheid beslissingen die het model maakt, zeker bij grotere datasets.
Het volgende fragment traint het beslisboomclassificatiemodel op de trainingssubset:
from sklearn import tree
decision_tree = tree.DecisionTreeClassifier(random_state=42, max_depth=4)
decision_tree.fit(X_train, y_train)
En voor de visualisatie kopieer je gewoon het volgende fragment. De optionele parameters filled en feature_names maken de boom makkelijker te interpreteren:
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()

Vier niveaus diepe beslisboom. Afbeelding door de auteur.
Let op: de beslissingen die het model neemt betekenen niets als het model niet accuraat is. Later in het artikel laat ik je zien hoe je de nauwkeurigheid kunt inschatten.
Weet je de cake-analogie van eerder nog? Tijd om die in de praktijk te brengen.
Elke keer dat je een boommodel traint met sklearn, krijg je toegang tot de eigenschap feature_importances_. Combineer die met de featurenamen en je hebt alle data die je nodig hebt om te zien welke attributen het meest bijdragen aan de voorspelling.
Laten we het in actie zien! Train eerst een random-forestclassifier op de trainingssubset:
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(n_estimators=25, random_state=42)
random_forest.fit(X_train, y_train)
De visualisatie komt nu neer op het extraheren en sorteren van feature-importance en het vervangen van indexen door featurenamen:
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()

Random-forestplot van feature-importance. Afbeelding door de auteur.
Het lijkt erop dat een GPA op een 4,0-schaal en een GMAT-score het meest bijdragen aan toelating tot een MBA-programma. Daarachter volgt werkervaring, wat een vereiste is voor MBA-studies. Wat iemands hoofdvak was en de sector waarin iemand werkt, is veel minder relevant.
Feature-importance schetst het totaalplaatje, maar wat als je machinelearningmodellen wil visualiseren op het niveau van een individuele voorspelling?
Daar komen SHAP en LIME om de hoek kijken. Ik bespreek eerst SHAP. Je weet al wat het is, dus ik sla de theorie over.
Ik pas een gradient-boostingmodel toe op onze regressiedataset om te zien welke impact individuele features hebben op verzekeringskosten. Het volgende fragment laat zien hoe je het model fit en SHAP-waarden berekent met een shap.Explainer()-model:
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)
Met SHAP kun je een hele reeks plots maken.
Ik begin met waterfall() en bekijk de SHAP-waarden voor de eerste voorspelling:
shap.plots.waterfall(shap_values[0])

Uitleg bij de eerste voorspelling. Afbeelding door de auteur.
In dit specifieke geval verhoogt roken de verzekeringskosten drastisch. De features die het meest bijdragen aan het verlagen van de kosten zijn BMI (gerelateerd aan gewicht) en leeftijd. Andere features hebben minimale of geen impact.
Je kunt bovenstaande grafiek ook in een meer compact formaat weergeven:
shap.plots.force(shap_values[0])

Bondige uitleg bij de eerste voorspelling. Afbeelding door de auteur.
De informatie is nog steeds hetzelfde: rode features verhogen de kosten, blauwe features verlagen ze. Het punt waar ze samenkomen toont de verzekeringskosten voor één instantie.
De beeswarm-plot laat zien welke features het belangrijkst zijn door de SHAP-waarden van elke feature voor elk sample te plotten. De features zijn gesorteerd op de som van de absolute SHAP-waardes over alle samples. De kleur geeft de featurewaarde weer (rood is hoog en blauw is laag):
shap.plots.beeswarm(shap_values)

Samenvattend effect van alle features. Afbeelding door de auteur.
Kortom: jong niet-roken met een redelijk BMI verlaagt de verzekeringskosten.
De laatste SHAP-visualisatie die ik wil laten zien is het staafdiagram van de gemiddelde absolute waarde. Het berekent de gemiddelde absolute waarde van alle SHAP-waarden per feature:
shap.plots.bar(shap_values)

De gemiddelde absolute waarde van alle SHAP-waarden voor alle features. Afbeelding door de auteur.
Met andere woorden: het is een chique manier om globale feature-importance te berekenen — de grafiek is niet gekoppeld aan een individuele voorspelling.
En daarmee zijn we klaar met SHAP. Vervolgens verleg ik de focus naar LIME.
Net als SHAP draait LIME om interpreteerbare machine learning.
Het heeft niet zoveel visualisatietypen in de gereedschapskist, maar het doet één ding goed — in elk geval bij tabeldata.
Voor demonstratiedoeleinden laad ik de classificatiedataset en maak ik er bovendien een binaire classificatietaak van door wachtlijstvermeldingen te vervangen door afwijzingen. Dat maakt het makkelijker om de output van LIME te begrijpen:
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)
De klasse LimeTabularExplainer() ontvangt nu trainingsdata, kolomnamen, de namen van de categorieën in de doelvariabele en de machinelearningmodus (classificatie of regressie):
explainer = lime_tabular.LimeTabularExplainer(
training_data=np.array(X_train),
feature_names=X_train.columns,
class_names=["Admit", "Deny"],
mode="classification"
)
Daarna kun je de methode explain_instance() aanroepen om één enkele voorspelling te interpreteren op basis van klassificatiekansen:
exp = explainer.explain_instance(
data_row=X_test.iloc[0],
predict_fn=random_forest.predict_proba
)
exp.show_in_notebook(show_table=True)

LIME-uitleg (1). Afbeelding door de auteur.
Het LIME-model is voor 96% zeker dat deze MBA-toelating wordt afgewezen. Features zoals gmat en gender hadden de grootste impact op de beslissing.
Laten we nu hetzelfde doen voor een instantie die is toegelaten tot het MBA-programma:
exp = explainer.explain_instance(
data_row=X_test.iloc[234],
predict_fn=random_forest.predict_proba
)
exp.show_in_notebook(show_table=True)

LIME-uitleg (2). Afbeelding door de auteur.
Dezelfde features hadden nu het tegenovergestelde effect! Deze persoon had een hoge gmat-score, wat de grootste bijdrage levert aan toelating tot het MBA-programma.
En daarmee zijn we klaar met boommodellen! Straks leer je hoe je lineaire modellen voor regressietaken visualiseert.
Als je net begint met voorspellend modelleren, wordt het niet basaler dan lineaire regressie. Het is een eenvoudig model dat makkelijk te begrijpen is en goed werkt, mits de relaties in je dataset lineair zijn.
Er zijn nog andere lineaire modellen, maar in dit onderdeel werk ik alleen met lineaire regressie.
Begin met het laden van de regressiedataset, splits die in trainings- en testsubsets en fit een lineair regressiemodel op het trainingsgedeelte:
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)
Het eerste type visualisatie dat ik verken zijn modelcoëfficiënten.
In gewone taal komt een lineair regressiemodel neer op één enkele vergelijking. Je herinnert je misschien y = mx+b van de middelbare school — hetzelfde idee geldt hier.
De regressievergelijking wordt uitgebreid tot y = w0 + w1_x1 + w2_x2 + … + wn_xn om meerdere parameters te accommoderen. Het doel van het model is om de beste schatting van gewichten (w) te vinden, gegeven de set invoerkenmerken (x).
Waarom is dit belangrijk?
Omdat je na het trainen van het model toegang hebt tot de coëfficiënten (gewichten) en hun bijdrage kunt analyseren. Features met een grotere coëfficiënt hebben een hogere bijdrage aan het voorspellen van de doelvariabele.
Je kunt de coëfficiënten opvragen via de parameter coef_ van een getraind model.
Het volgende fragment haalt de coëfficiënten op en plot ze als een horizontaal staafdiagram:
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()

Coëfficiënten van het lineaire regressiemodel. Afbeelding door de auteur.
De features smoker, BMI en age zijn de grootste positieve bijdragers aan de verzekeringskosten — als ze stijgen, stijgt ook het bedrag. Er zijn ook een paar negatieve coëfficiënten, wat betekent dat ze het totale bedrag verlagen.
Het andere gangbare type regressievisualisatie is de residualplot. In gewone taal maak je een spreidingsdiagram met voorspelde waarden op de X-as en residuals (ware waarden - voorspelde waarden) op de Y-as.
Idealiter zijn er geen patronen zichtbaar in de residuals en liggen ze gecentreerd rond 0. Met andere woorden: ze zijn normaal verdeeld.
Gebruik dit codefragment om de residuals van een lineair regressiemodel op de verzekeringsdataset te visualiseren:
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()

Residuals van het lineaire regressiemodel. Afbeelding door de auteur.
Het is niet de beste residualplot die ik heb gezien. Hij ziet er esthetisch goed uit, maar de waarden liggen overal. Als je als machinelearningpracticus een vergelijkbare residualplot krijgt, heb je nog veel werk te doen.
Straks laat ik je 3 manieren zien om neurale netwerkmodellen te visualiseren.
Als er één gebied binnen machine learning is waar visualisatie en interpretatie het meest tellen, dan zijn het wel neurale netwerken.
Deze zijn lastig te bevatten, zelfs op het meest fundamentele niveau. Je hebt verschillende laagtypen, activatiefuncties en backpropagatie — om er maar een paar te noemen. Daarom zijn neurale netwerken vaak synoniem met blackboxmodellen.
Dat hoeft niet zo te zijn.
In dit onderdeel laat ik je drie manieren zien om neurale netwerken te visualiseren: architectuurplots, realtime trainingsmetrics en Grad-CAM.
Mijn library naar keuze is TensorFlow. Als je er nog nooit van hebt gehoord, we habben een TensorFlow-cursus voor beginners waarmee je snel van start gaat!
Als je de architectuur van je neurale netwerkmodel visualiseert, haal je één ding uit de mystiek — shapes.
Met andere woorden: je ziet hoe de onderliggende matrix in grootte verandert terwijl hij door de lagen beweegt. Dat kan een lastig onderwerp zijn voor nieuwkomers, dus iedere visualisatie is welkom.
Ter demonstratie gebruik ik TensorFlow om een basismodel voor handgeschreven cijferherkenning te maken. Daarna gebruik ik de functie plot_model() om de modelarchitectuur als afbeelding lokaal op te slaan.
Kijk zelf maar:
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)

Architectuur van een neuraal netwerkmodel. Afbeelding door de auteur.
Hoewel nuttig, brengt dit type visualisatie je maar tot op zekere hoogte.
Je weet hoe de data onderweg transformeert, maar je hebt geen idee hoe een neuraal netwerkmodel tot zijn conclusies komt. Dat behandel ik hierna.
Grad-CAM, of Gradient Class Activation Mapping, is een populaire techniek om convolutionele neurale netwerkmodellen te visualiseren.
Concreter: het helpt je te begrijpen welke delen van een invoerafbeelding het meest bijdragen aan de voorspellingen van het model. Zie het als een feature-importanceplot van een beslisboom, maar dan op standje 11.
Het is een geavanceerde interpreteerbaarheidstechniek die werkt voor alle convolutionele modellen, ongeacht de architectuur, en je helpt te begrijpen waarom een neuraal netwerk een bepaalde voorspelling doet.
Maar er is een probleem — het is niet triviaal om in Python te implementeren. Hier is een overzicht op hoofdlijnen van het algoritme:
Het is best een gecompliceerd proces, en om het makkelijker te maken gebruik ik een voorgetraind ResNet50-model dat al 1000 verschillende afbeeldingstypen kan classificeren.
Maar eerst laad je de benodigde libraries en de afbeelding waarvoor je een Grad-CAM wilt bekijken. Ik gebruik een stockfoto van een hond:
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)

Voorbeeldafbeelding van een hond. Afbeelding door de auteur.
Nu begint het leuke werk. In het volgende codefragment implementeer ik het eerder beschreven vierstappenproces. Je vindt boven elke codelijn commentaar voor makkelijker begrip:
# 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))
Het was veel, zeker, maar je kunt nu eindelijk de heatmap plotten die door het Grad-CAM-algoritme is geproduceerd:
plt.grid(False)
plt.imshow(gradcam)

Grad-CAM-heatmap. Afbeelding door de auteur.
Lichtere punten duiden op gebieden waar de activatie het hoogst was, maar de heatmap op zich vertelt je niet veel.
Je bent veel beter af door hem over de originele afbeelding te leggen en de transparantie iets te verlagen:
plt.grid(False)
plt.imshow(image)
plt.imshow(gradcam, alpha=0.5)

Afbeelding van een hond met een grad-CAM-overlay. Afbeelding door de auteur.
Ter interpretatie: de belangrijkste factor waardoor deze afbeelding als “golden retriever” is geclassificeerd, is de kop, wat logisch is.
Je kunt Grad-CAM gebruiken om zeker te weten dat je model voorspellingen op de juiste manier maakt. Stel je in dit geval voor dat de heatmap iets anders laat zien, zoals de stoel op de achtergrond, als belangrijkste factor. Je zou dat model niet vertrouwen, toch?
Het trainen van een neuraal netwerk kan lang duren. Het goede nieuws is dat je niet hoeft te wachten tot de training klaar is om een indruk te krijgen van de prestaties van het model. Libraries zoals TensorBoard kunnen je dat in real time laten zien.
TensorBoard wordt meegeleverd met TensorFlow, dus je hoeft niets te installeren om mee te kunnen doen.
Voor demonstratiedoeleinden train ik een eenvoudige cijferclassificatie voor 25 epochs. Het belangrijkste onderdeel is de callback—daarin specificeer je het pad en de indeling voor trainingslogs, die TensorBoard zo gaat gebruiken.
Dit is de code die je nodig hebt om het model te trainen en trainingslogs op te slaan:
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]
)

Trainingsproces van het model. Afbeelding door de auteur.
Terwijl het model traint, start je TensorBoard vanuit de terminal en geef je het pad op naar de map met trainingslogs:
tensorboard --logdir=logs/fit
TensorBoard draait standaard op poort 6006:

TensorBoard-metrieken (1). Afbeelding door de auteur.
De bovenstaande afbeelding laat het bias-histogram en de nauwkeurigheid per epoch zien, zowel voor de trainings- als validatiesets. Je ziet dat de nauwkeurigheid voor beide erg hoog is, dicht bij 100% – het is alleen de Y-as-schaal die te klein is.
Andere tabbladen gaan in op specifiekere metrieken en laten je bepaalde zaken finetunen, zoals je hieronder kunt zien:

TensorBoard-metrieken (2). Afbeelding door de auteur.
Kortom, TensorBoard is een handige tool voor het visualiseren van modelprestaties en kan je helpen de prestaties te analyseren terwijl het model nog aan het trainen is.
In dit laatste deel wil ik een stap terug doen en ingaan op meer generieke metrieken om de prestaties van het model te visualiseren.
Je ziet er drie: de confusion matrix, ROC-curve en de Precision-Recall-curve.
Omdat ze gekoppeld zijn aan classificatieproblemen, moet je de MBA-dataset laden. Om het eenvoudiger te maken, heb ik er ook een binaire classificatie van gemaakt door wachtlijst-inschrijvingen als geweigerd in te stellen. De rest van het codefragment splitst de data in trainings- en testsubsets en traint eerst een random forest-classificatiemodel:
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)
Laten we in de eerste metriek duiken: de confusion matrix.
Een confusion matrix laat zien hoe goed je model presteert. In een ideaal scenario zouden de waarden op de diagonaal van linksboven naar rechtsonder de enige niet-nul elementen zijn, wat aangeeft dat het model geen foutieve voorspellingen heeft gedaan.
Maar dat gebeurt in de echte wereld niet vaak.
Gebruik de volgende richtlijnen om een confusion matrix te interpreteren (voor binaire classificatie):
Er is geen algemene regel of je je meer zorgen moet maken over false positives of false negatives. Het eerste is pijnlijker bij MBA-toelatingen, aangezien het model de student als toegelaten classificeerde, maar dat in werkelijkheid niet zo was. In andere gevallen, zoals bij kankerdiagnose, is het cruciaal om het aantal false negatives te minimaliseren, omdat je iemand niet gezond wilt verklaren als diegene kanker heeft.
Daar komt domeinkennis om de hoek kijken.
Hoe dan ook, terug naar de code. Het volgende fragment berekent de confusion matrix van ons random forest-model op de testset en gebruikt de klasse ConfusionMatrixDisplay om een visualisatie te maken:
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()

Plot van de confusion matrix. Afbeelding door de auteur.
De klassen zijn niet in balans, maar het aantal onjuiste voorspellingen is verbazingwekkend hoog.
Laten we kijken wat de ROC-curve te zeggen heeft.
ROC staat voor Receiver Operating Characteristics, en is een curve die de prestatie-evaluatie van een binair classificatiemodel weergeeft. In het geval van multiclass-classificatie moet je twee klassen tegelijk vergelijken.
De ROC-curve laat een afruil zien tussen een true positive rate (TPR, sensitiviteit, recall) en een false positive rate (FPR) over verschillende classificatiedrempels. TPR staat op de Y-as en FPR op de X-as. Elk punt vertegenwoordigt een andere drempel voor de classificatiebeslissing (kansscore die wordt gebruikt om een instantie als positief of negatief te classificeren).
De curve wordt meestal uitgezet tegen een diagonaal van (0, 0) naar (1, 1), wat een willekeurige classifier voorstelt.
Als de curve boven de diagonaal ligt, betekent dit dat je model beter presteert dan een willekeurige classifier. Een enkele scalair vat dat samen. Die heet AUC (Area Under the Curve) en loopt van 0 tot 1, waarbij hoger beter is en 0,5 willekeurig is.
Kort gezegd wil je dat de curve zo dicht mogelijk bij de linkerbovenhoek ligt.
Gebruik het volgende fragment om ROC en AUC te berekenen en de curve te plotten:
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()

Plot van de ROC-curve. Afbeelding door de auteur.
Ter interpretatie: het random forest-model is veel beter dan een willekeurige classifier en heeft een redelijke balans tussen true positives en false positives. Er is nog steeds sprake van enige misclassificatie, en je zou de curve omhoog en naar links moeten proberen te trekken door de data te optimaliseren of een ander machinelearningmodel te kiezen.
Deze curve lijkt op de ROC-curve maar laat de afruil zien tussen precision en recall voor verschillende classificatiedrempels. Hij toont precision op de Y-as en recall op de X-as en heeft doorgaans de voorkeur boven ROC wanneer klassen uit balans zijn.
Dat is het geval in de MBA-toelatingsdataset, dus een PR-curve lijkt een goede keuze!
Belangrijk om te weten: precision-recall-curves richten zich alleen op de minderheidsklasse en geven een beter beeld van hoe goed het model de belangrijkste instanties identificeert (toegelaten studenten, gedetecteerde kankers, enzovoort).
Gebruik het volgende fragment om precision- en recall-waarden te berekenen en ze in een grafiek te plotten:
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()

Plot van de precision-recall-curve. Afbeelding door de auteur.
Ter interpretatie: het model presteert slecht, wat blijkt uit de AUC-score van 0,42. Wanneer het model probeert de recall te verhogen, levert het te veel in op precision, wat leidt tot veel false positives. Kort gezegd: dit model is niet goed geschikt voor deze dataset, of de dataset zelf is niet voldoende voorbewerkt.
Samengevat: het veld van machine learning is complex en vaak niet intuïtief. Als beginner heb je moeite om de grote lijnen te begrijpen. Werk je met een zakelijke klant, dan begrijpen zij waarschijnlijk het technische jargon niet.
Datavisualisatie helpt in beide scenario’s de kloof te dichten.
Vandaag heb je alle verschillende grafiektypen gezien die je kunt gebruiken om regressie- en classificatiemodellen te visualiseren, evenals het beslissingsproces van neurale netwerken en de interpretatie van enkele voorspellingen met SHAP en LIME. Het is veel om te verwerken, dus voel je vrij om dit artikel meerdere keren te herlezen!
Als je helemaal nieuw bent met het onderwerp, raden we aan om te beginnen met onze basiscursus machine learning. Daarna is een meer toegepaste cursus met Python een goede volgende stap.
Als je al wat ervaring hebt maar niet begrijpt hoe alles op grotere schaal samenkomt, raden we je aan om onze cursus machine learning voor productie te proberen.
Modelvisualisatie helpt de kloof te dichten tussen jouw technische expertise en de kennis van een zakelijk publiek; zij begrijpen het jargon meestal niet en hebben baat bij vereenvoudigde uitleg. Ook als je net begint met machine learning, kan modelvisualisatie je helpen de fundamenten beter te begrijpen.
Helemaal niet! Het volgt het principe van “schrijven eens, overal draaien”. Je schrijft (of kopieert mijn) visualisatiecode en gebruikt die in al je projecten, met nul of minimale codewijzigingen.
Meestal niet. Regressiemodellen gebruiken andere evaluatiemetrieken (bijv. coëfficiëntplots, residuplots) dan classificatiemodellen (bijv. confusion matrix, ROC/PR-curve). Sommige metrieken zoals nauwkeurigheid kunnen in beide scenario’s werken, maar doorgaans moet je verschillende sets evaluatiemetrieken en visualisaties gebruiken.
Ja, dat is het doel van uitlegbare machine learning. Python-libraries zoals SHAP en LIME doen het meeste zware werk voor je en rapporteren hoeveel invloed elke variabele heeft bij het doen van een enkele voorspelling. Deze libraries zijn onmisbaar als je het beslissingsproces wilt begrijpen of het aan een zakelijk publiek wilt uitleggen.
Ja, een techniek genaamd Grad-CAM wordt gebruikt om een heatmap te krijgen van de beslissingspunten van het model. Je kunt de heatmap vervolgens boven op je originele afbeelding leggen om te zien welke gebieden het meest bijdragen aan het beslissingsproces van het model.
Leer meer over machine learning met deze cursussen!
Cursus
Cursus
Cursus

blog
Adel Nehme
15 min