Aller au contenu principal

Reconnaître des chiffres

Reconnaître des chiffres manuscrits avec un réseau de neurones simple en Python (dataset MNIST).

Notions théoriques

Un réseau de neurones est un programme qui apprend à reconnaître des motifs (ici des chiffres manuscrits) en s’entraînant sur de nombreux exemples.

On lui montre des milliers d’images de chiffres écrits à la main + la bonne réponse (0, 1, 2… 9).
Au départ il devine n’importe quoi. À force de voir ses erreurs, il ajuste ses « réglages internes » pour devenir de plus en plus précis.

Les éléments principaux :

  • Les neurones : petites unités qui reçoivent plusieurs nombres, font une combinaison pondérée de ces nombres, ajoutent un petit décalage (biais), puis appliquent une fonction qui transforme le résultat en valeur entre 0 et 1.

  • Les couches :

    • Couche d’entrée : les 784 pixels de l’image 28x28 pixels (chaque pixel = un nombre entre 0 et 1)
    • Couche(s) cachée(s) : les neurones qui mélangent et transforment l’information
    • Couche de sortie : 10 neurones → chacun représente un chiffre (0 à 9). Le neurone qui a la valeur la plus haute indique la prédiction du réseau.
  • Les poids : chaque connexion entre deux neurones a un petit nombre (le poids) qui dit à quel point cette connexion est importante. C’est la valeur de ces poids que le réseau va apprendre.

  • L’apprentissage : on montre des images par petits groupes → le réseau fait une prédiction → on mesure l’erreur → on ajuste légèrement tous les poids pour réduire cette erreur. On répète ça très souvent.

L’astuce qui permet d’ajuster des dizaines de milliers de poids en même temps s’appelle la rétropropagation : l’erreur est renvoyée en arrière à travers le réseau pour indiquer à chaque poids dans quelle direction et de combien il doit bouger.

Le dataset MNIST :

  • 60 000 images pour l’entraînement
  • 10 000 images pour tester les performances
  • Chaque image = un chiffre écrit à la main

Un réseau très simple (une seule couche cachée de 20 à 40 neurones) arrive déjà à reconnaître environ 93–96 % des chiffres après quelques minutes d’entraînement.

astuce

On peut obtenir de très bons résultats avec très peu de neurones et sans matériel puissant. C’est parfait pour comprendre le principe.

Exemple pratique

Imaginons que nous voulons apprendre à un réseau à reconnaître les chiffres manuscrits du dataset MNIST.

Voici exactement ce qui se passe, étape par étape :

  1. Préparation des images
    Chaque image 28x28 pixels est transformée en une longue liste de 784 nombres (un par pixel).
    Les pixels noirs sont proches de 0, les pixels blancs proches de 1.
    La bonne réponse (l’étiquette) est transformée en une liste de 10 nombres : presque tous à 0, sauf un 1 à la position du vrai chiffre.
    Exemple : pour un « 5 » → [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]

  2. Création du réseau
    On décide de la structure :

    • 784 neurones d’entrée (un par pixel)
    • 30 neurones dans une couche cachée
    • 10 neurones en sortie (un par chiffre possible)

    Au tout début, tous les poids sont des petits nombres aléatoires (ni trop grands, ni tous à zéro).

  3. Entraînement – une passe complète
    On prend un petit groupe de 10 images (un « mini-lot »).

    Pour chaque image du groupe, on fait ceci :

    a. Passe avant (forward)

    • On envoie les 784 pixels dans la couche d’entrée.
    • Chaque neurone de la couche cachée reçoit ces 784 valeurs, multiplie chacune par son poids, fait la somme + ajoute son biais → applique une fonction qui donne un nombre entre 0 et 1.
    • On obtient 30 nombres (la « pensée intermédiaire » de la couche cachée).
    • Ces 30 nombres sont envoyés à la couche de sortie → même calcul → on obtient 10 nombres entre 0 et 1.
    • Le plus grand des 10 nombres indique le chiffre que le réseau pense avoir vu.

    b. Mesure de l’erreur
    On compare les 10 nombres obtenus avec la bonne réponse (ex. [0,0,0,0,0,1,0,0,0,0] pour un 5).
    Plus les valeurs sont différentes → plus l’erreur est grande.

    c. Rétropropagation (backward)
    On calcule dans quel sens et de combien chaque poids a contribué à l’erreur.
    On remonte couche par couche, de la sortie vers l’entrée, en propageant l’information sur l’erreur.

    d. Ajustement des poids
    Pour chaque poids, on le modifie un tout petit peu dans la direction qui réduit l’erreur.
    On fait la même chose pour tous les biais.

    On répète ce cycle (a → b → c → d) pour les 10 images du mini-lot, puis on passe au mini-lot suivant.

  4. Répétition sur plusieurs époques
    Une époque = passer une fois par toutes les 60 000 images d’entraînement (soit 6 000 mini-lots de 10 images).
    On fait généralement 20 à 40 époques.
    À chaque époque, le réseau devient un peu meilleur.

  5. Évaluation finale
    Une fois l’entraînement terminé, on teste le réseau sur les 10 000 images qu’il n’a jamais vues.
    On compte combien de fois il donne la bonne réponse.
    → Résultat typique avec cette structure simple : entre 93 % et 96 % de bonnes réponses.

  6. Ce que le réseau a appris
    Les poids de la première couche ont appris à détecter des traits simples (traits verticaux, horizontaux, courbes…).
    Les poids de la couche suivante combinent ces traits pour reconnaître des formes plus complexes (boucles du 8, barre du 7…).
    La couche finale décide du chiffre le plus probable.

Test de mémorisation/compréhension


Que reçoit la couche d’entrée du réseau ?


Combien de neurones y a-t-il en sortie pour reconnaître 10 chiffres ?


Que fait la rétropropagation ?


Que signifie un score de 95 % sur les images de test ?


Au début de l’entraînement, les poids sont :



TP pour réfléchir et résoudre des problèmes

Reconnaître des chiffres écrits à la main avec un réseau simple

Nous allons reproduire, comprendre et observer le fonctionnement d’un réseau de neurones très simple qui reconnaît des chiffres manuscrits (dataset MNIST), en utilisant le code minimaliste créé par Michael Nielsen.


Étape 1 – Télécharger et organiser le projet de référence

Téléchargez le dépôt complet depuis l’adresse officielle :

https://github.com/mnielsen/neural-networks-and-deep-learning

git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git

Conservez uniquement les 2 fichiers suivants dans votre dossier de travail (vous pouvez supprimer les autres) :

- mnist_loader.py
- network.py

Étape 2 – Installer la dépendance minimale

Ouvrez un Terminal dans le dossier mnielsen.

  • Pour utiliser un environnement virtuel, exécutez la commande suivante ::

    python -m venv venv  
    venv\Scripts\activate (Windows)
    source venv/bin/activate (Linux/Mac)
  • Pour installez la dépendance numpy, exécutez la commande suivante :

    pip install numpy
astuce

Si pip n’est pas reconnu : assurez-vous que Python est ajouté au PATH lors de l’installation.
Sinon relancez l’installateur Python et cochez « Add Python to PATH ».


Étape 3 – Charger et observer les données MNIST

Créez un nouveau fichier Python dans le même dossier, appelé test_mnist.py

Copiez-collez le code suivant dedans :

import mnist_loader

training_data, validation_data, test_data = mnist_loader.load_data_wrapper()

print("Nombre d'images d'entraînement :", len(training_data))
print("Exemple de format d'une image :", training_data[0][0].shape)
print("Exemple de format d'une étiquette :", training_data[0][1].shape)

Exécutez ce fichier, avec la commande :

python test_mnist.py

Vous devez obtenir quelque chose comme :

Nombre d'images d'entraînement : 50000  
Exemple de format d'une image : (784, 1)
Exemple de format d'une étiquette : (10, 1)
astuce

Si vous obtenez une erreur « No module named mnist_loader » → assurez-vous que vous êtes bien dans le dossier qui contient mnist_loader.py

Si le chargement est très long la première fois → c’est normal, le script télécharge automatiquement les 4 fichiers .gz du site officiel de Yann LeCun (~50 Mo au total) et les décompresse. Cela ne se reproduira plus ensuite.


Étape 4 – Créer et entraîner un réseau très simple

Toujours dans le même dossier, créez un fichier train_simple.py avec le contenu suivant :

import mnist_loader
from network import Network

# Chargement des données
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()

# Création du réseau : 784 entrées → 30 neurones cachés → 10 sorties
net = Network([784, 30, 10])

# Entraînement : 10 époques, mini-lots de 10 images, taux d'apprentissage = 3.0
print("Début de l'entraînement...")
net.SGD(training_data, epochs=10, mini_batch_size=10, eta=3.0,
test_data=test_data)

Exécutez ce fichier, avec la commande :

python train_simple.py

Laissez tourner (comptez 2 à 8 minutes selon votre machine).

Vous devriez voir s’afficher, à la fin de chaque époque, le nombre de chiffres correctement reconnus sur les 10 000 images de test.

info

Résultats attendus après 10 époques (varie un peu selon l’initialisation aléatoire) :

Époque 0 → ~8500–9000 / 10000  
Époque 5 → ~9300–9500 / 10000
Époque 9 → ~9400–9600 / 10000 (soit 94–96 %)

Si vous obtenez nettement moins (genre < 80 %) → relancez simplement le script (les poids sont réinitialisés aléatoirement à chaque exécution).


Étape 5 – Tester manuellement une image

Ajoutez ce code à la fin de votre fichier train_simple.py (après l’entraînement) :

# On récupère la première image de test
image, label = test_data[0]

# Prédiction du réseau
prediction = net.feedforward(image)

print("Sortie brute du réseau (10 valeurs) :")
print(prediction)

print("\nChiffre prédit :", prediction.argmax())
print("Vrai chiffre :", label.argmax())

Relancez le script complet.

Observez les 10 valeurs : normalement une est beaucoup plus élevée que les autres.

info

Exemple de sortie typique :

Sortie brute du réseau (10 valeurs) :  
[[0.0012]
[0.0003]
[0.0087]
[0.0124]
[0.0031]
[0.8921] ← valeur très élevée
[0.0045]
[0.0762]
[0.0009]
[0.0006]]

Chiffre prédit : 5
Vrai chiffre : 5

Étape 6 – Question de réflexion

Après avoir obtenu environ 94 à 96 % de reconnaissance :

Parmi les 4 à 6 % d’erreurs restantes, quelles sortes de chiffres sont les plus souvent mal reconnus selon vous ?
Pourquoi ces confusions sont-elles logiques pour un humain aussi ?

(Indice : regardez les images mal classées avec matplotlib si vous voulez aller plus loin.)

info

Les confusions les plus fréquentes sont généralement :

49  
71
53 ou 56
83 ou 80 (quand très mal écrits)

Raisons logiques :

  • Traits très proches (la barre du 7 ressemble à un 1 mal tracé)
  • Formes symétriques ou ambiguës quand l’écriture est petite ou penchée
  • Bruit / traits parasites qui perturbent les motifs appris

C’est exactement le même type d’erreur que commettent beaucoup d’humains sur des écritures très rapides ou mal formées.


Bonus

Vous venez de construire, entraîner et tester votre premier réseau de neurones.

Vous pouvez maintenant modifier en toute sécurité :

  • le nombre de neurones cachés (essayer 15, 50, 100…)
  • le nombre d’époques (essayer 5, 20, 30…)
  • le taux d’apprentissage eta (essayer 1.0, 4.0, 0.5…)

… et observer comment ces choix influencent le résultat final.