import numpy as np # pour manipuler des valeurs numériques
import pandas as pd # pour manipuler des dataframes
import matplotlib.pyplot as plt # pour afficher des graphiques
import seaborn as sns # graphique spécialement fait pour faire de la data vizualisation
# charger les données
data = pd.read_csv('wine_dataset.csv', sep = ";")
# regarder les 5 premières lignes de data
data.head(5)
Combien avons-nous de variables et d'exemples ?
print(data.shape)
Le jeu de données contient 6497 exemples (lignes) et 13 variables (colonnes)
data = data.drop(columns=['quality'])
fig = plt.figure(figsize=(10,12))
fig.subplots_adjust(wspace=0.6, hspace=0.6)
for i, col in enumerate(data.columns.drop('color')):
ax = fig.add_subplot(4,4,i+1)
g = sns.FacetGrid(data=data, hue='color')
g.map(sns.distplot, col, ax=ax)
plt.close()
plt.show()
Sélection de variables ("à vue d'oeil") : Il semble raisonnable de sélectionner les variables explicatives volatile_acidity
, chlorides
, free_sulfur_dioxide
et total_sulfur_dioxide
.
data_reduced = data[['volatile acidity',
'chlorides',
'free sulfur dioxide',
'total sulfur dioxide',
'color']] # ne pas oublier notre "y"
Construisons notre base de test et notre base d'apprentissage :
from sklearn.model_selection import train_test_split
X = data_reduced.drop(columns='color')
y = data_reduced['color']
X_train, X_test, y_train, y_test = train_test_split(X, y)
help(train_test_split)
D'après la documentation de la fonction train_test_split
, les proporitions des échantillons d'apprentissage et de test sont de 75% - 25%. Faisons une petite vérification :
print(y_train.size / (y_train.size + y_test.size))
print(y_test.size / (y_train.size + y_test.size))
Entraînons une régression logistique :
from sklearn.linear_model import LogisticRegression
f = LogisticRegression()
f.fit(X_train, y_train)
Regardons les erreurs d'apprentissage et de test.
Remarque : f.score() nous donne la métrique accuracy qui est tout simplement la proportion de bonnes prédictions
print("Erreur d\'apprentissage :", 1-f.score(X_train, y_train))
print("Erreur de test :", 1-f.score(X_test, y_test))
print("Score sur le train-set :", f.score(X_train, y_train))
print("Score sur le test-set :", f.score(X_test, y_test))
Dans la suite, on pourra considérer directement le score plutôt que l'erreur.
from sklearn.linear_model import Perceptron
perceptron = Perceptron()
perceptron.fit(X_train, y_train)
print("Score sur le test-set :", perceptron.score(X_test, y_test))
La régression logistique semble plus performante que le perceptron. Essayons maintenant un k-NN avec $k \in\{1, \cdots, 19\}$ :
from sklearn.neighbors import KNeighborsClassifier
knn = {}
score_train = []
score_test = []
k_range = range(1,20)
for k in k_range:
knn[k] = KNeighborsClassifier(n_neighbors=k)
knn[k].fit(X_train,y_train)
score_train.append(knn[k].score(X_train,y_train))
score_test.append(knn[k].score(X_test,y_test))
k_best = np.argmax(score_test) + 1
print('Score de test du meilleur prédicteur kNN :',score_test[k_best])
plt.plot(list(k_range),score_train,label='erreur d\'apprentissage')
plt.plot(list(k_range),score_test,label='erreur de test')
plt.legend()
plt.show()
le k-NN est plus performant que le perceptron mais moins perfomant que la régression logistique. La régression logistique reste donc pour l'instant imbattable.
pd.crosstab(y_test, f.predict(X_test))
Dans la matrice de confusion, les lignes correspondent aux étiquettes réelles, et les colonnes aux étiquettes prédites. Pour chaque étiquette prédite, on a le nombre correspondant de chaque étiquette réelle. On remarque remarque qu'en proportition, un vin rouge ($y = 1$) est plus souvent prédit comme vin blanc ($y = 0$) que l'inverse.
from sklearn.utils import resample, shuffle
def balanced_data(X, y):
# 0 : white
# 1 : red
label_counts = y.value_counts()
X_white = X[y==0]
y_white = y[y==0]
X_less_white, y_less_white = resample(X_white,
y_white,
n_samples=label_counts[1],
replace=False)
X_ = pd.concat([X_less_white, X[y==1]])
y_ = pd.concat([y_less_white, y[y==1]])
X_, y_ = shuffle(X_,y_)
return(X_, y_)
X_test_, y_test_ = balanced_data(X_test, y_test)
print(y_test_.value_counts())
print('Régression logistique :', f.score(X_test_, y_test_))
print('Peceptron :', perceptron.score(X_test_, y_test_))
print('Meilleur kNN :',knn[k_best].score(X_test_, y_test_))
Lorsque l'échantillon de test contient autant de vins rouges que de vins blancs, le score de chaque prédicteur est inférieur.
X_train_, y_train_ = balanced_data(X_train, y_train)
print(y_train_.value_counts())
f_ = LogisticRegression()
f_.fit(X_train_, y_train_)
print('Régression logistique:', f_.score(X_test_,y_test_))
perceptron_ = Perceptron()
perceptron_.fit(X_train_,y_train_)
print('Perceptron:', perceptron_.score(X_test_,y_test_))
knn_ = {}
score_train = []
score_test = []
k_range = range(1, 20)
for k in k_range:
knn_[k] = KNeighborsClassifier(n_neighbors=k)
knn_[k].fit(X_train_,y_train_)
score_test.append(knn_[k].score(X_test_,y_test_))
k_best = np.argmax(score_test)
print('Meilleur kNN:', score_test[k_best])
Même si on a réduit la taille de l'échantillon d'apprentissage, du fait que celui-ci contienne autant de vins rouges que de vins blancs, on observe une légère amélioration des scores des prédicteurs obtenus.
from sklearn.model_selection import train_test_split
X_train_all, X_test_all, y_train_all, y_test_all = train_test_split(data.drop(columns='color'),
data['color'])
X_train_, y_train_ = balanced_data(X_train_all, y_train_all)
X_test_, y_test_ = balanced_data(X_test_all, y_test_all)
f_ = LogisticRegression()
f_.fit(X_train_, y_train_)
print('Régression logistique:',f_.score(X_test_,y_test_))
perceptron_ = Perceptron()
perceptron_.fit(X_train_,y_train_)
print('Perceptron', perceptron_.score(X_test_,y_test_))
knn_ = {}
score_train = []
score_test = []
k_range = range(1,20)
for k in k_range:
knn_[k] = KNeighborsClassifier(n_neighbors=k)
knn_[k].fit(X_train_,y_train_)
score_test.append(knn_[k].score(X_test_,y_test_))
k_best = np.argmax(score_test)
print('Meilleur kNN', score_test[k_best])
On observe une amélioration de tous les prédicteurs (régression logistique, kNN, et perceptron). Il est à noter que la performance du perceptron devient meilleur que les k-NN
Chargeons tout d'abord le jeu de données :
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target
print(X[1])
Modifions l'image de sorte à avoir un array de dimension 2 de taille 8x8 :
print(X[1].reshape(8,8))
Voici à quoi cela ressemble avec des jeux de couleur
plt.figure()
plt.imshow(X[1].reshape(8,8),cmap=plt.cm.gray_r)
plt.show()
for i in range(0,10):
print('Chiffre',i,':', sum(y==i))
Les classes sont à peu près équilibrées.
from sklearn.model_selection import train_test_split
X_cv, X_test, y_cv, y_test = train_test_split(X, y, train_size=200)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
knn = KNeighborsClassifier(n_neighbors=5)
scores = cross_val_score(knn, X_cv, y_cv, cv=7)
print(scores)
scores
contient les scores de validation pour chacun des 7 couples d'échantillons apprentissage/validation considérés par la 7-CV. Le score final de la 7-CV est donc la moyenne de ces 7 scores.
print("Score de validation de la 7-CV de l'algorithme 5NN: ", np.mean(scores))
from sklearn.model_selection import validation_curve
k_range = range(1,21)
train_scores, valid_scores = validation_curve(KNeighborsClassifier(),
X_cv,
y_cv,
"n_neighbors",
k_range,
cv=7)
train_mean_score = np.mean(train_scores,axis=1)
valid_mean_score = np.mean(valid_scores,axis=1)
plt.plot(k_range, train_mean_score, label='Score d\'apprentissage')
plt.plot(k_range, valid_mean_score, label='Score de test')
plt.xlabel("Hyperparamètre k")
plt.ylabel("Score 7-CV")
plt.legend()
plt.show()
k_best = k_range[np.argmax(valid_mean_score)]
k_best
Pour les grandes valeurs de k considérées, on voit que les scores d'apprentissage et de test décroissent toutes les deux lorsque k croît. De plus, on sait que le biais de l'algorithme kNN augmente lorsque k croît. On en déduit qu'il y a sous-apprentissage (underfitting) pour les grandes valeurs de k. Il est donc inutile de considérer des valeurs k encore plus grandes.
from sklearn.model_selection import learning_curve
train_size_range = range(10,151,5)
train_sizes, train_scores, valid_scores = learning_curve(
KNeighborsClassifier(n_neighbors=k_best),
X_cv,y_cv,train_sizes=train_size_range,
cv=7)
train_mean_score = np.mean(train_scores,axis=1)
valid_mean_score = np.mean(valid_scores,axis=1)
plt.plot(train_sizes, train_mean_score, label='Score d\'apprentissage')
plt.plot(train_sizes, valid_mean_score, label='Score de test')
plt.xlabel("Taille de l'échanillon d'apprentissage")
plt.ylabel("Score 7-CV")
plt.legend()
plt.show()
On voit que la courbe de score de test atteint ici un plateau (les résultats peuvent varier). Il est donc inutile de considérer des échantillons d'apprentissage plus grands.
import numpy as np
knn_dict = {}
score_train = []
score_test = []
for size in train_size_range:
X_train_,X_test_,y_train_,y_test_ = train_test_split(X_cv,y_cv,train_size=size)
knn_dict[size] = KNeighborsClassifier(n_neighbors=k_best).fit(X_train_,y_train_)
score_train.append(knn_dict[size].score(X_train_,y_train_))
score_test.append(knn_dict[size].score(X_test_,y_test_))
plt.plot(train_size_range, score_train, label='Score d\'apprentissage')
plt.plot(train_size_range, score_test, label='Score de test')
plt.xlabel("Taille de l'échantillon d'apprentissage")
plt.ylabel("Score")
plt.legend()
plt.show()
Le score de la validation croisée est obtenue par la moyenne de plusieurs (ici 7) scores de validation simple. Elle présente donc beaucoup moins de fluctuations, et permet, ici pour la lecture des courbes d'apprentissage, de mieux saisir le comportement des courbes.
knn_best = KNeighborsClassifier(n_neighbors=k_best).fit(X_cv,y_cv)
print('Score :',knn_best.score(X_test,y_test))
import pandas as pd
pd.crosstab(y_test,knn_best.predict(X_test))
L'erreur la plus courante est ici le 3 qui est pris pour un 8 (les résultats peuvent varier).
def shift_image(image,direction):
dx, dy = direction
x_shifted_image = np.zeros((8,8))
if dx > 0:
x_shifted_image[:,dx:] = image[:,:-dx]
elif dx < 0:
x_shifted_image[:,:dx] = image[:,-dx:]
else:
x_shifted_image = image
xy_shifted_image = np.zeros((8,8))
if dy > 0:
xy_shifted_image[:-dy] = x_shifted_image[dy:]
elif dy < 0:
xy_shifted_image[-dy:] = x_shifted_image[:dy]
else:
xy_shifted_image = x_shifted_image
return xy_shifted_image
plt.figure()
plt.imshow(shift_image(X[1].reshape(8,8),np.array([-1,1])),cmap=plt.cm.gray_r)
plt.show()
plt.figure()
plt.imshow(shift_image(X[1].reshape(8,8),np.array([2,-1])),cmap=plt.cm.gray_r)
plt.show()
X_augmented = np.empty((0,64))
y_augmented = np.empty((0,1))
foo = [-1,0,1]
for dx in foo:
for dy in foo:
X_augmented = np.append(X_augmented,[shift_image(x.reshape(8,8),np.array([dx,dy])).reshape(64) for x in X],axis=0)
y_augmented = np.append(y_augmented,y)
X_augmented_train, X_augmented_test, y_augmented_train, y_augmented_test = train_test_split(X_augmented,y_augmented,train_size=400)
from sklearn.model_selection import validation_curve
k_range = range(1,21)
train_scores, valid_scores = validation_curve(KNeighborsClassifier(),
X_augmented_train,
y_augmented_train,
"n_neighbors",
k_range,
cv=7)
train_mean_score = np.mean(train_scores,axis=1)
valid_mean_score = np.mean(valid_scores,axis=1)
plt.plot(k_range, train_mean_score, label='Score d\'apprentissage')
plt.plot(k_range, valid_mean_score, label='Score de test')
plt.xlabel("Hyperparamètre k")
plt.ylabel("Score 7-CV")
plt.legend()
plt.show()
k_best = k_range[np.argmax(valid_mean_score)]
train_size_range = range(10,251,10)
train_sizes, train_scores, valid_scores = learning_curve(
KNeighborsClassifier(n_neighbors=k_best),
X_augmented_train,y_augmented_train,train_sizes=train_size_range,
cv=7)
train_mean_score = np.mean(train_scores,axis=1)
valid_mean_score = np.mean(valid_scores,axis=1)
plt.plot(train_sizes, train_mean_score, label='Score d\'apprentissage')
plt.plot(train_sizes, valid_mean_score, label='Score de test')
plt.xlabel("Taille de l'échanillon d'apprentissage")
plt.ylabel("Score 7-CV")
plt.legend()
plt.show()
Le score d'apprentissage reste très élevé. Le score de test est nettement inférieur, continue d'augmenter avec avec la taille de l'échantille d'apprentissage. On est donc en situation de sur-apprentissage (overfitting). Il serait intéressant d'augmenter la taille de l'échantillon d'apprentissage. Travaillons avec un échantillon d'apprentissage/validation de taille 15000.
X_augmented_train, X_augmented_test, y_augmented_train, y_augmented_test = train_test_split(X_augmented,y_augmented,train_size=15000)
train_size_range = range(1000,10001,1000)
train_sizes, train_scores, valid_scores = learning_curve(
KNeighborsClassifier(n_neighbors=k_best),
X_augmented_train,y_augmented_train,train_sizes=train_size_range,
cv=7)
train_mean_score = np.mean(train_scores,axis=1)
valid_mean_score = np.mean(valid_scores,axis=1)
plt.plot(train_sizes, train_mean_score, label='Score d\'apprentissage')
plt.plot(train_sizes, valid_mean_score, label='Score de test')
plt.xlabel("Taille de l'échanillon d'apprentissage")
plt.ylabel("Score 7-CV")
plt.legend()
plt.show()
La courbe du score de test semble (presque) atteindre un plateau. Il semble donc que nous ayons un échantillon d'apprentissage/validation suffisamment grand. Cronstruisons à présent notre estimateur kNN sur l'ensemble de cet échantillon, et examions son score sur l'échantillon de test.
print('Score :',KNeighborsClassifier(n_neighbors=k_best).fit(X_augmented_train,y_augmented_train).score(X_augmented_test,y_augmented_test))
Le prédicteur obtenu est meilleur.