Concepts d'entrainement et d'evaluation de modeles
Introduction
Avant de deployer un modele IA, vous devez l'entrainer correctement et evaluer ses performances de maniere rigoureuse. Un modele qui performe bien sur les donnees d'entrainement mais echoue en production est pire que pas de modele du tout — il donne une fausse confiance.
Imaginez un etudiant qui memorise toutes les reponses d'un examen passe sans comprendre les concepts. Il obtiendra 100% a cet examen specifique, mais echouera face a de nouvelles questions. C'est exactement ce qui arrive a un modele qui fait du surapprentissage (overfitting) sur ses donnees d'entrainement.
1. Le pipeline d'entrainement ML
Le pipeline d'entrainement est la sequence structuree d'etapes qui transforme des donnees brutes en un modele deployable.
Voir le pipeline d'entrainement ML
Chaque etape de ce pipeline est critique. Sauter ou brusquer une etape peut mener a des modeles qui semblent bons sur papier mais echouent en production.
2. Decoupage des donnees : Train / Validation / Test
Pourquoi decouper les donnees ?
Nous decoupons les donnees en ensembles separes pour obtenir une estimation honnete de la performance de notre modele sur des donnees qu'il n'a jamais vues.
Voir la strategie de decoupage des donnees
| Ensemble | Objectif | Utilisation | Taille typique |
|---|---|---|---|
| Entrainement | Apprendre les patterns a partir des features | Utilise pendant model.fit() | 60-70% |
| Validation | Ajuster les hyperparametres, selectionner le meilleur modele | Utilise pendant la selection du modele | 15-20% |
| Test | Evaluation finale sans biais | Utilise une seule fois a la fin | 15-20% |
L'ensemble de test ne doit jamais etre utilise pour prendre des decisions pendant le developpement. Il n'est utilise que pour l'evaluation finale. Si vous regardez la performance sur le test et revenez modifier votre modele, vous avez contamine votre evaluation.
Exemple de code : Decoupage des donnees
from sklearn.model_selection import train_test_split
# Premier decoupage : separer l'ensemble de test
X_temp, X_test, y_temp, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Deuxieme decoupage : entrainement et validation
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=0.25, random_state=42, stratify=y_temp
)
# Resultat : 60% train, 20% validation, 20% test
print(f"Training: {len(X_train)} samples ({len(X_train)/len(X)*100:.0f}%)")
print(f"Validation: {len(X_val)} samples ({len(X_val)/len(X)*100:.0f}%)")
print(f"Test: {len(X_test)} samples ({len(X_test)/len(X)*100:.0f}%)")
Le parametre stratify=y assure que chaque sous-ensemble a les memes proportions de classes que le jeu de donnees original. C'est essentiel pour les jeux de donnees desequilibres (ex. : 95% classe A, 5% classe B).
3. Validation croisee
Le probleme d'un decoupage unique
Un seul decoupage train/validation peut etre chanceux ou malchanceux. Peut-etre que tous les exemples "faciles" se sont retrouves dans l'ensemble d'entrainement. La validation croisee resout cela en testant plusieurs decoupages.
Imaginez evaluer un restaurant en ne mangeant qu'un seul plat un seul jour. Peut-etre que le chef etait malade ce jour-la, ou au contraire c'etait son meilleur plat. Il vaut mieux revenir k fois et essayer differents plats pour une evaluation fiable.
Validation croisee K-Fold
| Type | Description | Quand l'utiliser |
|---|---|---|
| K-Fold | Decoupe en K folds, chacun servant de test a tour de role | Usage general, K=5 ou K=10 |
| K-Fold stratifie | K-Fold preservant les proportions de classes | Classification desequilibree |
| Leave-One-Out (LOO) | K = nombre d'echantillons | Tres petits jeux de donnees (< 100) |
| K-Fold repete | Repete K-Fold plusieurs fois avec des graines differentes | Estimation tres robuste |
| Time Series Split | Respecte l'ordre temporel des donnees | Donnees sequentielles / series temporelles |
Exemple de code : Validation croisee
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
# Validation croisee stratifiee 5-Fold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
print(f"Scores par fold : {scores}")
print(f"Precision moyenne : {scores.mean():.4f} ± {scores.std():.4f}")
# Plusieurs metriques a la fois
from sklearn.model_selection import cross_validate
results = cross_validate(
model, X_train, y_train, cv=cv,
scoring=['accuracy', 'f1_weighted', 'precision_weighted', 'recall_weighted'],
return_train_score=True
)
for metric in ['accuracy', 'f1_weighted']:
train_score = results[f'train_{metric}'].mean()
test_score = results[f'test_{metric}'].mean()
print(f"{metric}: Train={train_score:.4f}, Val={test_score:.4f}")
4. Ajustement des hyperparametres
Les hyperparametres sont des reglages que vous choisissez avant l'entrainement (contrairement aux parametres du modele, qui sont appris pendant l'entrainement).
| Parametre | Hyperparametre | Appris pendant l'entrainement ? |
|---|---|---|
| Poids du modele | — | ✅ Oui |
| — | Taux d'apprentissage | ❌ Non (vous le choisissez) |
| — | Nombre d'arbres (n_estimators) | ❌ Non |
| — | Profondeur maximale (max_depth) | ❌ Non |
| — | Regularisation (C, alpha) | ❌ Non |
Grid Search
Teste toutes les combinaisons possibles. Exhaustif mais couteux.
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 10, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
# Total : 3 × 4 × 3 × 3 = 108 combinaisons × 5 folds = 540 ajustements
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring='f1_weighted',
n_jobs=-1,
verbose=1
)
grid_search.fit(X_train, y_train)
print(f"Best params: {grid_search.best_params_}")
print(f"Best score: {grid_search.best_score_:.4f}")
best_model = grid_search.best_estimator_
Random Search
Echantillonne aleatoirement l'espace de recherche. Plus efficace quand l'espace est grand.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_distributions = {
'n_estimators': randint(50, 500),
'max_depth': [3, 5, 10, 20, None],
'min_samples_split': randint(2, 20),
'min_samples_leaf': randint(1, 10),
'max_features': uniform(0.1, 0.9)
}
random_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_distributions,
n_iter=50, # seulement 50 combinaisons aleatoires (vs 108+ pour grid)
cv=5,
scoring='f1_weighted',
n_jobs=-1,
random_state=42
)
random_search.fit(X_train, y_train)
print(f"Best params: {random_search.best_params_}")
print(f"Best score: {random_search.best_score_:.4f}")
| Methode | Avantages | Inconvenients | Quand l'utiliser |
|---|---|---|---|
| Grid Search | Exhaustif, garanti de trouver le meilleur | Tres lent (exponentiel) | Petit espace de recherche |
| Random Search | Plus rapide, explore mieux l'espace | Pas de garantie d'optimalite | Grand espace de recherche |
| Optimisation bayesienne | Intelligent, converge rapidement | Plus complexe a implementer | Modeles couteux a entrainer |
5. Metriques d'evaluation
Metriques de classification
La matrice de confusion
La matrice de confusion est la base de toutes les metriques de classification.
- Vrai Positif : Il y a un incendie, l'alarme sonne ✅
- Faux Positif : Pas d'incendie, l'alarme sonne quand meme (toast brule) 🚨
- Faux Negatif : Il y a un incendie, mais l'alarme ne sonne pas 💀
- Vrai Negatif : Pas d'incendie, l'alarme reste silencieuse ✅
Un FN (manquer un vrai incendie) est beaucoup plus grave qu'un FP (fausse alarme). Le choix de la metrique depend du cout des erreurs.
Metriques derivees de la matrice de confusion
| Metrique | Formule | Question a laquelle elle repond |
|---|---|---|
| Exactitude (Accuracy) | (VP + VN) / Total | Quelle fraction des predictions est correcte ? |
| Precision | VP / (VP + FP) | Parmi les predictions positives, combien sont vraies ? |
| Rappel (Sensibilite) | VP / (VP + FN) | Parmi les vrais positifs, combien ont ete detectes ? |
| Score F1 | 2 × (Precision × Rappel) / (Precision + Rappel) | Moyenne harmonique de la Precision et du Rappel |
| Specificite | VN / (VN + FP) | Parmi les vrais negatifs, combien ont ete identifies ? |
from sklearn.metrics import (
accuracy_score, precision_score, recall_score,
f1_score, confusion_matrix, classification_report
)
y_pred = model.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred, average='weighted'):.4f}")
print(f"Recall: {recall_score(y_test, y_pred, average='weighted'):.4f}")
print(f"F1-Score: {f1_score(y_test, y_pred, average='weighted'):.4f}")
print("\nClassification Report:")
print(classification_report(y_test, y_pred))
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
fig, ax = plt.subplots(figsize=(8, 6))
ConfusionMatrixDisplay.from_estimator(model, X_test, y_test, ax=ax, cmap='Purples')
ax.set_title("Confusion Matrix")
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=150)
plt.show()
Courbe AUC-ROC
La courbe ROC (Receiver Operating Characteristic) trace le taux de vrais positifs vs le taux de faux positifs a differents seuils de classification. L'AUC (Area Under the Curve) resume cela en un seul chiffre.
| Valeur AUC | Interpretation |
|---|---|
| 1.0 | Classificateur parfait |
| 0.9 - 1.0 | Excellent |
| 0.8 - 0.9 | Bon |
| 0.7 - 0.8 | Acceptable |
| 0.5 | Aleatoire (aucune competence) |
| < 0.5 | Pire qu'aleatoire |
from sklearn.metrics import roc_curve, roc_auc_score
# Pour la classification binaire
y_proba = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
auc_score = roc_auc_score(y_test, y_proba)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='#7c3aed', lw=2, label=f'ROC Curve (AUC = {auc_score:.4f})')
plt.plot([0, 1], [0, 1], 'k--', lw=1, label='Random Classifier')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Quand utiliser quelle metrique ?
| Scenario | Metrique prioritaire | Pourquoi |
|---|---|---|
| Detection de spam | Precision | Eviter de marquer des emails legitimes comme spam (minimiser les FP) |
| Depistage du cancer | Rappel | Ne pas manquer de vrais cancers (minimiser les FN) |
| Classes equilibrees | Exactitude ou F1 | Les deux types d'erreur sont egalement couteux |
| Classes desequilibrees | F1, AUC-ROC | L'exactitude est trompeuse avec un desequilibre de classes |
| Classement des predictions | AUC-ROC | Mesure la qualite du classement a travers les seuils |
Sur un jeu de donnees avec 95% de classe A et 5% de classe B, un modele qui predit toujours A aura 95% d'exactitude. C'est pourquoi l'exactitude seule est insuffisante pour les jeux de donnees desequilibres. Utilisez toujours F1, Precision, Rappel et AUC-ROC en complement.
Metriques de regression
| Metrique | Formule | Interpretation |
|---|---|---|
| MSE (Mean Squared Error) | Σ(y - ŷ)² / n | Penalise fortement les grandes erreurs |
| RMSE (Root MSE) | √MSE | Meme unite que la variable cible |
| MAE (Mean Absolute Error) | Σ|y - ŷ| / n | Erreur moyenne en valeur absolue |
| R² (Coefficient de determination) | 1 - (SS_res / SS_tot) | Proportion de variance expliquee (0 a 1) |
| MAPE | Σ|y - ŷ|/|y| × 100 / n | Erreur en pourcentage |
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MSE: {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"MAE: {mae:.4f}")
print(f"R²: {r2:.4f}")
6. Surapprentissage vs Sous-apprentissage
Voir la comparaison biais-variance
| Caracteristique | Sous-apprentissage | Bon ajustement | Surapprentissage |
|---|---|---|---|
| Exactitude train | ❌ Faible | ✅ Elevee | ✅ Tres elevee |
| Exactitude test | ❌ Faible | ✅ Elevee | ❌ Faible |
| Complexite du modele | Trop simple | Juste bien | Trop complexe |
| Biais | Eleve | Faible | Faible |
| Variance | Faible | Faible | Elevee |
Comment detecter
# Entrainer et evaluer sur les deux ensembles
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
gap = train_score - test_score
print(f"Train: {train_score:.4f}")
print(f"Test: {test_score:.4f}")
print(f"Gap: {gap:.4f}")
if train_score < 0.7 and test_score < 0.7:
print("⚠️ Underfitting: model too simple")
elif gap > 0.10:
print("⚠️ Overfitting: model too complex")
else:
print("✅ Good generalization")
Remedes
| Probleme | Solutions |
|---|---|
| Sous-apprentissage | Augmenter la complexite du modele, ajouter plus de features, reduire la regularisation, entrainer plus longtemps |
| Surapprentissage | Ajouter plus de donnees, augmenter la regularisation, reduire les features, utiliser le dropout, arret precoce, validation croisee |
7. Compromis biais-variance
Le compromis biais-variance est un concept fondamental en machine learning qui decrit la tension entre deux sources d'erreur.
- Biais eleve : Le tireur vise systematiquement trop a gauche (erreur systematique)
- Variance elevee : Les tirs sont disperses partout autour de la cible (instabilite)
- Objectif : Tirs groupes au centre (biais faible + variance faible)
| Variance faible | Variance elevee | |
|---|---|---|
| Biais faible | ✅ Ideal (generalisation) | ⚠️ Surapprentissage |
| Biais eleve | ⚠️ Sous-apprentissage | ❌ Pire cas |
8. Courbes d'apprentissage
Les courbes d'apprentissage tracent la performance du modele en fonction de la taille de l'ensemble d'entrainement ou du nombre d'iterations. C'est l'outil de diagnostic visuel le plus puissant pour detecter le surapprentissage et le sous-apprentissage.
from sklearn.model_selection import learning_curve
import numpy as np
import matplotlib.pyplot as plt
train_sizes, train_scores, val_scores = learning_curve(
RandomForestClassifier(n_estimators=100, random_state=42),
X_train, y_train,
cv=5,
train_sizes=np.linspace(0.1, 1.0, 10),
scoring='accuracy',
n_jobs=-1
)
train_mean = train_scores.mean(axis=1)
train_std = train_scores.std(axis=1)
val_mean = val_scores.mean(axis=1)
val_std = val_scores.std(axis=1)
plt.figure(figsize=(10, 6))
plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std, alpha=0.1, color='#7c3aed')
plt.fill_between(train_sizes, val_mean - val_std, val_mean + val_std, alpha=0.1, color='#f59e0b')
plt.plot(train_sizes, train_mean, 'o-', color='#7c3aed', label='Training Score')
plt.plot(train_sizes, val_mean, 's-', color='#f59e0b', label='Validation Score')
plt.xlabel('Training Set Size')
plt.ylabel('Accuracy')
plt.title('Learning Curve')
plt.legend(loc='lower right')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Interpreter les courbes d'apprentissage
| Pattern | Diagnostic | Action |
|---|---|---|
| Les deux courbes basses et convergentes | Sous-apprentissage | Utiliser un modele plus complexe ou plus de features |
| Train elevee, validation basse, ecart persistant | Surapprentissage | Ajouter plus de donnees, regulariser, simplifier le modele |
| Les deux courbes elevees et convergentes | Bon ajustement | Le modele est pret pour le deploiement |
| La courbe de validation monte encore a la fin | Plus de donnees necessaires | Collecter plus de donnees d'entrainement |
9. Tout assembler
Voici un exemple complet combinant tous les concepts :
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import (
train_test_split, StratifiedKFold, GridSearchCV
)
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
# 1. Load data
data = load_iris()
X, y = data.data, data.target
# 2. Split: 60% train, 20% val, 20% test
X_temp, X_test, y_temp, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=0.25, random_state=42, stratify=y_temp
)
# 3. Build a pipeline (preprocessing + model)
pipeline = Pipeline([
('scaler', StandardScaler()),
('clf', RandomForestClassifier(random_state=42))
])
# 4. Hyperparameter tuning with cross-validation
param_grid = {
'clf__n_estimators': [50, 100, 200],
'clf__max_depth': [3, 5, None],
}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
grid_search = GridSearchCV(
pipeline, param_grid, cv=cv,
scoring='f1_weighted', n_jobs=-1
)
grid_search.fit(X_train, y_train)
print(f"Best hyperparameters: {grid_search.best_params_}")
print(f"Best CV F1-score: {grid_search.best_score_:.4f}")
# 5. Validate on validation set
best_model = grid_search.best_estimator_
val_pred = best_model.predict(X_val)
print(f"\nValidation Accuracy: {accuracy_score(y_val, val_pred):.4f}")
# 6. Final evaluation on test set
test_pred = best_model.predict(X_test)
print(f"\nTest Set Results:")
print(classification_report(y_test, test_pred, target_names=data.target_names))
Resume
🔑 Points cles a retenir
- Decoupage des donnees : Toujours decouper en train/validation/test. L'ensemble de test n'est que pour l'evaluation finale.
- Validation croisee : Utiliser K-Fold (K=5 ou 10) pour des estimations de performance robustes.
- Ajustement des hyperparametres : GridSearch pour les petits espaces, RandomSearch pour les grands espaces.
- Metriques : Choisir la metrique en fonction du cout des erreurs (Precision vs Rappel).
- Matrice de confusion : Base de toutes les metriques de classification. Apprenez a la lire.
- Surapprentissage : Performance d'entrainement elevee, performance de test faible → modele trop complexe.
- Sous-apprentissage : Performance faible partout → modele trop simple.
- Biais-Variance : L'objectif est de minimiser les deux simultanement.
- Courbes d'apprentissage : Outil visuel essentiel pour diagnostiquer les problemes.
Lectures complementaires
| Ressource | Lien |
|---|---|
| Guide de selection de modele scikit-learn | sklearn.model_selection |
| Validation croisee : evaluer la performance des estimateurs | sklearn Cross-Validation |
| Metriques et scoring | sklearn Metrics |
| Comprendre le compromis biais-variance | Scott Fortmann-Roe |