← Tous les projets
Étude de cas · Python

Détecter les faux billets à partir de six mesures

Comparer quatre modèles de machine learning pour distinguer les vrais billets en euros des faux à partir de leur seule géométrie, puis livrer le meilleur sous forme de script prêt à l’emploi.

Client
ONCFM, organisme de lutte contre le faux-monnayage (cas OpenClassrooms)
Rôle
Data Analyst, projet de formation
Année
2026
Outils
  • Python
  • scikit-learn
  • Pandas
  • Seaborn
  • VS Code

La question

L’ONCFM lutte contre la contrefaçon des billets en euros. Chaque billet qui arrive à l’organisme passe dans une machine qui relève six mesures : sa longueur, sa diagonale, sa hauteur de chaque côté et les deux marges entre le bord et l’image imprimée. Au fil des années, l’organisme a remarqué que les faux diffèrent légèrement des vrais par leurs dimensions, de quelques fractions de millimètre invisibles à l’œil nu.

La commande : construire un algorithme capable de distinguer un vrai billet d’un faux à partir de ces six mesures seulement, comparer quatre méthodes (régression logistique, K-means, KNN et forêt aléatoire), puis livrer la meilleure sous forme de script capable de traiter un fichier de nouveaux billets. Une priorité avant tout : laisser passer le moins de faux billets possible.

  • 1 500billets pour l’apprentissage, dont 500 faux
  • 6mesures par billet, en millimètres
  • 99 %des billets de test correctement classés
  • 98/100faux billets détectés dans le jeu de test

Comprendre les mesures

Avant toute modélisation, j’ai regardé le comportement de chaque mesure. Quatre d’entre elles (la diagonale, les deux hauteurs et la marge haute) suivent de belles courbes en cloche : la plupart des billets se concentrent autour d’une valeur moyenne, et les valeurs extrêmes sont rares. Sur ces quatre mesures, vrais et faux billets se chevauchent : leurs valeurs tombent dans la même plage, si bien qu’aucune d’elles ne suffit, seule, à les distinguer. Deux racontent une autre histoire : la marge basse est asymétrique à droite, et la longueur présente deux pics distincts (distribution bimodale), la signature de deux populations qui se mélangent à peine.

Les corrélations avec l’authenticité l’ont confirmé. Les vrais billets sont plus longs et ont des marges plus petites.

Force du lien avec l’authenticité (corrélation en valeur absolue)
  • Longueur (+)0,85
  • Marge basse (−)0,78
  • Marge haute (−)0,61

Les hauteurs sont des signaux bien plus faibles, et la diagonale n’a presque aucun lien avec l’authenticité.

Certains billets sortaient nettement de la plage habituelle, surtout sur la marge basse. Je les ai gardés volontairement : ces dimensions inhabituelles sont très probablement les faux eux-mêmes. Les supprimer aurait fait disparaître le signal même dont les modèles ont besoin.

Préparer les données

37 billets n’avaient pas de marge basse. Plutôt que de combler ces trous avec une médiane, j’ai utilisé une régression linéaire sur les cinq autres mesures, car la marge basse est fortement liée à la longueur (−0,67). Tous les billets ont été conservés. Point important : la régression n’a utilisé que les mesures, jamais l’étiquette « vrai » ou « faux ». Sinon, la réponse que les modèles doivent trouver se serait glissée dans leurs données d’entrée (une fuite de données) et leurs scores auraient été trop optimistes ; un nouveau billet à analyser n’a de toute façon pas d’étiquette.

J’ai ensuite séparé les données en un jeu d’entraînement et un jeu de test, puis standardisé les mesures pour qu’aucune variable ne pèse plus que les autres du seul fait de son échelle. L’échelle est calculée sur le seul jeu d’entraînement : les billets de test n’influencent rien, ce qui évite une seconde fuite de données.

# Découpage 80/20, en gardant le ratio 2:1 vrais/faux dans les deux jeux
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.20, random_state=42, stratify=y
)

# L'échelle est apprise sur les billets d'entraînement uniquement
scaler = StandardScaler()
x_train_scaled = scaler.fit_transform(X_train)
x_test_scaled = scaler.transform(X_test)

Quatre modèles, un même cadre d’évaluation

Chaque modèle a été entraîné sur les mêmes 1 200 billets et jugé sur les mêmes 300 qu’il n’avait jamais vus, avec les mêmes outils : une matrice de confusion pour compter chaque type d’erreur, l’accuracy (taux de bonnes prédictions), et la courbe ROC avec son score AUC pour vérifier la capacité du modèle à bien classer les billets quel que soit le seuil.

ModèleApprentissageAccuracyErreurs / 300AUC
Régression logistiqueSupervisé99,0 %30,9995
Forêt aléatoireSupervisé99,0 %30,9994
KNN (k plus proches voisins)Supervisé98,7 %40,9972
K-means (2 clusters)Non supervisé98,7 %4—

Le résultat du K-means est le plus parlant. Sans qu’on lui ait jamais indiqué quels billets étaient faux, il les a répartis en deux groupes qui correspondent à la réalité dans 98,7 % des cas. Or, avec deux groupes, le K-means les sépare par une frontière linéaire (l’équivalent d’une droite, en six dimensions) : ce résultat confirme que vrais et faux billets forment deux populations presque linéairement séparables, ce qui explique les excellentes performances de tous les modèles.

Régression logistique sur les 300 billets de test
Prédit fauxPrédit vrai
Réellement faux98bien détectés2passés entre les mailles
Réellement vrai1fausse alerte199bien validés

Deux faux billets sur 100 sont passés entre les mailles, et un seul vrai billet a été signalé à tort.

Pourquoi le modèle le plus simple l’a emporté

La régression logistique et la forêt aléatoire ont terminé à égalité : même matrice de confusion, même accuracy, et des scores AUC à 0,0001 d’écart. Quand deux modèles sont aussi proches, c’est tout le reste qui les départage.

Comme l’a montré le K-means, les classes sont presque linéairement séparables : par conséquent, la puissance supplémentaire de cent arbres de décision n’apporte rien ici. C’est le principe de parcimonie, plus connu sous le nom de rasoir d’Ockham : à performances égales, le modèle le plus simple doit l’emporter. La régression logistique est plus rapide, moins sujette au surapprentissage et surtout explicable : chaque mesure reçoit un coefficient, si bien que n’importe qui à l’organisme peut comprendre pourquoi un billet a été signalé.

Du notebook à l’outil opérationnel

Le modèle final est sauvegardé sous forme d’un pipeline unique qui regroupe la standardisation et la régression logistique : les nouveaux billets peuvent ainsi être évalués directement à partir des mesures brutes, sans refaire aucune étape de préparation.

Le script charge un fichier de nouveaux billets et renvoie, pour chacun, un verdict et la probabilité d’être vrai. J’ai ajouté une fonctionnalité qui ne figurait pas dans la commande : les billets sur lesquels le modèle hésite sont signalés pour une vérification humaine au lieu d’être tranchés à l’aveugle.

# Signaler les billets incertains pour une vérification humaine
confiance = modele.predict_proba(df_taille_billets)[:, 1]   # probabilité d'être vrai
ecart = np.abs(confiance - 0.5) * 2                          # 0 = pile ou face, 1 = certitude totale
seuil = 0.30
resultats["Vérification humaine"] = np.where(ecart < seuil, "À vérifier", "")
BilletVerdictProbabilité d’être vrai
B_1Vrai0,995
B_2Faux0,003
B_3Vrai0,999
B_4Faux0,000
B_5Faux0,011

Pour aller plus loin

  • Ajuster le seuil de décision pour mieux détecter les faux. Un faux billet non détecté coûte bien plus cher qu’une fausse alerte : exiger une probabilité plus élevée avant de déclarer un billet vrai laisserait moins de faux en circulation, au prix de quelques vérifications supplémentaires.
  • Confirmer le classement des modèles par validation croisée. Le classement repose sur un seul découpage entre entraînement et test : avec trois ou quatre erreurs sur 300 billets, un autre découpage aurait pu inverser l’ordre. La validation croisée répète l’évaluation sur plusieurs découpages (par exemple cinq) et en fait la moyenne, ce qui rend la comparaison plus fiable.

Ce que j’en retiens

J’ai appris que le meilleur modèle n’est pas toujours le plus puissant. La forêt aléatoire a obtenu le même score, mais la régression logistique est celle qu’une personne de l’organisme peut comprendre : un coefficient par mesure, ce qui permet de voir pourquoi un billet a été signalé. Ajouter une vérification humaine pour les billets sur lesquels le modèle hésite m’a appris qu’un bon outil connaît aussi ses limites.