Explorer les algorithmes d'apprentissage automatique supervisé
Publié: 2022-03-11L'objectif principal de cette lecture est de comprendre suffisamment de méthodologie statistique pour pouvoir tirer parti des algorithmes d'apprentissage automatique de la bibliothèque scikit-learn de Python, puis appliquer ces connaissances pour résoudre un problème d'apprentissage automatique classique.
La première étape de notre voyage nous emmènera à travers une brève histoire de l'apprentissage automatique. Ensuite, nous plongerons dans différents algorithmes. Lors de notre dernier arrêt, nous utiliserons ce que nous avons appris pour résoudre le problème de prédiction du taux de survie du Titanic.
Quelques clauses de non-responsabilité :
- Je suis un ingénieur logiciel full-stack, pas un expert en algorithme d'apprentissage automatique.
- Je suppose que vous connaissez quelques bases de Python.
- Ceci est exploratoire, donc tous les détails ne sont pas expliqués comme dans un tutoriel.
Cela dit, plongeons dedans !
Une introduction rapide aux algorithmes d'apprentissage automatique
Dès qu'on s'aventure dans ce domaine, on se rend compte que le machine learning est moins romantique qu'on ne le pense. Au départ, j'étais plein d'espoir qu'après avoir appris plus, je serais capable de construire ma propre Jarvis AI, qui passerait toute la journée à coder des logiciels et à gagner de l'argent pour moi, afin que je puisse passer des journées entières à l'extérieur à lire des livres, à conduire une moto, et profiter d'un style de vie téméraire pendant que mon Jarvis personnel rend mes poches plus profondes. Cependant, j'ai vite réalisé que la base des algorithmes d'apprentissage automatique est la statistique, ce que je trouve personnellement ennuyeux et sans intérêt. Heureusement, il s'est avéré que les statistiques « ennuyeuses » ont des applications très fascinantes.
Vous découvrirez bientôt que pour accéder à ces applications fascinantes, vous devez très bien comprendre les statistiques. L'un des objectifs des algorithmes d'apprentissage automatique est de trouver des dépendances statistiques dans les données fournies.
Les données fournies peuvent aller de la vérification de la pression artérielle par rapport à l'âge à la recherche de texte manuscrit basé sur la couleur de divers pixels.
Cela dit, j'étais curieux de voir si je pouvais utiliser des algorithmes d'apprentissage automatique pour trouver des dépendances dans les fonctions de hachage cryptographiques (SHA, MD5, etc.) - cependant, vous ne pouvez pas vraiment le faire car les primitives cryptographiques appropriées sont construites de telle manière qu'ils éliminent les dépendances et produisent une sortie très difficile à prévoir. Je pense que, compte tenu d'un temps infini, les algorithmes d'apprentissage automatique pourraient casser n'importe quel modèle de chiffrement.
Malheureusement, nous n'avons pas beaucoup de temps, nous devons donc trouver un autre moyen d'exploiter efficacement la crypto-monnaie. Jusqu'où en sommes-nous jusqu'à présent ?
Une brève histoire des algorithmes d'apprentissage automatique
Les racines des algorithmes d'apprentissage automatique viennent de Thomas Bayes, qui était un statisticien anglais qui a vécu au 18ème siècle. Son article An Essay Towards Solving a Problem in the Doctrine of Chances sous-tend le théorème de Bayes, qui est largement appliqué dans le domaine des statistiques.
Au XIXe siècle, Pierre-Simon Laplace publie Théorie analytique des probabilités , développant les travaux de Bayes et définissant ce que nous connaissons aujourd'hui comme le théorème de Bayes. Peu de temps auparavant, Adrien-Marie Legendre avait décrit la méthode des « moindres carrés », également largement utilisée aujourd'hui en apprentissage supervisé.
Le XXe siècle est la période où la majorité des découvertes connues du public ont été faites dans ce domaine. Andrey Markov a inventé les chaînes de Markov, qu'il a utilisées pour analyser des poèmes. Alan Turing a proposé une machine à apprendre qui pourrait devenir artificiellement intelligente, préfigurant essentiellement les algorithmes génétiques. Frank Rosenblatt a inventé le Perceptron , suscitant un énorme enthousiasme et une grande couverture médiatique.
Mais ensuite, les années 1970 ont vu beaucoup de pessimisme autour de l'idée de l'IA - et donc, un financement réduit - donc cette période est appelée un hiver de l'IA . La redécouverte de la rétropropagation dans les années 1980 a provoqué une résurgence de la recherche en apprentissage automatique. Et aujourd'hui, c'est à nouveau un sujet brûlant.
Le regretté Leo Breiman a fait la distinction entre deux paradigmes de modélisation statistique : la modélisation des données et la modélisation algorithmique. La « modélisation algorithmique » désigne plus ou moins les algorithmes d'apprentissage automatique comme la forêt aléatoire .
L'apprentissage automatique et les statistiques sont des domaines étroitement liés. Selon Michael I. Jordan, les idées d'apprentissage automatique, des principes méthodologiques aux outils théoriques, ont eu une longue préhistoire dans les statistiques. Il a également suggéré la science des données comme terme fictif pour le problème global sur lequel les spécialistes de l'apprentissage automatique et les statisticiens travaillent implicitement.
Catégories d'algorithmes d'apprentissage automatique
Le domaine de l'apprentissage automatique repose sur deux piliers principaux appelés apprentissage supervisé et apprentissage non supervisé . Certaines personnes considèrent également qu'un nouveau domaine d'étude - l'apprentissage en profondeur - est distinct de la question de l'apprentissage supervisé par rapport à l'apprentissage non supervisé.
L'apprentissage supervisé consiste à présenter à un ordinateur des exemples d'entrées et leurs sorties souhaitées. Le but de l'ordinateur est d'apprendre une formule générale qui mappe les entrées aux sorties. Cela peut encore être décomposé en :
- Apprentissage semi-supervisé , c'est-à-dire lorsque l'ordinateur reçoit un ensemble d'entraînement incomplet avec certaines sorties manquantes
- Apprentissage actif , c'est-à-dire lorsque l'ordinateur ne peut obtenir des étiquettes de formation que pour un ensemble très limité d'instances. Lorsqu'ils sont utilisés de manière interactive, leurs ensembles de formation peuvent être présentés à l'utilisateur pour étiquetage.
- Apprentissage par renforcement , c'est-à-dire lorsque les données d'entraînement ne sont fournies qu'en retour aux actions du programme dans l'environnement dynamique, comme conduire un véhicule ou jouer à un jeu contre un adversaire
En revanche, l'apprentissage non supervisé , c'est quand aucune étiquette n'est donnée et c'est à l'algorithme de trouver la structure dans son entrée. L'apprentissage non supervisé peut être un objectif en soi lorsque nous avons seulement besoin de découvrir des schémas cachés.
L'apprentissage en profondeur est un nouveau domaine d'étude qui s'inspire de la structure et de la fonction du cerveau humain et qui repose sur des réseaux de neurones artificiels plutôt que sur de simples concepts statistiques. L'apprentissage en profondeur peut être utilisé dans les approches supervisées et non supervisées.
Dans cet article, nous ne passerons en revue que certains des algorithmes d'apprentissage automatique supervisés les plus simples et les utiliserons pour calculer les chances de survie d'un individu dans le naufrage tragique du Titanic. Mais en général, si vous n'êtes pas sûr de l'algorithme à utiliser, un bon point de départ est la feuille de triche de l'algorithme d'apprentissage automatique de scikit-learn.
Modèles d'apprentissage automatique supervisé de base
L'algorithme le plus simple possible est peut-être la régression linéaire. Parfois, cela peut être représenté graphiquement par une ligne droite, mais malgré son nom, s'il existe une hypothèse polynomiale, cette ligne pourrait plutôt être une courbe. Dans tous les cas, il modélise les relations entre la variable dépendante scalaire $y$ et une ou plusieurs valeurs explicatives désignées par $x$.
En termes simples, cela signifie que la régression linéaire est l'algorithme qui apprend la dépendance entre chaque $x$ et $y$ connu, de sorte que plus tard nous pouvons l'utiliser pour prédire $y$ pour un échantillon inconnu de $x$.
Dans notre premier exemple d'apprentissage supervisé, nous utiliserons un modèle de régression linéaire de base pour prédire la tension artérielle d'une personne compte tenu de son âge. Il s'agit d'un ensemble de données très simple avec deux caractéristiques significatives : l'âge et la tension artérielle.
Comme déjà mentionné ci-dessus, la plupart des algorithmes d'apprentissage automatique fonctionnent en trouvant une dépendance statistique dans les données qui leur sont fournies. Cette dépendance s'appelle une hypothèse et est généralement notée $h(\theta)$.
Pour comprendre l'hypothèse, commençons par charger et explorer les données.
import matplotlib.pyplot as plt from pandas import read_csv import os # Load data data_path = os.path.join(os.getcwd(), "data/blood-pressure.txt") dataset = read_csv(data_path, delim_whitespace=True) # We have 30 entries in our dataset and four features. The first feature is the ID of the entry. # The second feature is always 1. The third feature is the age and the last feature is the blood pressure. # We will now drop the ID and One feature for now, as this is not important. dataset = dataset.drop(['ID', 'One'], axis=1) # And we will display this graph %matplotlib inline dataset.plot.scatter(x='Age', y='Pressure') # Now, we will assume that we already know the hypothesis and it looks like a straight line h = lambda x: 84 + 1.24 * x # Let's add this line on the chart now ages = range(18, 85) estimated = [] for i in ages: estimated.append(h(i)) plt.plot(ages, estimated, 'b')
[<matplotlib.lines.Line2D at 0x11424b828>]
Sur le graphique ci-dessus, chaque point bleu représente notre échantillon de données et la ligne bleue est l'hypothèse que notre algorithme doit apprendre. Alors, quelle est exactement cette hypothèse de toute façon?
Afin de résoudre ce problème, nous devons apprendre la dépendance entre $x$ et $y$, qui est notée $y = f(x)$. Donc $f(x)$ est la fonction cible idéale. L'algorithme d'apprentissage automatique essaiera de deviner la fonction d'hypothèse $h(x)$ qui est l'approximation la plus proche de l'inconnue $f(x)$.
La forme d'hypothèse la plus simple possible pour le problème de régression linéaire ressemble à ceci : $h_\theta(x) = \theta_0 + \theta_1 * x$. Nous avons une seule variable scalaire d'entrée $x$ qui produit une seule variable scalaire $y$, où $\theta_0$ et $\theta_1$ sont des paramètres que nous devons apprendre. Le processus d'ajustement de cette ligne bleue dans les données est appelé régression linéaire. Il est important de comprendre que nous n'avons qu'un seul paramètre d'entrée $x_1$ ; cependant, de nombreuses fonctions d'hypothèse incluront également l'unité de biais ($x_0$). Notre hypothèse résultante a donc la forme $h_\theta(x) = \theta_0 * x_0 + \theta_1 * x_1$. Mais on peut éviter d'écrire $x_0$ car il est presque toujours égal à 1.
Revenir à la ligne bleue. Notre hypothèse ressemble à $h(x) = 84 + 1,24x$, ce qui signifie que $\theta_0 = 84$ et $\theta_1 = 1,24$. Comment pouvons-nous dériver automatiquement ces valeurs $\theta$ ?
Nous devons définir une fonction de coût . Essentiellement, la fonction de coût calcule simplement l'erreur quadratique moyenne entre la prédiction du modèle et la sortie réelle.
\[J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Par exemple, notre hypothèse prédit que pour quelqu'un qui a 48 ans, sa tension artérielle devrait être $h(48) = 84 + 1,24 * 48 = 143mmHg$ ; cependant, dans notre échantillon d'entraînement, nous avons la valeur de 130 mmHg$. Par conséquent, l'erreur est $(143 - 130)^2 = 169$. Nous devons maintenant calculer cette erreur pour chaque entrée de notre jeu de données d'entraînement, puis la sommer ensemble ($\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i )})^2$) et en retirer la valeur moyenne.
Cela nous donne un nombre scalaire unique qui représente le coût de la fonction. Notre objectif est de trouver des valeurs $\theta$ telles que la fonction de coût soit la plus faible ; en d'autres termes, nous voulons minimiser la fonction de coût. Cela semblera, espérons-le, intuitif : si nous avons une petite valeur de fonction de coût, cela signifie que l'erreur de prédiction est également faible.
import numpy as np # Let's calculate the cost for the hypothesis above h = lambda x, theta_0, theta_1: theta_0 + theta_1 * x def cost(X, y, t0, t1): m = len(X) # the number of the training samples c = np.power(np.subtract(h(X, t0, t1), y), 2) return (1 / (2 * m)) * sum(c) X = dataset.values[:, 0] y = dataset.values[:, 1] print('J(Theta) = %2.2f' % cost(X, y, 84, 1.24))
J(Theta) = 1901.95
Maintenant, nous devons trouver de telles valeurs de $\theta$ telles que la valeur de notre fonction de coût soit minimale. Mais comment fait-on cela ?
\[minJ(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Il existe plusieurs algorithmes possibles, mais le plus populaire est la descente de gradient . Afin de comprendre l'intuition derrière la méthode de descente de gradient, commençons par la tracer sur le graphique. Par souci de simplicité, nous supposerons une hypothèse plus simple $h(\theta) = \theta_1 * x$. Ensuite, nous allons tracer un graphique 2D simple où $x$ est la valeur de $\theta$ et $y$ est la fonction de coût à ce stade.
import matplotlib.pyplot as plt fig = plt.figure() # Generate the data theta_1 = np.arange(-10, 14, 0.1) J_cost = [] for t1 in theta_1: J_cost += [ cost(X, y, 0, t1) ] plt.plot(theta_1, J_cost) plt.xlabel(r'$\theta_1$') plt.ylabel(r'$J(\theta)$') plt.show()
La fonction de coût est convexe, ce qui signifie que sur l'intervalle $[a, b]$ il n'y a qu'un seul minimum. Ce qui signifie encore une fois que les meilleurs paramètres $\theta$ sont au point où la fonction de coût est minimale.
Fondamentalement, la descente de gradient est un algorithme qui essaie de trouver l'ensemble des paramètres qui minimisent la fonction. Il commence par un ensemble initial de paramètres et prend itérativement des étapes dans le sens négatif du gradient de la fonction.
Si nous calculons la dérivée d'une fonction d'hypothèse en un point spécifique, cela nous donnera une pente de la ligne tangente à la courbe en ce point. Cela signifie que nous pouvons calculer la pente à chaque point du graphique.
Voici comment fonctionne l'algorithme :
- Nous choisissons un point de départ aléatoire (aléatoire $\theta$).
- Calculez la dérivée de la fonction de coût à ce point.
- Faites le petit pas vers la pente $\theta_j := \theta_j - \lambda * \frac{\partial}{\partial \theta_j} * J(\theta)$.
- Répétez les étapes 2-3 jusqu'à ce que nous convergeons.
Or, la condition de convergence dépend de l'implémentation de l'algorithme. Nous pouvons nous arrêter après 50 pas, après un certain seuil, ou n'importe quoi d'autre.
import math # Example of the simple gradient descent algorithm taken from Wikipedia cur_x = 2.5 # The algorithm starts at point x gamma = 0.005 # Step size multiplier precision = 0.00001 previous_step_size = cur_x df = lambda x: 2 * x * math.cos(x) # Remember the learning curve and plot it while previous_step_size > precision: prev_x = cur_x cur_x += -gamma * df(prev_x) previous_step_size = abs(cur_x - prev_x) print("The local minimum occurs at %f" % cur_x)
The local minimum occurs at 4.712194
Nous n'implémenterons pas ces algorithmes dans cet article. Au lieu de cela, nous utiliserons le scikit-learn
largement adopté, une bibliothèque d'apprentissage automatique Python open source. Il fournit de nombreuses API très utiles pour différents problèmes d'exploration de données et d'apprentissage automatique.
from sklearn.linear_model import LinearRegression # LinearRegression uses the gradient descent method # Our data X = dataset[['Age']] y = dataset[['Pressure']] regr = LinearRegression() regr.fit(X, y) # Plot outputs plt.xlabel('Age') plt.ylabel('Blood pressure') plt.scatter(X, y, color='black') plt.plot(X, regr.predict(X), color='blue') plt.show() plt.gcf().clear()
<matplotlib.figure.Figure at 0x120fae1d0>
print( 'Predicted blood pressure at 25 yo = ', regr.predict(25) ) print( 'Predicted blood pressure at 45 yo = ', regr.predict(45) ) print( 'Predicted blood pressure at 27 yo = ', regr.predict(27) ) print( 'Predicted blood pressure at 34.5 yo = ', regr.predict(34.5) ) print( 'Predicted blood pressure at 78 yo = ', regr.predict(78) )
Predicted blood pressure at 25 yo = [[ 122.98647692]] Predicted blood pressure at 45 yo = [[ 142.40388395]] Predicted blood pressure at 27 yo = [[ 124.92821763]] Predicted blood pressure at 34.5 yo = [[ 132.20974526]] Predicted blood pressure at 78 yo = [[ 174.44260555]]
Types de données statistiques
Lorsque vous travaillez avec des données pour des problèmes d'apprentissage automatique, il est important de reconnaître différents types de données. Nous pouvons avoir des données numériques (continues ou discrètes), catégorielles ou ordinales.
Les données numériques ont un sens en tant que mesure. Par exemple, l'âge, le poids, le nombre de bitcoins qu'une personne possède ou le nombre d'articles que la personne peut écrire par mois. Les données numériques peuvent être subdivisées en types discrets et continus.
- Les données discrètes représentent des données qui peuvent être comptées avec des nombres entiers, par exemple, le nombre de pièces dans un appartement ou le nombre de lancers de pièces.
- Les données continues ne peuvent pas nécessairement être représentées par des nombres entiers. Par exemple, si vous mesurez la distance à laquelle vous pouvez sauter, cela peut être 2 mètres, 1,5 mètre ou 1,652245 mètre.
Les données catégorielles représentent des valeurs telles que le sexe, l'état matrimonial, le pays, etc. de la personne. Ces données peuvent prendre une valeur numérique, mais ces chiffres n'ont aucune signification mathématique. Vous ne pouvez pas les additionner.
Les données ordinales peuvent être un mélange des deux autres types, en ce sens que les catégories peuvent être numérotées de manière mathématiquement significative. Un exemple courant est celui des évaluations : souvent, on nous demande d'évaluer les choses sur une échelle de un à dix, et seuls les nombres entiers sont autorisés. Bien que nous puissions l'utiliser numériquement, par exemple pour trouver une note moyenne pour quelque chose, nous traitons souvent les données comme si elles étaient catégoriques lorsqu'il s'agit d'y appliquer des méthodes d'apprentissage automatique.
Régression logistique
La régression linéaire est un algorithme génial qui nous aide à prédire des valeurs numériques, par exemple, le prix de la maison avec la taille et le nombre de pièces spécifiques. Cependant, parfois, nous pouvons également vouloir prédire des données catégorielles, pour obtenir des réponses à des questions telles que :
- Est-ce un chien ou un chat ?
- Cette tumeur est-elle maligne ou bénigne ?
- Ce vin est-il bon ou mauvais ?
- Cet e-mail est-il un spam ou non ?
Ou même:
- Quel numéro est sur la photo ?
- À quelle catégorie appartient cet e-mail ?
Toutes ces questions sont spécifiques au problème de classification . Et l'algorithme de classification le plus simple est appelé régression logistique , qui est finalement identique à la régression linéaire sauf qu'il a une hypothèse différente.
Tout d'abord, nous pouvons réutiliser la même hypothèse linéaire $h_\theta(x) = \theta^TX$ (celle-ci est sous forme vectorisée). Alors que la régression linéaire peut produire n'importe quel nombre dans l'intervalle $[a, b]$, la régression logistique ne peut produire que des valeurs dans $[−1, 1]$, qui est la probabilité que l'objet tombe dans une catégorie donnée ou non.
En utilisant une fonction sigmoïde , nous pouvons convertir n'importe quelle valeur numérique pour représenter une valeur sur l'intervalle $[−1, 1]$.
\[f(x) = \frac{1}{1 + e^x}\]Maintenant, au lieu de $x$, nous devons passer une hypothèse existante et nous obtiendrons donc :
\[f(x) = \frac{1}{1 + e^{\theta_0 + \theta_1 * x_1 + ... + \theta_n * x_n}}\]Après cela, nous pouvons appliquer un seuil simple disant que si l'hypothèse est supérieure à zéro, c'est une valeur vraie, sinon fausse.
\[h_\theta(x) = \begin{cases} 1 & \mbox{if } \theta^TX > 0 \\ 0 & \mbox{else} \end{cases}\]Cela signifie que nous pouvons utiliser la même fonction de coût et le même algorithme de descente de gradient pour apprendre une hypothèse de régression logistique.
Dans notre prochain exemple d'algorithme d'apprentissage automatique, nous indiquerons aux pilotes de la navette spatiale s'ils doivent ou non utiliser le contrôle d'atterrissage automatique ou manuel. Nous disposons d'un très petit ensemble de données (15 échantillons) composé de six caractéristiques et de la vérité terrain .
Dans les algorithmes d'apprentissage automatique, le terme « vérité terrain » fait référence à la précision de la classification de l'ensemble d'apprentissage pour les techniques d'apprentissage supervisé.
Notre ensemble de données est complet, ce qui signifie qu'il ne manque aucune fonctionnalité ; cependant, certaines fonctionnalités ont un "*" au lieu de la catégorie, ce qui signifie que cette fonctionnalité n'a pas d'importance. Nous remplacerons tous ces astérisques par des zéros.
from sklearn.linear_model import LogisticRegression # Data data_path = os.path.join(os.getcwd(), "data/shuttle-landing-control.csv") dataset = read_csv(data_path, header=None, names=['Auto', 'Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility'], na_values='*').fillna(0) # Prepare features X = dataset[['Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility']] y = dataset[['Auto']].values.reshape(1, -1)[0] model = LogisticRegression() model.fit(X, y) # For now, we're missing one important concept. We don't know how well our model # works, and because of that, we cannot really improve the performance of our hypothesis. # There are a lot of useful metrics, but for now, we will validate how well # our algorithm performs on the dataset it learned from. "Score of our model is %2.2f%%" % (model.score(X, y) * 100)
Score of our model is 73.33%
Validation?
Dans l'exemple précédent, nous avons validé les performances de notre modèle à l'aide des données d'apprentissage. Cependant, est-ce maintenant une bonne option, étant donné que notre algorithme peut sous-ajuster ou sur-ajuster les données ? Prenons l'exemple le plus simple lorsque nous avons une caractéristique qui représente la taille d'une maison et une autre qui représente son prix.
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score # Ground truth function ground_truth = lambda X: np.cos(15 + np.pi * X) # Generate random observations around the ground truth function n_samples = 15 degrees = [1, 4, 30] X = np.linspace(-1, 1, n_samples) y = ground_truth(X) + np.random.randn(n_samples) * 0.1 plt.figure(figsize=(14, 5)) models = {} # Plot all machine learning algorithm models for idx, degree in enumerate(degrees): ax = plt.subplot(1, len(degrees), idx + 1) plt.setp(ax, xticks=(), yticks=()) # Define the model polynomial_features = PolynomialFeatures(degree=degree) model = make_pipeline(polynomial_features, LinearRegression()) models[degree] = model # Train the model model.fit(X[:, np.newaxis], y) # Evaluate the model using cross-validation scores = cross_val_score(model, X[:, np.newaxis], y) X_test = X plt.plot(X_test, model.predict(X_test[:, np.newaxis]), label="Model") plt.scatter(X, y, edgecolor='b', s=20, label="Observations") plt.xlabel("x") plt.ylabel("y") plt.ylim((-2, 2)) plt.title("Degree {}\nMSE = {:.2e}".format( degree, -scores.mean())) plt.show()

Le modèle d'algorithme d'apprentissage automatique est sous- ajusté s'il ne peut généraliser ni les données d'apprentissage ni les nouvelles observations. Dans l'exemple ci-dessus, nous utilisons une hypothèse linéaire simple qui ne représente pas vraiment l'ensemble de données d'entraînement réel et aura de très mauvaises performances. Habituellement, le sous-ajustement n'est pas discuté car il peut être facilement détecté avec une bonne métrique.
Si notre algorithme se souvient de chaque observation qui lui a été présentée, ses performances seront médiocres sur les nouvelles observations en dehors de l'ensemble de données d'apprentissage. C'est ce qu'on appelle le surajustement . Par exemple, un modèle polynomial de 30e degré passe par la plupart des points et a un très bon score sur l'ensemble d'apprentissage, mais tout ce qui est en dehors de cela fonctionnerait mal.
Notre jeu de données se compose d'une entité et est simple à tracer dans un espace 2D ; cependant, dans la vraie vie, nous pouvons avoir des ensembles de données avec des centaines d'entités, ce qui les rend impossibles à tracer visuellement dans l'espace euclidien. Quelles autres options avons-nous pour voir si le modèle est sous-ajusté ou surajusté ?
Il est temps de vous présenter le concept de la courbe d'apprentissage . Il s'agit d'un graphique simple qui représente l'erreur quadratique moyenne sur le nombre d'échantillons d'apprentissage.
Dans les supports d'apprentissage, vous verrez généralement des graphiques similaires à ceux-ci :
Cependant, dans la vraie vie, vous n'obtiendrez peut-être pas une image aussi parfaite. Traçons la courbe d'apprentissage pour chacun de nos modèles.
from sklearn.model_selection import learning_curve, ShuffleSplit # Plot learning curves plt.figure(figsize=(20, 5)) for idx, degree in enumerate(models): ax = plt.subplot(1, len(degrees), idx + 1) plt.title("Degree {}".format(degree)) plt.grid() plt.xlabel("Training examples") plt.ylabel("Score") train_sizes = np.linspace(.6, 1.0, 6) # Cross-validation with 100 iterations to get a smoother mean test and training # score curves, each time with 20% of the data randomly selected as a validation set. cv = ShuffleSplit(n_splits=100, test_size=0.2, random_state=0) model = models[degree] train_sizes, train_scores, test_scores = learning_curve( model, X[:, np.newaxis], y, cv=cv, train_sizes=train_sizes, n_jobs=4) train_scores_mean = np.mean(train_scores, axis=1) test_scores_mean = np.mean(test_scores, axis=1) plt.plot(train_sizes, train_scores_mean, 'o-', color="r", label="Training score") plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Test score") plt.legend(loc = "best") plt.show()
Dans notre scénario simulé, la ligne bleue, qui représente le score d'entraînement, ressemble à une ligne droite. En réalité, il diminue encore légèrement - vous pouvez en fait le voir dans le graphe polynomial du premier degré, mais dans les autres, c'est trop subtil pour le dire à cette résolution. Nous voyons au moins clairement qu'il existe un énorme écart entre les courbes d'apprentissage pour la formation et les observations de test avec un scénario « à biais élevé ».
Sur le graphique du taux d'apprentissage "normal" au milieu, vous pouvez voir comment les lignes de score d'entraînement et de score de test se rejoignent.
Et sur le graphique "forte variance", vous pouvez voir qu'avec un faible nombre d'échantillons, les scores de test et d'entraînement sont très similaires ; cependant, lorsque vous augmentez le nombre d'échantillons, le score d'apprentissage reste presque parfait tandis que le score du test s'en éloigne.
Nous pouvons corriger les modèles de sous-ajustement (également appelés modèles à fort biais ) si nous utilisons une hypothèse non linéaire, par exemple, l'hypothèse avec plus de caractéristiques polynomiales.
Notre modèle de surajustement ( variance élevée ) passe par chaque exemple présenté ; cependant, lorsque nous introduisons des données de test, l'écart entre les courbes d'apprentissage s'élargit. Nous pouvons utiliser la régularisation, la validation croisée et davantage d'échantillons de données pour corriger les modèles de surajustement.
Validation croisée
L'une des pratiques courantes pour éviter le surajustement consiste à conserver une partie des données disponibles et à les utiliser comme ensemble de test. Cependant, lors de l'évaluation de différents paramètres de modèle, tels que le nombre de caractéristiques polynomiales, nous risquons toujours de surajuster l'ensemble de test car les paramètres peuvent être modifiés pour obtenir les performances optimales de l'estimateur et, à cause de cela, notre connaissance de l'ensemble de test peut fuite dans le modèle. Pour résoudre ce problème, nous devons conserver une partie supplémentaire de l'ensemble de données, appelée « ensemble de validation ». La formation se poursuit sur l'ensemble d'apprentissage et, lorsque nous pensons avoir atteint les performances optimales du modèle, nous pouvons effectuer une évaluation finale à l'aide de l'ensemble de validation.
Cependant, en partitionnant les données disponibles en trois ensembles, nous réduisons considérablement le nombre d'échantillons pouvant être utilisés pour l'apprentissage des modèles, et les résultats peuvent dépendre d'un choix aléatoire particulier pour la paire d'ensembles d'apprentissage-validation.
Une solution à ce problème est une procédure appelée validation croisée. Dans la validation croisée standard $k$-fold, nous divisons les données en sous-ensembles $k$, appelés folds. Ensuite, nous entraînons itérativement l'algorithme sur des plis $k-1$ tout en utilisant le pli restant comme ensemble de test (appelé « pli restant »).
La validation croisée vous permet d'ajuster les paramètres uniquement avec votre ensemble d'entraînement d'origine. Cela vous permet de conserver votre ensemble de test comme un ensemble de données vraiment invisible pour la sélection de votre modèle final.
Il existe de nombreuses autres techniques de validation croisée, telles que leave P out , stratified $k$-fold , shuffle and split , etc., mais elles dépassent le cadre de cet article.
Régularisation
C'est une autre technique qui peut aider à résoudre le problème de surajustement du modèle. La plupart des ensembles de données ont un modèle et un peu de bruit. Le but de la régularisation est de réduire l'influence du bruit sur le modèle.
Il existe trois principales techniques de régularisation : Lasso, Tikhonov et filet élastique.
La régularisation L1 (ou régularisation Lasso ) sélectionnera certaines fonctionnalités à réduire à zéro, de sorte qu'elles ne joueront aucun rôle dans le modèle final. L1 peut être considéré comme une méthode pour sélectionner des caractéristiques importantes.
La régularisation L2 (ou régularisation de Tikhonov ) forcera toutes les caractéristiques à être relativement petites, de sorte qu'elles auront moins d'influence sur le modèle.
Le filet élastique est la combinaison de L1 et L2.
Normalisation (mise à l'échelle des fonctionnalités)
La mise à l'échelle des fonctionnalités est également une étape importante lors du prétraitement des données. Notre jeu de données peut avoir des entités avec des valeurs $[-\infty, \infty]$ et d'autres entités avec une échelle différente. Il s'agit d'une méthode pour normaliser les plages de valeurs indépendantes.
La mise à l'échelle des fonctionnalités est également un processus important pour améliorer les performances des modèles d'apprentissage. Tout d'abord, la descente de gradient convergera beaucoup plus rapidement si toutes les caractéristiques sont mises à l'échelle selon la même norme. De plus, de nombreux algorithmes - par exemple, les machines à vecteurs de support (SVM) - fonctionnent en calculant la distance entre deux points et si l'une des caractéristiques a des valeurs larges, la distance sera fortement influencée par cette caractéristique.
Soutenir les machines vectorielles
SVM est un autre algorithme d'apprentissage automatique très populaire qui peut être utilisé pour les problèmes de classification et de régression. Dans SVM, nous traçons chaque observation sous la forme d'un point dans un espace $n$-dimensionnel où $n$ est le nombre d'entités que nous avons. La valeur de chaque caractéristique est la valeur de coordonnées particulières. Ensuite, on essaie de trouver un hyperplan qui sépare suffisamment bien deux classes.
Après avoir identifié le meilleur hyperplan, nous voulons ajouter des marges, ce qui séparerait davantage les deux classes.
SVM est très efficace lorsque le nombre de fonctionnalités est très élevé ou si le nombre de fonctionnalités est supérieur au nombre d'échantillons de données. Cependant, étant donné que SVM fonctionne sur une base vectorielle, il est crucial de normaliser les données avant l'utilisation.
Les réseaux de neurones
Les algorithmes de réseaux de neurones sont probablement le domaine le plus passionnant des études d'apprentissage automatique. Les réseaux de neurones tentent d'imiter la façon dont les neurones du cerveau sont connectés entre eux.
Voici à quoi ressemble un réseau de neurones. Nous combinons un grand nombre de nœuds ensemble où chaque nœud prend un ensemble d'entrées, applique des calculs sur eux et produit une valeur.
Il existe une grande variété d'algorithmes de réseaux de neurones pour l'apprentissage supervisé et non supervisé. Les réseaux de neurones peuvent être utilisés pour conduire des voitures autonomes, jouer à des jeux, faire atterrir des avions, classer des images, etc.
Le tristement célèbre Titanic
Le RMS Titanic était un paquebot britannique qui a coulé dans l'océan Atlantique Nord le 15 avril 1912 après être entré en collision avec un iceberg. Il y avait environ 2 224 membres d'équipage et passagers, et plus de 1 500 sont morts, ce qui en fait l'une des catastrophes maritimes commerciales les plus meurtrières de tous les temps.
Maintenant, puisque nous comprenons l'intuition derrière les algorithmes d'apprentissage automatique les plus élémentaires utilisés pour les problèmes de classification, nous pouvons appliquer nos connaissances pour prédire le résultat de survie des personnes à bord du Titanic.
Notre jeu de données sera emprunté à la plateforme de compétitions de science des données Kaggle.
import os from pandas import read_csv, concat # Load data data_path = os.path.join(os.getcwd(), "data/titanic.csv") dataset = read_csv(data_path, skipinitialspace=True) dataset.head(5)
Identifiant du passager | Survécu | Pclasse | Nom | Sexe | Âge | SibSp | Dessécher | Billet | Tarif | Cabine | Embarqué | |
0 | 1 | 0 | 3 | Braund, M. Owen Harris | Masculin | 22,0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mme John Bradley (Florence Briggs Th... | Femme | 38,0 | 1 | 0 | CP 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Mlle Laina | Femme | 26,0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mme Jacques Heath (Lily May Peel) | Femme | 35,0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, M. William Henry | Masculin | 35,0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
Notre première étape serait de charger et d'explorer les données. Nous avons 891 enregistrements de test ; chaque enregistrement a la structure suivante :
- PassengerId – ID du passager à bord
- survie - Si la personne a survécu ou non à l'accident
- pclass - Classe de billet, par exemple, 1er, 2e, 3e
- gender – Sexe du passager : Homme ou femme
- nom – titre inclus
- âge – Âge en années
- sibsp - Nombre de frères et sœurs / conjoints à bord du Titanic
- parch – Nombre de parents/enfants à bord du Titanic
- billet – Numéro du billet
- tarif – Tarif passager
- cabine – Numéro de cabine
- embarqué – Port d'embarquement
Cet ensemble de données contient à la fois des données numériques et catégorielles. Habituellement, c'est une bonne idée de plonger plus profondément dans les données et, sur cette base, de formuler des hypothèses. Cependant, dans ce cas, nous sauterons cette étape et passerons directement aux prédictions.
import pandas as pd # We need to drop some insignificant features and map the others. # Ticket number and fare should not contribute much to the performance of our models. # Name feature has titles (eg, Mr., Miss, Doctor) included. # Gender is definitely important. # Port of embarkation may contribute some value. # Using port of embarkation may sound counter-intuitive; however, there may # be a higher survival rate for passengers who boarded in the same port. dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False) dataset = dataset.drop(['PassengerId', 'Ticket', 'Cabin', 'Name'], axis=1) pd.crosstab(dataset['Title'], dataset['Sex'])
Title \ Sex | Femme | Masculin |
Capt | 0 | 1 |
Col | 0 | 2 |
Countess | 1 | 0 |
Don | 0 | 1 |
Docteur | 1 | 6 |
Jonkheer | 0 | 1 |
Dame | 1 | 0 |
Major | 0 | 2 |
Maître | 0 | 40 |
Mademoiselle | 182 | 0 |
Mlle | 2 | 0 |
Mme | 1 | 0 |
Monsieur | 0 | 517 |
Mme | 125 | 0 |
Mme | 1 | 0 |
Rev | 0 | 6 |
Monsieur | 0 | 1 |
# We will replace many titles with a more common name, English equivalent, # or reclassification dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',\ 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Other') dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss') dataset['Title'] = dataset['Title'].replace('Ms', 'Miss') dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs') dataset[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()
Titre | Survived | |
0 | Maître | 0.575000 |
1 | Mademoiselle | 0.702703 |
2 | Monsieur | 0.156673 |
3 | Mme | 0.793651 |
4 | Autre | 0.347826 |
# Now we will map alphanumerical categories to numbers title_mapping = { 'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4, 'Other': 5 } gender_mapping = { 'female': 1, 'male': 0 } port_mapping = { 'S': 0, 'C': 1, 'Q': 2 } # Map title dataset['Title'] = dataset['Title'].map(title_mapping).astype(int) # Map gender dataset['Sex'] = dataset['Sex'].map(gender_mapping).astype(int) # Map port freq_port = dataset.Embarked.dropna().mode()[0] dataset['Embarked'] = dataset['Embarked'].fillna(freq_port) dataset['Embarked'] = dataset['Embarked'].map(port_mapping).astype(int) # Fix missing age values dataset['Age'] = dataset['Age'].fillna(dataset['Age'].dropna().median()) dataset.head()
Survived | Pclass | Sexe | Âge | SibSp | Parch | Tarif | Embarked | Titre | |
0 | 0 | 3 | 0 | 22,0 | 1 | 0 | 7.2500 | 0 | 1 |
1 | 1 | 1 | 1 | 38,0 | 1 | 0 | 71.2833 | 1 | 3 |
2 | 1 | 3 | 1 | 26,0 | 0 | 0 | 7.9250 | 0 | 2 |
3 | 1 | 1 | 1 | 35,0 | 1 | 0 | 53.1000 | 0 | 3 |
4 | 0 | 3 | 0 | 35,0 | 0 | 0 | 8.0500 | 0 | 1 |
At this point, we will rank different types of machine learning algorithms in Python by using scikit-learn
to create a set of different models. It will then be easy to see which one performs the best.
- Logistic regression with varying numbers of polynomials
- Support vector machine with a linear kernel
- Support vector machine with a polynomial kernel
- Neural network
For every single model, we will use $k$-fold validation.
from sklearn.model_selection import KFold, train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures, StandardScaler from sklearn.neural_network import MLPClassifier from sklearn.svm import SVC # Prepare the data X = dataset.drop(['Survived'], axis = 1).values y = dataset[['Survived']].values X = StandardScaler().fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = None) # Prepare cross-validation (cv) cv = KFold(n_splits = 5, random_state = None) # Performance p_score = lambda model, score: print('Performance of the %s model is %0.2f%%' % (model, score * 100)) # Classifiers names = [ "Logistic Regression", "Logistic Regression with Polynomial Hypotheses", "Linear SVM", "RBF SVM", "Neural Net", ] classifiers = [ LogisticRegression(), make_pipeline(PolynomialFeatures(3), LogisticRegression()), SVC(kernel="linear", C=0.025), SVC(gamma=2, C=1), MLPClassifier(alpha=1), ]
# iterate over classifiers models = [] trained_classifiers = [] for name, clf in zip(names, classifiers): scores = [] for train_indices, test_indices in cv.split(X): clf.fit(X[train_indices], y[train_indices].ravel()) scores.append( clf.score(X_test, y_test.ravel()) ) min_score = min(scores) max_score = max(scores) avg_score = sum(scores) / len(scores) trained_classifiers.append(clf) models.append((name, min_score, max_score, avg_score)) fin_models = pd.DataFrame(models, columns = ['Name', 'Min Score', 'Max Score', 'Mean Score'])
fin_models.sort_values(['Mean Score']).head()
Nom | Min Score | Max Score | Mean Score | |
2 | Linear SVM | 0.793296 | 0.821229 | 0.803352 |
0 | Logistic Regression | 0.826816 | 0.860335 | 0.846927 |
4 | Neural Net | 0.826816 | 0.860335 | 0.849162 |
1 | Logistic Regression with Polynomial Hypotheses | 0.854749 | 0.882682 | 0.869274 |
3 | RBF SVM | 0.843575 | 0.888268 | 0.869274 |
Ok, so our experimental research says that the SVM classifier with a radial basis function (RBF) kernel performs the best. Now, we can serialize our model and re-use it in production applications.
import pickle svm_model = trained_classifiers[3] data_path = os.path.join(os.getcwd(), "best-titanic-model.pkl") pickle.dump(svm_model, open(data_path, 'wb'))
Machine learning is not complicated, but it's a very broad field of study, and it requires knowledge of math and statistics in order to grasp all of its concepts.
Right now, machine learning and deep learning are among the hottest topics of discussion in Silicon Valley, and are the bread and butter of almost every data science company, mainly because they can automate many repetitive tasks including speech recognition, driving vehicles, financial trading, caring for patients, cooking, marketing, and so on.
Now you can take this knowledge and solve challenges on Kaggle.
This was a very brief introduction to supervised machine learning algorithms. Luckily, there are a lot of online courses and information about machine learning algorithms. I personally would recommend starting with Andrew Ng's course on Coursera.
Ressources
- Andrew Ng's course on Coursera
- Kaggle datasets
- A deep learning reading list
- A list of free books on machine learning algorithms, data mining, deep learning, and related topics
- Une introduction à la théorie de l'apprentissage automatique et à ses applications : un didacticiel visuel avec des exemples