En apprenant la programmation avec Python, vous constaterez sûrement l’émergence croissante de techniques d’analyse et d’utilisation de données. Le Machine Learning, également appelé Apprentissage Automatique, est sans aucun doute l’un des domaines que vous souhaitez maîtriser (c’est pourquoi vous êtes sur cette page). Le Machine Learning vous permet de faire des prédictions en utilisant des algorithmes complexes appliqués à des données. Les utilisations du Machine Learning sont infinies, ce qui en fait une compétence majeure à ajouter à votre CV.
Dans ce tutoriel, je vais vous expliquer les principes fondamentaux du Machine Learning et comment vous familiariser avec le Machine Learning en utilisant Python. Heureusement, Python dispose d’un écosystème de bibliothèques incroyable qui facilite l’application du Machine Learning. Nous utiliserons les excellentes bibliothèques Scikit-learn, Pandas et Matplotlib dans ce tutoriel. Si vous souhaitez approfondir le Machine Learning et maîtriser tous les fondamentaux, je vous invite à consulter cette formation complète en cliquant ici.
Le Data Set
Avant de nous lancer dans le Machine Learning, nous allons explorer un jeu de données et déterminer ce qui pourrait être intéressant à prédire.
Le jeu de données provient de BoardGameGeek et contient des informations sur 80 000 jeux de société. Vous pouvez trouver un exemple de jeu de société sur le site ici. Toutes ces informations ont été extraites au format CSV par Sean Beck et peuvent être téléchargées ici. Le jeu de données contient plusieurs informations sur chaque jeu de société. Voici une liste des informations les plus intéressantes :
- nom – nom du jeu de société.
- temps de jeu – le temps de jeu (indiqué par le fabricant).
- temps de jeu minimal – le temps de jeu minimum (indiqué par le fabricant).
- temps de jeu maximal – la durée de jeu maximale (indiquée par le fabricant).
- âge minimal – l’âge minimum recommandé pour jouer.
- utilisateurs évalués – le nombre d’utilisateurs qui ont évalué le jeu.
- note moyenne – la note moyenne attribuée au jeu par les utilisateurs (de 0 à 10).
- poids total – le poids total attribué par les utilisateurs. Le poids est une mesure subjective définie par BoardGameGeek qui décrit le niveau d’engagement ou d’implication d’un jeu. Vous pouvez trouver une explication complète ici.
- poids moyen – la moyenne de tous les poids subjectifs (de 0 à 5).
Python : introduction à la bibliothèque Pandas
La première étape de notre exploration consiste à lire les données et à afficher quelques statistiques sommaires. Pour ce faire, nous allons utiliser la bibliothèque Pandas. Pandas fournit des structures de données et des outils d’analyse de données qui permettent de manipuler les données beaucoup plus rapidement et efficacement qu’avec Python seul. La structure de données la plus couramment utilisée est appelée un DataFrame. Il s’agit d’une extension d’une matrice, nous discuterons donc d’abord de ce qu’est une matrice avant de revenir aux DataFrames. Voici à quoi ressemble notre jeu de données (j’ai supprimé certaines colonnes pour faciliter la lecture) :
id,type,name,yearpublished,minplayers,maxplayers,playingtime
12333,boardgame,Twilight Struggle,2005,2,2,180
120677,boardgame,Terra Mystica,2012,2,5,150
Cela est dans un format appelé CSV (comma-separated values, soit valeurs séparées par des virgules). Chaque ligne de données correspond à un jeu de société différent, et les différentes informations relatives à chaque jeu de société sont séparées par des virgules sur la même ligne. La première ligne est l’en-tête et décrit chaque information. Chaque information complète, en parcourant chaque valeur, constitue une colonne. On peut facilement concevoir un fichier CSV comme une matrice :
yearpublished,minplayers,maxplayers,playingtime
2005,2,2,180
2012,2,5,150
J’ai supprimé certaines colonnes ici pour faciliter la lecture, mais cela vous donne une idée de l’aspect visuel des données. Une matrice est une structure de données bidimensionnelle, avec des lignes et des colonnes. Nous pouvons accéder aux éléments d’une matrice par leur position. La première ligne commence par “yearpublished”, la deuxième ligne commence par “2005”, et la troisième ligne par “2012”. La première colonne est “yearpublished”, la deuxième est “minplayers”, etc.
Les matrices en Python peuvent être utilisées à l’aide de la bibliothèque NumPy. Cependant, les matrices ont quelques inconvénients. Vous ne pouvez pas accéder facilement aux colonnes et aux lignes par leur nom, et chaque colonne doit avoir le même type de données. Cela signifie que nous ne pouvons pas stocker efficacement les données de nos jeux de société dans une matrice : la colonne “name” contient des chaînes de caractères, tandis que la colonne “yearpublished” contient des entiers. Nous ne pouvons donc pas les stocker tous les deux dans la même matrice.
En revanche, un DataFrame peut avoir différents types de données dans chaque colonne. Il possède de nombreuses fonctionnalités intégrées pour l’analyse des données, telles que la recherche de colonnes par leur nom. Pandas nous donne accès à ces fonctionnalités et simplifie le travail avec les données.
Lire nos données
Nous allons maintenant lire nos données à partir d’un fichier CSV dans un DataFrame Pandas, en utilisant la méthode “read_csv”.
# Importer la bibliothèque pandas.
import pandas as pd
# Lire les données.
games = pd.read_csv("board_games.csv")
# Afficher les noms des colonnes du DataFrame games.
print(games.columns)
Ce code lit les données et affiche tous les noms de colonnes. Les colonnes qui sont présentes dans les données mais que je n’ai pas répertoriées sont assez explicites.
print(games.shape)
Nous pouvons également voir la dimension du DataFrame, ce qui montre qu’il a 81 312 lignes (ou jeux de société) et 20 colonnes (ou caractéristiques) décrivant chaque jeu.
Tracer nos variables cibles
Il pourrait être intéressant de prédire la note moyenne qu’un humain attribuerait à un nouveau jeu de société. Cela est stocké dans la colonne “average_rating”, qui correspond à la moyenne de toutes les notes des utilisateurs pour un jeu de société. Prédire cette caractéristique pourrait être utile, par exemple, pour les fabricants de jeux de société qui réfléchissent au type de jeu à créer. Nous pouvons accéder à une colonne d’un DataFrame Pandas en utilisant “games[‘average_rating’]”. Cela extraira une seule colonne du DataFrame. Tracions un histogramme de cette colonne pour visualiser la distribution des notes. Nous allons utiliser Matplotlib pour générer la visualisation. Matplotlib est la principale bibliothèque de tracé que vous découvrirez en apprenant la programmation Python. La plupart des autres bibliothèques de tracé, telles que Seaborn et ggplot2, sont construites sur Matplotlib. Importons les fonctions de tracé de Matplotlib avec la commande “import matplotlib.pyplot as plt”. Nous pouvons ensuite tracer et afficher les graphiques.
# Importer matplotlib.
import matplotlib.pyplot as plt
# Créer un histogramme de toutes les notes de la colonne average_rating.
plt.hist(games["average_rating"])
# Afficher le graphique.
plt.show()
Ce que nous pouvons voir ici, c’est qu’il y a pas mal de jeux de société avec une note de 0. Il existe une répartition assez normale des notes, avec quelques biais à droite, et une note moyenne autour de 6 (si vous excluez les zéros).
Exploration des notes de 0
Est-ce qu’il y a vraiment autant de jeux très mauvais qui ont reçu la note de 0 ? Ou est-ce qu’il se passe autre chose ? Nous devons plonger plus profondément dans les données pour vérifier cela. Avec Pandas, nous pouvons sélectionner des sous-ensembles de données à l’aide d’objets Series booléens (les vecteurs ou une colonne/ligne d’un DataFrame, sont appelés objets Series Pandas). Voici un exemple :
print(games[games["average_rating"] == 0])
Le code ci-dessus créera un nouveau DataFrame avec uniquement les lignes de “games” où la valeur de la colonne “average_rating” est égale à 0. Nous pouvons ensuite indexer le DataFrame résultant pour obtenir les valeurs souhaitées. Il y a deux façons d’indexer dans Pandas : nous pouvons indexer par nom de ligne ou de colonne, ou par position. L’indexation par nom ressemble à “games[‘average_rating’]” – cela renverra toute la colonne “average_rating” de “games”. L’indexation par position ressemble à “games.iloc[0]”. Cela renverra la première ligne du DataFrame. Nous pouvons également passer plusieurs index à la fois – “games.iloc[0,0]” renverra la première colonne de la première ligne de “games”.
# Afficher la première ligne du DataFrame games avec des notes de 0.
# La méthode .iloc sur des dataframes nous permet d'indexer par position.
print(games[games["average_rating"] == 0].iloc[0])
# Afficher la première ligne de tous les jeux de société dont la note est supérieure à 0.
print(games[games["average_rating"] > 0].iloc[0])
Cela nous montre que la principale différence entre un jeu de société avec une note de 0 et un jeu avec une note supérieure à 0 est que le jeu classé 0 n’a aucun commentaire. La colonne “users_rated” est à 0. En filtrant tous les jeux de société sans avis, nous pouvons éliminer une grande partie du “bruit”.
Supprimer les jeux de société sans aucun avis
# Supprimer chaque ligne sans aucun avis utilisateur.
games = games[games["users_rated"] > 0]
# Supprimer les lignes contenant des valeurs manquantes.
games = games.dropna(axis=0)
Nous venons de filtrer toutes les lignes sans aucun avis utilisateur. Pendant que nous y sommes, nous avons également supprimé toutes les lignes contenant des valeurs manquantes. De nombreux algorithmes de Machine Learning ne peuvent pas traiter les valeurs manquantes, nous avons donc besoin d’une manière de les gérer. Filtrer les valeurs manquantes est une technique courante, mais cela signifie que nous pouvons potentiellement perdre des données précieuses. Il existe de nombreuses autres techniques pour traiter les données manquantes.
Classification des jeux de société
Nous avons constaté qu’il peut exister des jeux de société très différents. Un ensemble (que nous venons de supprimer) était l’ensemble des jeux sans aucune critique. Un autre ensemble pourrait être un ensemble de jeux avec de très bonnes notes… Une technique pour en savoir plus sur ces jeux est une technique appelée “clustering”. Le clustering vous permet de rechercher facilement des modèles dans vos données en regroupant des lignes similaires (dans ce cas, des jeux de société). Nous allons utiliser un type particulier de clustering appelé “clustering k-means”. Scikit-learn dispose d’une excellente implémentation du clustering k-means que nous pouvons utiliser. Scikit-learn est la principale bibliothèque de Machine Learning en Python. Elle contient les implémentations des algorithmes les plus courants, tels que les “random forests”, les “support vector machines” et la “régression logistique”. Scikit-learn possède une API solide pour appliquer ces algorithmes.
# Importer le modèle de clustering k-means.
from sklearn.cluster import KMeans
# Initialiser le modèle avec 2 paramètres - le nombre de clusters et random state.
kmeans_model = KMeans(n_clusters=5, random_state=1)
# Sélectionner uniquement les colonnes numériques de games.
good_columns = games._get_numeric_data()
# Adapter le modèle en utilisant les bonnes colonnes.
kmeans_model.fit(good_columns)
# Obtenir les étiquettes des clusters.
labels = kmeans_model.labels_
Pour utiliser l’algorithme de clustering dans Scikit-learn, nous allons d’abord l’initialiser avec deux paramètres : “n_clusters” définit le nombre de clusters de jeux que nous voulons, et “random_state” est une graine aléatoire que nous avons définie pour reproduire les mêmes résultats plus tard. Nous obtenons ensuite uniquement les colonnes numériques de notre DataFrame. La plupart des algorithmes de Machine Learning ne peuvent pas fonctionner directement sur des données textuelles et ne peuvent prendre que des nombres en entrée. En obtenant uniquement les colonnes numériques, nous éliminons le type et le nom, qui ne sont pas utilisables par l’algorithme de clustering. Enfin, nous ajustons notre modèle k-means à nos données et obtenons les étiquettes de cluster pour chaque ligne.
Tracer les Clusters
Maintenant que nous avons les étiquettes des clusters, nous pouvons tracer ces clusters. Un problème bloquant est que nos données comportent de nombreuses colonnes – il est impensable de pouvoir visualiser les choses dans plus de trois dimensions. Nous devons donc réduire la dimensionnalité de nos données sans perdre trop d’informations. Une façon de procéder est d’utiliser une technique appelée “analyse en composantes principales” ou “PCA” (Principal Components Analysis). La PCA prend plusieurs colonnes et les transforme en moins de colonnes tout en essayant de conserver les informations uniques de chaque colonne. Pour simplifier, supposons que nous ayons deux colonnes, “total_owners” et “total_traders”. Il existe une corrélation entre ces deux colonnes et des informations qui se chevauchent. La PCA comprimera ces informations dans une seule colonne avec de nouveaux numéros tout en essayant de ne pas perdre d’informations. Nous allons essayer de transformer nos données de jeux de société en deux dimensions afin de pouvoir les tracer facilement.
# Importer le modèle PCA.
from sklearn.decomposition import PCA
# Créer un modèle PCA.
pca_2 = PCA(n_components=2)
# Adapter le modèle PCA aux colonnes numériques précédentes.
plot_columns = pca_2.fit_transform(good_columns)
# Faire un graphique en nuage de points pour chaque type de jeu de société à partir des clusters.
plt.scatter(x=plot_columns[:,0], y=plot_columns[:,1], c=labels)
# Afficher le graphique.
plt.show()
Nous commençons par initialiser un modèle PCA à partir de Scikit-learn. La PCA n’est pas une technique de Machine Learning, mais Scikit-learn comprend également d’autres modèles utiles pour le Machine Learning. Les techniques de réduction de dimensionnalité comme la PCA sont largement utilisées lors du prétraitement des données pour les algorithmes de Machine Learning. Ensuite, nous transformons nos données en deux colonnes et les traçons. Lorsque nous traçons les colonnes, nous les distinguons en fonction de leur affectation à leurs clusters respectifs. Le graphique nous montre qu’il y a 5 clusters distincts. Nous pourrions explorer davantage les jeux de chaque groupe pour en savoir plus sur les facteurs à l’origine du regroupement des jeux.
Déterminer quoi prédire
Avant de nous lancer dans le Machine Learning, nous devons déterminer deux choses : comment mesurer l’erreur et ce que nous allons prédire. Nous avons pensé précédemment que “average_rating” pourrait être une bonne caractéristique à prédire, et notre exploration renforce cette hypothèse. Il existe plusieurs façons de mesurer l’erreur (beaucoup sont répertoriées ici). En règle générale, lorsque nous effectuons une régression et prédisons des variables continues, nous avons besoin d’une mesure d’erreur différente de celle utilisée pour la classification et la prédiction de valeurs discrètes. Pour cela, nous utiliserons l’erreur quadratique moyenne : elle est facile à calculer et simple à comprendre. Elle nous montre à quel point, en moyenne, nos prédictions sont proches (ou non) de nos valeurs réelles.
Trouver des corrélations
Maintenant que nous voulons prédire “average_rating”, voyons quelles colonnes pourraient être intéressantes pour notre prédiction. Une solution consiste à rechercher la corrélation entre “average_rating” et chacune des autres colonnes. Cela nous indiquera quelles autres colonnes pourraient prédire au mieux “average_rating”. Nous pouvons utiliser la méthode “corr” sur les DataFrames Pandas pour trouver facilement des corrélations. Cela nous donnera la corrélation entre chaque colonne et chaque autre colonne. Le résultat étant un DataFrame, nous pouvons l’indexer et n’obtenir que les corrélations de la colonne “average_rating”.
print(games.corr()["average_rating"])
Nous voyons que les colonnes “average_weight” et “id” sont les plus corrélées à la notation. Les identifiants de la colonne “id” sont probablement attribués lorsque le jeu de société est ajouté à la base de données, ce qui indique probablement que les jeux fabriqués ultérieurement ont des scores plus élevés. Peut-être que les critiques n’étaient pas aussi agréables aux débuts de BoardGameGeek, ou que les jeux plus anciens étaient de moins bonne qualité. “average_weight” indique la “profondeur” ou la complexité d’un jeu. Il est donc possible que les jeux plus complexes soient mieux notés.
Choisir les prédicteurs parmi les colonnes
Avant de commencer à faire des prédictions, sélectionnons uniquement les colonnes pertinentes pour l’entraînement de notre algorithme. Nous voudrons supprimer certaines colonnes qui ne sont pas numériques, ainsi que les colonnes qui ne peuvent être calculées qu’en connaissant déjà la note moyenne. Garder ces colonnes pourrait fausser la tâche du modèle, qui consiste à prédire le classement sans aucune connaissance préalable. L’utilisation de colonnes qui ne peuvent être calculées qu’avec la connaissance de la cible peut entraîner un surapprentissage (overfitting), où votre modèle est bon dans l’ensemble d’entraînement, mais ne se généralise pas bien aux données futures. La colonne “bayes_average_rating” semble dériver d’une certaine manière de “average_rating”, nous pouvons donc la supprimer.
# Obtenir toutes les colonnes du DataFrame games.
columns = games.columns.tolist()
# Filtrer les colonnes pour supprimer celles que nous ne voulons pas.
columns = [c for c in columns if c not in ["bayes_average_rating", "average_rating", "type", "name"]]
# Stocker la variable que nous voulons prédire.
target = "average_rating"
Séparer le jeu de données en un ensemble d’entraînement et un ensemble de test
Nous voulons être en mesure de déterminer avec quelle précision un algorithme utilise nos métriques d’erreur. Cependant, évaluer l’algorithme sur les mêmes données que celles sur lesquelles il a été entraîné entraînera un surapprentissage (overfitting). Nous voulons que l’algorithme apprenne à faire des prédictions à partir de règles généralisées, et non à mémoriser comment faire des prédictions spécifiques. Un exemple est l’apprentissage des mathématiques. Si vous mémorisez que “1 + 1 = 2” et “2 + 2 = 4”, vous pourrez répondre parfaitement à toutes les questions portant sur “1 + 1” et “2 + 2”. Vous obtiendrez une erreur de 0. Cependant, si quelqu’un vous demande autre chose en dehors de votre ensemble de formation (où vous connaissez la réponse), comme “3 + 3”, vous ne pourrez pas le résoudre. En revanche, si vous êtes capable de généraliser et d’apprendre davantage, vous ferez parfois des erreurs car vous n’avez pas mémorisé les solutions – vous pourrez peut-être résoudre “3453 + 353535” de manière incorrecte, mais vous serez en mesure de résoudre tout autre problème supplémentaire. Si votre erreur semble étonnamment faible lorsque vous entraînez un algorithme de Machine Learning, vous devez toujours vérifier si vous ne faites pas de surapprentissage. Pour éviter le surapprentissage, nous allons entraîner notre algorithme sur un ensemble composé de 80% des données et le tester sur un autre ensemble composé de 20% des données (le reste). Pour ce faire, nous échantillonnons d’abord au hasard 80% des lignes dans l’ensemble d’entraînement, puis nous mettons tout le reste dans l’ensemble de test.
# Importer une fonction pour séparer les ensembles.
from sklearn.model_selection import train_test_split
# Générer l'ensemble d'entraînement. Fixer random_state pour reproduire les résultats ultérieurement.
train, test = train_test_split(games, test_size=0.2, random_state=1)
# Afficher les dimensions des deux ensembles.
print(train.shape)
print(test.shape)
Ci-dessus, nous exploitons le fait que chaque ligne d’un DataFrame Pandas possède un index unique pour sélectionner une ligne qui ne fait pas partie de l’ensemble d’entraînement et se trouve dans l’ensemble de test.
Adapter un modèle de régression linéaire
La régression linéaire est un algorithme de Machine Learning puissant et couramment utilisé. Elle prédit la variable cible en utilisant des combinaisons linéaires des variables prédictives. Supposons que nous ayons deux valeurs, 3 et 4. Une combinaison linéaire serait de “3 0.5 + 4 0.5″. Une combinaison linéaire consiste à multiplier chaque nombre par une constante et à additionner les résultats. La régression linéaire ne fonctionne bien que lorsque les variables prédictives et la variable cible sont linéairement corrélées. Comme nous l’avons vu précédemment, certaines des variables prédictives sont corrélées à la cible. La régression linéaire devrait donc nous convenir. Nous pouvons utiliser l’implémentation de régression linéaire de Scikit-learn, tout comme nous avons utilisé l’implémentation de “k-means” précédemment.
# Importer le modèle LinearRegression.
from sklearn.linear_model import LinearRegression
# Initialiser le modèle.
model = LinearRegression()
# Adapter le modèle aux données d'entraînement.
model.fit(train[columns], train[target])
Lorsque nous entraînons le modèle, nous passons en paramètre la matrice des prédicteurs, qui comprend toutes les colonnes du DataFrame que nous avons précédemment sélectionnées. Si vous passez une liste à un DataFrame Pandas lors de son indexation, un nouveau DataFrame est généré avec toutes les colonnes de la liste. Nous transmettons également la variable cible, pour laquelle nous souhaitons faire des prédictions. Le modèle apprend à partir de l’équation qui fait correspondre les prédicteurs à la cible avec une erreur minimale.
Erreur des prédictions
Après avoir entraîné le modèle, nous pouvons faire des prédictions sur de nouvelles données. Ces nouvelles données doivent être exactement dans le même format que les données d’entraînement, sinon le modèle ne produira pas de prédictions précises. Notre ensemble de test est identique à l’ensemble d’entraînement (à l’exception des lignes correspondant à des jeux de société différents). Nous sélectionnons le même sous-ensemble de colonnes dans l’ensemble de test, puis nous effectuons des prédictions dessus.
# Générer des prédictions pour l'ensemble de test.
predictions = model.predict(test[columns])
# Calculer l'erreur entre nos prédictions et les valeurs réelles que nous connaissons.
mean_squared_error(predictions, test[target])
Une fois que nous avons les prédictions, nous sommes en mesure de calculer l’erreur entre les prédictions de l’ensemble de test et les valeurs réelles. L’erreur quadratique moyenne est calculée en soustrayant chaque valeur prédite de la valeur réelle, en élevant les différences au carré, puis en les additionnant. Ensuite, nous divisons le résultat par le nombre total de valeurs prédites. Cela nous donnera l’erreur moyenne pour chaque prédiction.
En explorant plus en profondeur, vous pouvez essayer les choses suivantes :
- Utiliser le support vector machine.
- Combinez plusieurs modèles pour créer de meilleures prédictions.
- Prédire une colonne différente, comme l’average_weight.
- Générer des caractéristiques à partir du texte, comme la longueur du nom du jeu, le nombre de mots, etc.
Voulez-vous en savoir plus sur le Machine Learning avec Python ? MonCoachData propose des formations sur le Machine Learning et la Data Science, à partir de données réelles et en élaborant des projets concrets. Apprenez le Machine Learning dès aujourd’hui et découvrez tous les cours disponibles.