top of page

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 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)


Il était quand même temps que je mette des captures d'écran de cette appli, d'autant qu'on peut y voir un magnifique "5" tracé de ma main.
Il était quand même temps que je mette des captures d'écran de cette appli, d'autant qu'on peut y voir un magnifique "5" tracé de ma main.

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

Mon "5" se refait une beauté dans le pipeline
Mon "5" se refait une beauté dans le pipeline

Dans le projet MNIST Express, toute cette logique est concentrée dans une fonction :


preprocess_user_drawing()

Elle vit dans :

mnist_express/preprocessing.py

Et 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 valeurs

Dans l'application Streamlit :

X_input = preprocessed_img.reshape(1, 784)

Le modèle reçoit donc un vecteur de 784 features.


Mon "5" de tout à l'heure, mais pré-traité !
Mon "5" de tout à l'heure, mais pré-traité !

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.py

On 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


RETROUVEZ-MOI

  • X
  • GitHub
  • LinkedIn
  • Instagram
  • YouTube
bottom of page