Ultralytics YOLO27 : Inscris-toi sur la liste d’attente.
Get Started
Leçonintermédiaire

Entraîner un classificateur d’images

Organise un jeu de données dans des dossiers, entraîne yolo26n-cls.pt sur MNIST160, lis les probabilités et la précision top-1, puis envoie les prédictions à faible confiance à une personne pour vérification.

La classification d’images répond à une question par image : qu’est-ce que c’est ? Elle nécessite les annotations les moins coûteuses de toutes les tâches — un nom de dossier — et s’exécute le plus rapidement, ce qui en fait le deuxième étage par défaut après un détecteur et le premier modèle à choisir pour un poste d’inspection qui voit une pièce par image. Son jeu de données ne comporte pas de fichier YAML : la structure des dossiers fait donc office de contrat. Un classificateur donne toujours une réponse, même pour des images qui ne ressemblent à rien de ce qu’il a déjà vu. Savoir quand ne pas lui faire confiance représente la moitié du travail.

Résultat

Entraîne yolo26n-cls.pt sur MNIST160, indique la précision top-1, lis les probabilités de classe à partir de result.probs et choisis un seuil de confiance qui envoie les images incertaines en vérification.

Parcours accéléré
Si tu connais déjà les bases, voici la version courte.
  1. Place les images dans train/<class>/ et val/<class>/ ; les noms des dossiers deviennent les noms des classes, triés par ordre alphabétique.

  2. yolo classify train data=mnist160 model=yolo26n-cls.pt epochs=1 imgsz=64.

  3. Évalue le modèle sur la précision top-1 (metrics.top1) ; avec cinq classes ou moins, la précision top-5 est toujours de 1,0.

  4. Lis result.probs.top1 et .top1conf, puis envoie les prédictions dont la confiance est inférieure au seuil à une personne.

Pratique

Structure des dossiers du jeu de données#

Classification d’images Ultralytics YOLO de trois feuilles portant les étiquettes saine, rouille et oïdium, chacune accompagnée de son score de confiance

Les jeux de données de classification sont des répertoires, pas des fichiers YAML. Indique le dossier racine à data= :

parts/
├── train/
│   ├── good/      img_0001.jpg ...
│   ├── scratch/   img_0412.jpg ...
│   └── dent/      img_0733.jpg ...
└── val/
    ├── good/
    ├── scratch/
    └── dent/

Voici ce que le chargeur en fait, d’après le code source installé et le guide des jeux de données de classification :

SituationComportement
Indices de classenoms des dossiers sous train/, triés par ordre alphabétique → dent=0, good=1, scratch=2
Dossier de validationval/, puis validation/, puis valid/ ; utilise test/ en dernier recours, avec un avertissement
Pas de dossier train/, seulement des dossiers de classeseffectue automatiquement une division 80/20 en train/ et val/

Sur la plateforme Ultralytics, un jeu de données de classification associe une seule étiquette au niveau de l’image à chaque image, au lieu de formes :

Jeu de données de classification sur la plateforme Ultralytics : un convoyeur de boîtes blanches portant une seule étiquette au niveau de l’image, class2, choisie dans le panneau des classes

Un nouveau dossier renumérote tes classes

Symptôme : après l'ajout d'une classe, une ancienne intégration signale que les éléments good sont des éléments dent. Cause : l'ajout d'un dossier nommé bent le place en premier dans le tri et décale tous les index suivants. Solution : associe toujours les index aux noms avec model.names (ou result.names) ; ne code jamais un index en dur.

Entraîner sur MNIST160#

MNIST160 contient 160 chiffres manuscrits, 80 dans train/ et 80 dans test/, chacun réparti dans dix dossiers de chiffres — un jeu suffisamment petit pour un test rapide sur CPU. Il ne comporte pas de dossier val/ ; observe donc l’avertissement du chargeur et son recours à test/ :

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=1, imgsz=64, device="cpu")
metrics = model.val()
print(f"top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
WARNING ⚠️ Dataset 'split=val' not found, using 'split=test' instead.
val: .../datasets/mnist160/test... found 80 images in 10 classes ✅
                   all      0.162      0.463
top-1: 0.162  top-5: 0.463

Une époque suffit à peine à faire progresser la nouvelle tête à dix classes. Lors de notre exécution, epochs=10 a atteint une précision top-1 de 0,55 et une précision top-5 de 0,95 en une dizaine de secondes sur le CPU d’un ordinateur portable — la boucle fonctionne, mais il faut davantage d’entraînement. Le guide de la tâche de classification répertorie les tailles des modèles préentraînés.

Top-1 correspond à la proportion d’images dont la classe la plus probable est correcte. Top-5 compte une prédiction comme correcte si la bonne classe figure parmi les cinq plus probables. La précision top-5 est utile pour les 1 000 classes d’ImageNet, qui vont de catégories générales à des catégories très précises ; avec cinq classes ou moins, elle vaut toujours 1,0 et, avec dix classes, elle est généreuse. Évalue le modèle sur la précision top-1 et une matrice de confusion par classe.

Exemples d’images ImageNet organisées selon des classes générales à très précises, comme mammifère puis husky, ou véhicule puis trimaran

Lire les probabilités dans le code#

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")  # ImageNet, 1,000 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

probs = result.probs
print(f"top-1: {result.names[probs.top1]} ({float(probs.top1conf):.2f})")
for i, conf in zip(probs.top5, probs.top5conf.tolist()):
    print(f"  {result.names[i]:20s} {conf:.3f}")
top-1: minibus (0.52)
  minibus              0.516
  police_van           0.369
  trolleybus           0.096
  minivan              0.006
  ambulance            0.003

probs.top1 est un indice, probs.top5 une liste d’indices et probs.data le vecteur complet des probabilités. Remarque le score de confiance top-1 : 0,52 dépasse à peine un pile ou face entre minibus et police_van. C’est sur ce nombre que tu règles l’orientation des prédictions.

Envoyer les prédictions incertaines à une personne#

En production, un classificateur n’a généralement pas besoin d’avoir raison pour chaque image ; il doit avoir raison pour les images qu’il traite seul. Ce script entraîne le modèle pendant 10 époques, puis mesure l’effet de différents seuils de confiance sur les 80 chiffres mis de côté :

from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=10, imgsz=64, device="cpu", plots=False)  # model now holds best.pt
print(model.names)  # folder names, sorted, became class indices

test = Path(model.trainer.data["test"])  # mnist160/test, the split the loader validated on
images = sorted(test.rglob("*.png"))
results = model(images, imgsz=64, verbose=False)
truth = [p.parent.name for p in images]

for threshold in (0.0, 0.3, 0.6):
    kept = [(r, t) for r, t in zip(results, truth) if r.probs.top1conf >= threshold]
    correct = sum(r.names[r.probs.top1] == t for r, t in kept)
    print(f"threshold {threshold:.1f}: auto-accept {len(kept):2d}/{len(images)}, accuracy on accepted {correct / max(len(kept), 1):.0%}")
{0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
threshold 0.0: auto-accept 80/80, accuracy on accepted 55%
threshold 0.3: auto-accept 41/80, accuracy on accepted 68%
threshold 0.6: auto-accept 10/80, accuracy on accepted 80%

Chaque ligne représente un compromis : augmenter le seuil améliore la précision des prédictions acceptées et envoie davantage d’images à une personne. À 0,6, ce modèle ne traite seul que 10 chiffres sur 80 — il n’est pas prêt à automatiser quoi que ce soit ; la solution consiste à l’entraîner davantage, pas à choisir un seuil plus astucieux. Réexécute le même tableau après chaque cycle d’entraînement : il t’indique à la fois la précision obtenue et le volume exact de vérifications que génère chaque seuil.

Règle de décision

Choisis le seuil le plus bas dont la précision sur les images acceptées répond à tes exigences, mesurée sur des données mises de côté. Vérifie ensuite que le volume de révisions généré est gérable par ton équipe ; sinon, améliore le modèle.

Pièges#

Aucune classe « aucune de ces réponses »

Symptôme : une station vide, une pièce incorrecte ou une main devant l'objectif reçoit une étiquette de classe avec une forte confiance. Cause : les probabilités softmax totalisent toujours 1, donc le classificateur choisit toujours l'une de ses classes. Solution : ajoute un dossier other/ contenant précisément ces images, puis envoie les prédictions top1conf faibles en révision.

Valider une tête préentraînée sur de nouveaux dossiers

Symptôme : un modèle préentraîné obtient un score proche de zéro sur ton jeu de données avant tout entraînement. Cause : yolo26n-cls.pt prédit 1 000 index ImageNet, et tes dossiers réutilisent les index 0–9 avec des significations différentes : sur MNIST160, le dossier 0 correspond à l'index 0, auquel la tête préentraînée associe tench. Nous avons mesuré une précision top-1 de 0.000 de cette façon. Solution : commence par affiner le modèle, puis valide-le.

Dossiers avec fuite de données

Symptôme : une précision top-1 proche de 1.0 en validation, mais bien plus faible en production. Cause : des photos quasi identiques de la même pièce se trouvent dans train/ et val/. Solution : répartis les données par pièce ou par session, comme dans Répartir correctement le jeu de données.

Les classificateurs sont préentraînés à imgsz=224, et non à 640. Pour les petits objets dans une grande image, mieux vaut détecter puis classifier (leçon suivante) qu’utiliser un modèle imgsz plus grand.

Essayer

Entraîne yolo26n-cls.pt sur mnist160 pendant 1 époque, puis pendant 10 époques avec imgsz=64, et consigne les précisions top-1 et top-5 pour les deux exécutions. Affiche model.names pour vérifier comment les noms de dossiers sont devenus des indices de classe. Ensuite, exécute route.py et choisis le seuil que tu déploierais pour obtenir une précision d’au moins 75 % sur les images acceptées automatiquement.

Terminé lorsque
Tu as terminé la leçon lorsque tous les éléments suivants sont réunis.
  • Tu as les précisions top-1 et top-5 pour les exécutions de 1 et de 10 époques.

  • Tu as affiché model.names et tu peux expliquer la règle des indices alphabétiques et pourquoi la précision top-5 ne sert à rien pour un problème à cinq classes.

  • Tu as indiqué un seuil, sa précision sur les images acceptées et le nombre d’images, sur les 80, qu’il envoie en vérification.

Afficher la solution
from ultralytics import YOLO

for epochs in (1, 10):
    model = YOLO("yolo26n-cls.pt")
    model.train(data="mnist160", epochs=epochs, imgsz=64, device="cpu")
    metrics = model.val()
    print(f"{epochs:2d} epochs  top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
print(model.names)  # {0: '0', 1: '1', ..., 9: '9'}

Nos résultats : 1 époque → top-1 0,162, top-5 0,463 ; 10 époques → top-1 0,550, top-5 0,950. Avec cinq classes, la bonne réponse figure toujours parmi les cinq premières ; la précision top-5 vaut donc 1,0 par construction et ne t’apprend rien.

Pour atteindre une précision de 75 % sur les images acceptées, route.py indique un seuil de 0,6 (80 % pour 10 images acceptées), ce qui envoie 70 des 80 chiffres en vérification. Voilà la réponse honnête : le seuil respecte l’exigence de précision, mais le modèle n’est pas encore utile. Entraîne-le plus longtemps et réexécute le tableau avant de choisir un seuil pour la production.

Et ensuite ?

Ensuite, nous allons enchaîner les tâches : un détecteur alimentera un classificateur, et un modèle de pose alimentera un traqueur.