Features ou modèle¶
On se pose toujours la question du modèle de machine learning qui conviendrait le mieux à notre problème. Faut-il choisir un modèle complexe avec des features brutes ou plutôt un modèle simple avec des features retravaillées ?
[2]:
import sklearn
import matplotlib.pyplot as plt
import random
import math
import numpy
On s’arrête quelque temps sur la fresque présente sur le site de scikit-learn / Classifier comparison.
[3]:
from IPython.core.display import Image
Image(
"https://scikit-learn.org/stable/_images/sphx_glr_plot_classifier_comparison_001.png",
width=1000,
)
[3]:
Un exemple fréquemment utilisé pour illustrer la difficulté du problème est celui de deux cercles concentriques (seconde ligne).
[3]:
X1 = [(random.gauss(0, 1), random.gauss(0, 1)) for i in range(0, 100)]
X2 = [(random.gauss(4, 0.5), random.random() * 2 * math.pi) for i in range(0, 100)]
X2 = [(x[0] * math.cos(x[1]), x[0] * math.sin(x[1])) for x in X2]
Y1 = [0 for i in X1]
Y2 = [1 for i in X2]
plt.plot([x[0] for x in X1], [x[1] for x in X1], "o")
plt.plot([x[0] for x in X2], [x[1] for x in X2], "o")
plt.title("Nuage concentrique");
On applique un modèle linéaire simple : la régression logistique (assez semblable au modèle LDA = Linear Discriminant Analysis).
[4]:
X = numpy.array(X1 + X2)
Y = numpy.array(Y1 + Y2)
[5]:
import sklearn
from sklearn.linear_model import LogisticRegression
clr = LogisticRegression()
clr.fit(X, Y)
[5]:
LogisticRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
La séparation opérée par le modèle est loin d’être optimale.
[6]:
from matplotlib.colors import ListedColormap
import warnings
import numpy as np
def plot_clf_2classes(clf, X, y, title):
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max()
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max()
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500), np.linspace(y_min, y_max, 500))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
fig, ax = plt.subplots()
ax.imshow(
Z,
interpolation="nearest",
extent=(xx.min(), xx.max(), yy.min(), yy.max()),
aspect="auto",
origin="lower",
cmap=plt.cm.coolwarm,
)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
contours = ax.contour(xx, yy, Z, levels=[0], linewidths=2, linetypes="--")
ax.scatter(X[:, 0], X[:, 1], s=30, c=y, cmap=plt.cm.Paired)
ax.set_xticks(())
ax.set_yticks(())
ax.set_title(title)
return ax
plot_clf_2classes(clr, X, Y, "LogisticRegression");
On passe alors à un modèle toujours simple mais plus long à entraîner avec les plus proches voisins.
[7]:
from sklearn.neighbors import KNeighborsClassifier
clr = KNeighborsClassifier()
clr.fit(X, Y)
[7]:
KNeighborsClassifier()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier()
[8]:
plot_clf_2classes(clr, X, Y, "kNN");
C’est nettement mieux mais le modèle n’est plus aussi interprétable que le précédent, il est nettement plus long à calculer. Plus la frontière entre les classes est grande, plus il faut d’exemples dans la base d’apprentissage. Les autres modèles (arbre de décision, réseaux de neurones) proposent des séparations plus ou moins proches de la solution optimale. Le modèle SVC fonctionne bien sur ce problème.
[9]:
from sklearn.svm import SVC
clr = SVC()
clr.fit(X, Y)
[9]:
SVC()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SVC()
[10]:
plot_clf_2classes(clr, X, Y, "SVC");
Cette approche est quelque peu séduisante. Elle donne l’impression qu’il suffit de parcourir la liste des modèles disponibles pour trouver celui qui convient le mieux. Sur un problème aussi simple et petit, cela ne pose pas de problème. Un très grand nombre d’observations réduit considérablement de choix. Les plus proches voisins ou les SVM sont peu recommandés dans ce cas. Le nombre de variables ou features peut devenir un obstacle : en grande dimension, les algorithmes d’optimisation convergent moins bien.
Lorsque plus rien ne marche, il faut revenir aux données et essayer de comprendre pourquoi les modèles n’arrivent pas à capturer l’information. On cherche alors à construire une combinaison non linéaire des variables initiales. Dans notre cas, il suffit d’ajouter les produits des variables initiales pour se ramener à un problème de classification linéaire : , , , , .
[11]:
Xext = numpy.zeros((len(X), 5))
Xext[:, :2] = X
Xext[:, 2] = X[:, 0] ** 2
Xext[:, 3] = X[:, 1] ** 2
Xext[:, 4] = X[:, 0] * X[:, 1]
[12]:
clr = LogisticRegression()
clr.fit(Xext, Y)
[12]:
LogisticRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
[13]:
clr.coef_
[13]:
array([[-0.13422662, -0.08574201, 1.1199182 , 1.52645729, -0.2371076 ]])
La fonction de dessin suivante reprend la précédente à ceci près que le nombre de features est plus grand sous pour autant modifier la projection qu’on souhaite obtenir qui considère toujours les deux variables initiales sans les variations polynômiales.
[14]:
def plot_clf_2classes_poly(clf, X, y, title):
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max()
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max()
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500), np.linspace(y_min, y_max, 500))
Z = clf.predict(
np.c_[
xx.ravel(),
yy.ravel(),
(xx * xx).ravel(),
(yy * yy).ravel(),
(xx * yy).ravel(),
]
)
Z = Z.reshape(xx.shape)
fig, ax = plt.subplots(1, 1)
ax.imshow(
Z,
interpolation="nearest",
extent=(xx.min(), xx.max(), yy.min(), yy.max()),
aspect="auto",
origin="lower",
cmap=plt.cm.coolwarm,
)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
contours = ax.contour(xx, yy, Z, levels=[0], linewidths=2, linetypes="--")
ax.scatter(X[:, 0], X[:, 1], s=30, c=y, cmap=plt.cm.Paired)
ax.set_xticks(())
ax.set_yticks(())
ax.set_title(title)
return ax
plot_clf_2classes_poly(clr, Xext, Y, "LogisticRegression + features**2");
Un problème qui n’était pas linéaire l’est devenu en ajoutant les bonnes features. D’une manière générale, il est utile d’essayer de convertir toute connaissance a priori d’un problème en features de façon à aider l’apprentissage d’un modèle. Le cas le plus fréquent est le calcul de statistiques exhaustive sur un groupe d’observations liées :
On dispose de tous les achats des utilisateurs d’un site.
On veut prédire la probabilité que l’utilisateur achète lors de sa prochaine visite.
On utilise pour cela des moyennes calculées sur l’ensemble des achats précédents : on prédit au niveau achat avec des features calculées sur des groupes d”achats.
Pour aller plus loin, voir Régression logistique, diagramme de Voronoï, k-Means.