Classification#
Links: notebook
, html, PDF
, python
, slides, GitHub
Notebook autour d’un cas de classification binaire.
from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline
Les données#
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X, y = data.data, data.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y)
X.shape
(569, 30)
ACP#
On projette les données dans un plan, avec une couleur pour chaque classe pour voir si le problème paraît simple ou non. Pour projeter les données, le plus simple est une ACP. On conserver trois axes.
from sklearn.decomposition import PCA
acp = PCA(3)
acp.fit(X_train, y_train)
PCA(copy=True, iterated_power='auto', n_components=3, random_state=None,
svd_solver='auto', tol=0.0, whiten=False)
xacp = acp.transform(X_train)
xacp.shape
(426, 3)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].plot(xacp[y_train == 0, 0], xacp[y_train == 0, 1], '.', label="cl0")
ax[0].plot(xacp[y_train == 1, 0], xacp[y_train == 1, 1], '.', label="cl1")
ax[1].plot(xacp[y_train == 0, 1], xacp[y_train == 0, 2], '.', label="cl0")
ax[1].plot(xacp[y_train == 1, 1], xacp[y_train == 1, 2], '.', label="cl1")
ax[0].legend();ax[1].legend();
ax[0].set_title("axes 1, 2");ax[1].set_title("axes 2, 3");

Le problème n’a pas l’air trop compliqué.
Régression logistique#
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(solver='lbfgs')
logreg.fit(xacp, y_train)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='auto', n_jobs=None, penalty='l2',
random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
warm_start=False)
Les coefficients. Si sont les coordonnées des
variables projetées sur les trois axes, le modèle définit une droite
.
logreg.coef_, logreg.intercept_
(array([[-0.01244239, 0.02897613, -0.0194635 ]]), array([-1.48191575]))
La courbe définit la frontière entre les
deux classes, celle où la probabilité d’appartenir à l’une ou l’autre
classe est égale à
. On ajoute cette frontière sur
chacune des deux projections.
. Sur la
première projection,
, donc la droite a pour équation :
.
C’est ce qu’on représente ici.
import numpy
X1 = numpy.array(list(range(-1000, 1000, 10)))
X2 = (-logreg.intercept_[0]-logreg.coef_[0, 0]*X1) / logreg.coef_[0, 1]
X22 = numpy.array(list(range(-100, 100, 10)))
X3 = (-logreg.intercept_[0]-logreg.coef_[0, 1]*X22) / logreg.coef_[0, 2]
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].plot(xacp[y_train == 0, 0], xacp[y_train == 0, 1], '.', label="cl0")
ax[0].plot(xacp[y_train == 1, 0], xacp[y_train == 1, 1], '.', label="cl1")
ax[0].plot(X1, X2, '.', label="logreg")
ax[1].plot(xacp[y_train == 0, 1], xacp[y_train == 0, 2], '.', label="cl0")
ax[1].plot(xacp[y_train == 1, 1], xacp[y_train == 1, 2], '.', label="cl1")
ax[1].plot(X22, X3, '.', label="logreg")
ax[0].legend();ax[1].legend();
ax[0].set_title("axes 1, 2");ax[1].set_title("axes 2, 3");

On calcule la matrice de confusion. Il faut projeter les variables initiales sur les axes de l’ACP puis prédire.
xacp_test = acp.transform(X_test)
pred = logreg.predict(xacp_test)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, pred)
array([[42, 4],
[ 4, 93]], dtype=int64)
Courbe ROC#
La courbe ROC est une courbe souvent utilisée pour mesurer la pertinence
d’un modèle. La matrice de confusion n’utilise pas la probabilité
retournée par le modèle, cette dernière exprime si le point
est plus ou moins loin de la frontière de prédiction. En principe, plus
on est loin, moins le modèle prédictif se trompe. C’est ce que la courbe
ROC reflète, plus elle est élevé, plus c’est le cas (pour en savoir plus
: courbe
ROC).
from sklearn.metrics import roc_curve
y_pred_grd = logreg.predict_proba(xacp_test)[:, 1]
fpr_grd, tpr_grd, _ = roc_curve(y_test, y_pred_grd)
plt.figure(1, figsize=(3, 3))
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr_grd, tpr_grd);

La ligne en pointillée serait la courbe ROC obtenue pour une prédiction aléatoire. On en est loin.
Frontière polynômiale#
On souhaite une frontière polynômiale. On ajoute pour cela un
prétraitement qui calcule .
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2)
poly.fit(xacp)
PolynomialFeatures(degree=2, include_bias=True, interaction_only=False,
order='C')
xacp_poly = poly.transform(xacp)
xacp.shape, xacp_poly.shape
((426, 3), (426, 10))
logreg2 = LogisticRegression(solver='lbfgs', max_iter=1000)
logreg2.fit(xacp_poly, y_train)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=1000,
multi_class='auto', n_jobs=None, penalty='l2',
random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
warm_start=False)
Les nouveaux coefficients du modèle.
list(zip(poly.get_feature_names(), logreg2.coef_.ravel()))
[('1', 0.00013474687830331624),
('x0', -0.009796886245028377),
('x1', 0.03422545572030919),
('x2', -0.005487261266497426),
('x0^2', 6.912136603531012e-07),
('x0 x1', -4.353833813532155e-05),
('x0 x2', -0.00019255718749305347),
('x1^2', -0.0004269054807770011),
('x1 x2', -0.0018760934646931198),
('x2^2', -0.0042992267040219976)]
La frontière est plus difficile à tracer et il n’est pas évident de trouver son équation à moins de résoudre un polynôme du second degré. On calcule plutôt les prédictions pour de nombreux points disposés sur une grille en leur donnant une couleur différente selon que la classe prédite est 0 ou 1.
Xp1 = numpy.array(list(range(-1000, 1000, 10)))
Xp2 = numpy.array(list(range(-100, 100, 2)))
cote0 = []
cote1 = []
for x1 in Xp1:
for x2 in Xp2:
x = numpy.array([[x1, x2, 0]])
xp = poly.transform(x)
yp = logreg2.predict(xp)
if yp == 1:
cote1.append((x1, x2))
else:
cote0.append((x1, x2))
cote0 = numpy.array(cote0)
cote1 = numpy.array(cote1)
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].plot(cote0[:, 0], cote0[:, 1], '.', label='yp0')
ax[0].plot(cote1[:, 0], cote1[:, 1], '.', label='yp1')
ax[0].plot(xacp[y_train == 0, 0], xacp[y_train == 0, 1], '.', label="cl0")
ax[0].plot(xacp[y_train == 1, 0], xacp[y_train == 1, 1], '.', label="cl1")
ax[0].plot(X1, X2, '.', label="logreg")
ax[1].plot(xacp[y_train == 0, 1], xacp[y_train == 0, 2], '.', label="cl0")
ax[1].plot(xacp[y_train == 1, 1], xacp[y_train == 1, 2], '.', label="cl1")
ax[1].plot(X22, X3, '.', label="logreg")
ax[0].legend();ax[1].legend();
ax[0].set_xlim([-1000, 1000]);ax[0].set_ylim([-150, 150]);
ax[0].set_title("axes 1, 2");ax[1].set_title("axes 2, 3");

Ca paraît un peu mieux.
confusion_matrix(y_test,
logreg2.predict(
poly.transform(
acp.transform(X_test))))
array([[41, 5],
[ 4, 93]], dtype=int64)
Pipeline#
Ce n’est pas évident de se souvenir de toutes les étapes. Le mieux est sans doute de tout insérer dans un pipeline.
from sklearn.pipeline import Pipeline
pipe = Pipeline([
('acp', PCA(3)),
('poly', PolynomialFeatures(2)),
('lr', LogisticRegression(solver='lbfgs', max_iter=1000))
])
pipe.fit(X_train, y_train)
pred = pipe.predict(X_test)
confusion_matrix(y_test, pred)
array([[41, 5],
[ 4, 93]], dtype=int64)
C’est plus lisible.