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.
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 :
-
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] -
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).
-
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.
-
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. -
É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. -
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
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
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)
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.
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.
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.)
Les confusions les plus fréquentes sont généralement :
4 ↔ 9
7 ↔ 1
5 ↔ 3 ou 5 ↔ 6
8 ↔ 3 ou 8 ↔ 0 (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.