Réseau de neurones convolutif PyTorch

Le réseau de neurones convolutif PyTorch (CNN) est une classe de modèles d'apprentissage profond spécialement conçus pour traiter des données à topologie de type grille (comme les images).

CNN est une technologie essentielle pour les tâches de vision par ordinateur (telles que la classification d'images, la détection d'objets et la segmentation).

Le diagramme suivant montre la structure et le flux de travail d'un réseau de neurones convolutif (CNN) typique utilisé pour des tâches de reconnaissance d'images.

Dans la figure, la couche de sortie du CNN donne les probabilités pour trois classes : Donald (0.2), Goofy (0.1) et Tweety (0.7), ce qui indique que le réseau estime que l'image d'entrée est très probablement Tweety.

Voici une brève description de chaque partie :

  • Image d'entrée: les données d'image brutes reçues par le réseau.

  • Convolution: Un noyau glisse sur l'image d'entrée pour extraire des caractéristiques et générer des cartes de caractéristiques.

  • Pooling: Généralement appliqué après la couche convolutive, le max pooling ou l'average pooling réduit la taille des cartes de caractéristiques tout en préservant les caractéristiques importantes, produisant des cartes de caractéristiques après pooling.

  • Extraction de caractéristiques: La combinaison de plusieurs couches de convolution et de pooling extrait progressivement des caractéristiques de haut niveau à partir de l'image.

  • Couche d'aplatissement: Convertit les cartes de caractéristiques multidimensionnelles en un vecteur unidimensionnel pour l'entrée dans la couche entièrement connectée.

  • Couche entièrement connectée: Similaire aux couches traditionnelles des réseaux de neurones, elle mappe les caractéristiques extraites vers les classes de sortie.

  • Classification: La couche de sortie du réseau effectue la classification en fonction de la sortie de la couche entièrement connectée.

  • Distribution probabiliste: La couche de sortie donne la probabilité pour chaque classe, représentant la probabilité que l'image d'entrée appartienne à chaque classe.

Structure de base des réseaux de neurones convolutifs

1. Couche d'entrée

Il reçoit les données brutes de l'image. Une image est généralement représentée sous la forme d'un tableau tridimensionnel, où deux dimensions représentent la largeur et la hauteur de l'image, et la troisième dimension représente les canaux de couleur (par exemple, une image RGB a trois canaux).

2. Couche de convolution

Il utilise des noyaux de convolution pour extraire des caractéristiques locales, telles que les contours et les textures.

Formule :

  • x : image d'entrée.
  • k : noyau de convolution (matrice de poids).
  • b : biais.

Un groupe de filtres apprenables (ou noyaux de convolution) est appliqué à l'image d'entrée via des opérations de convolution pour extraire des caractéristiques locales.

Chaque filtre glisse sur l'image d'entrée et génère une carte de caractéristiques, représentant l'activation du filtre à différentes positions.

Une couche de convolution peut avoir plusieurs filtres ; chaque filtre génère une carte de caractéristiques, et toutes les cartes de caractéristiques forment un ensemble de cartes de caractéristiques.

3. Fonction d'activation

Une fonction d'activation non linéaire, telle que ReLU (Rectified Linear Unit), est généralement appliquée après la couche de convolution pour introduire de la non-linéarité, permettant au réseau d'apprendre des motifs plus complexes.

La fonction ReLU est définie comme :f(x)=max(0,x), c'est-à-dire que si l'entrée est inférieure à 0, la sortie vaut 0 ; sinon, la sortie vaut la valeur d'entrée.

4. Couche de pooling

  • Elle est utilisée pour réduire les dimensions spatiales des cartes de caractéristiques, diminuant ainsi le coût de calcul et le nombre de paramètres tout en conservant les informations sur les caractéristiques les plus importantes.
  • Les opérations de pooling les plus courantes sont le pooling maximal et le pooling moyen.
  • Le pooling maximal sélectionne la valeur maximale dans la région, tandis que le pooling moyen calcule la valeur moyenne dans la région.

5. Couche de normalisation (optionnelle)

  • Par exemple, la normalisation de réponse locale (LRN) ou la normalisation par lots.
  • Ces couches aident à accélérer le processus d'entraînement et à améliorer la stabilité du modèle.

6. Couche entièrement connectée

  • À la fin d'un CNN, les cartes de caractéristiques extraites par les couches précédentes sont aplaties en un vecteur unidimensionnel, puis transmises à la couche entièrement connectée.
  • Chaque neurone de la couche entièrement connectée est connecté à tous les neurones de la couche précédente, combinant les caractéristiques pour la classification ou la régression finale.

7. Couche de sortie

Selon la tâche, la couche de sortie peut prendre différentes formes.

Pour les tâches de classification, la fonction Softmax est généralement utilisée pour convertir la sortie en une distribution de probabilité, représentant la probabilité que l'entrée appartienne à chaque classe.

8. Fonction de perte

Elle sert à mesurer la différence entre les prédictions du modèle et les étiquettes réelles.

Les fonctions de perte courantes incluent la perte d'entropie croisée pour les tâches de classification multiclasse et l'erreur quadratique moyenne (MSE) pour les tâches de régression.

9. Optimiseur

Il est utilisé pour mettre à jour les poids du réseau en fonction du gradient de la fonction de perte. Les optimiseurs courants incluent la descente de gradient stochastique (SGD), Adam, RMSprop, etc.

10. Régularisation (facultative)

Il inclut des techniques telles que Dropout et la régularisation L1/L2 pour éviter le surapprentissage du modèle.

Ces couches peuvent être empilées pour former des structures de réseau plus profondes afin d'améliorer la capacité d'apprentissage du modèle.

La profondeur et la complexité d'un CNN peuvent être ajustées en fonction des exigences de la tâche.


Implémentation d'un exemple de CNN avec PyTorch

L'exemple suivant montre comment construire un modèle CNN simple avec PyTorch pour la classification de chiffres sur le jeu de données MNIST.

Étapes principales :

  • Chargement et prétraitement des données: Utiliseztorchvisionchargez et prétraitez les données MNIST.
  • Construction du modèle: Définissez les couches convolutives, de pooling et entièrement connectées.
  • Entraînement: Entraînez le modèle à l'aide d'une fonction de perte et d'un optimiseur.
  • Évaluation: Calculez la précision du modèle sur l'ensemble de test.
  • Visualisation: Afficher quelques échantillons de test et leurs résultats de prédiction.

1. Importer les bibliothèques requises

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

2. Chargement des données

Utilisez le jeu de données MNIST fourni par torchvision pour charger et prétraiter les données.

Exemple

transform = transforms.Compose([
    transforms.ToTensor(),  # Convertir en tenseur
    transforms.Normalize((0.5,), (0.5,))  # Normaliser vers [-1, 1]
])

# Charger le jeu de données MNIST
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

3. Définir le modèle CNN

Utilisernn.Moduleconstruire un CNN.

Exemple

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # Définir la couche de convolution : 1 canal d'entrée, 32 canaux de sortie, taille du noyau 3x3
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
        # Définir la couche de convolution : 32 canaux d'entrée, 64 canaux de sortie
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
        # Définir la couche entièrement connectée
        self.fc1 = nn.Linear(64 * 7 * 7, 128)  # Taille d'entrée = taille de la carte de caractéristiques * nombre de canaux
        self.fc2 = nn.Linear(128, 10)  # 10 classes

    def forward(self, x):
        x = F.relu(self.conv1(x))  # Première couche de convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = F.relu(self.conv2(x))  # Deuxième couche de convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = x.view(-1, 64 * 7 * 7) # Opération d'aplatissement
        x = F.relu(self.fc1(x))    # Couche entièrement connectée + ReLU
        x = self.fc2(x)            # Sortie de la couche entièrement connectée
        return x

# Créer une instance du modèle
model = SimpleCNN()

4. Définir la fonction de perte et l'optimiseur

Utiliser la perte d'entropie croisée et l'optimiseur de descente de gradient stochastique.

criterion = nn.CrossEntropyLoss()  # 多分类交叉熵损失
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)  # 学习率和动量

5. Entraîner le modèle

Entraîner le modèle pendant 5 époques et afficher la perte d'entraînement après chaque époque.

Exemple

num_epochs = 5
model.train()  # Passer en mode entraînement

for epoch in range(num_epochs):
    total_loss = 0
    for images, labels in train_loader:
        # Passe avant
        outputs = model(images)
        loss = criterion(outputs, labels)
       
        # Rétropropagation
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()
    print(f"Époque [{epoch+1}/{num_epochs}], Perte : {total_loss / len(train_loader):.4f}")

6. Tester le modèle

Évaluer la précision du modèle sur l'ensemble de test.

Exemple

model.eval()  # Passer en mode évaluation
correct = 0
total = 0

with torch.no_grad():  # Aucun calcul de gradient nécessaire pendant l'évaluation
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs, 1)  # Prédire les classes
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Précision du test : {accuracy:.2f}%")

7. Code complet

Le code complet est le suivant :

Exemple

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


# 1. Chargement et prétraitement des données
transform = transforms.Compose([
    transforms.ToTensor(),  # Convertir en tenseur
    transforms.Normalize((0.5,), (0.5,))  # Normaliser vers [-1, 1]
])

# Charger le jeu de données MNIST
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 2. Définir le modèle CNN
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # Définir les couches convolutives
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)  # Entrée 1 canal, sortie 32 canaux
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)  # Entrée 32 canaux, sortie 64 canaux
        # Définir les couches entièrement connectées
        self.fc1 = nn.Linear(64 * 7 * 7, 128)  # Aplatir et alimenter la couche entièrement connectée
        self.fc2 = nn.Linear(128, 10)  # 10 classes

    def forward(self, x):
        x = F.relu(self.conv1(x))  # Première convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = F.relu(self.conv2(x))  # Deuxième convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = x.view(-1, 64 * 7 * 7) # Aplatir
        x = F.relu(self.fc1(x))    # Couche entièrement connectée + ReLU
        x = self.fc2(x)            # Couche de sortie finale
        return x

# Créer une instance du modèle
model = SimpleCNN()

# 3. Définir la fonction de perte et l'optimiseur
criterion = nn.CrossEntropyLoss()  # Perte d'entropie croisée multiclasse
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# 4. Entraînement du modèle
num_epochs = 5
model.train()  # Passer le modèle en mode entraînement

for epoch in range(num_epochs):
    total_loss = 0
    for images, labels in train_loader:
        outputs = model(images)  # Passe avant
        loss = criterion(outputs, labels)  # Calculer la perte

        optimizer.zero_grad()  # Effacer les gradients
        loss.backward()  # Rétropropagation
        optimizer.step()  # Mettre à jour les paramètres

        total_loss += loss.item()

    print(f"Époque [{epoch+1}/{num_epochs}], Perte : {total_loss / len(train_loader):.4f}")

# 5. Test du modèle
model.eval()  # Passer le modèle en mode évaluation
correct = 0
total = 0

with torch.no_grad():  # Désactiver le calcul des gradients
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Précision du test : {accuracy:.2f}%")

Explication des résultats d'exécution

1. Perte d'entraînement en sortie

Le code affiche une perte moyenne après chaque époque, par exemple :

Epoch [1/5], Loss: 0.2325
Epoch [2/5], Loss: 0.0526
Epoch [3/5], Loss: 0.0366
Epoch [4/5], Loss: 0.0273
Epoch [5/5], Loss: 0.0221

Explication :La diminution progressive de la perte indique que le modèle converge étape par étape.

2. Exactitude sur l'ensemble de test

Le code affiche l'exactitude de classification finale sur l'ensemble de test, par exemple :

Test Accuracy: 98.96%

Explication :Le modèle atteint une exactitude de classification de 98.96% sur l'ensemble de test MNIST, ce qui est un bon résultat pour un modèle CNN simple.

7. Visualiser les résultats

Nous pouvons visualiser quelques échantillons et leurs résultats de prédiction dans les données de test.

Exemple

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt

# 1. Chargement et prétraitement des données
transform = transforms.Compose([
    transforms.ToTensor(),  # Convertir en tenseur
    transforms.Normalize((0.5,), (0.5,))  # Normaliser vers [-1, 1]
])

# Charger le jeu de données MNIST
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)

train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 2. Définir le modèle CNN
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # Définir les couches convolutives
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)  # Entrée 1 canal, sortie 32 canaux
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)  # Entrée 32 canaux, sortie 64 canaux
        # Définir les couches entièrement connectées
        self.fc1 = nn.Linear(64 * 7 * 7, 128)  # Aplatir et alimenter la couche entièrement connectée
        self.fc2 = nn.Linear(128, 10)  # 10 classes

    def forward(self, x):
        x = F.relu(self.conv1(x))  # Première convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = F.relu(self.conv2(x))  # Deuxième convolution + ReLU
        x = F.max_pool2d(x, 2)     # Pooling maximal
        x = x.view(-1, 64 * 7 * 7) # Aplatir
        x = F.relu(self.fc1(x))    # Couche entièrement connectée + ReLU
        x = self.fc2(x)            # Couche de sortie finale
        return x

# Créer une instance du modèle
model = SimpleCNN()

# 3. Définir la fonction de perte et l'optimiseur
criterion = nn.CrossEntropyLoss()  # Perte d'entropie croisée multiclasse
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# 4. Entraînement du modèle
num_epochs = 5
model.train()  # Passer le modèle en mode entraînement

for epoch in range(num_epochs):
    total_loss = 0
    for images, labels in train_loader:
        outputs = model(images)  # Passe avant
        loss = criterion(outputs, labels)  # Calculer la perte

        optimizer.zero_grad()  # Effacer les gradients
        loss.backward()  # Rétropropagation
        optimizer.step()  # Mettre à jour les paramètres

        total_loss += loss.item()

    print(f"Époque [{epoch+1}/{num_epochs}], Perte : {total_loss / len(train_loader):.4f}")

# 5. Test du modèle
model.eval()  # Passer le modèle en mode évaluation
correct = 0
total = 0

with torch.no_grad():  # Désactiver le calcul des gradients
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Précision du test : {accuracy:.2f}%")

# 6. Visualiser les résultats de test
dataiter = iter(test_loader)
images, labels = next(dataiter)
outputs = model(images)
_, predictions = torch.max(outputs, 1)

fig, axes = plt.subplots(1, 6, figsize=(12, 4))
for i in range(6):
    axes[i].imshow(images[i][0], cmap='gray')
    axes[i].set_title(f"Étiquette : {labels[i]}\nPréd : {predictions[i]}")
    axes[i].axis('off')
plt.show()

Les résultats de visualisation montrent les étiquettes réelles et les valeurs prédites de 6 échantillons de test, par exemple :

  • Le coin supérieur gauche de l'image est le chiffre manuscrit.

  • La section du titre affiche la valeur prédite du modèle et l'étiquette réelle.

Remarque : quelques erreurs courantes

Lors du téléchargement du jeu de données MNIST, le téléchargement échoue en raison d'un échec de vérification du certificat SSL.

Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Failed to download (trying next):
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)>
...

Solutions

Ouvrez un terminal et exécutez la commande suivante pour installer/mettre à jour le certificat SSL :

/Applications/Python\ 3.x/Install\ Certificates.command

Remplacez 3.x par la version de Python que vous avez réellement installée.

Après avoir exécuté cette commande, relancez votre code Python et il devrait pouvoir télécharger le jeu de données MNIST avec succès.

Autres extensions