Image classification with KNN and PCA¶
Dans ce TP, nous allons utiliser une partie de la base de visages “Labeled Faces in the Wild” provenant de http://vis-www.cs.umass.edu/lfw/. Cette base contient 5749 personnes et 13233 images de taille 62 x 47 pixels. Certaines personnes ne sont représentées qu’une seule fois tandis que d’autres sont représentées très souvent (plus de 80 fois). Nous utiliserons ici seulement 7 personnes représentées 1288 fois.
# Importer les librairies
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
from sklearn.neighbors import DistanceMetric
from sklearn.decomposition import PCA
from time import perf_counter
I- Chargement des données:¶
Charger les données¶
Charger les donner, puis afficher les en utilisant la fonction plot_gallery() fournie.
def plot_gallery(images):
# Affiche les 12 premières images contenues dans images
# images est de taille Nb image*Ny*Nx
plt.figure(figsize=(7.2, 7.2))
plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)
for i in range(12):
plt.subplot(3, 4, i + 1)
plt.imshow(images[i], cmap=plt.cm.gray)
plt.xticks(())
plt.yticks(())
plt.show()
#Charger et afficher les données :
[X, y, name]=np.load("TP1.npy",allow_pickle=True )
plot_gallery(X)
Question¶
Sachant que X représente les features, y les labels et name le nom des classes, déterminer la taille des images, le nombre d’images et le nombre de classes.
Retrouver l’identité des 12 personnes affichées. Est-ce que les classes sont équiprobables ?
Retrouver le nombre d’exemples par classe. On pourra pour cela utiliser l’histogramme (plt.hist()).
print("la taille des images est : ", len(X[0]),'x',len(X[0][0]),"pixels")
print("le nombre d'images est : ", len(y))
print("le nombre de classes est : ", len(name))
la taille des images est : 62 x 47 pixels le nombre d'images est : 1288 le nombre de classes est : 7
for i,n in enumerate(name):
print(i+1,": ",n)
1 : Ariel Sharon 2 : Colin Powell 3 : Donald Rumsfeld 4 : George W Bush 5 : Gerhard Schroeder 6 : Hugo Chavez 7 : Tony Blair
print("Les classes ne sont pas équiprobables")
Les classes ne sont pas équiprobables
plt.hist(y,bins=7)
(array([ 77., 236., 121., 530., 109., 71., 144.]), array([0. , 0.85714286, 1.71428571, 2.57142857, 3.42857143, 4.28571429, 5.14285714, 6. ]), <a list of 7 Patch objects>)
Partitionnement de la base d’apprentissage¶
Partitionner la base en une base d’apprentissage et une base de test en mettant 25% des données en test (fonction train_test_split()) pour obtenir les variables X_train, X_test, y_train et y_test.
X_train,X_test,y_train,y_test=train_test_split(X, y, test_size=0.25, random_state=42)
Question¶
- Combien y a-t-il d’images en train et en test ?
- Quelles sont les dimensions des quatre variables X_train, X_test, y_train et y_test ?
print("nombre d'images en train : ", len(X_train))
print("nombre d'images en test : ", len(X_test))
print("Dimensions de X_train : ", np.shape(X_train))
print("Dimensions de X_test : ", np.shape(X_test))
print("Dimensions de y_train : ", len(y_train))
print("Dimensions de y_test : ", len(y_test))
nombre d'images en train : 966 nombre d'images en test : 322 Dimensions de X_train : (966, 62, 47) Dimensions de X_test : (322, 62, 47) Dimensions de y_train : 966 Dimensions de y_test : 322
II. Prétraitement des données:¶
Redimensionnement des données¶
Pour réaliser une classification par kppv, on utilise un codage rétinien. Chaque image est donc représentée par un vecteur de caractéristique de dimension 𝑛 = 2914. Redimensionner X_train et X_test de façon à ce qu’ils aient pour dimension 𝑁 x 𝑛 (np.reshape()) où N est le nombre d’exemples.
# a. Redimensionnement des données:
X_train=np.reshape(X_train,(966,2914))
X_test=np.reshape(X_test,(322,2914))
X_train.shape,X_test.shape
((966, 2914), (322, 2914))
Mise en forme des données pour la classification¶
Mettre en forme les données (train et test) en utilisant la classe StandardScaler. On estimera la moyenne et l’écart-type de chaque dimension sur les données d’apprentissage, puis on transformera les données (train et test) en utilisant ces valeurs. Aller sur la documentation en ligne de StandardScaler pour voir quelle méthode de cette classe utiliser.
# b. Mise en forme des données pour la classification:
Scaler=StandardScaler()
Scaler.fit(X_train)
X_train=Scaler.transform(X_train)
X_test=Scaler.transform(X_test)
Question¶
A quoi consiste la mise en forme des données ?
Comment sont-elles transformées ?
Standardiser les fonctionnalités en supprimant la moyenne et en mettant à l'échelle la variance unitaire
Le score standard d'un échantillon x est calculé comme suit:
z = (x - u) / s
Où u est la moyenne des échantillons d'apprentissage ou 0 si with_mean=False, et s est l'écart type des échantillons d'apprentissage ou 1 si with_std=False.
Le centrage et la mise à l'échelle se produisent indépendamment sur chaque fonctionnalité en calculant les statistiques pertinentes sur les échantillons de l'ensemble d'apprentissage. La moyenne et l'écart type sont ensuite stockés pour être utilisés sur des données ultérieures à l'aide de la transformation.
III. Classification par les KPPV:¶
Classifieur 1PPV¶
Définir le classifieur 1PPV en utilisant la classe KNeighborsClassifier(). On souhaite utiliser la distance euclidienne et le 1PPV.
Train le classifieur sur X_train, y_train.
Réaliser la classification des exemples de test en utilisant la méthode predict().
Questions¶
- Que représente la matrice de confusion ?
- Que vaut sa somme ?
- Est-ce que les classes sont équilibrées ?
#a. Classifieur 1PPV:
neigh = KNeighborsClassifier(n_neighbors=1,p=2)
neigh.fit(X_train, y_train)
y_pred=neigh.predict(X_test)
Afficher la matrice de confusion (fonction confusion_matrix()) et estimer le taux dereconnaissance à partir des éléments de cette matrice. Vérifier que le taux est identique à celui renvoyé par la fonction accuracy_score().
print(confusion_matrix(y_test, y_pred, labels=range(7)),"\n")
print("taux de reconnaissance : ",accuracy_score(y_test, y_pred))
[[ 5 1 0 4 2 1 0] [ 2 31 4 12 4 3 4] [ 3 5 15 4 0 0 0] [ 2 5 9 102 6 7 15] [ 1 2 3 5 9 1 4] [ 0 1 0 4 2 8 0] [ 1 3 2 10 2 2 16]] taux de reconnaissance : 0.577639751552795
sum(confusion_matrix(y_test, y_pred, labels=range(7)))
array([ 14, 48, 33, 141, 25, 22, 39])
(5+31+15+102+9+8+16)/sum(sum(confusion_matrix(y_test, y_pred, labels=range(7))))
0.577639751552795
Classifieur KPPV¶
Faire varier le K des KPPV et tracer l’évolution du taux de reconnaissance en fonction de K.
#c. Classifieur KPPV et distance de Manhattan:
score=[]
kplot=[]
for k in range(1,5,1):
neigh = KNeighborsClassifier(n_neighbors=k)
neigh.fit(X_train, y_train)
y_pred=neigh.predict(X_test)
score.append(accuracy_score(y_test, y_pred))
kplot.append(k)
# Variation du taux de reconnaissance en fonction de k :
plt.plot(kplot,score)
[<matplotlib.lines.Line2D at 0x7f770dcac580>]
Questions :¶
Conclusion ? Interpréter l’évolution des résultats en fonction de K
Classifieur KPPV et distance de Manhattan¶
Réaliser les mêmes tests avec la distance de Manhattan.
#c. Classifieur KPPV et distance de Manhattan:
score=[]
kplot=[]
for k in range(1,5,1):
neigh = KNeighborsClassifier(n_neighbors=k, metric='manhattan')
neigh.fit(X_train, y_train)
y_pred=neigh.predict(X_test)
score.append(accuracy_score(y_test, y_pred))
kplot.append(k)
# Variation du taux de reconnaissance en fonction de k :
plt.plot(kplot,score)
[<matplotlib.lines.Line2D at 0x7f770dc93f70>]
Questions :¶
- Conclusion ? Interpréter l’évolution des résultats en fonction de K.
- Pour résoudre ce problème de classification, quelle distance et valeur de K choisissez-vous suite à ces tests ?
TP2: Analyse en composantes principales, classification et reconstruction :¶
print("il y a", len(X_train) ,"données en apprentissage")
print("il y a",len(X_test) ,"données en test")
print("la dimension des données après redimensionnement : X_train dimension = " ,X_train.shape," X_test dimension = ",X_test.shape)
il y a 966 données en apprentissage il y a 322 données en test la dimension des données après redimensionnement : X_train dimension = (966, 2914) X_test dimension = (322, 2914)
II. Analyse en composantes principales et classification¶
- Définissez la décomposition en composantes principales en utilisant la fonction PCA() en gardant le maximum de composantes, ajuster le modèle sur X_train ( ) puis tracer les variances en utilisant l’attribut pca.explained_variance_ratio_
#Définissez la décomposition en composantes principales en gardant le maximum de composantes
pca = PCA(n_components=None)
#ajuster le modèle sur X_train
pca.fit(X_train)
#1. tracer les variances
plt.plot(np.cumsum(pca.explained_variance_ratio_))
[<matplotlib.lines.Line2D at 0x7f770bbbceb0>]
- Redéfinissez la décomposition en utilisant la fonction en conservant 100 composantes, ajuster le modèle sur X_train, puis transformez les données X_train et X_test pour obtenir X_train1 et X_test1.
#2.Redéfinissez la décomposition en utilisant la fonction PCA() en conservant 100 composantes
pca = PCA(n_components=100)
#ajuster le modèle sur X_train, puis transformez les données X_train et X_test pour obtenir X_train1 et X_test1.
pca.fit(X_train)
X_train1=pca.transform(X_train)
X_test1=pca.transform(X_test)
- Réaliser la classification sur les données de départ puis sur les nouvelles données avec la méthode du 5PPV et la distance de Manhattan. Conclure sur le taux de reconnaissance et les temps de calcul qui peuvente être déterminés par :
from time import perf_counter
tps1 = perf_counter()
tps2 = perf_counter()
print("Durée de classification",tps2 - tps1)
#3. a- Classifieur 5PPV sur les données de depart:
tps1 = perf_counter()
neigh = KNeighborsClassifier(n_neighbors=5,metric='manhattan')
neigh.fit(X_train, y_train)
y_pred=neigh.predict(X_test)
print("taux de reconnaissance : ",accuracy_score(y_test, y_pred))
tps2 = perf_counter()
print("Durée de classification",tps2 - tps1)
taux de reconnaissance : 0.6645962732919255 Durée de classification 1.1849916400001348
#3. b- Classifieur 5PPV sur les nouvelles données:
tps1_acp = perf_counter()
neigh = KNeighborsClassifier(n_neighbors=5,metric='manhattan')
neigh.fit(X_train1, y_train)
y_pred1=neigh.predict(X_test1)
print("taux de reconnaissance : ",accuracy_score(y_test, y_pred1))
tps2_acp = perf_counter()
print("Durée de classification",tps2_acp - tps1_acp)
taux de reconnaissance : 0.6925465838509317 Durée de classification 0.050232807000156754
Questions :¶
Que représentent les valeurs renvoyées par pca.explained_variance_ratio_ ?
Observer la taille de X_train1 et X_test1. Quelle est la nouvelle dimension des données ?
Comment varient les temps de calcul entre une classification avec ou sans ACP ?
Comment varient les taux de reconnaissance ?
Les valeurs renvoyées par pca.explained_variance_ratio_ représentent: Les valeurs propres exprimées en pourcentage de la variance expliquée par chacune des composantes sélectionnées.
#2.
print("Les nouvelles dimensions des données X_train1 et X_test1 sont : X_train1 dimension = " ,X_train1.shape," X_test1 dimension = ",X_test1.shape,"\n")
print("Le temps de calcul dans une classification avec ACP: ", tps2_acp - tps1_acp)
print("Le taux de reconnaissance dans une classification avec ACP : ",accuracy_score(y_test, y_pred1),"\n" )
print("Le temps de calcul dans une classification sans ACP: ", tps2 - tps1)
print("Le taux de reconnaissance dans une classification sans ACP : ",accuracy_score(y_test, y_pred) )
Les nouvelles dimensions des données X_train1 et X_test1 sont : X_train1 dimension = (966, 100) X_test1 dimension = (322, 100) Le temps de calcul dans une classification avec ACP: 0.09524810099992465 Le taux de reconnaissance dans une classification avec ACP : 0.7018633540372671 Le temps de calcul dans une classification sans ACP: 2.8797286280000662 Le taux de reconnaissance dans une classification sans ACP : 0.6645962732919255
Les resultats son meilleur avec PCA, moins de temps de calcule pour une reconnaissance pareil ou même mieux.
III. Analyse en composantes principales et reconstruction¶
Le but est de compresser les images afin qu’elle prenne moins de place en mémoire. On va donc définir sur X_train la façon de compresser. Puis on comprimera et décomprimera les images de X_test afin de voir les pertes induites par la compression.
Définissez la décomposition en composantes principales en utilisant la fonction PCA() en conservant 50 composantes et ajuster le modèle sur X_train.
Récupérer les vecteurs propres en utilisant un attribut de PCA().
Redimensionner les vecteurs propres en images propres (np.reshape()) de manière à pourvoir les visualiser sous forme d’images (array de taille 50x62x47). On utilisera la fonction plot_gallery() pour la visualisation.
pca = PCA(n_components=50)
pca.fit(X_train)
plot_gallery(np.reshape(pca.components_,(50,62,47)))
Questions:
- Que représentent les vecteurs propres ?
- Quelle est leur taille ?
Les vecteurs propres représentent:
Les axes principaux dans l'espace d'entités, représentant les directions de variance maximale dans les données. Les composants sont triés par explication_variance_.
print("La taille des vecteurs propres : ",len(pca.components_) )
pca.components_.shape
La taille des vecteurs propres : 50
(50, 2914)
- On souhaite comprimer les images de X_test afin de les transmettre en utilisant le moins de bande passante possible. Pour cela, les 50 images propres sont transmises une fois. Pour chaque nouvelle image, on transmet uniquement ses composantes dans le nouveau système d’axe de dimension 50. L’image est ensuite reconstruite à l’arrivée. Appliquer l’ACP des images de X_test (X_testC)
#3 . l’ACP des images de X_test (X_testC):
X_testC=pca.transform(X_test)
X_testC.shape
(322, 50)
- Reconstruisez les images à partir X_testC pour obtenir les images X_testR à partir d’une des méthodes de PCA(). Afficher les images reconstruites et les comparer visuellement aux images de départ.
X_testR_manuelle=np.dot(X_testC,pca.components_)
plot_gallery(np.reshape(X_testR_manuelle,(322,62,47)))
#4 . Reconstruisez les images à partir X_testC pour obtenir les images X_testR:
X_testR=pca.inverse_transform(X_testC)
plot_gallery(np.reshape(X_test,(322,62,47)))
plot_gallery(np.reshape(X_testR,(322,62,47)))
- Comparer les images initiales et reconstruites de manière quantitative en faisant la moyenne des distances euclidiennes :
E= (X_testR-X_test)**2
E = np.mean(np.sqrt(np.sum(E,axis=0)))
#5. t la moyenne des distances euclidiennes
E= (X_testR-X_test)**2
E = np.mean(np.sqrt(np.sum(E,axis=0)))
E
7.5021877
Questions:
Comparer les tailles de X_test et X_testC et en déduire le taux de compression.
Observer la taille de X_testR. Quel est le principe de la reconstruction des images ?
Comment passe-t-on de X_testC à X_testR ?
print("le taux de compression = ", (2914/50)*0.01)
le taux de compression = 0.5828
X_testR.shape, X_testC.shape
((322, 2914), (322, 50))
- Faire varier le nombre de composantes conservées de 10 à 950 par pas de 50 et calculer l’erreur de reconstruction. Afficher l’erreur de reconstruction en fonction du nombre de composantes.
#6.
E=[]
I=[]
for i in range(10,950,50):
pca = PCA(n_components=i)
pca.fit(X_train)
X_testC=pca.transform(X_test)
X_testR=pca.inverse_transform(X_testC)
e=(X_testR-X_test)**2
E.append(np.mean(np.sqrt(np.sum(e,axis=0))))
I.append(i)
plot_gallery(np.reshape(X_testR,(322,62,47)))
#variation de l’erreur de reconstruction en fonction du nombre de composantes
plt.plot(I,E)
[<matplotlib.lines.Line2D at 0x7f77101b92b0>]
- Comment varie l’erreur de reconstruction en fonction du nombre de composantes ?
- Comparer visuellement les images initiales et reconstruites à partir de 950 composantes. Conclusion ?