W3docs

Machine Learning : Entraînement et test en Python

Divisez vos données en ensembles d'entraînement et de test en Python avec scikit-learn. Couvre test_size, random_state, stratify et les métriques.

La séparation train/test est l'étape la plus fondamentale dans la construction d'un modèle de machine learning. L'idée est simple : gardez une partie de vos données cachée du modèle pendant l'entraînement, puis mesurez ses performances sur cette portion cachée. Sans cette séparation, vous n'avez aucun moyen honnête de savoir si votre modèle a réellement appris un motif ou s'il a simplement mémorisé les exemples d'entraînement.

Ce chapitre explique comment fonctionne train_test_split de scikit-learn, ce que fait chaque paramètre, et comment évaluer le modèle résultant — pour les problèmes de régression et de classification.

Pourquoi séparer les données d'entraînement des données de test ?

Un modèle qui s'entraîne et s'évalue sur les mêmes données semblera beaucoup plus précis qu'il ne l'est réellement. C'est ce qu'on appelle la fuite de données ou le surapprentissage (overfitting) : le modèle a mémorisé les exemples d'entraînement plutôt que d'apprendre un motif généralisable.

Imaginez un étudiant qui étudie 100 questions d'entraînement et passe ensuite un examen avec ces mêmes 100 questions. Il peut obtenir 100% — mais ce score ne vous dit rien sur sa compréhension réelle du sujet.

La séparation train/test prévient cela en :

  • Entraînant le modèle sur une portion des données afin qu'il puisse apprendre des motifs.
  • Testant le modèle sur une portion séparée et inédite pour mesurer les performances réelles.

Un découpage typique est 80% entraînement / 20% test, bien que le bon ratio dépende de la quantité de données disponibles.

La fonction train_test_split

scikit-learn fournit train_test_split dans son module model_selection. Elle mélange aléatoirement votre jeu de données et le divise en deux parties :

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

Les quatre valeurs de retour sont toujours dans cet ordre : features d'entraînement, features de test, labels d'entraînement, labels de test.

Paramètres clés

ParamètreTypeDescription
test_sizefloat ou intFraction (0–1) ou nombre absolu d'échantillons de test. La valeur par défaut est 0.25.
train_sizefloat ou intComplément de test_size. En général, vous définissez l'un ou l'autre, pas les deux.
random_stateintGraine du générateur de nombres aléatoires. Utilisez n'importe quel entier pour rendre la division reproductible.
stratifyarray-likePassez y ici pour préserver les proportions des classes dans les deux divisions. Essentiel pour les jeux de données déséquilibrés.
shuffleboolIndique s'il faut mélanger avant de diviser. La valeur par défaut est True. Définissez False pour les données de séries temporelles.

Effet de test_size

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)   # 150 samples

for ts in [0.1, 0.2, 0.3]:
    X_tr, X_te, _, _ = train_test_split(X, y, test_size=ts, random_state=42)
    print(f"test_size={ts}: train={len(X_tr)}, test={len(X_te)}")

Résultat :

test_size=0.1: train=135, test=15
test_size=0.2: train=120, test=30
test_size=0.3: train=105, test=45

random_state et reproductibilité

Sans random_state, la division est différente à chaque exécution du script. Définissez-le sur n'importe quel entier pour obtenir des résultats reproductibles :

import numpy as np
from sklearn.model_selection import train_test_split

X = np.arange(10).reshape(-1, 1)
y = np.arange(10)

_, X_te1, _, _ = train_test_split(X, y, test_size=0.3, random_state=42)
_, X_te2, _, _ = train_test_split(X, y, test_size=0.3, random_state=42)

print("Same random_state → same split:", list(X_te1.ravel()) == list(X_te2.ravel()))
# True

Le choix de l'entier (42, 0, 1, etc.) n'a pas d'importance — tant que vous utilisez la même valeur de façon cohérente.

Exemple de régression : prédire les prix des maisons

L'exemple suivant génère un jeu de données synthétiques de tailles et de prix de maisons, entraîne un modèle de régression linéaire et l'évalue sur l'ensemble de test mis de côté.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Generate synthetic data: house size (sq ft) → price
np.random.seed(42)
n = 200
sqft = np.random.randint(500, 3500, n).astype(float)
price = 150 * sqft + np.random.randn(n) * 20000

X = sqft.reshape(-1, 1)
y = price

# Split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
print("Training samples:", len(X_train))   # 160
print("Testing samples:", len(X_test))     # 40

# Train
model = LinearRegression()
model.fit(X_train, y_train)

# Evaluate on the test set
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2  = r2_score(y_test, y_pred)

print(f"Mean Squared Error: {mse:,.0f}")
print(f"R² Score:           {r2:.4f}")
print(f"Coefficient:        {model.coef_[0]:.2f}")
print(f"Intercept:          {model.intercept_:.2f}")

Résultat :

Training samples: 160
Testing samples: 40
Mean Squared Error: 489,271,263
R² Score:           0.9651
Coefficient:        147.55
Intercept:          5237.02

Interprétation des métriques :

  • MSE (Mean Squared Error) est la différence au carré moyenne entre les prédictions et les valeurs réelles. Plus la valeur est faible, mieux c'est, mais l'échelle dépend de votre variable cible (ici, des dollars).
  • va de 0 à 1. Une valeur de 0,965 signifie que le modèle explique environ 96,5% de la variance des prix des maisons — un bon ajustement sur ce jeu de données simple.

Pour en savoir plus sur la régression linéaire, consultez La régression linéaire en Python.

Exemple de classification : espèces de fleurs Iris

Pour une tâche de classification, la précision et un rapport de classification sont plus instructifs que le MSE.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

X, y = load_iris(return_X_y=True)

# stratify=y ensures each class is proportionally represented in both splits
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print("Train class distribution:", np.bincount(y_train))  # [40 40 40]
print("Test class distribution: ", np.bincount(y_test))   # [10 10 10]

model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)

y_pred = model.predict(X_test)
print("\nAccuracy:", round(accuracy_score(y_test, y_pred), 4))
print()
print(classification_report(y_test, y_pred,
      target_names=["setosa", "versicolor", "virginica"]))

Résultat :

Train class distribution: [40 40 40]
Test class distribution:  [10 10 10]

Accuracy: 0.9667

              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        10
  versicolor       1.00      0.90      0.95        10
   virginica       0.91      1.00      0.95        10

    accuracy                           0.97        30
   macro avg       0.97      0.97      0.97        30
weighted avg       0.97      0.97      0.97        30

Pourquoi stratify=y est important : Sans lui, une division aléatoire d'un jeu de données déséquilibré pourrait placer la plupart des échantillons d'une classe rare dans l'entraînement, n'en laissant aucun dans l'ensemble de test. stratify=y garantit que chaque classe apparaît dans les mêmes proportions dans les deux divisions.

Pour en savoir plus sur la classification, consultez La régression logistique en Python et La matrice de confusion en Python.

Pièges courants

Prétraitement avant ou après la division ?

Divisez toujours avant d'ajuster les étapes de prétraitement (mise à l'échelle, encodage, imputation). Si vous mettez à l'échelle toutes vos données puis les divisez, l'ensemble de test a influencé le scaler — ce qui constitue une forme de fuite de données.

L'ordre correct :

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # fit ONLY on training data
X_test_scaled  = scaler.transform(X_test)       # transform test with same parameters

Consultez La mise à l'échelle des features en Python pour une présentation complète.

Mélange et données de séries temporelles

train_test_split mélange les données par défaut. Pour les données de séries temporelles, c'est incorrect — vous entraîneriez le modèle sur des données futures pour prédire le passé. Définissez shuffle=False et assurez-vous que vos données sont triées chronologiquement avant la division :

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, shuffle=False
)

Quand une seule division ne suffit pas

Une seule division 80/20 vous donne une estimation des performances du modèle qui dépend des échantillons qui se sont retrouvés dans l'ensemble de test. La validation croisée répète la division plusieurs fois et fait la moyenne des scores, donnant une estimation beaucoup plus stable — surtout sur de petits jeux de données.

Choisir une métrique d'évaluation

La bonne métrique dépend du type de problème :

ProblèmeMétriques courantes
RégressionMSE, RMSE, MAE, R²
Classification binaireAccuracy, Précision, Rappel, F1, AUC-ROC
Classification multi-classesAccuracy, F1 macro/pondéré

Pour la classification binaire, consultez La courbe AUC-ROC en Python et La matrice de confusion en Python. Pour le réglage des hyperparamètres une fois que vous avez une division fonctionnelle, consultez Grid Search en Python.

Résumé

  • Divisez vos données avant tout prétraitement pour éviter la fuite de données.
  • Utilisez test_size=0.2 comme valeur par défaut raisonnable ; ajustez selon la taille du jeu de données.
  • Définissez random_state sur n'importe quel entier pour obtenir des divisions reproductibles.
  • Utilisez stratify=y pour les tâches de classification, notamment sur des données déséquilibrées.
  • Définissez shuffle=False pour les données de séries temporelles.
  • Une seule division train/test est une base rapide ; utilisez la validation croisée pour des estimations de performance plus fiables.
Was this page helpful?