MNIST Express - 6/8 - L'entrée utilisateur, ce vrai défi
- 10 mars
- 4 min de lecture
Dernière mise à jour : 22 mars
Lien vers l'appli : https://mnist-express.streamlit.app/
Lien vers le repo du projet : https://github.com/vohorgeez/MNIST-Express
Dans les articles précédents, nous avons construit petit à petit un classificateur de chiffres manuscrits basé sur k-Nearest Neighbors.
Nous avons parlé de géométrie, de distances, de réduction de dimension, et même de la grande illusion de l'accuracy. Mais il reste un problème plus terre à terre. Un problème que tous les programmes informatiques (ou presque) rencontrent tôt ou tard.
L'UTILISATEUR.
L'HUMAIN.
TOI QUI LIT CES LIGNES. 🫵🏻🫵🏻🫵🏻
(oui je te juge)

Le modèle vit dans un monde à part
Le dataset MNIST contient des images très propres :
images 28x28
fond noir
chiffre blanc
chiffre centré
épaisseur homogène
Autrement dit : un monde idéal.
Mais dans une vraie application, l'utilisateur (cette odieuse personne) ne fait jamais ça. Ce qu'il fait, plutôt :
des chiffres trop gros
trop petits
décalés
coupés
parfois à moitié dessinés
parfois illisibles
Il peut même lui arriver de troller et ne rien faire du tout. Une calamité, j'vous jure.
Le modèle, lui, ne comprend l'imperfection structurelle de l'utilisateur. Dans son innocence immaculée, il ne comprend que des vecteurs de 784 nombres.
Donc, la première mission du système est simple :
transformer un dessin humain chaotique en quelque chose qui ressemble à MNIST.
Le pipeline du prétraitement

Dans le projet MNIST Express, toute cette logique est concentrée dans une fonction :
preprocess_user_drawing()Elle vit dans :
mnist_express/preprocessing.pyEt elle fait plusieurs choses successives.
Etape 1 - Trouver le vrai dessin
L'utilisateur dessine sur un canvas de 280 x 280 pixels. Mais bien souvent, le chiffre occupe seulement une petite zone, et le reste est vide.
On commence donc par binariser l'image.
Dans le code :
def binarize(draw, threshold=30)On transforme l'image en deux valeurs :
0 --> fond
1 --> pixels utiles
C'est ce qui nous permettra de repérer où se situe la zone utile du dessin.
Etape 2 - Trouver la boîte autour du chiffre
Une fois les pixels utiles identifiés, on calcule une bounding box. Autrement dit, le plus petit rectangle contenant le chiffre.
Dans le code :
def useful_bounding_box(binarized)Cette étape est importante, sinon un chiffre dessiné tout petit dans un coin serait envoyé tel quel au modèle... et il serait presque invisible.
Etape 3 - Redimensionner le chiffre
Une fois le chiffre isolé, on doit le transformer pour qu'il ressemble au dataset MNIST. Dans MNIST, le chiffre occupe environ 20 pixels, et l'image finale fait 28x28.
Le code applique donc un redimensionnement :
scale = 20 / max(h, w)Puis, il place le chiffre dans une image 28 x 28 centrée. Cela imite très précisément la structure du dataset MNIST.
Etape 4 - Recentrer le chiffre
Même après redimensionnement, le chiffre peut être légèrement décalé. On calcule donc le centre de masse de l'image.
Dans le code :
calculate_mass_center()Puis on recentre l'image pour que le chiffre se retrouve au milieu?
center()ça parait anodin comme détail, mais il améliore énormément la robustesse du modèle. Un k-NN est très sensible aux translations dans l'image. Et si vous avez lu mes derniers articles, vous vous en doutez... n'est-ce pas ? 👀
Etape 5 - Conversion vers le format du modèle
Une fois l'image propre, il faut la transformer en ce que le modèle comprend :
(28 x 28) -> 784 valeursDans l'application Streamlit :
X_input = preprocessed_img.reshape(1, 784)Le modèle reçoit donc un vecteur de 784 features.

La prédiction 🔮
La prédiction se fait ensuite avec le modèle k-NN :
predict_knn()Le système renvoie :
la classe prédite
la probabilité estimée
le temps d'inférence
Puis, l'application affiche :
la prédiction
le top-3 des classes
l'image prétraitée
Et ce dernier point est très important, puisqu'il permet à l'utilisateur de voir ce que le modèle "voit" réellement.
Sur l'attention accordée aux données d'entrée
Le machine learning, finalement, ça n'est pas seulement entraîner un modèle, optimiser l'accuracy et tester des algorithmes. En réalité c'est très largement insuffisant si on ne sait pas transformer les données entrantes pour qu'elles ressemblent un minimum à celles du dataset d'entraînement.
C'est exactement ce que fait notre pipeline : il transforme un dessin humain imprévisible en une image ressemblant à MNIST.
Tester le chaos
Ce pipeline est tellement important qu'il ne peut évidemment pas échapper aux fourches caudines des tests...
Notamment dans :
tests/test_preprocessing.pyOn y vérifie, par exemple :
qu'une image vide renvoie une image 28 x 28 vide
qu'un chiffre décalé soit recentré
que toutes les entrées soient transformées en vecteurs 784.
Ces tests garantissent que le système reste robuste.
Le mot de la fin
Les démos de machine learning mettent souvent la lumière sur le modèle, la prédiction et l'accuracy... on retient moins le travail invisible qui les relit. Et pourtant, dans la plupart des systèmes d'IA, la partie la plus complexe est peut-être moins le modèle que le pipeline qui transforme les données du monde réel en données utilisables.
Dans MNIST Express, ce pipeline est précisément ce qui permet à un k-NN très simple de fonctionner sur des dessins utilisateurs : c'est sans doute là que se joue le gros de la bataille.





Commentaires