iancr
PyTorch : Construis ton premier réseau de neurones en 30 minutes

PyTorch : Construis ton premier réseau de neurones en 30 minutes

De quoi avez-vous besoin

Packages

  • torch
  • torchvision

Difficulté

Avancé

Tu veux faire du deep learning mais tu ne sais pas par où commencer ? Tu as entendu parler de TensorFlow, Keras, PyTorch et tu es perdu dans la jungle des frameworks ? Ce tutoriel est fait pour toi. On va prendre PyTorch — le framework le plus utilisé en recherche et dans l'industrie — et construire ensemble ton premier réseau de neurones, de A à Z, avec du code que tu peux copier-coller. Pas de jargon inutile, pas de maths intimidantes. Juste du concret.


Pourquoi PyTorch ?

PyTorch est aujourd'hui le standard de facto du deep learning. Développé par Meta (Facebook), il est utilisé par OpenAI, Tesla, DeepMind et des milliers de chercheurs. Ses trois forces :

  • La syntaxe Python native : tu écris du Python, pas un DSL exotique
  • Le calcul dynamique : tu peux débugger avec print() comme dans n'importe quel script
  • L'écosystème : torchvision pour la vision, torchaudio pour l'audio, HuggingFace qui s'appuie dessus

Bref, si tu veux faire de l'IA sérieuse, PyTorch est ton allié.


Ce qu'on va construire

Aujourd'hui, on va créer un réseau de neurones capable de reconnaître des chiffres manuscrits (le célèbre dataset MNIST). À la fin de ce tutoriel, tu auras :

  1. Compris ce qu'est un tensor (le bloc de base)
  2. Utilisé autograd pour le calcul automatique des gradients
  3. Construit un réseau de neurones à deux couches
  4. Entraîné ton modèle sur des vraies données
  5. Obtenu plus de 95 % de précision

Étape 1 : Installation

D'abord, on crée un environnement propre et on installe PyTorch.

bash
# Créer un dossier de projet
mkdir mon-premier-pytorch && cd mon-premier-pytorch

# Créer un environnement virtuel
python3 -m venv venv
source venv/bin/activate

# Installer PyTorch (CPU = pas besoin de carte graphique)
pip install torch torchvision

💡 Ce que tu dois voir : pip télécharge PyTorch et ses dépendances (~150 Mo). L'installation prend 1 à 2 minutes. Aucune erreur ne doit apparaître en rouge.

Vérifie que tout fonctionne :

python
import torch
print(f"PyTorch version : {torch.__version__}")
print(f"GPU dispo : {torch.cuda.is_available()}")

Si la première ligne affiche une version (ex: 2.x.x), tout est bon. Le GPU n'est pas nécessaire pour ce tutoriel le CPU suffit.


Étape 2 : Les tensors, le bloc de base

Un tensor, c'est l'équivalent d'un tableau NumPy... mais avec un super-pouvoir : il peut être envoyé sur une carte graphique (GPU) et il garde une trace de toutes les opérations pour le calcul des gradients.

python
import torch

# Créer un tensor à partir d'une liste Python
x = torch.tensor([1.0, 2.0, 3.0, 4.0])
print(x)          # tensor([1., 2., 3., 4.])
print(x.shape)    # torch.Size([4])
print(x.dtype)    # torch.float32

# Créer des tensors de zéros, de uns, ou aléatoires
zeros = torch.zeros(3, 4)      # 3 lignes, 4 colonnes
ones = torch.ones(2, 5)        # 2 lignes, 5 colonnes
random = torch.randn(3, 3)     # distribution normale

# Opérations mathématiques
a = torch.tensor([1.0, 2.0])
b = torch.tensor([3.0, 4.0])
print(a + b)       # tensor([4., 6.])
print(a * b)       # tensor([3., 8.]) — multiplication élément par élément
print(a @ b)       # tensor(11.) — produit scalaire

💡 Ce que tu dois voir : Chaque print() affiche le résultat attendu. Pas d'erreur.

Le concept clé : tout dans PyTorch passe par les tensors. Une image = un tensor de forme (hauteur, largeur, canaux). Un batch de données = un tensor de forme (taille_batch, caractéristiques).


Étape 3 : Autograd la magie de la dérivation automatique

Le deep learning repose sur la descente de gradient : on calcule la dérivée (le gradient) de l'erreur par rapport à chaque paramètre, puis on ajuste les paramètres dans la direction qui réduit l'erreur.

PyTorch fait ça tout seul avec autograd. Regarde :

python
# Un tensor qui demande le suivi des gradients
w = torch.tensor([2.0], requires_grad=True)

# On définit une fonction : f(w) = w² + 3w + 1
f = w**2 + 3*w + 1

# On calcule le gradient df/dw automatiquement
f.backward()

print(f"f(w) = {f.item()}")        # f(w) = 11.0
print(f"df/dw = {w.grad.item()}")  # df/dw = 7.0  (car 2w + 3 = 2*2 + 3 = 7)

💡 Ce que tu dois voir : La valeur du gradient calculé automatiquement correspond au calcul manuel. C'est cette magie qui permet d'entraîner des réseaux de neurones avec des millions de paramètres.


Étape 4 : Charger les données MNIST

MNIST, c'est 70 000 images de chiffres manuscrits (28×28 pixels en noir et blanc). PyTorch les télécharge pour nous :

python
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# Pipeline de transformation : image → tensor → normalisation
transform = transforms.Compose([
    transforms.ToTensor(),                          # Convertit l'image en tensor (0 à 1)
    transforms.Normalize((0.1307,), (0.3081,))      # Normalise avec moyenne et écart-type MNIST
])

# Télécharger et charger les données
train_data = datasets.MNIST(
    root="./data",        # Stockage local
    train=True,           # Le dataset d'entraînement
    download=True,        # Télécharge si pas déjà fait
    transform=transform   # Applique la transformation
)

test_data = datasets.MNIST(
    root="./data",
    train=False,          # Le dataset de test
    transform=transform
)

# DataLoader : distribue les données par batch et les mélange
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=1000, shuffle=False)

print(f"Images d'entraînement : {len(train_data)}")
print(f"Images de test       : {len(test_data)}")

💡 Ce que tu dois voir : Le téléchargement prend ~10 secondes. Tu vois 60000 images d'entraînement et 10000 de test.


Étape 5 : Construire le réseau de neurones

On va créer un réseau super simple, mais efficace : une couche d'entrée de 784 neurones (28×28 pixels), une couche cachée de 128 neurones, et une couche de sortie de 10 neurones (1 par chiffre).

python
import torch.nn as nn

class MonPremierReseau(nn.Module):
    def __init__(self):
        super().__init__()
        self.couche_cachee = nn.Linear(28*28, 128)   # Entrée (784 pixels) → 128 neurones cachés
        self.couche_sortie = nn.Linear(128, 10)        # 128 neurones cachés → 10 classes (0 à 9)
        self.relu = nn.ReLU()                           # Fonction d'activation (introduit la non-linéarité)
    
    def forward(self, x):
        # x a la forme (batch_size, 1, 28, 28) — on l'aplatit
        x = x.view(-1, 28*28)          # (batch_size, 784)
        x = self.relu(self.couche_cachee(x))   # (batch_size, 128) avec ReLU
        x = self.couche_sortie(x)               # (batch_size, 10)
        return x

# Instancier le modèle
modele = MonPremierReseau()
print(modele)

💡 Ce que tu dois voir : Le print(modele) affiche l'architecture complète du réseau. Chaque couche Linear montre le nombre de paramètres.

Pourquoi cette architecture ? 784 pixels on les compresse en 128 caractéristiques intermédiaires on les projette en 10 scores (un par chiffre). La fonction ReLU remplace les valeurs négatives par zéro c'est ce qui permet au réseau d'apprendre des relations non-linéaires.


Étape 6 : La boucle d'entraînement

C'est le cœur du deep learning. On répète 5 fois (5 époques) le cycle : prédire mesurer l'erreur corriger les paramètres.

python
import torch.optim as optim

# Fonction de perte : mesure l'écart entre prédictions et réalité
loss_fn = nn.CrossEntropyLoss()

# Optimiseur : ajuste les paramètres selon le gradient
optimizer = optim.Adam(modele.parameters(), lr=0.001)

EPOQUES = 5

modele.train()  # Mode entraînement

for epoque in range(EPOQUES):
    perte_totale = 0
    corrects = 0
    
    for images, labels in train_loader:
        # 1. Remettre les gradients à zéro
        optimizer.zero_grad()
        
        # 2. Forward pass : prédire
        predictions = modele(images)
        
        # 3. Calculer l'erreur
        perte = loss_fn(predictions, labels)
        
        # 4. Backward pass : calculer les gradients
        perte.backward()
        
        # 5. Mettre à jour les paramètres
        optimizer.step()
        
        # Stats pour l'affichage
        perte_totale += perte.item()
        corrects += (predictions.argmax(dim=1) == labels).sum().item()
    
    precision = 100 * corrects / len(train_data)
    print(f"Époque {epoque+1}/{EPOQUES} | Perte: {perte_totale/len(train_loader):.4f} | Précision: {precision:.2f}%")

💡 Ce que tu dois voir : La perte diminue et la précision augmente à chaque époque. À la fin de la 5 époque, tu devrais dépasser 95 % de précision sur les données d'entraînement.

Comprendre la boucle

Chaque itération de la boucle fait 5 choses : 1. zero_grad() On efface les gradients de l'étape précédente 2. Forward Le modèle prédit les sorties pour le batch d'images 3. Loss On mesure l'écart entre ce qui est prédit et la réalité 4. Backward Autograd calcule les gradients (les dérivées de l'erreur) 5. Step L'optimiseur ajuste les paramètres dans la bonne direction


Étape 7 : Évaluer le modèle

Maintenant qu'on a entraîné, vérifions si le modèle généralise bien (données qu'il n'a jamais vues).

python
modele.eval()  # Mode évaluation (désactive dropout, etc.)

corrects = 0
total = 0

with torch.no_grad():  # Pas besoin de gradients pour l'évaluation
    for images, labels in test_loader:
        predictions = modele(images)
        corrects += (predictions.argmax(dim=1) == labels).sum().item()
        total += labels.size(0)

precision_test = 100 * corrects / total
print(f"\n🎉 Précision sur les données de test : {precision_test:.2f}%")

💡 Ce que tu dois voir : Une précision autour de 96-97 % sur le jeu de test. Pour un réseau aussi simple (deux couches), c'est excellent !


Étape 8 : Utiliser ton modèle sur tes propres images

Tu veux tester ton modèle sur un chiffre que tu as dessiné ? Voici comment faire une prédiction :

python
import torch.nn.functional as F

def predire_chiffre(modele, image_tensor):
    """
    image_tensor : tensor de forme (1, 28, 28) avec valeurs entre 0 et 1
    Retourne le chiffre prédit et les probabilités de chaque classe.
    """
    modele.eval()
    with torch.no_grad():
        # Ajouter la dimension batch : (1, 1, 28, 28)
        x = image_tensor.unsqueeze(0)
        logits = modele(x)
        probabilites = F.softmax(logits, dim=1)
        chiffre_pred = logits.argmax(dim=1).item()
        
        print(f"Chiffre prédit : {chiffre_pred}")
        for i, prob in enumerate(probabilites.squeeze()):
            barre = "█" * int(prob.item() * 30)
            print(f"  {i}: {barre} {prob.item():.3f}")
        return chiffre_pred

# Exemple avec une image du jeu de test
image_test, label_vrai = test_data[0]
print(f"Vrai chiffre : {label_vrai}")
predire_chiffre(modele, image_test)

💡 Ce que tu dois voir : Le modèle affiche la prédiction correcte avec une barre de confiance quasi pleine.


Pièges classiques

1. Oublier model.train() et model.eval()

Certaines couches (comme Dropout et BatchNorm) se comportent différemment en entraînement et en évaluation. Si ta précision de test est bizarrement basse, vérifie que tu as bien appelé .eval() avant d'évaluer.

2. Confondre view() et reshape()

view() exige que le tensor soit contigu en mémoire. Si tu as un bug RuntimeError: view size is not compatible, utilise reshape() ou appelle .contiguous() avant.

3. Laisser requires_grad=True pendant l'évaluation

Ça consomme de la mémoire pour rien. Utilise torch.no_grad() pendant l'inférence ça accélère le code et réduit l'empreinte mémoire.

4. Batch size trop grand

Si tu dépasses la mémoire de ta carte graphique, réduis la taille du batch (batch_size=32 au lieu de 64 par exemple).

5. Ne pas normaliser les données

Si tu oublies la normalisation, ton réseau va soit ne pas converger, soit converger très lentement. Les données doivent être centrées autour de 0.


Pour aller plus loin

1. Ajoute des convolutions (CNN)

Remplace les couches Linear par des couches nn.Conv2d. Les CNN sont la raison pour laquelle la vision par ordinateur a explosé. Avec un CNN simple (2 convolutions + 2 linéaires), tu passeras facilement à 99 % de précision.

2. Sauvegarde et recharge ton modèle

python
torch.save(modele.state_dict(), "mon_modele.pth")  # Sauvegarder
modele.load_state_dict(torch.load("mon_modele.pth")) # Recharger

3. Passe sur GPU

Si tu as une carte graphique NVIDIA, ajoute .to("cuda") partout :

python
modele = modele.to("cuda")
images, labels = images.to("cuda"), labels.to("cuda")

L'entraînement sera 10 à 50 fois plus rapide.

4. Explore les modèles pré-entraînés

Avec torchvision.models, tu peux charger un ResNet, un EfficientNet ou un ViT déjà entraîné sur ImageNet et le fine-tuner sur tes propres données en quelques lignes.


Récapitulatif

Tu as maintenant un vrai réseau de neurones qui reconnaît des chiffres manuscrits avec plus de 95 % de précision. Tu as compris les trois piliers de PyTorch :

Concept

Rôle

Tensor

Conteneur de données avec suivi des gradients

Autograd

Calcul automatique des dérivées

nn.Module

Brique de base pour construire des réseaux

Tu peux maintenant explorer la classification d'images plus complexes, la génération de texte, ou même entraîner un modèle de diffusion. Tout se construit sur ces briques.

Bon code, et n'oublie pas : le deep learning, c'est 20 % de théorie et 80 % de pratique. Plus tu codes, plus tu comprends.

💡 Idée d'illustration : Une bannière montrant un réseau de neurones simplifié avec des flèches reliant 784 pixels d'entrée 128 neurones cachés 10 neurones de sortie (chiffres 0 à 9), sur fond de code Python avec des couleurs chaudes.