Cours Python · Module 9: Machine learning
Scikit-learn - le pisteur d'espèces
Dans cette leçon8
Imagine que tu rentres d'expédition avec un carnet plein de mesures : longueur des crocs, envergure des ailes, poids. Tu aimerais que la machine reconnaisse toute seule de quel animal il s'agit. C'est exactement le travail du machine learning classique - et son outil le plus populaire en Python s'appelle scikit-learn.
1pip install scikit-learnUne seule commande installe toute la trousse du pisteur : les modèles, quelques jeux de données d'exemple et les outils de préparation. Dans cette leçon tu vas croiser plusieurs pisteurs très différents, et tu découvriras qu'ils parlent tous exactement la même langue.
Un seul rituel, tous les modèles
Avant de découvrir les modèles un par un, retiens la chose la plus importante de la leçon. Chaque modèle de scikit-learn - arbre, forêt ou voisinage - répond aux trois mêmes verbes :
fit(X, y)- apprends : montre au modèle les mesuresXet les bonnes réponsesy.predict(X)- reconnais : pour de nouvelles mesures, renvoie l'espèce attendue.score(X, y)- évalue : dis quel pourcentage de réponses tombe juste.
C'est la langue commune des pisteurs : apprends-la une fois et chaque nouveau modèle n'est plus qu'un pisteur de plus qui la parle. Quel que soit l'algorithme, tu écriras toujours model.fit(X_train, y_train) pour l'entraîner, puis predictions = model.predict(X_test) pour l'interroger. Les modèles ci-dessous diffèrent par leur façon de penser, jamais par leur mode d'emploi.
L'arbre de décision - le pisteur qui pose des questions
Le pisteur le plus simple est celui qui enchaîne les questions : "Pèse-t-il plus de 100 kg ? A-t-il une crinière ?". C'est le principe de l'arbre de décision : il découpe les animaux question après question, jusqu'à ce qu'il ne reste qu'une seule espèce possible au bout de la branche.
1from sklearn.tree import DecisionTreeClassifier
2from sklearn.datasets import load_iris
3
4iris = load_iris()
5X, y = iris.data, iris.target
6
7tree = DecisionTreeClassifier(max_depth=3, random_state=42)
8tree.fit(X_train, y_train)
9
10accuracy = tree.score(X_test, y_test)Nos trois verbes sont déjà là : fit apprend, score évalue. Le paramètre max_depth=3 limite la longueur de la chaîne de questions - plus l'arbre est profond, mieux il colle aux données d'entraînement, mais plus il risque de sur-apprendre et de se tromper sur des animaux qu'il n'a jamais vus. C'est le premier compromis du ML que tu sentiras vraiment en pratique.
Le grand avantage de l'arbre, c'est que tu peux le voir - dessiner littéralement ses questions :
1from sklearn.tree import plot_tree
2import matplotlib.pyplot as plt
3
4plt.figure(figsize=(20, 10))
5plot_tree(tree, feature_names=iris.feature_names, class_names=iris.target_names, filled=True)
6plt.show()Ce dessin est ton meilleur allié quand il faut expliquer une décision du modèle à quelqu'un d'autre : chaque branche correspond à une question que l'arbre a posée, et le chemin complet raconte pourquoi telle espèce a été retenue plutôt qu'une autre.
La Random Forest - le conseil des pisteurs
Un arbre seul est capricieux : décale deux mesures et il change d'avis. Et si on interrogeait cent pisteurs pour ne garder que l'avis majoritaire ? C'est exactement la Random Forest : un ensemble de nombreux arbres de décision, entraînés chacun sur un tirage différent, qui votent ensuite ensemble. Ce n'est donc ni un arbre unique, ni un réseau de neurones, ni un algorithme de clustering - le clustering travaille sans étiquettes, alors qu'ici chaque arbre apprend sur des réponses connues.
1from sklearn.ensemble import RandomForestClassifier
2
3forest = RandomForestClassifier(
4 n_estimators=100, # nombre de pisteurs dans le conseil
5 max_depth=10,
6 min_samples_split=5,
7 random_state=42
8)
9forest.fit(X_train, y_train)
10
11importance = forest.feature_importances_
12for name, imp in zip(feature_names, importance):
13 print(f"{name}: {imp:.4f}")n_estimators=100 fixe le nombre de pisteurs autour de la table : plus d'arbres donnent en général une réponse plus stable, au prix du temps de calcul. max_depth=10 et min_samples_split=5 empêchent chaque arbre de devenir trop bavard. La forêt offre en prime un cadeau que l'arbre seul ne donne pas aussi facilement : feature_importances_ indique quelles mesures ont le plus aidé à reconnaître l'espèce. Retiens cette propriété - en pratique, c'est elle qui te dira ce qu'il vaut la peine de mesurer sur le terrain.
SVM - le pisteur qui trace la frontière
Autre façon de penser : au lieu d'enchaîner les questions, trace sur la carte des mesures une frontière qui sépare les espèces. La Support Vector Machine (SVM) sert avant tout à la classification en trouvant l'hyperplan optimal - la séparation qui laisse la marge la plus large possible entre les groupes. Elle possède bien une variante pour la régression, mais la réduire à ça serait faux ; et elle ne génère pas de texte et ne compresse pas d'images - ce sont des familles d'algorithmes complètement différentes.
1from sklearn.svm import SVC
2
3svm = SVC(
4 kernel='rbf', # forme de la limite: linear, poly, rbf
5 C=1.0,
6 gamma='scale',
7 probability=True # active predict_proba
8)
9svm.fit(X_train, y_train)
10
11proba = svm.predict_proba(X_test)Le paramètre kernel décide si l'hyperplan reste droit (linear) ou s'il peut se courber (rbf, poly, plus rarement sigmoid). C règle la sévérité de la régularisation : une grande valeur tolère peu d'erreurs sur l'entraînement, une petite laisse la frontière plus souple. Enfin, probability=True débloque quelque chose de précieux : predict_proba renvoie non seulement l'espèce, mais la confiance du modèle - par exemple "80 % lion, 20 % guépard". Très utile quand tu préfères écarter une reconnaissance douteuse plutôt que de deviner au hasard.
KNN - le pisteur qui regarde ses voisins
Le pisteur le plus intuitif n'apprend presque rien. Le KNN (K plus proches voisins) classe selon les k voisins les plus proches : pour un nouvel animal, il cherche les spécimens connus qui lui ressemblent le plus et adopte leur espèce. Aucune descente de gradient, aucun arbre construit, aucun réseau de neurones - rien que des distances comparées au moment de la question.
1from sklearn.neighbors import KNeighborsClassifier
2
3knn = KNeighborsClassifier(
4 n_neighbors=5, # combien de voisins on regarde
5 weights='uniform',
6 metric='euclidean'
7)
8knn.fit(X_train, y_train)
9predictions = knn.predict(X_test)n_neighbors=5 signifie "regarde les 5 animaux connus les plus proches et prends la majorité". Avec weights='uniform' chaque voisin pèse pareil ; avec 'distance', les plus proches pèsent davantage. Attention au piège : comme le KNN mesure des distances, si une caractéristique est en tonnes et une autre en millimètres, la première écrasera le résultat. C'est pour ça que le prétraitement, un peu plus bas, n'est pas optionnel pour ce modèle.
La régression - quand la réponse est un nombre
Jusqu'ici nous devinions une espèce, donc une catégorie. Et si tu voulais prédire un nombre - le poids d'un animal à partir de ses dimensions ? C'est la régression, et elle s'appuie exactement sur les mêmes trois verbes.
1from sklearn.linear_model import LinearRegression, Ridge, Lasso
2
3linear = LinearRegression()
4linear.fit(X_train, y_train)
5
6ridge = Ridge(alpha=1.0) # frein L2 sur les coefficients trop grands
7ridge.fit(X_train, y_train)
8
9lasso = Lasso(alpha=0.1) # frein L1: annule les mesures inutiles
10lasso.fit(X_train, y_train)
11
12print(f"Coefficients: {linear.coef_}")
13print(f"Intercept: {linear.intercept_}")LinearRegression cherche une relation droite entre les mesures et le résultat. Ridge et Lasso en sont les versions prudentes : elles ajoutent une pénalité contre l'excès, pour que le modèle ne se colle pas désespérément aux données d'entraînement. La différence tient en une phrase : Lasso sait ramener certains coefficients exactement à zéro, ce qui en fait aussi un outil pour décider quelles mesures méritent d'être gardées.
Quand la relation n'a plus rien de droit, la forêt de tout à l'heure existe aussi en version "nombres" :
1from sklearn.ensemble import RandomForestRegressor
2
3rf_reg = RandomForestRegressor(n_estimators=100, random_state=42)
4rf_reg.fit(X_train, y_train)
5predictions = rf_reg.predict(X_test)Regarde à quel point ce code ressemble à celui du classifieur : seule la fin du nom change (Regressor au lieu de Classifier). C'est encore la langue commune - une fois un modèle maîtrisé, tu reconnais le suivant sans effort.
Le prétraitement - aligner les mesures avant l'entraînement
Reviens au piège du KNN. Tout modèle qui calcule des distances se perd quand les caractéristiques n'ont pas la même échelle. Avant l'entraînement, l'ordre des gestes est toujours le même : charger les données brutes, traiter les valeurs manquantes (NaN), encoder les variables catégorielles, puis standardiser les caractéristiques numériques. Impossible d'encoder ou de standardiser des colonnes qu'on n'a pas encore chargées ni nettoyées.
1from sklearn.preprocessing import StandardScaler, MinMaxScaler, LabelEncoder, OneHotEncoder
2
3scaler = StandardScaler() # moyenne 0, dispersion 1
4X_scaled = scaler.fit_transform(X_train)
5X_test_scaled = scaler.transform(X_test)
6
7minmax = MinMaxScaler() # compresse dans la plage 0-1
8X_normalized = minmax.fit_transform(X_train)
9
10le = LabelEncoder() # lion / elephant / cheetah -> 0 / 1 / 2
11y_encoded = le.fit_transform(['lion', 'elephant', 'cheetah'])
12
13ohe = OneHotEncoder(sparse_output=False) # transforme les noms en colonnes 0/1
14species_onehot = ohe.fit_transform(species_array.reshape(-1, 1))Il y a ici une règle facile à casser et qui fausse tout : le scaler s'apprend uniquement sur les données d'entraînement, avec scaler.fit_transform(X_train), tandis que les données de test sont seulement transformées par transform. Sinon le modèle "aperçoit" le test et ta note sera artificiellement gonflée. Les deux derniers outils traduisent les noms d'espèces en nombres, parce qu'un modèle ne comprend que des nombres : LabelEncoder pour les réponses, OneHotEncoder pour les variables catégorielles.
Le Pipeline - tout emballer dans un seul rituel
Puisqu'il faut préparer les données avant chaque modèle, l'erreur guette : traiter l'entraînement autrement que le test, ou oublier une étape en chemin. Le Pipeline colle la préparation et le modèle en un seul objet qui garde l'ordre à ta place. Le rituel tient en cinq gestes : importer Pipeline, définir les étapes (scaler, classifier), créer le Pipeline, appeler fit(), appeler predict().
1from sklearn.pipeline import Pipeline
2from sklearn.preprocessing import StandardScaler
3from sklearn.ensemble import RandomForestClassifier
4
5pipeline = Pipeline([
6 ('scaler', StandardScaler()),
7 ('classifier', RandomForestClassifier(n_estimators=100))
8])
9
10pipeline.fit(X_train, y_train)
11predictions = pipeline.predict(X_test)L'essentiel : le pipeline parle lui aussi nos trois verbes - fit standardise et entraîne toute la chaîne d'un seul appel, et predict applique automatiquement le même scaler aux nouvelles données. Le piège du prétraitement disparaît alors tout seul, puisqu'il devient impossible de faire les choses dans le mauvais ordre.
Retiens une seule chose de cette leçon : avec scikit-learn tu n'apprends pas dix outils différents, mais une seule langue commune - fit, predict, score - que parlent tous les modèles. Le reste, c'est choisir le pisteur adapté à la piste.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Qu'est-ce qu'une Random Forest ?
2. À quoi sert une Support Vector Machine (SVM) ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri horizontal
Range le code pour entraîner un modèle Random Forest :
- Éditeur de code
Crée un RandomForestClassifier avec n_estimators=100, entraîne-le et évalue-le.
- Clique dans l'ordre
Clique dans l'ordre pour composer l'appel de prédiction du modèle :
- Tri vertical
Range les étapes de création d'un Pipeline dans sklearn :
- Tri vertical
Range les étapes dans le bon ordre :
- Clique dans l'ordre
Clique sur les boutons pour standardiser les données :