
Arrêtez de vous recycler aveuglément : utilisez PSI pour créer un pipeline de surveillance plus intelligent
nettoyé les données, effectué quelques transformations, les modélisé, puis déployé votre modèle pour qu’il soit utilisé par le client.
Cela représente beaucoup de travail pour un data scientist. Mais le travail n’est pas terminé une fois que le modèle atteint le monde réel.
Tout semble parfait sur votre tableau de bord. Mais sous le capot, quelque chose ne va pas. La plupart des modèles n’échouent pas bruyamment. Elles ne « crashent » pas comme une application buggée. Au lieu de cela, ils… dérivent.
N’oubliez pas que vous devez toujours le surveiller pour vous assurer que les résultats sont exacts.
L’un des moyens les plus simples d’y parvenir est de vérifier si le les données dérivent.
En d’autres termes, vous mesurerez si la distribution du nouvelles données frapper votre modèle est similaire à la distribution des données utilisées pour l’entraîner.
Pourquoi les mannequins ne crient pas
Lorsque vous déployez un modèle, vous pariez que le futur ressemblera au passé. Vous vous attendez à ce que les nouvelles données présentent des modèles similaires par rapport aux données utilisées pour les entraîner.
Réfléchissons-y un instant : si j’entraînais mon modèle à reconnaître les pommes et les oranges, que se passerait-il si tout à coup, tout ce que mon modèle recevait étaient des ananas ?
Oui, les données du monde réel sont compliquées. Le comportement des utilisateurs change. Des changements économiques se produisent. Même un petit changement dans votre pipeline de données peut tout gâcher.
Si vous attendez que des mesures telles que la précision ou le RMSE diminuent, vous êtes déjà en retard. Pourquoi? Parce que les étiquettes mettent souvent des semaines, voire des mois, à arriver. Vous avez besoin d’un moyen d’éviter les ennuis avant que les dégâts ne soient causés.
PSI : le détecteur de fumée de données
Le Indice de stabilité de la population (PSI) est un outil classique. Il est né dans le monde du risque de crédit pour surveiller les modèles de prêt.
L’indice de stabilité de la population (PSI) est une mesure statistique fondée sur la théorie de l’information qui quantifie la différence entre une distribution de probabilité et une distribution de probabilité de référence.
[1]
Il ne se soucie pas de la précision de votre modèle. Il ne s’intéresse qu’à une chose : Les données reçues aujourd’hui sont-elles différentes des données utilisées lors de la formation ?
Cette métrique est un moyen de quantifier la quantité de « masse » déplacée entre les compartiments. Si vos données d’entraînement comptaient 10 % d’utilisateurs dans une certaine tranche d’âge, mais que la production en comptait 30 %, PSI le signalera.
Interprétez-le : ce que vous disent les chiffres
Nous suivons généralement ces règles empiriques de seuil :
- PSI < 0,10 : Tout va bien. Vos données sont stables.
- 0,10 ≤PSI < 0,25 : Quelque chose change. Vous devriez probablement enquêter.
- PSI ≥ 0,25 : Changement majeur. Votre modèle fait peut-être de mauvaises suppositions.
Code
Le script Python de cet exercice effectuera les étapes suivantes.
- Divisez les données en « seaux » (quantiles).
- Il calcule le pourcentage de données dans chaque compartiment pour votre ensemble de formation et votre ensemble de production.
- La formule compare ensuite ces pourcentages. S’ils sont presque identiques, le PSI reste proche de zéro. Plus ils divergent, plus le score grimpe.
Voici le code de la fonction de calcul PSI.
def psi(ref, new, bins=10):
# Data to array
ref, new = np.array(ref), np.array(new)
# Generate 10 equal buckets between 0% and 100%
quantiles = np.linspace(0, 1, bins + 1)
breakpoints = np.quantile(ref, quantiles)
# Counting the number of samples in each bucket
ref_counts = np.histogram(ref, breakpoints)[0]
new_counts = np.histogram(new, breakpoints)[0]
# Calculating the percentage
ref_pct = ref_counts / len(ref)
new_pct = new_counts / len(new)
# If any bucket is zero, add a very small number
# to prevent division by zero
ref_pct = np.where(ref_pct == 0, 1e-6, ref_pct)
new_pct = np.where(new_pct == 0, 1e-6, new_pct)
# Calculate PSI and return
return np.sum((ref_pct - new_pct) * np.log(ref_pct / new_pct))
C’est rapide, bon marché et ne nécessite pas de « vraies » étiquettes pour fonctionner, ce qui signifie que vous n’avez pas besoin d’attendre quelques semaines pour avoir suffisamment de prédictions pour calculer des métriques telles que le RMSE. C’est pourquoi c’est un favori de la production.
PSI vérifie si les données actuelles de votre modèle ont trop changé par rapport aux données utilisées pour le construire. En comparant les données actuelles à une référence, cela permet de garantir que votre modèle reste stable et fiable.
Là où le PSI brille
- PSI est génial car il est facile à automatiser
- Vous pouvez l’exécuter quotidiennement sur chaque fonctionnalité.
Là où ce n’est pas le cas
- Cela peut être sensible à la façon dont vous choisissez vos compartiments.
- Cela ne vous dit pas pourquoi les données ont changé, seulement cela a changé.
- Il examine les fonctionnalités une par une.
- Cela pourrait manquer des interactions subtiles entre plusieurs variables.
Comment les équipes professionnelles l’utilisent
Les équipes matures ne se contentent pas d’examiner une seule valeur PSI. Ils suivent le s’orienter au fil du temps.
Un seul pic pourrait être un problème. Une progression constante vers le haut est le signe qu’il est temps de recycler votre modèle. Associez le PSI à d’autres mesures comme un bon vieux statistiques récapitulatives (moyenne, variance) pour une image complète.
Examinons rapidement cet exemple de jouet de données qui ont dérivé. Tout d’abord, nous générons des données aléatoires.
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression
# 1. Generate Reference Data
# np.random.seed(42)
X,y = make_regression(n_samples=1000, n_features=3, noise=5, random_state=42)
df = pd.DataFrame(X, columns= ['var1', 'var2', 'var3'])
df['y'] = y
# Separate X and y
X_ref, y_ref = df.drop('y', axis=1), df.y
# View data head
df.head()

Ensuite, nous formons le modèle.
# 2. Train Regression Model
model = LinearRegression().fit(X_ref, y_ref)
Maintenant, générons des données dérivées.
# Generate the Drift Data
X,y = make_regression(n_samples=500, n_features=3, noise=5, random_state=42)
df2 = pd.DataFrame(X, columns= ['var1', 'var2', 'var3'])
df2['y'] = y
# Add the drift
df2['var1'] = 5 + 1.5 * X_ref.var1 + np.random.normal(0, 5, 1000)
# Separate X and y
X_new, y_new = df2.drop('y', axis=1), df2.y
# View
df2.head()
Ensuite, nous pouvons utiliser notre fonction pour calculer le PSI. Vous devriez remarquer l’énorme variance du PSI pour la variable 1.
# 4. Calculate PSI for the drifted feature
for v in df.columns[:-1]:
psi_value= psi(X_ref[v], X_new[v])
print(f"PSI Score for Feature {v}: {psi_value:.4f}")
PSI Score for Feature var1: 2.3016
PSI Score for Feature var2: 0.0546
PSI Score for Feature var3: 0.1078
Et enfin, vérifions son impact sur l’estimation y.
# 5. Generate Estimates to see the impact
preds_ref = model.predict(X_ref[:5])
preds_drift = model.predict(X_new[:5])
print("\nSample Predictions (Reference vs Drifted):")
print(f"Ref Preds: {preds_ref.round(2)}")
print(f"Drift Preds: {preds_drift.round(2)}")
Sample Predictions (Reference vs Drifted):
Ref Preds: [-104.22 -57.58 -32.69 -18.24 24.13]
Drift Preds: [ 508.33 621.61 -241.88 13.19 433.27]
On peut également visualiser les différences par variable. Nous créons une fonction simple pour tracer les histogrammes superposés.
def drift_plot(ref, new):
fig = plt.hist(ref)
fig = plt.hist(new, color='r', alpha=.5);
return plt.show(fig)
# Calculate PSI for the drifted feature
for v in df.columns[:-1]:
psi_value= psi(X_ref[v], X_new[v])
print(f"PSI Score for Feature {v}: {psi_value:.4f}")
drift_plot(X_ref[v], X_new[v])
Voici les résultats.

La différence est énorme pour la variable 1 !
Avant de partir
Nous avons vu à quel point il est simple de calculer le PSI et comment il peut nous montrer où se produit la dérive. Nous avons rapidement identifié var1 comme notre variable problématique. Surveiller votre modèle sans surveiller vos données est un énorme angle mort.
Nous devons nous assurer que la même distribution de données identifiée lors de la formation du modèle est toujours valide, afin que le modèle puisse continuer à utiliser le modèle des données de référence pour estimer sur de nouvelles données.
Production ML consiste moins à créer le modèle « parfait » qu’à maintenir l’alignement avec la réalité.
Les meilleurs modèles ne se contentent pas de bien prédire. Ils savent quand le monde a changé.
Si vous avez aimé ce contenu, retrouvez-moi sur mon site internet.
https://gustavorsantos.me
Dépôt GitHub
Le code de cet exercice.
https://github.com/gurezende/Studying/blob/master/Python/statistics/data_drift/Data_Drift.ipynb
Références
[1. PSI Definition] https://arize.com/blog-course/population-stability-index-psi/
[2. Numpy Histogram] https://numpy.org/doc/2.2/reference/generated/numpy.histogram.html
[3. Numpy Linspace] https://numpy.org/devdocs/reference/generated/numpy.linspace.html
[4. Numpy Where] https://numpy.org/devdocs/reference/generated/numpy.where.html
[5. Make Regression data] https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_regression.html



