cours scikit learn

Scikit-Learn et la classification

Ce cours vous présente Scikit-Learn, la bibliothèque Python de référence pour le machine learning, en se concentrant sur les modèles de classification.

Vous découvrirez les fondements théoriques de l’apprentissage supervisé et comment implémenter différents modèles de classification avec Scikit-Learn.

Ce que vous allez apprendre :

  • Les principes fondamentaux de l’apprentissage supervisé
  • La différence entre régression et classification
  • Comment préparer vos données pour un problème de classification
  • Les algorithmes de classification essentiels et leurs implémentations
  • Comment évaluer et améliorer les performances de vos modèles

Ce que vous devez maîtriser :

  • Connaissances de base en Python (variables, fonctions, boucles)
  • Notions élémentaires de mathématiques (algèbre linéaire, probabilités)
  • Compréhension basique des concepts de données (tableaux, matrices)
  • Installation de Python et des bibliothèques scientifiques (numpy, pandas)

Comprendre les bases de l’apprentissage supervisé

schéma apprentissage supervisé

Qu’est-ce que l’apprentissage supervisé ?

L’apprentissage supervisé est une approche de machine learning où l’algorithme apprend à partir de données étiquetées.

Imaginez un professeur (les données étiquetées) qui guide un élève (l’algorithme) jusqu’à ce qu’il puisse faire des prédictions par lui-même sur de nouvelles données.

Dans ce paradigme, nous fournissons à l’algorithme :

  • Des exemples d’entrée (features) : caractéristiques ou attributs décrivant chaque observation
  • Des sorties attendues (labels) : réponses correctes associées à chaque exemple

L’objectif est de trouver une fonction qui relie correctement les entrées aux sorties, et qui peut généraliser à de nouvelles observations jamais vues auparavant.

# Structure typique d'un problème d'apprentissage supervisé
import numpy as np
from sklearn.model_selection import train_test_split

# Données d'entrée (X) et sorties attendues (y)
X = np.array([[0, 1], [1, 0], [1, 1], [0, 0]])  # Caractéristiques
y = np.array([0, 0, 1, 0])                      # Étiquettes

# Division en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42)

print(f"Données d'entraînement: {X_train.shape[0]} exemples")
print(f"Données de test: {X_test.shape[0]} exemples")

L’apprentissage supervisé comporte plusieurs étapes clés :

  1. Collecte et préparation des données : recueillir des données pertinentes et les nettoyer
  2. Extraction des caractéristiques : identifier les variables qui permettront de prédire la sortie
  3. Choix du modèle : sélectionner l’algorithme approprié à votre problème
  4. Entraînement : ajuster les paramètres du modèle sur les données d’entraînement
  5. Évaluation : mesurer la performance sur des données non utilisées pour l’entraînement
  6. Optimisation : affiner le modèle pour améliorer ses performances

Classification vs Régression

L’apprentissage supervisé se divise en deux grandes catégories : la classification et la régression.

ClassificationRégression
Prédit une catégorie/classePrédit une valeur numérique continue
Sortie discrète (appartenance à un groupe)Sortie continue (nombre réel)
Exemples : spam/non-spam, bénin/malinExemples : prix, température, âge
Évaluation : précision, rappel, F1-scoreÉvaluation : RMSE, MAE, R²

Pour les problèmes de classification, on distingue également :

  • Classification binaire : deux classes possibles (0/1, oui/non)
  • Classification multi-classe : plus de deux classes mutuellement exclusives
  • Classification multi-label : possibilité d’appartenir à plusieurs classes simultanément

Voici comment ces problèmes se traduisent en pratique avec Scikit-Learn :

from sklearn.linear_model import LogisticRegression, LinearRegression
from sklearn.datasets import load_iris, load_diabetes

# Exemple de classification (Iris)
X_cls, y_cls = load_iris(return_X_y=True)
clf = LogisticRegression(max_iter=200)
clf.fit(X_cls, y_cls)
print(f"Classes prédites : {clf.predict(X_cls[:2])}")
print(f"Probabilités : {clf.predict_proba(X_cls[:2])[:, 1]}")

# Exemple de régression (Diabetes)
X_reg, y_reg = load_diabetes(return_X_y=True)
reg = LinearRegression()
reg.fit(X_reg, y_reg)
print(f"Valeurs prédites : {reg.predict(X_reg[:2])}")

Dans ce cours, nous nous concentrerons sur la classification, mais les concepts abordés vous aideront également à comprendre la régression.

Préparation des données pour l’apprentissage

La qualité de vos modèles dépend fortement de la qualité des données d’entrée. Une bonne préparation des données est cruciale et implique plusieurs étapes :

1. Nettoyage des données

Les données du monde réel sont rarement parfaites. Vous devrez gérer :

import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer

# Exemple de données avec valeurs manquantes
data = pd.DataFrame({
    'age': [25, 30, np.nan, 40],
    'income': [50000, np.nan, 70000, 60000],
    'education': ['Bachelor', 'Master', np.nan, 'PhD']
})

# Imputation des valeurs manquantes numériques
num_imputer = SimpleImputer(strategy='mean')
data[['age', 'income']] = num_imputer.fit_transform(data[['age', 'income']])

# Imputation des valeurs manquantes catégorielles
cat_imputer = SimpleImputer(strategy='most_frequent')
data[['education']] = cat_imputer.fit_transform(data[['education']].values)

print("Données après imputation :")
print(data)

2. Codage des variables catégorielles

Les algorithmes de machine learning travaillent avec des nombres. Les variables catégorielles doivent être transformées :

from sklearn.preprocessing import OneHotEncoder, LabelEncoder

# Encodage pour cible catégorielle (y)
label_encoder = LabelEncoder()
education_encoded = label_encoder.fit_transform(data['education'])
print(f"Encodage des étiquettes : {dict(zip(label_encoder.classes_, range(len(label_encoder.classes_))))}")

# Encodage One-Hot pour features catégorielles (X)
encoder = OneHotEncoder(sparse_output=False)
education_one_hot = encoder.fit_transform(data[['education']])
print("Encodage One-Hot :")
print(pd.DataFrame(education_one_hot, columns=encoder.get_feature_names_out(['education'])))

3. Normalisation et standardisation

Les algorithmes sensibles à l’échelle des variables nécessitent une normalisation :

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Standardisation (moyenne=0, écart-type=1)
scaler = StandardScaler()
age_income_scaled = scaler.fit_transform(data[['age', 'income']])
print("Données standardisées :")
print(pd.DataFrame(age_income_scaled, columns=['age', 'income']))

# Normalisation (valeurs entre 0 et 1)
normalizer = MinMaxScaler()
age_income_normalized = normalizer.fit_transform(data[['age', 'income']])
print("Données normalisées :")
print(pd.DataFrame(age_income_normalized, columns=['age', 'income']))

4. Division des données

Pour évaluer honnêtement votre modèle, vous devez séparer vos données :

from sklearn.model_selection import train_test_split

X = age_income_scaled  # Caractéristiques
y = education_encoded  # Cible

# Division en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42)

print(f"Ensemble d'entraînement : {X_train.shape[0]} exemples")
print(f"Ensemble de test : {X_test.shape[0]} exemples")

Scikit-Learn facilite grandement ces tâches de préparation grâce à ses nombreuses classes de prétraitement et à son API cohérente.

Premier modèle : La Régression Logistique

Principe et théorie

Malgré son nom contenant « régression », la régression logistique est un algorithme de classification. Son fonctionnement repose sur l’estimation de la probabilité qu’une observation appartienne à une classe donnée.

Le modèle de base de la régression logistique est :

  1. Calculer une combinaison linéaire des features : z = w₀ + w₁x₁ + w₂x₂ + … + wₙxₙ
  2. Appliquer la fonction sigmoïde pour transformer z en probabilité : p = 1 / (1 + e^(-z))
  3. Classifier en fonction d’un seuil (généralement 0.5) : classe = 1 si p ≥ 0.5, sinon classe = 0

La fonction sigmoïde est représentée graphiquement ainsi :

fonction sigmoïde

Pendant l’entraînement, le modèle cherche les coefficients w qui maximisent la vraisemblance des données observées, généralement en minimisant la fonction de perte d’entropie croisée.

Implémentation avec Scikit-Learn

Scikit-Learn rend l’utilisation de la régression logistique très simple :

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

# Charger les données (cancer du sein)
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target

# Description du jeu de données
print(f"Nombre d'exemples : {X.shape[0]}")
print(f"Nombre de caractéristiques : {X.shape[1]}")
print(f"Classes : {np.unique(y)}")
print(f"Noms des classes : {cancer.target_names}")
print(f"Distribution des classes : {np.bincount(y)}")

# Division en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42)

# Création et entraînement du modèle
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)

# Prédictions
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]  # Proba de la classe positive

# Évaluation
accuracy = accuracy_score(y_test, y_pred)
print(f"Précision : {accuracy:.3f}")

# Rapport détaillé
print("\nRapport de classification :")
print(classification_report(y_test, y_pred, target_names=cancer.target_names))

# Matrice de confusion
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=cancer.target_names,
            yticklabels=cancer.target_names)
plt.xlabel('Prédit')
plt.ylabel('Réel')
plt.title('Matrice de confusion')
plt.show()

# Coefficients du modèle
coef = pd.DataFrame(
    model.coef_[0],
    index=cancer.feature_names,
    columns=['Coefficient']
).sort_values('Coefficient', ascending=False)

plt.figure(figsize=(10, 8))
sns.barplot(x=coef.Coefficient, y=coef.index)
plt.title('Importance des caractéristiques')
plt.axvline(x=0, color='r', linestyle='--')
plt.show()

Interprétation et limites

La régression logistique offre plusieurs avantages :

  • Interprétabilité : les coefficients indiquent l’importance et la direction de l’influence de chaque feature
  • Probabilités : fournit naturellement des estimations de probabilité
  • Efficacité : rapide à entraîner et à faire des prédictions
  • Bien adapté aux petits jeux de données

Cependant, elle présente certaines limitations :

  • Linéarité : suppose une relation linéaire entre les features et le logarithme des odds (rapport de probabilité afin de mesurer le risque qu’un événement arrivant à un groupe, arrive également à un autre)
  • Indépendance des features : n’intègre pas les interactions entre variables par défaut
  • Sensibilité aux valeurs aberrantes
  • Difficulté avec les frontières de décision complexes

Pour visualiser ces limitations, voici un exemple de frontière de décision linéaire :

from sklearn.inspection import DecisionBoundaryDisplay

# Sélectionner deux caractéristiques pour la visualisation
X_2d = X[:, :2]  # Utiliser seulement les deux premières caractéristiques
X_train_2d, X_test_2d, y_train, y_test = train_test_split(
    X_2d, y, test_size=0.3, random_state=42)

# Modèle
model_2d = LogisticRegression()
model_2d.fit(X_train_2d, y_train)

# Afficher la frontière de décision
plt.figure(figsize=(10, 8))
disp = DecisionBoundaryDisplay.from_estimator(
    model_2d, X_2d, alpha=0.8, grid_resolution=1000)
plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y, 
           edgecolor='k', s=20, cmap=plt.cm.Paired)
plt.xlabel(cancer.feature_names[0])
plt.ylabel(cancer.feature_names[1])
plt.title('Frontière de décision de la régression logistique')
plt.colorbar(disp.contourf_)
plt.show()

Frontière de décision de la régression logistique (2D)

Évaluation des modèles de classification

Les métriques d’évaluation

L’évaluation correcte d’un modèle de classification est essentielle pour comprendre ses performances réelles. Voici les principales métriques à connaître :

Matrice de confusion

C’est la base de nombreuses métriques. Elle organise les prédictions en quatre catégories :

Prédiction PositivePrédiction Négative
Réellement PositifVrai Positif (VP)Faux Négatif (FN)
Réellement NégatifFaux Positif (FP)Vrai Négatif (VN)
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression # Un exemple de classifieur
from sklearn.metrics import confusion_matrix
from sklearn.datasets import make_classification # Pour générer des données d'exemple

# --- 1. Génération de données d'exemple ---
# Créons un problème de classification binaire simple
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# X: features (caractéristiques)
# y: target (étiquettes réelles, 0 ou 1 dans ce cas)

# --- 2. Division des données en ensembles d'entraînement et de test ---
# y_test contiendra les vraies étiquettes de notre ensemble de test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# test_size=0.3 signifie que 30% des données seront utilisées pour le test

# --- 3. Entraînement d'un modèle de classification ---
# Utilisons une Régression Logistique comme exemple
model = LogisticRegression(solver='liblinear') # 'liblinear' est un bon solveur pour les petits datasets
model.fit(X_train, y_train)

# --- 4. Faire des prédictions sur l'ensemble de test ---
# y_pred contiendra les étiquettes prédites par notre modèle pour l'ensemble de test
y_pred = model.predict(X_test)

# --- 5. Calculer et visualiser la matrice de confusion ---
cm = confusion_matrix(y_test, y_pred)

# Visualiser la matrice de confusion
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=['Prédit Négatif (0)', 'Prédit Positif (1)'], # Étiquettes pour l'axe X
            yticklabels=['Réel Négatif (0)', 'Réel Positif (1)'])   # Étiquettes pour l'axe Y
plt.xlabel('Valeurs Prédites par le Modèle')
plt.ylabel('Valeurs Réelles (Vérité Terrain)')
plt.title('Matrice de Confusion')
plt.show()
Matrice de Confusion

Précision, Rappel, F1-Score

  • Précision = VP / (VP + FP) : proportion de prédictions positives correctes
  • Rappel = VP / (VP + FN) : proportion de positifs réels correctement identifiés
  • F1-Score : moyenne harmonique de la précision et du rappel
from sklearn.metrics import precision_score, recall_score, f1_score

precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)

print(f"Précision : {precision:.3f}")
print(f"Rappel : {recall:.3f}")
print(f"F1-Score : {f1:.3f}")

Courbe ROC et AUC

La courbe ROC (Receiver Operating Characteristic) montre la performance à différents seuils de décision. L’AUC (Area Under Curve) mesure la capacité du modèle à distinguer les classes.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # Bien que non utilisé directement pour ROC, c'est bon de l'avoir si on fait aussi la matrice de confusion
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression # Un exemple de classifieur
from sklearn.datasets import make_classification # Pour générer des données d'exemple
from sklearn.metrics import roc_curve, roc_auc_score # Pour la courbe ROC et l'AUC

# --- 1. Génération de données d'exemple ---
# Créons un problème de classification binaire simple
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# X: features (caractéristiques)
# y: target (étiquettes réelles, 0 ou 1 dans ce cas)

# --- 2. Division des données en ensembles d'entraînement et de test ---
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# y_test contiendra les vraies étiquettes de notre ensemble de test

# --- 3. Entraînement d'un modèle de classification ---
# Utilisons une Régression Logistique comme exemple.
# Important : le modèle doit pouvoir prédire des probabilités (avoir une méthode predict_proba)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)

# --- 4. Calculer les probabilités pour la courbe ROC ---
# La courbe ROC nécessite les probabilités de la classe positive.
# model.predict_proba(X_test) retourne un array de shape (n_samples, n_classes)
# On s'intéresse à la probabilité de la classe positive (généralement la 2ème colonne, index 1)
y_prob = model.predict_proba(X_test)[:, 1]

# --- 5. Calculer les points de la courbe ROC et l'AUC ---
# fpr: Taux de Faux Positifs (False Positive Rate)
# tpr: Taux de Vrais Positifs (True Positive Rate) / Rappel (Recall)
# thresholds: Seuils de décision utilisés pour calculer fpr et tpr
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)

# --- 6. Tracer la courbe ROC ---
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='blue', lw=2, label=f'Courbe ROC (AUC = {auc:.3f})') # lw = linewidth
plt.plot([0, 1], [0, 1], color='grey', lw=2, linestyle='--', label='Classifieur Aléatoire (AUC = 0.5)')
plt.xlim([0.0, 1.0]) # Limites de l'axe X
plt.ylim([0.0, 1.05]) # Limites de l'axe Y (un peu de marge en haut)
plt.xlabel('Taux de Faux Positifs (1 - Spécificité)')
plt.ylabel('Taux de Vrais Positifs (Sensibilité / Rappel)')
plt.title('Receiver Operating Characteristic (ROC)')
plt.legend(loc="lower right") # Position de la légende
plt.grid(True) # Ajoute une grille pour une meilleure lisibilité
plt.show()

print(f"Aire sous la courbe ROC (AUC): {auc:.3f}")
Receiver Operating Characteristic (ROC)

Validation croisée

La validation croisée est une technique puissante pour obtenir une estimation plus fiable des performances d’un modèle. Elle permet d’utiliser efficacement toutes les données disponibles.

K-Fold Cross-Validation

La méthode la plus courante est la validation croisée k-fold, qui divise les données en k sous-ensembles et effectue k itérations d’entraînement/évaluation.

import numpy as np
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

# --- 1. Génération de données d'exemple ---
# Créons un problème de classification binaire simple
# Il est important d'utiliser l'ensemble des données (X, y) ici,
# car la validation croisée gère elle-même les divisions internes.
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# X: features (caractéristiques)
# y: target (étiquettes réelles, 0 ou 1 dans ce cas)

# --- 2. Configuration du modèle ---
# Vous pouvez utiliser n'importe quel modèle de classification de scikit-learn
model = LogisticRegression(solver='liblinear', random_state=42) # Ajout de random_state pour la reproductibilité du modèle

# --- 3. Configuration de la validation croisée (K-Fold) ---
# n_splits=5 signifie que les données seront divisées en 5 "plis" (folds)
# shuffle=True mélange les données avant de les diviser, ce qui est généralement une bonne pratique.
# random_state=42 assure que le mélange est le même à chaque exécution (reproductibilité).
cv = KFold(n_splits=5, shuffle=True, random_state=42)

# --- 4. Exécution de la validation croisée et calcul des scores ---
# 'scoring' peut être ajusté selon la métrique qui vous intéresse
# ('accuracy', 'roc_auc', 'f1', 'precision', 'recall', etc.)
# Ici, nous utilisons 'accuracy' (exactitude).
scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')

# --- 5. Affichage et interprétation des résultats ---
print(f"Scores d'exactitude pour chaque pli (fold) : {scores}")
print(f"Exactitude moyenne : {scores.mean():.3f}")
print(f"Écart-type de l'exactitude : {scores.std():.3f}")
print(f"Performance du modèle (exactitude) : {scores.mean():.3f} ± {scores.std():.3f}")

# (Optionnel) Visualisation simple des scores par pli
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.bar(range(1, len(scores) + 1), scores, tick_label=[f'Pli {i}' for i in range(1, len(scores) + 1)])
plt.ylim([min(scores) - 0.05, max(scores) + 0.05]) # Ajuster les limites pour une meilleure visualisation
plt.xlabel('Pli de Validation Croisée')
plt.ylabel('Exactitude (Accuracy)')
plt.title('Scores d\'Exactitude par Pli de Validation Croisée')
for index, value in enumerate(scores):
    plt.text(index + 1, value + 0.005, f"{value:.3f}", ha='center') # Affiche la valeur sur chaque barre
plt.show()
Scores d'Exactitude par Pli de Validation Croisée
Scores d’exactitude pour chaque pli (fold) : [0.855 0.855 0.875 0.865 0.865]
Exactitude moyenne : 0.863
Écart-type de l’exactitude : 0.007
Performance du modèle (exactitude) : 0.863 ± 0.007

Validation croisée stratifiée

Pour les problèmes de classification avec des classes déséquilibrées, il est préférable d’utiliser une validation croisée stratifiée qui maintient la proportion des classes dans chaque fold.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
import pandas as pd # Pour une manipulation plus facile des résultats pour le graphique

# --- 1. Génération de données d'exemple ---
# Créons un problème de classification binaire.
# Pour illustrer l'utilité de StratifiedKFold, on pourrait créer des classes déséquilibrées,
# mais pour la simplicité de cet exemple, nous utiliserons des classes équilibrées par défaut.
# make_classification(weights=[0.9, 0.1]) créerait des classes déséquilibrées.
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# X: features (caractéristiques)
# y: target (étiquettes réelles, 0 ou 1)

# --- 2. Configuration du modèle ---
model = LogisticRegression(solver='liblinear', random_state=42)

# --- 3. Configuration de la validation croisée stratifiée ---
# n_splits=5 signifie 5 plis.
# shuffle=True mélange les données avant de les diviser.
# random_state=42 assure la reproductibilité.
stratified_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# --- 4. Définition des métriques et calcul des scores ---
metrics = ['accuracy', 'precision', 'recall', 'f1']
results_summary = {} # Pour stocker les moyennes et écarts-types pour le graphique

print("Résultats de la validation croisée stratifiée (5 plis) :\n")
for metric in metrics:
    # cross_val_score retourne un array de scores, un pour chaque pli
    scores = cross_val_score(model, X, y, cv=stratified_cv, scoring=metric)
    mean_score = scores.mean()
    std_score = scores.std()
    results_summary[metric.capitalize()] = {'mean': mean_score, 'std': std_score}
    print(f"{metric.capitalize()} : {mean_score:.3f} ± {std_score:.3f}")

# --- 5. Visualisation des performances moyennes des métriques ---
# Préparation des données pour le graphique
metric_names = list(results_summary.keys())
mean_values = [results_summary[m]['mean'] for m in metric_names]
std_values = [results_summary[m]['std'] for m in metric_names]

plt.figure(figsize=(10, 6))
bars = plt.bar(metric_names, mean_values, yerr=std_values, capsize=5, color=['skyblue', 'lightcoral', 'lightgreen', 'gold'])
plt.ylabel('Score Moyen')
plt.title('Performance Moyenne du Modèle par Métrique (avec Validation Croisée Stratifiée)')
plt.ylim([0, 1.05]) # Les scores sont généralement entre 0 et 1

# Ajouter les valeurs exactes sur les barres pour une lecture facile
for bar in bars:
    yval = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2.0, yval + 0.02, f'{yval:.3f}', ha='center', va='bottom')

plt.figtext(0.5, -0.05,
            "Les barres d'erreur représentent ±1 écart-type de la moyenne des scores sur les 5 plis.\n"
            "Une barre d'erreur plus petite indique une performance plus stable pour cette métrique à travers les plis.",
            ha="center", fontsize=9, style='italic')

plt.tight_layout(rect=[0, 0.05, 1, 1]) # Ajuste pour éviter que le figtext ne soit coupé
plt.show()
Performance Moyenne du Modèle par Métrique (avec Validation Croisée Stratifiée)
Résultats de la validation croisée stratifiée (5 plis) :
Accuracy : 0.869 ± 0.009
Precision : 0.878 ± 0.020
Recall : 0.858 ± 0.016
F1 : 0.868 ± 0.008

Optimisation des hyperparamètres

Les hyperparamètres sont des paramètres qui ne sont pas appris pendant l’entraînement mais qui doivent être définis à l’avance. Leur optimisation est cruciale pour obtenir les meilleures performances.

Grid Search

La recherche par grille teste toutes les combinaisons d’hyperparamètres spécifiées.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import GridSearchCV, StratifiedKFold # Utiliser StratifiedKFold pour CV
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

# --- 1. Génération de données d'exemple ---
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)

# --- 2. Définition de la grille de paramètres ---
# 'C': Inverse du coefficient de régularisation. Des valeurs plus petites spécifient une régularisation plus forte.
# 'penalty': Type de norme utilisée dans la pénalisation (L1 ou L2).
# 'solver': Algorithme à utiliser dans le problème d'optimisation.
#   'liblinear' est bon pour les petits datasets et supporte L1 et L2.
#   'saga' est bon pour les grands datasets, supporte L1, L2 et elasticnet, et est plus rapide.
param_grid = {
    'C': [0.001, 0.01, 0.1, 1, 10, 100],
    'penalty': ['l1', 'l2'],
    'solver': ['liblinear', 'saga'] # Assurez-vous que les solveurs supportent les pénalités listées
}

# --- 3. Configuration et exécution de la recherche par grille ---
# Utilisation de StratifiedKFold pour la validation croisée (cv) pour une meilleure robustesse,
# surtout si les classes sont déséquilibrées (même si make_classification par défaut les équilibre).
cv_strategy = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

grid_search = GridSearchCV(
    estimator=LogisticRegression(max_iter=10000, random_state=42), # max_iter augmenté pour assurer la convergence des solveurs comme 'saga'
    param_grid=param_grid,
    cv=cv_strategy,       # Stratégie de validation croisée
    scoring='f1',         # Métrique pour évaluer les combinaisons. F1 est un bon choix pour les problèmes de classification.
    n_jobs=-1,            # Utiliser tous les cœurs CPU disponibles pour accélérer la recherche
    verbose=1             # Affiche des messages sur la progression
)

print("Début de la recherche par grille (GridSearchCV)...")
grid_search.fit(X, y)
print("Recherche par grille terminée.")

# --- 4. Affichage des meilleurs paramètres et score ---
print(f"\nMeilleurs paramètres trouvés : {grid_search.best_params_}")
print(f"Meilleur score F1 (moyenne sur CV) : {grid_search.best_score_:.3f}")

# --- 5. Affichage des résultats détaillés pour les meilleures combinaisons ---
results_df = pd.DataFrame(grid_search.cv_results_)
# Trier par le score de test (la métrique de 'scoring') et afficher les colonnes pertinentes
# 'rank_test_score' classe les combinaisons, 1 étant la meilleure.
top_results = results_df.sort_values(by='rank_test_score').head(10) # Afficher les 10 meilleures
print("\nTop 10 des combinaisons de paramètres (basé sur le score F1 moyen) :")
print(top_results[['params', 'mean_test_score', 'std_test_score', 'rank_test_score']])

# --- 6. Visualisation des résultats de GridSearchCV ---
# Visualisation de l'impact du paramètre 'C' sur le score F1, en distinguant par 'penalty' et 'solver'
# On va simplifier en ne visualisant que pour le meilleur 'solver' trouvé, ou en les séparant.

# Préparer les données pour la visualisation
plot_data = []
for i in range(len(results_df)):
    params = results_df.loc[i, 'params']
    plot_data.append({
        'C': params['C'],
        'penalty': params['penalty'],
        'solver': params['solver'],
        'mean_f1_score': results_df.loc[i, 'mean_test_score'],
        'std_f1_score': results_df.loc[i, 'std_test_score']
    })
plot_df = pd.DataFrame(plot_data)

# Créer un graphique pour chaque solveur
unique_solvers = plot_df['solver'].unique()
num_solvers = len(unique_solvers)

fig, axes = plt.subplots(num_solvers, 1, figsize=(12, 6 * num_solvers), sharex=True)
if num_solvers == 1: # Si un seul solveur, axes n'est pas un array
    axes = [axes]

for i, solver_name in enumerate(unique_solvers):
    ax = axes[i]
    solver_subset = plot_df[plot_df['solver'] == solver_name]
    sns.lineplot(data=solver_subset, x='C', y='mean_f1_score', hue='penalty', marker='o', ax=ax, errorbar='sd')
    # 'errorbar="sd"' affiche l'écart-type (std_test_score) comme bande d'erreur
    ax.set_xscale('log') # C est souvent exploré sur une échelle logarithmique
    ax.set_title(f'Performance (Score F1) vs. C pour le solveur "{solver_name}"')
    ax.set_ylabel('Score F1 Moyen')
    ax.grid(True, which="both", ls="--")
    ax.legend(title='Penalty')

axes[-1].set_xlabel('Paramètre C (Échelle Logarithmique)')
plt.tight_layout()
plt.figtext(0.5, -0.02, # Ajustez la position verticale si nécessaire
            "Les bandes d'erreur représentent ±1 écart-type du score F1 moyen sur les plis de validation croisée.\n"
            "Un score F1 plus élevé est meilleur. Les points indiquent les combinaisons testées.",
            ha="center", fontsize=9, style='italic')
plt.subplots_adjust(bottom=0.15) # Ajuster l'espacement pour le figtext
plt.show()
Grid Search

Random Search

Pour les espaces de paramètres plus grands, la recherche aléatoire est souvent plus efficace.

RandomizedSearchCV explore un nombre fixe de combinaisons d’hyperparamètres (n_iter) en les échantillonnant à partir de distributions que vous spécifiez. C’est souvent plus efficace que GridSearchCV lorsque l’espace des hyperparamètres est grand ou lorsque certains hyperparamètres sont continus.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from scipy.stats import loguniform # Pour échantillonner 'C' sur une échelle logarithmique

# --- 1. Génération de données d'exemple ---
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)

# --- 2. Définition des distributions des paramètres ---
# 'C': Échantillonné à partir d'une distribution log-uniforme.
#      loguniform(1e-3, 1e3) signifie que C sera échantillonné entre 0.001 et 1000,
#      avec une probabilité égale pour chaque ordre de grandeur.
# 'penalty': Choisi aléatoirement entre 'l1' et 'l2'.
# 'solver': Choisi aléatoirement entre 'liblinear' et 'saga'.
param_distributions = {
    'C': loguniform(1e-3, 1e3),
    'penalty': ['l1', 'l2'],
    'solver': ['liblinear', 'saga'] # Assurez-vous que les solveurs supportent les pénalités
}

# --- 3. Configuration et exécution de la recherche aléatoire ---
cv_strategy = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

random_search = RandomizedSearchCV(
    estimator=LogisticRegression(max_iter=10000, random_state=42), # max_iter augmenté
    param_distributions=param_distributions,
    n_iter=20,            # Nombre de combinaisons de paramètres à échantillonner
    cv=cv_strategy,       # Stratégie de validation croisée
    scoring='f1',         # Métrique d'évaluation
    n_jobs=-1,            # Utiliser tous les cœurs CPU
    verbose=1,            # Afficher des messages sur la progression
    random_state=42       # Pour la reproductibilité de l'échantillonnage
)

print("Début de la recherche aléatoire (RandomizedSearchCV)...")
random_search.fit(X, y)
print("Recherche aléatoire terminée.")

# --- 4. Affichage des meilleurs paramètres et score ---
print(f"\nMeilleurs paramètres trouvés : {random_search.best_params_}")
print(f"Meilleur score F1 (moyenne sur CV) : {random_search.best_score_:.3f}")

# --- 5. Affichage des résultats détaillés pour quelques combinaisons testées ---
results_df = pd.DataFrame(random_search.cv_results_)
# Trier par le score de test et afficher les colonnes pertinentes
# 'rank_test_score' classe les combinaisons, 1 étant la meilleure.
top_results_random = results_df.sort_values(by='rank_test_score').head(10) # Afficher les 10 meilleures
print("\nTop 10 des combinaisons de paramètres échantillonnées (basé sur le score F1 moyen) :")
print(top_results_random[['params', 'mean_test_score', 'std_test_score', 'rank_test_score']])

# --- 6. Visualisation des résultats de RandomizedSearchCV ---
# On va créer un scatter plot pour voir comment les scores F1 varient
# en fonction des valeurs de C échantillonnées, en distinguant par penalty et solver.

# Extraire les valeurs de C, penalty, solver et les scores pour le graphique
# Note: 'param_C' est le nom que scikit-learn donne à la colonne pour le paramètre 'C' dans cv_results_
# Pareil pour 'param_penalty' et 'param_solver'.
plot_df = pd.DataFrame({
    'C_sampled': results_df['param_C'].astype(float), # Assurer que C est numérique pour l'échelle log
    'penalty': results_df['param_penalty'],
    'solver': results_df['param_solver'],
    'mean_f1_score': results_df['mean_test_score'],
    'std_f1_score': results_df['std_test_score']
})

plt.figure(figsize=(14, 8))
# Utiliser scatterplot de seaborn pour différencier par 'hue' (penalty) et 'style' (solver)
scatter_plot = sns.scatterplot(
    data=plot_df,
    x='C_sampled',
    y='mean_f1_score',
    hue='penalty',
    style='solver',
    size='mean_f1_score', # Optionnel: varier la taille du point par score
    sizes=(50, 250),      # Optionnel: plage de tailles
    palette='viridis',
    legend='full'
)

# Mettre en évidence le meilleur point
best_idx = random_search.best_index_
plt.scatter(
    plot_df.loc[best_idx, 'C_sampled'],
    plot_df.loc[best_idx, 'mean_f1_score'],
    s=300,                  # Taille plus grande pour le meilleur point
    facecolors='none',      # Cercle vide
    edgecolors='red',       # Couleur de la bordure
    linewidth=2,
    label=f'Meilleur Point (F1={random_search.best_score_:.3f})'
)

plt.xscale('log')
plt.xlabel('Paramètre C Échantillonné (Échelle Logarithmique)')
plt.ylabel('Score F1 Moyen (sur CV)')
plt.title(f'Résultats de RandomizedSearchCV ({random_search.n_iter} itérations)')
plt.grid(True, which="both", ls="--")
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') # Placer la légende à l'extérieur
plt.tight_layout(rect=[0, 0, 0.85, 1]) # Ajuster pour la légende externe

plt.figtext(0.5, -0.03,
            "Chaque point représente une combinaison d'hyperparamètres échantillonnée et son score F1 moyen.\n"
            "La couleur indique la pénalité, le style du marqueur indique le solveur. Le meilleur point est cerclé en rouge.",
            ha="center", fontsize=9, style='italic')
plt.subplots_adjust(bottom=0.15)
plt.show()
Random Search

Pipeline scikit-learn

Scikit-Learn offre une fonctionnalité extrêmement puissante : les pipelines. Ils permettent d’enchaîner plusieurs étapes de prétraitement et de modélisation de manière cohérente.

Construction d’un pipeline complet

Voici un exemple de pipeline qui intègre toutes les étapes nécessaires :

import numpy as np # Pour np.nan
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn import set_config # Pour visualiser le pipeline

# Essayer d'importer 'display' pour les environnements IPython, sinon utiliser print.
try:
    from IPython.display import display
except ImportError:
    # Définir une fonction display factice qui utilise print si IPython n'est pas disponible
    # Cela évite une NameError si le code est exécuté hors d'un environnement IPython.
    print("IPython.display n'est pas disponible. Utilisation de print() pour l'affichage du pipeline.")
    def display(obj):
        print(obj)

# --- 1. Créer un jeu de données mixte avec valeurs manquantes ---
data = pd.DataFrame({
    'age': [25, 30, np.nan, 40, 35],
    'income': [50000, np.nan, 70000, 60000, 65000],
    'gender': ['M', 'F', 'M', 'F', np.nan],
    'education': ['Bachelor', 'Master', np.nan, 'PhD', 'Bachelor']
})
X = data.copy() # Nos features

# --- 2. Définir les colonnes numériques et catégorielles ---
numeric_features = ['age', 'income']
categorical_features = ['gender', 'education']

# --- 3. Prétraitement pour les colonnes numériques ---
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='mean')),
    ('scaler', StandardScaler())
])

# --- 4. Prétraitement pour les colonnes catégorielles ---
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# --- 5. Préprocesseur combinant les transformateurs ---
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

# --- 6. Pipeline final ---
pipe = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression(solver='liblinear', random_state=42))
])

# --- 7. Créer des variables cible et entraîner ---
y = np.array([0, 1, 0, 1, 0])
print("Entraînement du pipeline...")
pipe.fit(X, y)
print("Pipeline entraîné.")

# --- 8. Prédire sur de nouvelles données ---
new_data = pd.DataFrame({
    'age': [22, 32, np.nan],
    'income': [45000, 62000, 55000],
    'gender': ['F', np.nan, 'M'],
    'education': ['Bachelor', 'PhD', 'Master']
})
print("\nPrédictions sur de nouvelles données:")
prediction = pipe.predict(new_data)
probability = pipe.predict_proba(new_data)
for i in range(len(new_data)):
    print(f"\nDonnée {i+1}:")
    print(f"  Prédiction de classe : {prediction[i]}")
    print(f"  Probabilités par classe : {probability[i]} (Classe 0: {probability[i][0]:.3f}, Classe 1: {probability[i][1]:.3f})")

# --- 9. Visualisation de la Structure du Pipeline ---
print("\n--- Visualisation de la Structure du Pipeline ---")
set_config(display='diagram') # Active l'affichage en diagramme pour scikit-learn

# Affichage du pipeline.
# Dans Jupyter/IPython, cela rendra un diagramme HTML.
# Dans un script Python standard, cela imprimera une représentation textuelle structurée.
display(pipe) # Maintenant, 'display' est défini soit par IPython, soit par notre fonction factice.

print("\nExplication de l'affichage du pipeline:")
print("Si vous êtes dans un notebook Jupyter ou un environnement similaire, vous devriez voir un diagramme HTML interactif.")
print("Si vous exécutez ce script dans une console Python standard, vous verrez une représentation textuelle de la structure du pipeline.")
print("Les deux formats vous aident à comprendre comment les étapes sont organisées.")
Prédictions sur de nouvelles données:

Donnée 1:
  Prédiction de classe : 0
  Probabilités par classe : [0.68850722 0.31149278] (Classe 0: 0.689, Classe 1: 0.311)

Donnée 2:
  Prédiction de classe : 1
  Probabilités par classe : [0.39708338 0.60291662] (Classe 0: 0.397, Classe 1: 0.603)

Donnée 3:
  Prédiction de classe : 1
  Probabilités par classe : [0.47847465 0.52152535] (Classe 0: 0.478, Classe 1: 0.522)

--- Visualisation de la Structure du Pipeline ---
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer()),
                                                                  ('scaler',
                                                                   StandardScaler())]),
                                                  ['age', 'income']),
                                                 ('cat',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(strategy='most_frequent')),
                                                                  ('onehot',
                                                                   OneHotEncoder(handle_unknown='ignore',
                                                                                 sparse_output=False))]),
                                                  ['gender', 'education'])])),
                ('classifier',
                 LogisticRegression(random_state=42, solver='liblinear'))])

Avantages des pipelines

Les pipelines offrent de nombreux avantages :

  1. Encapsulation du workflow : toutes les étapes sont regroupées dans un seul objet
  2. Prévention des fuites de données : les transformations sont appliquées correctement sans contamination entre ensembles d’entraînement et de test
  3. Simplification de l’optimisation : la validation croisée et la recherche d’hyperparamètres peuvent être appliquées à l’ensemble du pipeline
  4. Facilité de déploiement : le pipeline entier peut être sauvegardé et réutilisé

Optimisation d’un pipeline

Vous pouvez optimiser tous les composants d’un pipeline simultanément :

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, StratifiedKFold # StratifiedKFold pour une CV robuste
from sklearn import set_config # Pour visualiser le pipeline

# Essayer d'importer 'display' pour les environnements IPython, sinon utiliser print.
try:
    from IPython.display import display
except ImportError:
    print("IPython.display n'est pas disponible. Utilisation de print() pour l'affichage des pipelines.")
    def display(obj):
        print(obj)

# --- 1. Créer un jeu de données mixte avec valeurs manquantes ---
data = pd.DataFrame({
    'age': [25, 30, np.nan, 40, 35, 28, np.nan, 42, 33, 38],
    'income': [50000, np.nan, 70000, 60000, 65000, 52000, 75000, np.nan, 68000, 61000],
    'gender': ['M', 'F', 'M', 'F', np.nan, 'M', 'F', 'M', 'F', 'M'],
    'education': ['Bachelor', 'Master', np.nan, 'PhD', 'Bachelor', 'Master', 'PhD', 'Bachelor', np.nan, 'PhD']
})
X_features = data.copy() # Nos features

# Créer des variables cible factices pour l'exemple (plus d'échantillons pour CV)
y_target = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1])

# --- 2. Définir les colonnes numériques et catégorielles ---
numeric_features = ['age', 'income']
categorical_features = ['gender', 'education']

# --- 3. Définir les transformateurs de prétraitement ---
# Prétraitement pour les colonnes numériques
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='mean')), # Stratégie sera optimisée
    ('scaler', StandardScaler())
])

# Prétraitement pour les colonnes catégorielles
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')), # Stratégie sera optimisée
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# Préprocesseur combinant les deux transformateurs
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

# --- 4. Pipeline final avec prétraitement et modèle ---
# Le solveur 'liblinear' supporte les pénalités 'l1' et 'l2'
# Les paramètres C et penalty seront optimisés.
pipe = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression(solver='liblinear', max_iter=1000, random_state=42))
])

# --- 5. Définir la grille de paramètres à optimiser pour GridSearchCV ---
# La notation 'nom_etape__nom_sous_etape__parametre' est utilisée pour accéder aux paramètres imbriqués.
param_grid = {
    'preprocessor__num__imputer__strategy': ['mean', 'median'],
    'preprocessor__cat__imputer__strategy': ['most_frequent', 'constant'], # 'constant' nécessite fill_value, mais SimpleImputer le gère
    'preprocessor__cat__imputer__fill_value': ['Unknown'], # Ajouté pour 'constant' strategy
    'classifier__C': [0.1, 1, 10],
    'classifier__penalty': ['l1', 'l2']
}

# --- 6. Configuration et exécution de la recherche par grille ---
# Utilisation de StratifiedKFold pour la validation croisée
cv_strategy = StratifiedKFold(n_splits=3, shuffle=True, random_state=42) # n_splits réduit à 3 pour l'exemple rapide

grid_search_pipeline = GridSearchCV(
    pipe,
    param_grid,
    cv=cv_strategy,
    scoring='f1', # Métrique d'évaluation (ex: 'accuracy', 'f1', 'roc_auc')
    n_jobs=-1,    # Utiliser tous les cœurs CPU disponibles
    verbose=1     # Afficher des messages sur la progression
)

print("Début de l'optimisation du pipeline avec GridSearchCV...")
grid_search_pipeline.fit(X_features, y_target)
print("Optimisation terminée.")

# --- 7. Affichage des meilleurs paramètres et du meilleur score ---
print(f"\nMeilleurs paramètres trouvés pour le pipeline : {grid_search_pipeline.best_params_}")
print(f"Meilleur score F1 (moyenne sur CV) : {grid_search_pipeline.best_score_:.3f}")

# --- 8. Affichage de la structure du meilleur pipeline trouvé ---
print("\n--- Structure du Meilleur Pipeline Estimé ---")
set_config(display='diagram')
best_pipeline_estimator = grid_search_pipeline.best_estimator_
display(best_pipeline_estimator)

# --- 9. Visualisation des résultats de GridSearchCV pour le pipeline ---
results_df = pd.DataFrame(grid_search_pipeline.cv_results_)

# Préparer les données pour la visualisation : afficher les N meilleures combinaisons
N_top_combinations = 15 # Nombre de meilleures combinaisons à afficher
top_n_results = results_df.sort_values(by='rank_test_score').head(N_top_combinations)

# Créer des étiquettes lisibles pour les paramètres
def params_to_string(params_dict):
    # Raccourcir les clés pour la lisibilité
    short_params = {
        'num_im_strat': params_dict.get('preprocessor__num__imputer__strategy', 'N/A'),
        'cat_im_strat': params_dict.get('preprocessor__cat__imputer__strategy', 'N/A'),
        # 'cat_fill': params_dict.get('preprocessor__cat__imputer__fill_value', 'N/A'), # Peut rendre l'étiquette trop longue
        'clf_C': params_dict.get('classifier__C', 'N/A'),
        'clf_pen': params_dict.get('classifier__penalty', 'N/A')
    }
    return '\n'.join([f"{k.split('_')[-1] if '_' in k else k}:{v}" for k, v in short_params.items()])


top_n_results['param_str'] = top_n_results['params'].apply(params_to_string)

plt.figure(figsize=(12, 8)) # Ajuster la taille pour la lisibilité
bar_plot = sns.barplot(
    x='mean_test_score',
    y='param_str',
    data=top_n_results,
    orient='h', # Barres horizontales pour mieux lire les étiquettes des paramètres
    palette='viridis',
    hue='param_str', # Pour que chaque barre ait une couleur distincte si palette le permet
    dodge=False, # Nécessaire si hue est utilisé avec y
    legend=False # La légende n'est pas utile ici car les étiquettes sont sur l'axe y
)

# Ajouter les barres d'erreur (std_test_score)
# Pour barplot horizontal, xerr est utilisé. Les positions y sont les indices.
y_coords = np.arange(len(top_n_results))
plt.errorbar(
    x=top_n_results['mean_test_score'],
    y=y_coords,
    xerr=top_n_results['std_test_score'],
    fmt='none', # Pas de marqueur de point
    capsize=5,
    color='black'
)

plt.xlabel('Score F1 Moyen (sur CV)')
plt.ylabel('Combinaisons d\'Hyperparamètres')
plt.title(f'Top {N_top_combinations} des Performances des Combinaisons d\'Hyperparamètres du Pipeline')
plt.xlim([min(0, top_n_results['mean_test_score'].min() - 0.1), max(1, top_n_results['mean_test_score'].max() + 0.1)]) # Ajuster les limites x
plt.tight_layout()
plt.figtext(0.5, -0.03,
            "Chaque barre représente une combinaison d'hyperparamètres et son score F1 moyen.\n"
            "Les barres d'erreur noires indiquent ±1 écart-type du score sur les plis de validation croisée.",
            ha="center", fontsize=9, style='italic')
plt.subplots_adjust(bottom=0.1) # Ajuster pour le figtext
plt.show()
Performances des Combinaisons d'Hyperparamètres du Pipeline

Vers d’autres modèles de classification

Scikit-Learn propose de nombreux autres algorithmes de classification, chacun avec ses forces et faiblesses. Voici un aperçu des plus importants :

AlgorithmeForcesFaiblessesUtilisations typiques
K plus proches voisins (KNN)Simple, intuitif, non-paramétriqueLent sur grands jeux de données, sensible à la dimensionnalitéRecommandation, classification d’images simples
Machines à vecteurs de support (SVM)Efficace en haute dimension, versatile (différents noyaux)Lent à entraîner sur grands jeux, difficile à interpréterReconnaissance d’images, classification de textes
Arbres de décisionTrès interprétable, gère données mixtes, non-linéaireTendance au surapprentissage, instableSystèmes de décision, scoring crédit
Forêts aléatoiresRobuste, haute performance, peu de paramètresMoins interprétable, plus lourdApplications générales, biologie, finance
Gradient BoostingSouvent le plus performantSensible aux hyperparamètres, peut surapprendreCompétitions ML, prédictions précises
Naïve BayesRapide, efficace avec peu de données, probabilisteHypothèse d’indépendance souvent irréalisteClassification de textes, filtrage spam

Pour vous aider à choisir, voici une comparaison rapide sur un jeu de données :

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import time

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

# Import des classifieurs
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB

from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler # Pour améliorer les performances de certains modèles

# --- 1. Génération de données d'exemple ---
# X: features, y: target
# Augmentation du nombre d'échantillons pour mieux voir les différences de temps
X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=42)

# --- 2. Division des données en ensembles d'entraînement et de test ---
# Et mise à l'échelle des features, ce qui est souvent bénéfique
X_train_orig, X_test_orig, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

# Mise à l'échelle des données
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train_orig)
X_test = scaler.transform(X_test_orig)


# --- 3. Liste des classifieurs à comparer ---
# Ajout de random_state pour la reproductibilité là où c'est pertinent
classifiers = [
    ("Régression Logistique", LogisticRegression(solver='liblinear', random_state=42)),
    ("K plus proches voisins", KNeighborsClassifier()), # n_neighbors=5 par défaut
    ("SVM", SVC(probability=True, random_state=42)), # probability=True peut être lent, nécessaire pour predict_proba
    ("Arbre de décision", DecisionTreeClassifier(random_state=42)),
    ("Forêt aléatoire", RandomForestClassifier(random_state=42)),
    ("Gradient Boosting", GradientBoostingClassifier(random_state=42)),
    ("Naïve Bayes (Gaussian)", GaussianNB())
]

# --- 4. Évaluation des modèles ---
results_list = [] # Renommé pour éviter conflit avec variable 'results' plus loin

print("Début de l'évaluation des modèles...\n")
for name, clf in classifiers:
    print(f"Évaluation du modèle : {name}")
    
    # Temps d'entraînement
    start_time_train = time.time()
    clf.fit(X_train, y_train)
    train_time = time.time() - start_time_train
    
    # Temps de prédiction
    start_time_predict = time.time()
    y_pred = clf.predict(X_test)
    predict_time = time.time() - start_time_predict
    
    # Calcul de la précision
    accuracy = accuracy_score(y_test, y_pred)
    
    results_list.append({
        "Modèle": name,
        "Précision": accuracy,
        "Temps d'entraînement (s)": train_time,
        "Temps de prédiction (s)": predict_time
    })
    print(f"  Précision : {accuracy:.4f}")
    print(f"  Temps d'entraînement : {train_time:.4f} s")
    print(f"  Temps de prédiction : {predict_time:.4f} s\n")

# --- 5. Afficher les résultats sous forme de DataFrame ---
results_df = pd.DataFrame(results_list).sort_values("Précision", ascending=False)
print("\n--- Tableau Récapitulatif des Performances ---")
print(results_df)

# --- 6. Visualisation des résultats ---

# Graphique 1: Précision des modèles
plt.figure(figsize=(12, 8))
sns.barplot(x="Précision", y="Modèle", data=results_df, palette="viridis", hue="Modèle", dodge=False, legend=False)
plt.title("Comparaison de la Précision des Modèles de Classification", fontsize=16)
plt.xlabel("Précision (Accuracy)", fontsize=14)
plt.ylabel("Modèle", fontsize=14)
plt.xlim(results_df["Précision"].min() * 0.9, results_df["Précision"].max() * 1.05) # Ajuster les limites pour la lisibilité
for index, row in results_df.iterrows():
    plt.text(row["Précision"] + 0.005, index, f"{row['Précision']:.3f}", color='black', ha="left", va="center")
plt.tight_layout()
plt.show()

# Graphique 2: Temps d'entraînement et de prédiction
# Préparation des données pour le graphique des temps
times_df = results_df.melt(id_vars="Modèle", 
                           value_vars=["Temps d'entraînement (s)", "Temps de prédiction (s)"],
                           var_name="Type de Temps", 
                           value_name="Temps (s)")

plt.figure(figsize=(14, 9))
sns.barplot(x="Modèle", y="Temps (s)", hue="Type de Temps", data=times_df, palette="muted")
plt.title("Comparaison des Temps d'Entraînement et de Prédiction", fontsize=16)
plt.xlabel("Modèle", fontsize=14)
plt.ylabel("Temps (secondes)", fontsize=14)
plt.xticks(rotation=45, ha="right")
plt.legend(title="Type de Temps")
# Optionnel: échelle logarithmique si les temps varient beaucoup
# plt.yscale('log') 
# plt.ylabel("Temps (secondes) - Échelle Logarithmique", fontsize=14)
plt.tight_layout()
plt.show()

print("\n--- Fin de la comparaison ---")

Comparaison des Temps d'Entraînement et de Prédiction
Comparaison des Temps d'Entraînement et de Prédiction 2

Conclusion et prochaines étapes pour devenir un expert

Vous avez maintenant une solide introduction aux modèles de classification avec Scikit-Learn. Pour approfondir vos connaissances, voici quelques pistes :

  1. Expérimentez avec différents jeux de données et algorithmes
  2. Explorez l’ingénierie des caractéristiques (feature engineering)
  3. Approfondissez votre compréhension des algorithmes spécifiques
  4. Participez à des compétitions Kaggle pour appliquer vos connaissances
  5. Étudiez les méthodes de traitement des données déséquilibrées

N’oubliez pas que le choix d’un modèle dépend toujours du contexte spécifique de votre problème : il n’existe pas de solution universelle en machine learning.

Scikit-Learn est une bibliothèque extrêmement riche – ce cours n’a fait qu’effleurer la surface de ses capacités. Continuez à explorer sa documentation détaillée et ses nombreux exemples pour devenir un expert en classification.

Mathieu Klopp

Mathieu Klopp est Data Scientist et ingénieur en Machine Learning, spécialisé dans le traitement du langage naturel (NLP) et les grands modèles de langage. Fort d'une expérience pratique en Python, clustering et analyse sémantique, il développe des solutions d'IA innovantes en combinant expertise technique et vision business.

Post navigation

Leave a Comment

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Si vous aimez cet article, vous aimerez peut-être aussi les suivants

TensorFlow

TensorFlow s’est imposé comme l’une des bibliothèques d’apprentissage automatique les plus populaires au monde. Développé par…

OpenAI

L’info en 30 secondesFondée en 2015 comme organisation à but non lucratif par des figures comme…