Mickaël Tits CETIC mickael.tits@cetic.be
Un sous-domaine important de l'analyse de données est le machine learning (apprentissage automatique). Le machine learning regroupe un ensemble de techniques permettant, à partir des données, d'apprendre automatiquement différents types de relations entre les variables.
On distingue généralement:
Le machine learning supervisé, qui permet d'apprendre des relations entre les données et des labels. Plus spécifiquement, on entraîne généralement un algorithme à prédire les labels (aussi appelés variables dépendantes, ou targets) un ensemble de variables (appelées variables prédictives, ou features). Un modèle consiste en un algorithme doté de paramètres et qui, à partir d'opérations entre les paramètres et les variables prédictives, prédit un nouveau label. L'apprentissage consiste alors à modifier les paramètres du modèle de manière à ce que les labels prédits soient les plus proches des labels réels. Autrement dit, on minimise l'erreur (absolue ou quadratique) moyenne des prédictions. Cette minimisation de l'erreur se fait généralement de manière itérative, par descente de gradient.
Il existe deux applications principales de l'apprentissage supervisé:
La classification: on a un ensemble de données, par exemple des images, à partir desquelles on aimerait prédire une classe: le label de chaque image est alors une variable catégorielle, par exemple "chat" ou "chien". Les variables prédictives permettant d'entraîner l'algorithme sont par exemple les pixels des images, ou des relations entre ces pixels.
La régression: le label à prédire à partir des données est une valeur continue: par exemple le prix d'une maison qu'on peut prédire à partir de différentes caractéristiques, ou l'âge d'une personne à prédire à partir d'une image.
Le machine learning non-supervisé, qui permet d'apprendre la structure desdonnées, ou des relations entre différentes données, telles que les similitudes entre différentes variables, ou différentes données (e.g.: Clustering, Réduction de dimensions, Détection d'anomalies). Ces techniques ne nécessitent pas de labelliser les données.
Chargez d'abord le dataframe préparé lors du chapitre précédent.
import pandas as pd
#Si vous venez d'exécuter le notebook précédent, vous pouvez simplement récupérer le fichier temporaire créé.
#df = pd.read_csv("houses_features.csv", index_col = 0)
#Vous pouvez aussi récupérer une version du fichier hébergée ici:
df = pd.read_csv("https://raw.githubusercontent.com/titsitits/Python_Data_Science/master/Donn%C3%A9es/houses_features.csv", index_col = 0)
dfbxl = df[df.city == "Bruxelles"]
df
#L'adresse n'a pas d'intérêt (on a déjà extrait la ville, qui est une catégorie intéressante)
dataset = df.drop("address",axis=1)
#dataset = dfbxl.drop("address",axis=1)
De nombreux algorithmes de machine learning nécessitent l'utilisation exclusive de variables prédictives numériques. En effet, ces algorithmes se basent entièrement sur des opérations algébriques à partir de ces variables pour prédire la variable dépendante. Afin de pouvoir utiliser l'information contenue dans les variables catégorielles, il existe différentes techniques pour les transformer en variables continues:
One-hot encoding (dummy variable extraction): Pour chaque catégorie possible d'une variable catégorielle, on crée une variable binaire, indiquant si l'échantillon est de cette catégorie ou non. Par exemple, la variable "Ville
" ["Bruxelles" ou "Namur"] peut être simplement transformée en une variable binaire "is_bruxelles
": "Bruxelles" => 1, "Namur" => 0
Si Ville
contient trois possibilités (e.g., ["Bruxelles" ou "Namur" ou "Fleurus"]), elle nécessitera la création de variables binaires, e.g.: is_bruxelles
et is_namur
. Pour chaque observation, une seule des deux variables ne peut être à 1 (puisque la variable Ville
ne peut être qu'une ville à la fois). Si les deux sont fausses, c'est que la ville est Fleurus.
De manière générale, une variable à n catégories nécessitera la création de n-1 variables binaires. On appelle généralement ces variables dummy.
Echelle : certaines variables sont ordinales, comme quality
[bien
, moyen
, mauvais
]. Celle-ci peut être directement transformée un variable numérique: [bien => 0, moyen = >1, mauvais => 2]. Si ce n'est pas le cas, l'interprétation sémantique de la variable, et sa combinaison avec une autre variable continue permet de définir une échelle. Par exemple, on peut extraire certaines caractéristiques numériques d'une ville à partir du prix moyen des maisons, ou du nombre d'habitants: ["Bruxelles" => 300000, "Namur" => 250000, "Fleurus" => 200000].
#One-hot encoding des variables catégorielles
dataset = pd.get_dummies(dataset, drop_first=True)
dataset
dataset.corr()
Une librairie Python incontournable pour le machine learning est la librairie Scikit-learn: https://scikit-learn.org/stable/ .
Elle inclut un geand nombre d'algorithmes de machine learning supervisé, non-supervisé, de préprocessing de caractéristiques, et de sélection de modèles de machine learning. L'API est assez simple, et est basée sur les librairies vues précédemment (Numpy, Scipy, Matplotlib).
La librairie contient de nombreuses classes permettant d'instancier des modèles de machine learning, que l'on peut ensuite utiliser pour:
Entraîner sur un ensemble de données d'entraînement (ou training set).
Chaque modèle contient généralement différents paramètres qui peuvent être adaptés aux données utilisées, afin d'améliorer les performances de l'algorithme. Pour vérifier les performances du modèles selon différents paramètres, on teste le modèle sur un set de données de validation (ou validation set). On parle généralement de processus de cross-validation.
Afin de vérifier que le modèle obtenu est efficace, on peut ensuite le tester avec de nouvelles données (autres que celles utilisées durant l'entraînement et la cross-validation), les données de test (ou test set).
Etant donné le peu de données de l'exemple, on utilisera un modèle simple (régression linéaire), avec un nombre restreint de caractéristiques (surface, rooms, et éventuellement la ville). On ne contentera d'un training set et d'un test set (pas de validation ici: on gardera les paramètres par défaut du modèle).
from sklearn.linear_model import LinearRegression
#On crée un objet de la classe LinearRegression
regressor = LinearRegression()
trainsize = 6
trainset = dataset.iloc[:trainsize]
testset = dataset.iloc[trainsize:]
features = ["surface","rooms"]
#Pour tester un autre set de features, décommenter la ligne suivante
features = ["surface","rooms","city_Fleurus","city_Namur"]
Xtrain, ytrain, Xtest, ytest = trainset[features], trainset["price"], testset[features], testset["price"]
regressor.fit(Xtrain, ytrain)
trainpred = regressor.predict(Xtrain)
testpred = regressor.predict(Xtest)
print(testpred[0], ytest.values[0])
regressor.coef_
from sklearn.metrics import mean_absolute_error
mae_train = mean_absolute_error(trainpred,ytrain)
mae_test = mean_absolute_error(testpred,ytest)
mae_train, mae_test
trainpred
testpred
ytest
from matplotlib import pyplot as plt
plt.scatter(ytrain,trainpred)
plt.scatter(ytest,testpred)
plt.legend(["train","test"])
plt.xlabel("Labels")
plt.ylabel("Prédictions")
#Des prédictions parfaites devraient se situer sur la droite suivante (erreur de prédiction nulle)
plt.plot([200000,600000],[200000,600000], 'g')
Le modèle semble indiquer qu'une des maisons du testset est sous-évaluée: le modèle estime sa valeur plus haute que le prix auquel elle est vendue.
output = df.iloc[trainsize:]
output = output.assign(predictions = testpred)
output["delta"] = output["price"] - output["predictions"]
output
La maison à la Rue de Fer 29, Namur serait donc fortement sous-évaluée. On remarque en l'occurrence qu'elle a un prix par pièce particulièrement faible.
df
On peut évaluer l'impact des variables prédictives sur la prédiction à partir de leur coefficient dans la régression linéaire. Néanmoins, pour que les coefficients soient comparables, il faut au préalable mettre à la même échelle les variable prédictives.
from sklearn.preprocessing import StandardScaler
#Scaling
scaler = StandardScaler()
Xtrain_scaled = scaler.fit_transform(Xtrain)
Xtest_scaled = scaler.transform(Xtest)
#Entraînement
regressor.fit(Xtrain_scaled, ytrain)
#Prédictions
trainpred = regressor.predict(Xtrain_scaled)
testpred = regressor.predict(Xtest_scaled)
(features,regressor.coef_)
Le coefficient le plus grand en valeur absolue est le deuxième, et semble indiquer que c'est le nombre de pièces qui influence le plus le prix.
from sklearn.linear_model import SGDRegressor
#On crée un objet de la classe SGDRegression
regressor = SGDRegressor(loss = "squared_loss", learning_rate = "constant", eta0 = 0.01, max_iter = 300)
regressor.fit(Xtrain_scaled, ytrain)
print(regressor.predict(Xtest_scaled)[0], ytest.values[0])
Lorsque l'ensemble de données est petit (comme dans cet exemple), il est intéressant d'utiliser une technique de "tournante" sur les données de test pour vérifier la qualité de l'algorithme choisi. Pour un ensemble de n observations, on divise l'échantillon en k sous-ensembles (de taille n/k), et pour chacun de ceux-ci, on entraîne un modèle sur toutes les autres données et on calcule les prédictions sur le sous-ensemble retenu. On entraîne donc k modèles pour prédire les outputs de chaque sous-ensemble gardé comme set de test. On peut ensuite calculer une métrique (e.g., MAE ou MSE) sur l'ensemble des données. Le leave-one-out est un cas particulier de la k-fold validation, où on garde à chaque itération une seule observation de test et on entraîne sur toutes les autres (k = n). On doit donc entraîner n modèles au total.
#Leave-one-out validation
from sklearn.metrics import mean_squared_error
import numpy as np
testpredictions = []
input_features = features
for i in dataset.index:
#trainset: tout sauf i
trainset = dataset[dataset.index!=i]
#testset: i (une seule observation)
testset = dataset.loc[i].to_frame().transpose()
Xtrain, ytrain, Xtest, ytest = trainset[input_features], trainset["price"], testset[input_features], testset["price"]
scaler = StandardScaler()
Xtrain_scaled = scaler.fit_transform(Xtrain)
Xtest_scaled = scaler.transform(Xtest)
regressor.fit(Xtrain_scaled, ytrain)
trainpred = regressor.predict(Xtrain_scaled)
testpred = regressor.predict(Xtest_scaled)
print(i, " - training mean error:", np.mean(np.abs(trainpred - ytrain)), "\n\t test mean error:", np.mean(np.abs(testpred - ytest)), "\n")
#On ajoute la prédiction pour i à la liste des prédictions de test
testpredictions.append(testpred[0])
testpredictions
mean_absolute_error(dataset["price"],testpredictions)
Si les performances sur le test set sont bien plus mauvaises que sur les données d'entraînement, il est fort probable qu'on soit dans une situation d'overfitting: le modèle a appris "par coeur" ce qu'il fallait prédire pour chaque donnée du training set. Il ne se généralise donc pas bien sur de nouvelles données. Afin d'éviter ce problème, on peut soit:
Si à l'inverse, les performances obtenues lors de l'entraînement sont faibles, on est en situation d'underfitting. Il faut alors au contraire augmenter la complexité au modèle et extraire des caractéristiques de meilleure qualité (ayant une relation pus forte avec le label à prédire).
Le dataset minimaliste utilisé ici a permis de présenter très simplement certains concepts de base du machine learning. Néanmoins, en pratique, l'utilisation du machine learning nécessite généralement de beaucoup plus grands ensemble de données, permettant notamment de développer des modèles plus complexes qu'une régression linéaire. Dans le Chapitre suivant, nous utiliserons un dataset réel et plus pertinent pour explorer quelques concepts fondamentaux du machine learning.
Vous pouvez maintenant passer au Chapitre 8 : Un exemple concret: estimation du prix d'une maison à Ames (Iowa, USA)