Audit de qualité des étiquettes
Vue d'ensemble
Le bruit dans les étiquettes est le problème de qualité des données le plus insidieux — il reste invisible jusqu'à ce que le modèle apprenne la mauvaise chose. Le confident learning (Northcutt et al., 2021) identifie les exemples probablement mal étiquetés en utilisant des probabilités prédites hors-échantillon.
Quand l'utiliser
Utilisez-le quand : les étiquettes des données d'entraînement proviennent de travailleurs crowdsourcés, de systèmes automatisés ou de supervision faible. Ne l'utilisez pas sur des données de référence validées par des experts sauf pour auditer une dérive.
Pipeline de Confident Learning
import numpy as np
from sklearn.model_selection import cross_val_predict
from sklearn.ensemble import RandomForestClassifier
def confident_learning_audit(X, y, n_folds=5):
"""
Returns indices of likely mislabeled examples.
Based on Northcutt et al. "Confident Learning: Estimating
Uncertainty in Dataset Labels" (JMLR 2021).
"""
n_classes = len(np.unique(y))
# 1. Out-of-sample predicted probabilities
proba = cross_val_predict(
RandomForestClassifier(n_estimators=100, random_state=42),
X, y, cv=n_folds, method="predict_proba"
)
# 2. Compute confident joint
# Estimated joint distribution of noisy labels × true labels
confident_joint = np.zeros((n_classes, n_classes))
for i in range(len(y)):
true_class = y[i]
pred_class = np.argmax(proba[i])
confidence = proba[i][pred_class]
# Count if predicted class has confidence above per-class threshold
class_threshold = np.percentile(proba[:, pred_class], 70)
if confidence > class_threshold:
confident_joint[true_class][pred_class] += 1
# 3. Find label issues: examples where predicted ≠ given AND confident
issues = []
per_class_thresholds = {
k: np.percentile(proba[:, k], 70) for k in range(n_classes)
}
for i in range(len(y)):
pred_class = np.argmax(proba[i])
if (pred_class != y[i] and
proba[i][pred_class] > per_class_thresholds[pred_class]):
issues.append(i)
# 4. Per-class noise estimates
noise_rates = {}
for k in range(n_classes):
n_in_class = np.sum(y == k)
n_noisy = np.sum((np.array(issues) != y[np.array(issues)]) &
(y[np.array(issues) == k]))
noise_rates[k] = n_noisy / n_in_class if n_in_class > 0 else 0
return {
"issue_indices": issues,
"n_issues": len(issues),
"issue_fraction": len(issues) / len(y),
"noise_rates": noise_rates,
"confident_joint": confident_joint,
}
Analyse par classe
| Classe | Total | Mal étiquetée | Taux de bruit | Action |
|---|---|---|---|---|
| Classe bruit élevé | N | M | > 0,10 | Vérifier les directives d'annotation |
| Bruit moyen | N | M | 0,05-0,10 | Vérifier 50 exemples |
| Bruit faible | N | M | < 0,05 | OK |
Que faire avec les problèmes détectés
- Ne jamais corriger automatiquement en fonction des prédictions du modèle — cela renforce le biais du modèle.
- Marquer pour révision humaine. Si impossible, supprimer de l'entraînement (pas du test).
- Ré-annoter un échantillon stratifié pour estimer le vrai taux de bruit.
- Si le taux de bruit > 20 %, envisager une ré-annotation plutôt qu'un nettoyage.