Ton modèle de machine learning te sort un prix, un diagnostic, une note. Mais pourquoi ? Si tu ne peux pas répondre à cette question, tu te promènes avec une boîte noire sous le bras. Le problème n'est pas que la prédiction soit fausse, c'est que tu ignores ce qui l'a provoquée. Une variable a-t-elle tout décidé ? Une autre a-t-elle été purement ignorée ?
SHAP répond à cette question. Basé sur les valeurs de Shapley, un concept venu de la théorie des jeux, il décompose chaque prédiction en une somme de contributions : + 30 000 dollars à cause du revenu médian du quartier, - 5 000 parce que la maison est vieille. Et ça marche avec à peu près n'importe quel modèle : forêt aléatoire, XGBoost, réseau de neurones. Ici, on s'y met en une vingtaine de lignes.
Étape 1 : installer et entraîner un modèle à expliquer
On installe SHAP, scikit-learn et matplotlib :
pip install shap scikit-learn matplotlibEnsuite, on entraîne un modèle de régression sur un vrai jeu de données : le prix des maisons en Californie. Huit variables (revenu médian du quartier, âge du bâti, nombre de pièces, population...) et une cible : le prix. Rien de plus classique.
import shap
from sklearn.datasets import fetch_california_housing
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
# 1. Des données réelles : le prix des maisons en Californie
X, y = fetch_california_housing(return_X_y=True, as_frame=True)
# 2. On garde un échantillon pour rester rapide (SHAP est coûteux)
X, y = X.sample(3000, random_state=42), y.loc[X.index]
# 3. On entraîne une forêt aléatoire
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestRegressor(n_estimators=60, random_state=42)
model.fit(X_train, y_train)
print("Score R2 :", round(model.score(X_test, y_test), 3))
# 4. On branche SHAP dessus
explainer = shap.TreeExplainer(model)
shap_values = explainer(X_test)La forêt aléatoire atteint un R2 d'environ 0,75 : le modèle est correct, mais il ne dit toujours pas comment il raisonne. TreeExplainer est taillé pour les modèles d'arbres (forêts, gradient boosting) et calcule les explications en quelques secondes. Pour un réseau de neurones, tu utiliseras plutôt DeepExplainer, et KernelExplainer comme solution qui marche partout.
Étape 2 : la vue globale, quelles variables comptent vraiment
Deux graphiques résument tout le modèle d'un coup d'œil :
# Vue globale : quelles variables pèsent le plus ?
shap.plots.bar(shap_values) # importance moyenne par variable
shap.plots.beeswarm(shap_values) # chaque point = une maisonLe bar plot classe les variables par importance moyenne. Résultat sans surprise : MedInc, le revenu médian du quartier, écrase tout le reste. Latitude et Longitude (l'emplacement, donc) suivent de loin. C'est la réponse à la question « mon modèle se base sur quoi ? »
Le beeswarm va plus loin : chaque point représente une maison. Un point rouge veut dire « valeur élevée », un point bleu « valeur faible ». Plus le revenu médian est élevé (points rouges à droite), plus le prix prédit grimpe. Et la population ? Un gros amas de points rouges pousse le prix vers le bas : les quartiers densément peuplés font baisser les prix. Aucune métrique d'importance « classique » ne te donne cette lecture.
Étape 3 : la vue locale, pourquoi CE logement vaut ce prix
Le bar plot te parle du modèle entier. Mais le vrai intérêt, c'est d'expliquer une prédiction précise. Prends la première maison du jeu de test :
# Vue locale : pourquoi CE logement vaut ce prix-là ?
shap.plots.waterfall(shap_values[0])Le waterfall démarre à la valeur de base (le prix moyen, environ 2,07, soit 207 000 dollars) puis additionne ou soustrait chaque contribution. Ici, le revenu médian du quartier ajoute 0,5 ; la latitude en retire 0,2 ; et au bout de la cascade, on retombe pile sur la prédiction finale f(x). Tu peux montrer ce graphique à n'importe qui : il se lit comme une facture détaillée.
Si les graphiques ne te suffisent pas, récupère les chiffres bruts :
import numpy as np
import pandas as pd
# Le poids moyen de chaque variable (valeur absolue des SHAP)
importance = np.abs(shap_values.values).mean(axis=0)
top = pd.Series(importance, index=X_test.columns).sort_values(ascending=False)
print(top.head(3))La colonne MedInc pèse en moyenne 0,58 en valeur absolue, loin devant AveOccup (0,23) et Latitude (0,21). Des chiffres, pas du blabla.
SHAP ne rend pas ton modèle plus précis, il le rend lisible. C'est exactement ce qu'il faut quand la décision a des conséquences : refuser un prêt, prioriser un patient, fixer un prix. En quelques lignes, tu passes de « le modèle dit 250 000 » à « le modèle dit 250 000 parce que le quartier est riche, malgré un bâti ancien ». Et ça change tout au moment d'expliquer l'IA à un humain.
Un dernier conseil : garde un œil sur les variables qui affichent un SHAP nul. Elles signifient que ton modèle ne les utilise pas du tout. C'est parfois la découverte la plus utile de la journée.






