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>
No description has been provided for this image
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'
No description has been provided for this image

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>
No description has been provided for this image
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

Pistes pour amélioration¶

cf. https://github.com/bochendong/few_shot_classification

Varier les plaisirs avec R !!!¶

cf. https://torch.mlverse.org/