Vérification des versions des librairies¶
In [ ]:
#version de Python
import sys
sys.version
Out[ ]:
'3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]'
In [ ]:
#version de PyTorch
#https://pytorch.org/
import torch
torch.__version__
Out[ ]:
'2.3.0+cpu'
In [ ]:
#version de torchvision
#https://pytorch.org/vision/stable/index.html
import torchvision as tv
tv.__version__
Out[ ]:
'0.18.0+cpu'
In [ ]:
#autres imports de librairie
import matplotlib.pyplot as plt
import numpy as np
Import et préparation des données TRAIN¶
In [ ]:
#changement de dossier - dossier racine de travail
import os
os.chdir("C:/Users/ricco/Desktop/demo")
Fonction pour transformation à la volée lors du chargement :
- Transformer image en tenseur compatible avec PyTorch
- Harmoniser la définition en (128 x 128)
In [ ]:
#resizing des images en entrée, elles n'ont pas toutes les mêmes définitions
#puis tranformation en tenseur
my_transform = tv.transforms.Compose(
[tv.transforms.Resize((128,128)),
tv.transforms.ToTensor()]
)
Chargement avec transformation à la volée.
In [ ]:
#chargement des images avec transformation à la volée
#on traite le sous-dossier "./train" pour les images d'apprentissage
dataTrain = tv.datasets.ImageFolder(root='./images/train',transform=my_transform)
Quelques vérifications
In [ ]:
#type de dataset
type(dataTrain)
Out[ ]:
torchvision.datasets.folder.ImageFolder
In [ ]:
#liste des classes
dataTrain.classes
Out[ ]:
['Beedrill', 'Cubone']
In [ ]:
#classe d'appartenance des images
print(dataTrain.targets)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
In [ ]:
#en associant les deux
np.array(dataTrain.classes)[np.array(dataTrain.targets)]
Out[ ]:
array(['Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Beedrill', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone', 'Cubone'], dtype='<U8')
In [ ]:
#distribution des classes dans l'échantillon TRAIN
np.unique(np.array(dataTrain.classes)[np.array(dataTrain.targets)],return_counts=True)
Out[ ]:
(array(['Beedrill', 'Cubone'], dtype='<U8'), array([24, 30], dtype=int64))
DataLoader permet d'organiser les données sous forme de d'objets itérables, éventuellement par blocs (batch) pour plus de rapidité.
In [ ]:
#on va traiter les images individuellement
#pour une meilleure lisibilité de notre tutoriel - batch_size = 1
#shuffle pour que l'ordre soit mélangé au hasard
#transformation en type dataloader
#cf. https://pytorch.org/tutorials/beginner/basics/data_tutorial.html
dataTrain_loader = torch.utils.data.DataLoader(dataTrain,
batch_size=1,
shuffle=True)
In [ ]:
#type du data loader
type(dataTrain_loader)
Out[ ]:
torch.utils.data.dataloader.DataLoader
Analyse de la structuration d'une image
In [ ]:
# créer un itérateur pour parcourir les images
dataiter = iter(dataTrain_loader)
In [ ]:
#accès à la première image avec next()
image, label = next(dataiter)
#type initial Tensor (de torch)
#oui, car transformation à la volée lors du chargement
type(image)
Out[ ]:
torch.Tensor
In [ ]:
#cast en numpy et dimensions
#1 parce que batch_size = 1
#3 parce que 3 couleurs
#(128 x 128) défintion de l'image
image.numpy().shape
Out[ ]:
(1, 3, 128, 128)
In [ ]:
#retrait de la dimension égale à 1 (du batch_size)
image.numpy().squeeze().shape
Out[ ]:
(3, 128, 128)
In [ ]:
#réorganisation pour que matplotlib puisse le gérer
#lors de l'affichage - bien regarder l'option "axes"
np.transpose(image.numpy().squeeze(),axes=(1,2,0)).shape
Out[ ]:
(128, 128, 3)
In [ ]:
#affichage de l'image maintenant
plt.imshow(np.transpose(image.numpy().squeeze(),(1,2,0)))
Out[ ]:
<matplotlib.image.AxesImage at 0x1fb82333590>
In [ ]:
#son étiquette (classe d'appartenance)
print(label)
tensor([1])
In [ ]:
#ou, plus explicitement
np.array(dataTrain.classes)[label]
Out[ ]:
'Cubone'
In [ ]:
#image suivante (sachant qu'il y a eu un shuffle)
image, label = next(dataiter)
#affichage
plt.imshow(np.transpose(image.numpy().squeeze(),(1,2,0)))
#label
np.array(dataTrain.classes)[label]
Out[ ]:
'Beedrill'
Réseau de neurones convolutifs avec PyTorch¶
Architecture du CNN¶
cf. https://fr.wikipedia.org/wiki/R%C3%A9seau_neuronal_convolutif
In [ ]:
import torch.nn as nn
import torch.nn.functional as F
#classe - CNN pour mes pokémons
class MyNet(nn.Module):
#outils pour le réseau
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
#voir calcul dimension flatten - http://layer-calc.com/
self.fc1 = nn.Linear(16*29*29, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 2) #2 classes !
#organisation des opérations
def forward(self, x):
#traitement convolutif
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
#vectorisation
x = torch.flatten(x, 1) # flatten all dimensions except batch
#perceptron multicouche à partir d'ici
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
In [ ]:
#instanciation de l'objet
net = MyNet()
Fonction de perte et algo d'optimisation
In [ ]:
#fonction de perte et algorithme d'optimisation
import torch.optim as optim
#fonction de perte - cf. les implications
#(1) sur la sortie du reséau (fonction de classement)
#(2) sur l'encodage de la cible (codage 0, 1, 2, ...)
#https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
criterion = nn.CrossEntropyLoss()
#Adam - une variante de la descente de gradient stochastique
#https://pytorch.org/docs/stable/generated/torch.optim.Adam.html
optimizer = optim.Adam(net.parameters())
Programmation du processus "fit" (attention, exécution prend un peu de temps).
In [ ]:
#nombre d'epochs
n_epochs = 15
#récupérer les valeurs de perte dans un vecteur
losses = np.zeros(n_epochs)
#itérer n_epochs fois sur la base TRAIN entière
for epoch in range(n_epochs):
#initialisation de la valeur de la perte pour un epoch
running_loss = 0.0
#pour chaque individu de train
for data in iter(dataTrain_loader):
# accès à un individu : image, étiquette
input, label = data
# ràz des gradients
optimizer.zero_grad()
# calcul de l'application du réseau (sortie)
output = net.forward(input) #ou simplement net(input)
#calcul de la perte
loss = criterion(output, label)
#calcul du gradient et rétropropagation
loss.backward()
#mise à jour des poids synaptiques
optimizer.step()
# additionner la perte pour un epoch
running_loss += loss.item()
#ajouter la perte mesurée dans le vecteur dédié
print(running_loss)
losses[epoch] = running_loss
print('Finished Training')
38.66519337892532 41.79279757477343 36.54771201312542 31.116625532507896 26.23994657629646 23.82244254462421 13.071514390350785 5.89361657304795 10.239030674868836 4.563875389364931 0.8179756197956181 0.022426105884775893 0.013739720717985904 0.009588307316604983 0.007090120222912333 Finished Training
Vérification pour une image
In [ ]:
#accès à une image quelconque
image, label = next(dataiter)
#affichage de l'image
plt.imshow(np.transpose(image.numpy().squeeze(),(1,2,0)))
Out[ ]:
<matplotlib.image.AxesImage at 0x1fb825629d0>
In [ ]:
#"appliquer" le modèle sur l'image'
output = net.forward(image)
#sortie = "score" (degré) d'appartenance aux classes
#mais non normalisée
#argmax pour l'affectation néanmoins
print(output)
tensor([[ 10.5080, -12.6221]], grad_fn=<AddmmBackward0>)
In [ ]:
#étiquette attribuée par le modèle
np.argmax(output.detach().numpy())
Out[ ]:
0
In [ ]:
#vraie étiquette d'appartenance
#attention, on a une liste
print(label.numpy())
[0]
In [ ]:
#ou pour obtenir le chiffre directement
print(label.numpy()[0])
0
In [ ]:
#vraie étiquette d'appartenance (bis)
print(np.array(dataTrain.classes)[label.numpy()[0]])
Beedrill
Evaluation sur les images TEST¶
Chargement et préparation
In [ ]:
#chargement des images en test - avec transformation à la volée
dataTest = tv.datasets.ImageFolder(root='./images/test',transform=my_transform)
#liste des classes
dataTest.targets
Out[ ]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
In [ ]:
#transformation en "loader" - pas besoin de shuffle ici
dataTest_loader = torch.utils.data.DataLoader(dataTest,
batch_size=1)
Prédictions des classes d'appartenance
In [ ]:
#evaluation - calcul de l'accuracy
#structure pour stocker classe prédite et classe réelle
lst_pred = []
lst_real = []
#itérer sur chaque individu
for data in iter(dataTest_loader):
#image et etiquette de l'individu
image,label = data
#prédiction du réseau
sortie = net.forward(image)
#en déduire la classe attribuée
pred = np.argmax(sortie.detach().numpy())
#et la classe réelle
real = label.numpy()[0]
#stocker
lst_pred.append(pred)
lst_real.append(real)
#affichages
print("Prediction : ")
print(lst_pred)
print("Appartenance réelle :")
print(lst_real)
Prediction : [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] Appartenance réelle : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Confrontation prédiction et classe effective d'appartenance.
In [ ]:
#calcul de l'accuracy
acc = np.mean((np.array(lst_pred)==np.array(lst_real)))
print(f"Accuracy = {acc}")
Accuracy = 0.9