Interprétabilité des modèles#

La science est ce que nous comprenons suffisamment bien pour l’expliquer à un ordinateur. L’art est tout le reste.

— Donald Knuth, Things a Computer Scientist Rarely Talks About

Les chapitres précédents ont présenté des modèles de complexité croissante : de la régression linéaire (chapitre 5) aux réseaux de neurones profonds (chapitres 16–20), en passant par les méthodes d’ensemble (chapitre 9) et les machines à vecteurs de support (chapitre 10). Cette montée en complexité s’accompagne d’un gain en performance prédictive, mais aussi d’une perte progressive de transparence : comprendre pourquoi un modèle produit une prédiction donnée devient de plus en plus difficile. Ce chapitre traite de l”interprétabilité des modèles d’apprentissage automatique — la capacité à comprendre, expliquer et justifier les décisions d’un modèle — et présente les outils théoriques et pratiques permettant d’ouvrir la boîte noire.

Introduction : le problème de la boîte noire#

Pourquoi l’interprétabilité est essentielle#

Un modèle d’apprentissage automatique qui produit des prédictions précises mais inexplicables pose plusieurs problèmes fondamentaux :

  • Confiance : un médecin n’acceptera pas un diagnostic automatisé sans comprendre le raisonnement sous-jacent. Un ingénieur ne déploiera pas un modèle en production s’il ne peut pas anticiper ses modes de défaillance.

  • Débogage : lorsqu’un modèle se trompe, il faut pouvoir identifier la source de l’erreur — une feature mal construite, un biais dans les données, un artefact du prétraitement — pour corriger le problème.

  • Exigences réglementaires : le Règlement Général sur la Protection des Données (RGPD) européen consacre un « droit à l’explication » pour les décisions automatisées. L’AI Act de l’Union Européenne impose des obligations de transparence pour les systèmes d’IA à haut risque.

  • Compréhension scientifique : en sciences, un modèle n’est pas une fin en soi mais un outil pour comprendre les phénomènes. Un modèle prédictif mais opaque ne fait pas progresser la connaissance.

  • Équité : détecter et corriger les biais discriminatoires (genre, origine, âge) dans un modèle nécessite de comprendre comment ces variables influencent les prédictions.

Remarque 253

Le terme « boîte noire » (black box) désigne un modèle dont le fonctionnement interne est opaque pour l’utilisateur. Les réseaux de neurones profonds, les forêts aléatoires avec des centaines d’arbres et les méthodes de gradient boosting sont des exemples typiques de boîtes noires. À l’opposé, un modèle linéaire ou un petit arbre de décision est une « boîte blanche » (white box) dont chaque composante est directement interprétable.

Interprétabilité et explicabilité#

Les termes interprétabilité et explicabilité sont souvent utilisés de manière interchangeable, mais une distinction utile peut être faite.

Définition 304 (Interprétabilité et explicabilité)

L”interprétabilité (interpretability) est la propriété intrinsèque d’un modèle permettant à un humain de comprendre son mécanisme de décision. Un modèle est interprétable si sa structure est suffisamment simple pour être appréhendée directement.

L”explicabilité (explainability) désigne la capacité à fournir des explications a posteriori du comportement d’un modèle, éventuellement complexe, à l’aide de techniques externes. Un modèle explicable n’est pas nécessairement interprétable : on peut expliquer les prédictions d’un réseau de neurones sans pour autant comprendre l’ensemble de ses paramètres.

On distingue également deux niveaux d’explication :

  • Explication globale : comprendre le comportement général du modèle sur l’ensemble des données (quelles features sont globalement importantes ? quelles relations le modèle a-t-il capturées ?).

  • Explication locale : comprendre pourquoi le modèle a produit une prédiction particulière pour une observation donnée.

Hide code cell source

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing, load_iris, load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, export_text
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
from sklearn.inspection import permutation_importance, PartialDependenceDisplay
from sklearn.metrics import accuracy_score, r2_score

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
np.random.seed(42)

Modèles intrinsèquement interprétables#

Avant de recourir à des techniques d’explication post hoc, il convient de rappeler que certains modèles sont intrinsèquement interprétables : leur structure même permet de comprendre le processus de décision.

Modèles linéaires : lecture des coefficients#

Le modèle linéaire (chapitre 5) est l’archétype du modèle interprétable. Dans un modèle de régression linéaire \(\hat{y} = \beta_0 + \beta_1 x_1 + \cdots + \beta_p x_p\), chaque coefficient \(\beta_j\) a une interprétation directe : c’est la variation de la prédiction \(\hat{y}\) associée à une augmentation d’une unité de \(x_j\), toutes choses égales par ailleurs.

Exemple 41 (Interprétation des coefficients d’une régression linéaire)

Considérons un modèle linéaire pour prédire le prix médian des logements en Californie. Si le coefficient associé à la variable MedInc (revenu médian) est \(\hat{\beta} = 0.85\), cela signifie qu’une augmentation d’une unité du revenu médian (en dizaines de milliers de dollars) est associée à une augmentation de \(0.85 \times 100\,000 = 85\,000\) dollars du prix médian, les autres variables étant fixées.

Hide code cell source

# Chargement des données California Housing
housing = fetch_california_housing(as_frame=True)
X_housing = housing.data
y_housing = housing.target

# Standardisation pour rendre les coefficients comparables
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_housing)

# Régression linéaire
lr = LinearRegression()
lr.fit(X_scaled, y_housing)

# Visualisation des coefficients
coefs = pd.Series(lr.coef_, index=housing.feature_names)
coefs_sorted = coefs.abs().sort_values(ascending=True)

fig, ax = plt.subplots(figsize=(8, 5))
colors = ['#e74c3c' if c < 0 else '#2ecc71' for c in coefs[coefs_sorted.index]]
ax.barh(coefs_sorted.index, coefs[coefs_sorted.index], color=colors, edgecolor='black')
ax.set_xlabel('Coefficient standardisé')
ax.set_title('Coefficients de la régression linéaire — California Housing')
ax.axvline(x=0, color='black', linewidth=0.8)
plt.tight_layout()
plt.show()

print(f"R² sur l'ensemble d'entraînement : {lr.score(X_scaled, y_housing):.3f}")
_images/fc0552309691b77a26cadfc24c4144e0603448a669061ae31d1cc91b899f368a.png
R² sur l'ensemble d'entraînement : 0.606

Remarque 254

Pour que les coefficients soient comparables entre eux, il est indispensable de standardiser les variables avant l’ajustement (chapitre 4). Sans standardisation, un coefficient élevé peut simplement refléter une échelle de mesure différente plutôt qu’une influence réelle plus forte. Après standardisation, la valeur absolue du coefficient mesure l’importance relative de chaque variable.

Arbres de décision : extraction de règles#

Les arbres de décision (chapitre 8) sont le second modèle intrinsèquement interprétable. Chaque chemin de la racine à une feuille se lit comme une suite de conditions logiques, directement traduisible en règle humainement compréhensible.

Hide code cell source

# Arbre de décision peu profond sur le jeu Iris
iris = load_iris(as_frame=True)
X_iris, y_iris = iris.data, iris.target

tree_clf = DecisionTreeClassifier(max_depth=3, random_state=42)
tree_clf.fit(X_iris, y_iris)

# Affichage textuel de l'arbre
rules = export_text(tree_clf, feature_names=list(iris.feature_names))
print("Règles de l'arbre de décision :")
print(rules)
Règles de l'arbre de décision :
|--- petal length (cm) <= 2.45
|   |--- class: 0
|--- petal length (cm) >  2.45
|   |--- petal width (cm) <= 1.75
|   |   |--- petal length (cm) <= 4.95
|   |   |   |--- class: 1
|   |   |--- petal length (cm) >  4.95
|   |   |   |--- class: 2
|   |--- petal width (cm) >  1.75
|   |   |--- petal length (cm) <= 4.85
|   |   |   |--- class: 2
|   |   |--- petal length (cm) >  4.85
|   |   |   |--- class: 2

Remarque 255

Un arbre de décision de profondeur 3 contient au plus \(2^3 = 8\) feuilles et peut être visualisé et compris par un non-spécialiste. Cependant, un arbre de profondeur 20 avec des milliers de feuilles devient aussi opaque qu’un réseau de neurones. L’interprétabilité d’un arbre dépend donc de sa taille. En pratique, on limite la profondeur (paramètre max_depth) pour préserver la lisibilité, au prix d’une perte potentielle de performance.

Modèles additifs généralisés (GAM)#

Les modèles additifs généralisés (Generalized Additive Models, GAM) étendent les modèles linéaires en remplaçant les termes linéaires par des fonctions non linéaires univariées :

Définition 305 (Modèle additif généralisé)

Un GAM modélise la variable cible comme une somme de fonctions lisses univariées des features :

\[g(\mathbb{E}[y]) = \beta_0 + f_1(x_1) + f_2(x_2) + \cdots + f_p(x_p)\]

\(g\) est une fonction de lien, et chaque \(f_j\) est une fonction lisse (typiquement un spline) estimée à partir des données. L’interprétation est directe : chaque \(f_j\) représente la contribution marginale de la variable \(x_j\) à la prédiction, indépendamment des autres variables.

Les GAM conservent l’interprétabilité des modèles linéaires — chaque variable contribue de manière additive et sa contribution peut être visualisée — tout en capturant des relations non linéaires. Leur principale limitation est l’absence d’interactions entre variables, sauf si celles-ci sont explicitement ajoutées.

Le compromis interprétabilité–performance#

Remarque 256

Il existe un compromis fondamental entre l’interprétabilité et la performance prédictive d’un modèle. Les modèles les plus interprétables (régression linéaire, petits arbres, GAM) sont généralement moins performants que les modèles complexes (forêts aléatoires, gradient boosting, réseaux profonds) sur des problèmes difficiles. Cependant, ce compromis n’est pas absolu : sur des données tabulaires de dimension modérée, un modèle linéaire bien régularisé ou un GAM peut rivaliser avec un gradient boosting, tout en offrant une transparence totale.

Importance des features#

L”importance des features (feature importance) est la forme la plus courante d’explication globale : elle attribue un score numérique à chaque variable, reflétant sa contribution à la performance du modèle. Deux grandes familles de méthodes coexistent.

Importance par permutation#

L’importance par permutation (permutation importance) est une méthode agnostique au modèle (model-agnostic) : elle s’applique à tout modèle supervisé.

Définition 306 (Importance par permutation)

L”importance par permutation de la variable \(x_j\) est définie comme la diminution de performance du modèle lorsque les valeurs de \(x_j\) sont aléatoirement permutées dans l’ensemble de test, brisant ainsi la relation entre \(x_j\) et la cible \(y\) :

\[\text{PI}_j = s - \frac{1}{K}\sum_{k=1}^{K} s_j^{(k)}\]

\(s\) est le score du modèle sur les données originales, et \(s_j^{(k)}\) est le score après la \(k\)-ième permutation aléatoire de \(x_j\).

L’idée est intuitive : si la permutation d’une variable dégrade fortement les prédictions, c’est que le modèle utilise cette variable ; si la performance reste inchangée, la variable est superflue.

Exemple 42 (Importance par permutation d’une forêt aléatoire)

Entraînons une forêt aléatoire sur le jeu California Housing et calculons l’importance par permutation de chaque variable.

Hide code cell source

# Séparation train/test
X_train, X_test, y_train, y_test = train_test_split(
    X_housing, y_housing, test_size=0.2, random_state=42
)

# Forêt aléatoire
rf_reg = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
rf_reg.fit(X_train, y_train)
print(f"R² (test) : {rf_reg.score(X_test, y_test):.3f}")

# Importance par permutation (sur le jeu de test)
perm_imp = permutation_importance(
    rf_reg, X_test, y_test, n_repeats=10, random_state=42, n_jobs=-1
)

# Tri et visualisation
sorted_idx = perm_imp.importances_mean.argsort()

fig, ax = plt.subplots(figsize=(8, 5))
ax.boxplot(
    perm_imp.importances[sorted_idx].T,
    vert=False,
    tick_labels=X_housing.columns[sorted_idx]
)
ax.set_title('Importance par permutation — Random Forest')
ax.set_xlabel('Diminution du score $R^2$')
plt.tight_layout()
plt.show()
R² (test) : 0.805
_images/90ad2fdea4aca6708cc763beb31a55e03428a745a29408605f8957cf89e7670c.png

Importance basée sur l’impureté#

Pour les modèles à base d’arbres, scikit-learn fournit une mesure d’importance intégrée : l’importance basée sur la diminution moyenne d’impureté (Mean Decrease in Impurity, MDI).

Définition 307 (Importance MDI)

L”importance MDI de la variable \(x_j\) dans un ensemble d’arbres est la somme pondérée des diminutions d’impureté (Gini ou entropie en classification, variance en régression) produites par les divisions sur \(x_j\), moyennée sur tous les arbres :

\[\begin{split}\text{MDI}_j = \frac{1}{T}\sum_{t=1}^{T} \sum_{\substack{v \in \mathcal{V}_t \\ j(v) = j}} p(v) \cdot \Delta I(v)\end{split}\]

\(T\) est le nombre d’arbres, \(\mathcal{V}_t\) l’ensemble des noeuds internes de l’arbre \(t\), \(j(v)\) la variable utilisée pour la division au noeud \(v\), \(p(v)\) la proportion d’observations atteignant \(v\), et \(\Delta I(v)\) la diminution d’impureté.

Hide code cell source

# Comparaison MDI vs permutation importance
mdi_imp = pd.Series(rf_reg.feature_importances_, index=housing.feature_names)
pi_imp = pd.Series(perm_imp.importances_mean, index=housing.feature_names)

fig, axes = plt.subplots(2, 1, figsize=(9, 9))

# MDI
mdi_sorted = mdi_imp.sort_values(ascending=True)
axes[0].barh(mdi_sorted.index, mdi_sorted.values, color='steelblue', edgecolor='black')
axes[0].set_title('Importance MDI (impureté)')
axes[0].set_xlabel('Importance')

# Permutation
pi_sorted = pi_imp.sort_values(ascending=True)
axes[1].barh(pi_sorted.index, pi_sorted.values, color='darkorange', edgecolor='black')
axes[1].set_title('Importance par permutation')
axes[1].set_xlabel('Diminution du $R^2$')

plt.suptitle('Comparaison des mesures d\'importance — California Housing', y=1.02)
plt.tight_layout()
plt.show()
_images/06490841e08ba24281019b54c5a915ffaf0e483e934f1bf6333ea6e9fe85d21d.png

Remarque 257

L’importance MDI présente un biais connu : elle surestime l’importance des variables à forte cardinalité (beaucoup de valeurs distinctes) car celles-ci offrent davantage de seuils de division possibles. L’importance par permutation, calculée sur un jeu de test indépendant, ne souffre pas de ce biais et constitue une mesure plus fiable. En pratique, on privilégie l’importance par permutation pour les conclusions finales, et l’importance MDI comme indicateur rapide lors de l’exploration.

Graphiques de dépendance partielle#

Les graphiques de dépendance partielle (Partial Dependence Plots, PDP) complètent les mesures d’importance en montrant comment la prédiction varie en fonction d’une ou deux variables.

Définition 308 (Dépendance partielle)

La fonction de dépendance partielle de la variable \(x_j\) est définie comme la moyenne de la prédiction du modèle sur toutes les valeurs possibles des autres variables :

\[\hat{f}_j(x_j) = \frac{1}{n}\sum_{i=1}^{n} \hat{f}(x_j, \mathbf{x}_{i, \setminus j})\]

\(\mathbf{x}_{i, \setminus j}\) désigne les valeurs des variables autres que \(x_j\) pour l’observation \(i\).

Hide code cell source

# Graphiques de dépendance partielle pour les 4 variables les plus importantes
fig, axes = plt.subplots(2, 2, figsize=(11, 9))

top_features = pi_imp.sort_values(ascending=False).head(4).index.tolist()
display = PartialDependenceDisplay.from_estimator(
    rf_reg, X_train, features=top_features, ax=axes,
    grid_resolution=50, n_jobs=-1
)

fig.suptitle('Dépendance partielle — Random Forest sur California Housing', y=1.05)
plt.tight_layout()
plt.show()
_images/9a1b8586281cac94dadd950b2a8b53d3ce4d41f374ea41507544447819648eb5.png

LIME : explications locales par modèle substitut#

Principe de LIME#

LIME (Local Interpretable Model-agnostic Explanations) est une méthode d’explication locale et agnostique au modèle, introduite par Ribeiro et al. (2016). L’idée fondamentale est la suivante : même si un modèle est globalement complexe, son comportement au voisinage d’un point particulier peut souvent être approximé par un modèle simple — typiquement un modèle linéaire.

Définition 309 (LIME)

Soit \(f\) un modèle complexe et \(\mathbf{x}_0\) une observation à expliquer. LIME cherche un modèle interprétable \(g \in \mathcal{G}\) (par exemple un modèle linéaire) qui approxime localement \(f\) au voisinage de \(\mathbf{x}_0\). Le modèle substitut \(g\) est obtenu par :

\[g^* = \arg\min_{g \in \mathcal{G}} \sum_{i=1}^{N} \pi_{\mathbf{x}_0}(\mathbf{z}_i) \left[f(\mathbf{z}_i) - g(\mathbf{z}_i)\right]^2 + \Omega(g)\]

où :

  • \(\mathbf{z}_1, \ldots, \mathbf{z}_N\) sont des perturbations de \(\mathbf{x}_0\) générées aléatoirement au voisinage de \(\mathbf{x}_0\)

  • \(\pi_{\mathbf{x}_0}(\mathbf{z}_i) = \exp\left(-\frac{\|\mathbf{z}_i - \mathbf{x}_0\|^2}{2\sigma^2}\right)\) est un noyau de proximité qui pondère les perturbations par leur distance à \(\mathbf{x}_0\)

  • \(\Omega(g)\) est un terme de régularisation qui pénalise la complexité de \(g\) (par exemple le nombre de features non nulles)

Algorithme#

L’algorithme LIME procède en quatre étapes :

  1. Perturbation : générer \(N\) échantillons \(\mathbf{z}_i\) au voisinage de \(\mathbf{x}_0\) en perturbant aléatoirement les valeurs des features.

  2. Prédiction : obtenir les prédictions \(f(\mathbf{z}_i)\) du modèle complexe pour chaque perturbation.

  3. Pondération : calculer les poids \(\pi_{\mathbf{x}_0}(\mathbf{z}_i)\) mesurant la proximité de chaque perturbation à \(\mathbf{x}_0\).

  4. Ajustement : entraîner un modèle linéaire pondéré sur les paires \((\mathbf{z}_i, f(\mathbf{z}_i))\) avec les poids \(\pi_{\mathbf{x}_0}(\mathbf{z}_i)\), en régularisant pour ne conserver que les features les plus influentes.

Les coefficients du modèle linéaire résultant fournissent l’explication locale : un coefficient positif indique que la feature pousse la prédiction vers le haut, un coefficient négatif vers le bas.

Exemple 43 (Application de LIME à une classification)

Illustrons LIME sur le jeu de données Wine avec un gradient boosting classifier. On expliquera la prédiction pour une observation particulière.

Hide code cell source

# Chargement du jeu Wine
wine = load_wine(as_frame=True)
X_wine, y_wine = wine.data, wine.target

X_train_w, X_test_w, y_train_w, y_test_w = train_test_split(
    X_wine, y_wine, test_size=0.2, random_state=42
)

# Gradient Boosting
gb_clf = GradientBoostingClassifier(n_estimators=100, random_state=42)
gb_clf.fit(X_train_w, y_train_w)
print(f"Accuracy (test) : {accuracy_score(y_test_w, gb_clf.predict(X_test_w)):.3f}")
Accuracy (test) : 0.944

Hide code cell source

# Implémentation simplifiée de LIME pour l'intuition pédagogique
import warnings
from sklearn.linear_model import Lasso

def lime_explain(model, x0, X_train, n_samples=1000, sigma=1.0, alpha=0.01):
    """
    Explication LIME simplifiée pour une observation x0.
    Retourne les coefficients du modèle linéaire local.
    """
    # Perturbations gaussiennes autour de x0
    noise = np.random.normal(0, sigma, size=(n_samples, X_train.shape[1]))
    Z = x0.values + noise * X_train.std().values

    # Prédictions du modèle complexe
    # On utilise la probabilité de la classe prédite pour x0
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", UserWarning)
        pred_class = model.predict(x0.values.reshape(1, -1))[0]
        f_Z = model.predict_proba(Z)[:, pred_class]

    # Poids de proximité (noyau exponentiel)
    distances = np.sqrt(np.sum((Z - x0.values) ** 2, axis=1))
    weights = np.exp(-distances ** 2 / (2 * (sigma * np.sqrt(X_train.shape[1])) ** 2))

    # Régression linéaire pondérée avec régularisation L1
    W = np.sqrt(weights)
    Z_w = Z * W[:, np.newaxis]
    f_w = f_Z * W

    lasso = Lasso(alpha=alpha, max_iter=10000)
    lasso.fit(Z_w, f_w)

    return pd.Series(lasso.coef_, index=X_train.columns)

# Explication pour l'observation 0 du jeu de test
obs_idx = 0
x0 = X_test_w.iloc[obs_idx]
pred = gb_clf.predict(x0.values.reshape(1, -1))[0]
pred_proba = gb_clf.predict_proba(x0.values.reshape(1, -1))[0]

print(f"Observation {obs_idx} : classe prédite = {pred} "
      f"(classe {wine.target_names[pred]})")
print(f"Probabilités : {dict(zip(wine.target_names, pred_proba.round(3)))}")

np.random.seed(42)
lime_coefs = lime_explain(gb_clf, x0, X_train_w)

# Visualisation des contributions LIME
top_k = 8
top_features_lime = lime_coefs.abs().sort_values(ascending=False).head(top_k)
lime_vals = lime_coefs[top_features_lime.index].sort_values()

fig, ax = plt.subplots(figsize=(8, 5))
colors = ['#e74c3c' if v < 0 else '#2ecc71' for v in lime_vals]
ax.barh(lime_vals.index, lime_vals.values, color=colors, edgecolor='black')
ax.set_xlabel('Contribution locale (coefficient LIME)')
ax.set_title(f'Explication LIME — Observation {obs_idx} '
             f'(classe {wine.target_names[pred]})')
ax.axvline(x=0, color='black', linewidth=0.8)
plt.tight_layout()
plt.show()
Observation 0 : classe prédite = 0 (classe class_0)
Probabilités : {np.str_('class_0'): np.float64(1.0), np.str_('class_1'): np.float64(0.0), np.str_('class_2'): np.float64(0.0)}
/home/loc/legacy_workspace/notebooks/.venv/lib/python3.13/site-packages/sklearn/utils/validation.py:2691: UserWarning: X does not have valid feature names, but GradientBoostingClassifier was fitted with feature names
  warnings.warn(
/home/loc/legacy_workspace/notebooks/.venv/lib/python3.13/site-packages/sklearn/utils/validation.py:2691: UserWarning: X does not have valid feature names, but GradientBoostingClassifier was fitted with feature names
  warnings.warn(
_images/c7adfe9a85438576e61a169dc948e3898ee703e85d5f6c07d41293fd5e65be67.png

Remarque 258

LIME présente plusieurs limitations importantes :

  • Instabilité : les explications dépendent de l’échantillonnage aléatoire des perturbations. Deux exécutions peuvent donner des explications légèrement différentes.

  • Choix du noyau : la largeur de bande \(\sigma\) du noyau de proximité influence fortement la taille du voisinage considéré, et donc l’explication. Il n’existe pas de méthode universelle pour choisir \(\sigma\).

  • Hypothèse de linéarité locale : si le modèle est fortement non linéaire même localement, l’approximation linéaire peut être trompeuse.

  • Indépendance des features : les perturbations sont générées en supposant les features indépendantes, ce qui peut créer des combinaisons irréalistes.

Malgré ces limitations, LIME reste un outil populaire pour sa simplicité et son universalité. Pour des explications plus robustes sur le plan théorique, on préfère les valeurs de Shapley présentées dans la section suivante.

SHAP : valeurs de Shapley pour l’apprentissage automatique#

Les valeurs de Shapley en théorie des jeux#

Les valeurs de Shapley sont un concept fondamental de la théorie des jeux coopératifs, introduit par Lloyd Shapley en 1953. Dans un jeu coopératif, un ensemble de joueurs collabore pour produire un gain total. La question est : comment répartir équitablement ce gain entre les joueurs ?

En apprentissage automatique, les « joueurs » sont les features, le « gain » est la prédiction du modèle, et la question devient : quelle est la contribution de chaque feature à la prédiction pour une observation donnée ?

Définition 310 (Valeur de Shapley)

Soit \(N = \{1, \ldots, p\}\) l’ensemble des features et \(v : 2^N \to \mathbb{R}\) une fonction de valeur qui associe à chaque sous-ensemble \(S \subseteq N\) la prédiction du modèle utilisant uniquement les features de \(S\). La valeur de Shapley de la feature \(j\) est :

\[\phi_j = \sum_{S \subseteq N \setminus \{j\}} \frac{|S|!\,(p - |S| - 1)!}{p!} \left[v(S \cup \{j\}) - v(S)\right]\]

Cette formule moyenne la contribution marginale de la feature \(j\) sur toutes les coalitions possibles \(S\) ne contenant pas \(j\), pondérées par le nombre de permutations correspondantes.

Les valeurs de Shapley sont l”unique répartition satisfaisant quatre propriétés souhaitables.

Définition 311 (Propriétés des valeurs de Shapley)

Les valeurs de Shapley satisfont les quatre axiomes suivants :

  1. Efficacité (efficiency) : la somme des contributions égale la différence entre la prédiction et la prédiction moyenne :

\[\sum_{j=1}^{p} \phi_j = f(\mathbf{x}) - \mathbb{E}[f(\mathbf{X})]\]
  1. Symétrie (symmetry) : si deux features contribuent de manière identique dans toutes les coalitions, elles reçoivent la même valeur de Shapley.

  2. Joueur nul (dummy) : une feature qui n’apporte aucune contribution marginale dans aucune coalition reçoit une valeur de Shapley nulle.

  3. Additivité (additivity) : pour un modèle combinant deux fonctions \(f = f_1 + f_2\), les valeurs de Shapley se décomposent : \(\phi_j(f) = \phi_j(f_1) + \phi_j(f_2)\).

Remarque 259

Le calcul exact des valeurs de Shapley nécessite d’évaluer la fonction de valeur pour tous les sous-ensembles possibles de features, soit \(2^p\) évaluations. Pour \(p = 20\) features, cela représente déjà plus d’un million de coalitions. En pratique, on utilise des approximations efficaces : KernelSHAP (échantillonnage pondéré, applicable à tout modèle) et TreeSHAP (algorithme polynomial exact pour les modèles à base d’arbres).

SHAP : Shapley Additive exPlanations#

SHAP (SHapley Additive exPlanations), introduit par Lundberg et Lee (2017), unifie plusieurs méthodes d’explication existantes — dont LIME — dans le cadre théorique des valeurs de Shapley. SHAP propose que chaque explication locale prenne la forme :

\[g(\mathbf{z}') = \phi_0 + \sum_{j=1}^{p} \phi_j z'_j\]

\(\mathbf{z}' \in \{0, 1\}^p\) est un vecteur binaire indiquant quelles features sont « présentes », \(\phi_0 = \mathbb{E}[f(\mathbf{X})]\) est la prédiction de base, et \(\phi_j\) est la valeur de Shapley de la feature \(j\).

TreeSHAP et KernelSHAP#

Deux algorithmes principaux sont proposés :

  • TreeSHAP : un algorithme en \(O(TLD^2)\) (où \(T\) est le nombre d’arbres, \(L\) le nombre de feuilles et \(D\) la profondeur) qui calcule les valeurs de Shapley exactes pour les modèles à base d’arbres (forêts aléatoires, gradient boosting). C’est l’algorithme de choix pour ces modèles.

  • KernelSHAP : une approximation basée sur la régression pondérée, applicable à tout modèle. Plus lent que TreeSHAP mais universel.

Exemple 44 (Calcul des valeurs SHAP pour une forêt aléatoire)

Calculons et visualisons les valeurs SHAP pour le modèle de forêt aléatoire entraîné sur California Housing.

Hide code cell source

# Calcul manuel simplifié des valeurs SHAP par approximation par permutation
# (pour illustration pédagogique sans dépendance externe)

import warnings

def approximate_shap(model, X, x0, n_samples=200):
    """
    Approximation des valeurs SHAP par échantillonnage de permutations.
    Pour chaque permutation des features, on mesure la contribution marginale.
    """
    p = X.shape[1]
    cols = X.columns
    shap_values = np.zeros(p)

    for _ in range(n_samples):
        # Permutation aléatoire des features
        perm = np.random.permutation(p)
        # Observation de référence aléatoire
        ref = X.iloc[np.random.randint(X.shape[0])].values.copy()

        x_before = ref.copy()
        x_after = ref.copy()

        for j in perm:
            x_after[j] = x0.values[j]
            with warnings.catch_warnings():
                warnings.simplefilter("ignore", UserWarning)
                pred_after = model.predict(x_after.reshape(1, -1))[0]
                pred_before = model.predict(x_before.reshape(1, -1))[0]
            shap_values[j] += (pred_after - pred_before)
            x_before[j] = x0.values[j]

    shap_values /= n_samples
    return pd.Series(shap_values, index=cols)

# Calcul pour une observation spécifique
np.random.seed(42)
obs_idx_h = 0
x0_h = X_test.iloc[obs_idx_h]
pred_h = rf_reg.predict(x0_h.values.reshape(1, -1))[0]
base_value = y_train.mean()

shap_vals = approximate_shap(rf_reg, X_train.iloc[:200], x0_h, n_samples=50)

print(f"Prédiction pour l'observation {obs_idx_h} : {pred_h:.3f}")
print(f"Valeur de base (moyenne) : {base_value:.3f}")
print(f"Somme des SHAP values : {shap_vals.sum():.3f}")
print(f"Différence prédiction - base : {pred_h - base_value:.3f}")
/home/loc/legacy_workspace/notebooks/.venv/lib/python3.13/site-packages/sklearn/utils/validation.py:2691: UserWarning: X does not have valid feature names, but RandomForestRegressor was fitted with feature names
  warnings.warn(
Prédiction pour l'observation 0 : 0.509
Valeur de base (moyenne) : 2.072
Somme des SHAP values : -1.514
Différence prédiction - base : -1.562

Hide code cell source

# Visualisation : diagramme en cascade (waterfall)
shap_sorted = shap_vals.reindex(shap_vals.abs().sort_values(ascending=True).index)

fig, ax = plt.subplots(figsize=(8, 6))
colors = ['#e74c3c' if v < 0 else '#2980b9' for v in shap_sorted.values]
ax.barh(range(len(shap_sorted)), shap_sorted.values, color=colors, edgecolor='black')
ax.set_yticks(range(len(shap_sorted)))
ax.set_yticklabels([f"{name} = {x0_h[name]:.2f}" for name in shap_sorted.index])
ax.set_xlabel('Valeur SHAP (contribution à la prédiction)')
ax.set_title(f'Contributions SHAP — Observation {obs_idx_h}\n'
             f'Base = {base_value:.2f}, Prédiction = {pred_h:.2f}')
ax.axvline(x=0, color='black', linewidth=0.8)
plt.tight_layout()
plt.show()
_images/d5da9d69d9c4e23c48f91a062f0a60718702aca7381ec85681c63c11382b8946.png

Visualisations SHAP globales#

Au-delà des explications individuelles, les valeurs SHAP permettent des visualisations globales puissantes.

Hide code cell source

# Calcul des SHAP values pour un sous-ensemble du jeu de test
n_explain = 5  # sous-ensemble réduit pour rester sous le timeout
np.random.seed(42)

shap_matrix = np.zeros((n_explain, X_test.shape[1]))
for i in range(n_explain):
    x0_i = X_test.iloc[i]
    sv = approximate_shap(rf_reg, X_train.iloc[:50], x0_i, n_samples=10)
    shap_matrix[i] = sv.values

shap_df = pd.DataFrame(shap_matrix, columns=housing.feature_names)

Hide code cell source

# Summary plot : importance globale basée sur SHAP
mean_abs_shap = shap_df.abs().mean().sort_values(ascending=True)

fig, ax = plt.subplots(figsize=(8, 5))
ax.barh(mean_abs_shap.index, mean_abs_shap.values, color='steelblue', edgecolor='black')
ax.set_xlabel('$|\\phi_j|$ moyen (importance SHAP)')
ax.set_title('Importance globale des features (SHAP) — California Housing')
plt.tight_layout()
plt.show()
_images/35292fac373faab300a7c0ee87fa44229014305aea73b28e621707d384b44a31.png

Hide code cell source

# SHAP dependence plot : valeur SHAP en fonction de la valeur de la feature
fig, axes = plt.subplots(2, 1, figsize=(9, 9))

for ax, feat in zip(axes, ['MedInc', 'AveOccup']):
    ax.scatter(X_test.iloc[:n_explain][feat], shap_df[feat],
               c=X_test.iloc[:n_explain]['Latitude'], cmap='coolwarm',
               alpha=0.6, edgecolor='grey', s=30)
    ax.set_xlabel(feat)
    ax.set_ylabel(f'SHAP value ($\\phi_{{{feat}}}$)')
    ax.set_title(f'Dependence plot — {feat}')
    ax.axhline(y=0, color='black', linewidth=0.8, linestyle='--')

plt.tight_layout()
plt.show()
_images/2625e39ef43210d389403abd8f9ac17aba2e8daa48201ed59586ec91bc7f0284.png

Remarque 260

Le dependence plot est l’un des graphiques les plus informatifs de SHAP. Il montre la valeur de Shapley \(\phi_j\) en fonction de la valeur de la feature \(x_j\), colorée par une variable d’interaction. Pour MedInc (revenu médian), on observe typiquement une relation monotone croissante : plus le revenu est élevé, plus la contribution à la prédiction du prix est positive. La couleur (ici la latitude) révèle les interactions : à revenu égal, la localisation géographique module l’effet sur le prix.

SHAP vs LIME : comparaison#

Critère

LIME

SHAP

Fondement théorique

Heuristique

Théorie des jeux (Shapley)

Propriétés garanties

Aucune

Efficacité, symétrie, joueur nul, additivité

Type d’explication

Locale

Locale + globale

Stabilité

Faible (dépend de l’échantillonnage)

Forte (valeurs uniques)

Vitesse (arbres)

Modérée

Très rapide (TreeSHAP)

Vitesse (modèle quelconque)

Modérée

Lente (KernelSHAP)

Remarque 261

En pratique, SHAP est aujourd’hui préféré à LIME pour la plupart des applications, en raison de ses garanties théoriques et de sa stabilité. L’exception concerne les modèles pour lesquels ni TreeSHAP ni un calcul rapide ne sont disponibles et où le temps de calcul est critique : dans ce cas, LIME peut constituer une alternative pragmatique.

Visualisation de l’attention et des gradients#

Pour les réseaux de neurones profonds (chapitres 16–20), les méthodes d’interprétabilité reposent sur la structure interne du réseau elle-même.

Cartes d’attention dans les Transformers#

Les modèles à base de Transformers (chapitre 23) utilisent un mécanisme d”attention qui produit naturellement des matrices de poids indiquant quelles parties de l’entrée le modèle « regarde » pour produire chaque élément de la sortie. Ces matrices d’attention peuvent être visualisées pour comprendre le comportement du modèle.

Définition 312 (Poids d’attention)

Dans un mécanisme d’attention (scaled dot-product attention), les poids d’attention pour une requête \(\mathbf{q}_i\) et un ensemble de clés \(\mathbf{K}\) sont :

\[\alpha_{ij} = \text{softmax}\left(\frac{\mathbf{q}_i \cdot \mathbf{k}_j}{\sqrt{d_k}}\right)\]

Le poids \(\alpha_{ij}\) indique l’importance relative de la position \(j\) pour le calcul de la sortie à la position \(i\). La visualisation de la matrice \((\alpha_{ij})\) fournit une carte d’attention interprétable.

Remarque 262

Les cartes d’attention doivent être interprétées avec prudence. Plusieurs études ont montré que les poids d’attention ne reflètent pas toujours fidèlement l’importance des tokens pour la prédiction finale. En particulier, dans les architectures multi-têtes et multi-couches, l’attention d’une seule tête à une seule couche peut être trompeuse. Des méthodes plus rigoureuses comme l”attention rollout ou les attributions par gradient offrent une image plus fiable.

Grad-CAM pour les réseaux convolutifs#

Pour les réseaux convolutifs (chapitre 19), Grad-CAM (Gradient-weighted Class Activation Mapping) est la méthode de référence pour visualiser les régions de l’image qui ont le plus contribué à la prédiction.

Définition 313 (Grad-CAM)

Soit \(A^k \in \mathbb{R}^{H \times W}\) la carte d’activation de la \(k\)-ième feature map de la dernière couche convolutive, et \(y^c\) le score de la classe \(c\) avant softmax. Les poids d’importance de chaque feature map sont :

\[\alpha_k^c = \frac{1}{H \times W}\sum_{i=1}^{H}\sum_{j=1}^{W} \frac{\partial y^c}{\partial A^k_{ij}}\]

La carte Grad-CAM est la combinaison linéaire positive des feature maps pondérées par ces poids :

\[L^c_{\text{Grad-CAM}} = \text{ReLU}\left(\sum_k \alpha_k^c A^k\right)\]

Le ReLU élimine les contributions négatives, ne conservant que les régions ayant une influence positive sur la classe \(c\).

Exemple 45 (Illustration du principe Grad-CAM)

Illustrons le concept de carte d’activation sur un exemple synthétique simulant des feature maps.

Hide code cell source

# Simulation pédagogique de cartes d'activation et Grad-CAM
np.random.seed(42)

# Simulons 4 feature maps de taille 7x7
H, W, K = 7, 7, 4
feature_maps = np.random.randn(K, H, W)

# Créons une "image" avec un objet localisé
feature_maps[0, 2:5, 2:5] += 3.0  # activation forte au centre
feature_maps[1, 0:3, 0:3] += 2.0  # activation en haut à gauche
feature_maps[2, 2:5, 2:5] += 2.5
feature_maps[3, 4:7, 4:7] += 1.0

# Poids d'importance simulés (gradient moyen)
alpha_weights = np.array([0.5, 0.1, 0.3, 0.05])

# Carte Grad-CAM
grad_cam = np.maximum(np.sum(alpha_weights[:, None, None] * feature_maps, axis=0), 0)

fig = plt.figure(figsize=(10, 10))
gs = fig.add_gridspec(2, 2, hspace=0.35, wspace=0.3)
ax_top = [fig.add_subplot(gs[r, c]) for r in range(2) for c in range(2)]
ax_bot = fig.add_subplot(gs[1, :])
# Réorganiser : 4 feature maps en 2×2, Grad-CAM centré en bas
# On recrée la grille : ligne 0 = feature maps 1&2, ligne 1 = feature maps 3&4 + Grad-CAM
fig.clear()
gs = fig.add_gridspec(3, 2, hspace=0.4, wspace=0.3)
ax_fm = [fig.add_subplot(gs[r, c]) for r in range(2) for c in range(2)]
ax_gc = fig.add_subplot(gs[2, :])

for k in range(K):
    im = ax_fm[k].imshow(feature_maps[k], cmap='viridis', vmin=-2, vmax=5)
    ax_fm[k].set_title(f'Feature map {k+1}\n$\\alpha_{k+1}$ = {alpha_weights[k]:.2f}')
    ax_fm[k].axis('off')

im = ax_gc.imshow(grad_cam, cmap='jet')
ax_gc.set_title('Grad-CAM')
ax_gc.axis('off')
plt.colorbar(im, ax=ax_gc, fraction=0.023)

fig.suptitle('Illustration du principe Grad-CAM', y=1.01)
plt.show()
_images/0478e9fbf29486b75efee7d9b49228ec9ae93ae7188aa9c33803d32ec31af36d.png

Méthodes par gradient#

Au-delà de Grad-CAM, plusieurs méthodes exploitent les gradients du modèle pour attribuer une importance à chaque feature d’entrée.

Définition 314 (Attribution par gradient)

L”attribution par gradient (vanilla gradient) pour une entrée \(\mathbf{x}\) et une classe cible \(c\) est simplement le gradient de la sortie par rapport à l’entrée :

\[\text{Attribution}_j = \frac{\partial f_c(\mathbf{x})}{\partial x_j}\]

Des variantes plus sophistiquées incluent :

  • Gradient \(\times\) Input : \(x_j \cdot \frac{\partial f_c}{\partial x_j}\)

  • Integrated Gradients : \(\text{IG}_j = (x_j - x'_j) \int_0^1 \frac{\partial f_c(\mathbf{x}' + \alpha(\mathbf{x} - \mathbf{x}'))}{\partial x_j} d\alpha\)

\(\mathbf{x}'\) est une entrée de référence (typiquement un vecteur nul).

Remarque 263

Les Integrated Gradients (Sundararajan et al., 2017) sont particulièrement intéressants car ils satisfont deux propriétés théoriques essentielles : la complétude (la somme des attributions égale la différence de prédiction entre l’entrée et la référence) et la sensibilité (toute feature qui modifie la prédiction reçoit une attribution non nulle). Ces propriétés font des Integrated Gradients un analogue des valeurs de Shapley pour les réseaux de neurones.

Interprétabilité et performance : quel modèle choisir ?#

Le compromis précision–interprétabilité#

Le choix d’un modèle ne repose pas uniquement sur sa performance prédictive. Dans de nombreuses applications, l’interprétabilité est une contrainte aussi importante que la précision.

Hide code cell source

# Illustration du compromis interprétabilité-performance
# Comparaison de modèles de complexité croissante sur California Housing

from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline

models = {
    'Régression linéaire': Pipeline([
        ('scaler', StandardScaler()),
        ('model', Ridge(alpha=1.0))
    ]),
    'Arbre (profondeur 5)': DecisionTreeRegressor(max_depth=5, random_state=42),
    'Arbre (profondeur 20)': DecisionTreeRegressor(max_depth=20, random_state=42),
    'Random Forest (100)': RandomForestRegressor(n_estimators=100, random_state=42),
    'Gradient Boosting (200)': GradientBoostingRegressor(
        n_estimators=200, random_state=42
    ),
}

results = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    train_score = model.score(X_train, y_train)
    test_score = model.score(X_test, y_test)
    results[name] = {'R² train': train_score, 'R² test': test_score}

results_df = pd.DataFrame(results).T
print(results_df.round(3).to_string())
                         R² train  R² test
Régression linéaire         0.613    0.576
Arbre (profondeur 5)        0.638    0.600
Arbre (profondeur 20)       0.996    0.627
Random Forest (100)         0.974    0.805
Gradient Boosting (200)     0.836    0.800

Hide code cell source

# Graphique du compromis
interpretability_scores = [0.95, 0.7, 0.3, 0.2, 0.15]
test_scores = results_df['R² test'].values

fig, ax = plt.subplots(figsize=(9, 6))
for i, name in enumerate(results_df.index):
    ax.scatter(interpretability_scores[i], test_scores[i], s=150,
               zorder=5, edgecolor='black')
    ax.annotate(name, (interpretability_scores[i], test_scores[i]),
                textcoords="offset points", xytext=(10, 8),
                fontsize=9, ha='left')

ax.set_xlabel('Interprétabilité (score qualitatif)', fontsize=12)
ax.set_ylabel('Performance ($R^2$ test)', fontsize=12)
ax.set_title('Compromis interprétabilité — performance')
ax.set_xlim(-0.05, 1.05)
ax.invert_xaxis()
plt.tight_layout()
plt.show()
_images/116bea19f7b23b6c7ffaa20ad8ffdc63cb78badc1e946d15e4dcf1a75fb507a9.png

Quand préférer un modèle interprétable ?#

Les modèles intrinsèquement interprétables sont à privilégier dans les situations suivantes :

  1. Domaines à haute criticité : médecine, justice, finance, où les décisions affectent directement des individus et où la transparence est une obligation éthique ou légale.

  2. Données tabulaires de dimension modérée : sur ces problèmes, l’écart de performance entre un modèle linéaire bien régularisé et un gradient boosting est souvent faible, alors que le gain en interprétabilité est considérable.

  3. Phase exploratoire : pour comprendre les données et identifier les variables pertinentes avant de passer à un modèle plus complexe.

  4. Débogage : un modèle interprétable sert de baseline contre laquelle comparer les modèles complexes. Si un réseau de neurones ne bat pas significativement une régression logistique, c’est un signal d’alerte.

Remarque 264

Cynthia Rudin, dans son article influent Stop Explaining Black Box Machine Learning Models for High Stakes Decisions and Use Interpretable Models Instead (2019), argumente que pour les décisions à fort enjeu, les méthodes d’explication post hoc (LIME, SHAP) ne sont pas suffisantes : elles fournissent une approximation de l’explication, pas l’explication elle-même. La recommandation est d’investir l’effort dans la construction de modèles intrinsèquement interprétables plutôt que d’expliquer des modèles opaques après coup.

Le cadre réglementaire#

Le contexte réglementaire autour de l’IA évolue rapidement et impose des exigences croissantes en matière de transparence.

Définition 315 (Obligations de transparence (AI Act))

Le Règlement européen sur l’intelligence artificielle (AI Act, 2024) classifie les systèmes d’IA selon leur niveau de risque :

  • Risque inacceptable : systèmes interdits (notation sociale, manipulation subliminale).

  • Risque élevé : systèmes soumis à des obligations strictes de transparence, documentation technique, évaluation de conformité. Exemples : diagnostic médical assisté, scoring de crédit, recrutement automatisé.

  • Risque limité : obligations de transparence (l’utilisateur doit savoir qu’il interagit avec une IA).

  • Risque minimal : pas d’obligations spécifiques.

Pour les systèmes à risque élevé, l’article 13 exige que le système soit « suffisamment transparent pour permettre aux utilisateurs d’interpréter les résultats du système et de les utiliser de manière appropriée ».

Hide code cell source

# Tableau récapitulatif des méthodes d'interprétabilité
recap_data = {
    'Méthode': [
        'Coefficients linéaires', 'Règles d\'arbre', 'GAM',
        'Importance par permutation', 'Importance MDI',
        'Dépendance partielle (PDP)', 'LIME', 'SHAP (Shapley)',
        'Grad-CAM', 'Integrated Gradients'
    ],
    'Portée': [
        'Globale', 'Globale', 'Globale',
        'Globale', 'Globale',
        'Globale', 'Locale', 'Locale + Globale',
        'Locale', 'Locale'
    ],
    'Agnostique': [
        'Non', 'Non', 'Non',
        'Oui', 'Non (arbres)',
        'Oui', 'Oui', 'Oui / Spécialisé',
        'Non (CNN)', 'Non (NN)'
    ],
    'Garanties théoriques': [
        'Fortes', 'Fortes', 'Fortes',
        'Modérées', 'Faibles',
        'Modérées', 'Faibles', 'Fortes (Shapley)',
        'Modérées', 'Fortes'
    ]
}

recap_df = pd.DataFrame(recap_data)
print(recap_df.to_string(index=False))
                   Méthode           Portée       Agnostique Garanties théoriques
    Coefficients linéaires          Globale              Non               Fortes
            Règles d'arbre          Globale              Non               Fortes
                       GAM          Globale              Non               Fortes
Importance par permutation          Globale              Oui             Modérées
            Importance MDI          Globale     Non (arbres)              Faibles
Dépendance partielle (PDP)          Globale              Oui             Modérées
                      LIME           Locale              Oui              Faibles
            SHAP (Shapley) Locale + Globale Oui / Spécialisé     Fortes (Shapley)
                  Grad-CAM           Locale        Non (CNN)             Modérées
      Integrated Gradients           Locale         Non (NN)               Fortes

Résumé#

Ce chapitre a présenté les outils fondamentaux pour comprendre et expliquer les décisions des modèles d’apprentissage automatique :

Concept

Idée clé

Interprétabilité intrinsèque

Modèles linéaires, arbres peu profonds, GAM : transparence par construction

Importance des features

Permutation importance (agnostique), MDI (arbres) : quelles variables comptent ?

Dépendance partielle

PDP : comment une variable influence la prédiction

LIME

Modèle substitut local : explication linéaire au voisinage d’un point

SHAP (Shapley)

Répartition équitable des contributions, fondée en théorie des jeux

TreeSHAP / KernelSHAP

Algorithmes efficaces pour les arbres / tout modèle

Grad-CAM

Carte d’activation pour les CNN : où le réseau regarde-t-il ?

Integrated Gradients

Attribution par gradient pour les réseaux de neurones

AI Act

Cadre réglementaire européen imposant la transparence

Remarque 265

L’interprétabilité n’est pas un luxe réservé aux applications réglementées : c’est une composante essentielle de la pratique rigoureuse de l’apprentissage automatique. Un modèle que l’on ne peut pas expliquer est un modèle que l’on ne peut pas déboguer, améliorer, ni déployer en confiance. Les outils présentés dans ce chapitre — de la simple lecture des coefficients d’une régression linéaire aux valeurs de Shapley en passant par les cartes d’attention et Grad-CAM — forment une boîte à outils complète pour naviguer entre le besoin de performance et l’exigence de transparence. La règle d’or est de toujours commencer par le modèle le plus simple qui répond au problème, et de ne complexifier que si les données et le contexte l’exigent.