Ultralytics YOLO27 : Inscris-toi sur la liste d’attente.
Get Started
Fondamentaux de la vision par ordinateur·Comprendre les métriques·Leçon 8/10
Leçondébutant

Des partitions qui disent la vérité

Repère les images presque identiques qui se retrouvent à la fois dans l’entraînement et la validation, puis répartis les données selon l’unité qui sera nouvelle en déploiement.

Si un modèle obtient 95 % en validation et 60 % en production, c’est généralement que la partition a faussé les résultats. Des images presque identiques — des images vidéo consécutives, la même caméra le même jour — se sont retrouvées dans les deux ensembles ; la validation a donc testé la mémorisation plutôt que la capacité de généralisation. Tu trouveras exactement ce type de fuite dans un vrai jeu de données, puis tu la corrigeras en répartissant les données par groupes.

Résultat

Détecte les images presque identiques entre les partitions, répartis les données selon une clé de groupe et vérifie qu’aucun groupe n’apparaît dans plusieurs partitions.

Parcours accéléré
Si tu connais déjà les bases, voici la version courte.
  1. L’entraînement ajuste les poids, la validation guide les décisions, le test ne sert qu’à une seule évaluation.

  2. Repère les images presque identiques entre les partitions avant de faire confiance à un score de validation.

  3. Trouve la clé de groupe : séquence vidéo, caméra, jour ou sujet.

  4. Mélange les groupes, pas les images, puis vérifie que chaque classe figure dans chaque partition.

  5. Gèle l’ensemble de test et ne l’utilise qu’une fois par expérience terminée.

Pratique

Trois partitions, trois rôles#

PartitionQui l’utilise pour apprendreQuand tu l’examines
EntraînementLe modèle : les poids sont ajustés iciÀ chaque époque
ValidationToi : hyperparamètres, nombre d’époques, taille du modèle, seuilsAprès chaque expérience
TestPersonne : il indique le résultat finalUne seule fois, une fois toutes les décisions prises

Chaque décision que tu prends en examinant une partition fait fuiter un peu de ses données vers le modèle. C'est le rôle de l'ensemble de validation. Si tu ajustes le modèle sur l'ensemble de test, son score cesse d'être une estimation honnête : tu as surajusté le modèle à tes propres choix. Dans Ultralytics, model.val(split="test") calcule le score de la partition de test à partir du fichier YAML de ton jeu de données.

Repérer la fuite dans un vrai jeu de données#

Les images de médicaments sont nommées Frame_0.jpg, Frame_4.jpg, Frame_8.jpg : ce sont des images extraites d’une vidéo. C’est un signal d’alerte : des images consécutives sont presque identiques, et une partition aléatoire les répartit des deux côtés. Vérifie-le en comparant de minuscules empreintes en niveaux de gris de chaque image de validation avec chaque image d’entraînement :

from pathlib import Path

import numpy as np
from PIL import Image
from ultralytics.data.utils import check_det_dataset

data = check_det_dataset("medical-pills.yaml")

def thumbnail(path):
    """A 32x18 grayscale fingerprint: enough to spot near-identical frames."""
    return np.asarray(Image.open(path).convert("L").resize((32, 18)), dtype=float)

train = {p.name: thumbnail(p) for p in sorted(Path(data["train"]).glob("*.jpg"))}
pairs = []
for p in sorted(Path(data["val"]).glob("*.jpg")):
    v = thumbnail(p)
    name, diff = min(((n, np.abs(v - t).mean()) for n, t in train.items()), key=lambda x: x[1])
    pairs.append((diff, p.name, name))

pairs.sort()
print(f"{sum(d < 5 for d, _, _ in pairs)} of {len(pairs)} val images have a near-twin in train (difference < 5)")
for diff, val_name, train_name in pairs[:3]:
    print(f"  {val_name} ~ {train_name}  difference {diff:.1f}")
17 of 23 val images have a near-twin in train (difference < 5)
  Frame_344.jpg ~ Frame_56.jpg  difference 0.3
  Frame_28.jpg ~ Frame_312.jpg  difference 0.5
  Frame_24.jpg ~ Frame_308.jpg  difference 1.8

Une différence de 0,3 sur une échelle de 0 à 255 signifie que les deux images sont pratiquement identiques. Ouvre val/Frame_24.jpg et train/Frame_308.jpg côte à côte : tu verras les mêmes plaquettes sous le même angle. 17 des 23 images de validation ont un équivalent dans l’ensemble d’entraînement ; le score de validation mesure donc surtout la capacité du modèle à se souvenir d’images qu’il a déjà vues. C’est acceptable pour un jeu de données de démonstration rapide, mais c’est précisément ce qu’il ne faut pas mettre en production.

Boîte de dialogue de redistribution des partitions d’Ultralytics Platform pour medical-pills, réglée sur 80 % d’entraînement, 20 % de validation et 0 % de test

Le même piège existe dans tous les outils. La description de cette boîte de dialogue indique qu’elle réattribue aléatoirement chaque image : elle est donc idéale lorsque les images sont des photos indépendantes, mais crée une fuite lorsqu’il s’agit d’images vidéo. Effectue d’abord une vérification des quasi-doublons et conserve une part de test supérieure à 0 % si tu veux obtenir un résultat final sur lequel personne n’a effectué d’ajustements.

Effectuer une partition par groupe#

La règle : les partitions doivent être indépendantes selon l’unité qui sera nouvelle au moment du déploiement.

DonnéesPartitionner par
VidéoSéquence ou session d’enregistrement
Parc de caméras fixesCaméra ou site
Scènes qui évoluent au fil du tempsJour ou semaine
Données médicales, sportives ou sur des personnesPatient, athlète ou sujet
Rafales de photosÉvénement de capture

Les noms de fichiers contiennent souvent la clé de groupe. Cet exemple crée 900 images à partir de 6 caméras et compare les deux stratégies :

import random
from collections import defaultdict

# File names carry the unit that must never straddle splits: camera_day_frame.jpg
images = [f"cam{c:02d}_day{d}_{f:04d}.jpg" for c in range(1, 7) for d in range(1, 4) for f in range(50)]

# WRONG: shuffle individual frames, so near-identical neighbors land in both splits
random.seed(0)
shuffled = random.sample(images, len(images))
train, val = shuffled[: int(0.8 * len(images))], shuffled[int(0.8 * len(images)) :]
shared = {i.split("_")[0] for i in train} & {i.split("_")[0] for i in val}
print(f"per-image split: {len(shared)} of 6 cameras appear in both train and val")

# RIGHT: shuffle whole cameras, then expand them back into images
groups = defaultdict(list)
for image in images:
    groups[image.split("_")[0]].append(image)
cameras = random.sample(sorted(groups), len(groups))
train = [i for cam in cameras[:5] for i in groups[cam]]
val = [i for cam in cameras[5:] for i in groups[cam]]
print(f"per-camera split: train {len(train)} images, val {len(val)} images, val cameras {cameras[5:]}")
per-image split: 6 of 6 cameras appear in both train and val
per-camera split: train 750 images, val 150 images, val cameras ['cam05']

Avec la partition par image, la validation ne voit jamais de nouvelle caméra. Avec la partition par caméra, le score estime ce qui se passe avec une caméra que le modèle n’a jamais vue : c’est le résultat dont tu as besoin.

Pièges#

Mélanger aléatoirement des données corrélées

Symptôme : la validation donne de bien meilleurs résultats que le premier test sur le terrain. Solution : trouve la clé de groupe — souvent cachée dans les noms de fichiers ou les métadonnées de capture —, répartis les données selon cette clé et lance la vérification des quasi-doublons pour confirmer.

Une classe absente de la validation

Symptôme : une classe rare sans AP, ou un AP calculé à partir de deux instances. Solution : après la séparation par groupes, compte chaque classe dans chaque sous-ensemble et échange des groupes entiers jusqu’à ce que chaque classe apparaisse dans des proportions similaires. sklearn.model_selection.StratifiedGroupKFold automatise cette opération.

Régler les paramètres sur le jeu de test

Symptôme : le score de test augmente progressivement au fil des expériences. Solution : gèle le jeu de test, constitue-le à partir de conditions proches de la production et ne le mesure qu’une fois par expérience terminée. Pour les petits jeux de données, calcule la moyenne sur plusieurs séparations avec la validation croisée k-fold plutôt que de réutiliser une seule séparation.

Partitionner correctement le jeu de données explique les ratios et comment appliquer toutes ces règles à un jeu de données complet.

Essayer

Effectue la vérification des quasi-doublons sur ton jeu de données (ou sur medical-pills). Note le nombre d’images de validation ayant un quasi-jumeau dans l’ensemble d’entraînement. Nomme ensuite ta clé de groupe, repartitionne les données selon cette clé, puis exécute la vérification des fuites de la solution sur les nouvelles listes de fichiers. Le résultat attendu est le nombre de quasi-jumeaux avant et après, ainsi qu’une vérification des fuites qui affiche une liste vide.

Terminé lorsque
Tu as terminé la leçon lorsque tous les éléments suivants sont réunis.
  • Tu as obtenu le nombre de quasi-jumeaux pour ta partition actuelle.

  • Tu as nommé ta clé de groupe et repartitionné les données selon cette clé.

  • La vérification des fuites affiche une liste vide pour tes nouvelles partitions.

Afficher la solution

Une vérification des fuites qui fonctionne avec n’importe quelle liste de noms de fichiers, à condition de lui fournir une fonction qui extrait la clé de groupe :

def leaked_groups(splits, key):
    """Return every group key that appears in more than one split."""
    owner, leaks = {}, set()
    for split, files in splits.items():
        for name in files:
            group = key(name)
            if owner.setdefault(group, split) != split:
                leaks.add(group)
    return sorted(leaks)

# Remplace ces listes par tes noms de fichiers, par exemple [p.name for p in Path("images/train").glob("*.jpg")]
splits = {
    "train": ["cam01_day1_0001.jpg", "cam01_day1_0002.jpg", "cam02_day1_0001.jpg"],
    "val": ["cam03_day2_0001.jpg", "cam01_day1_0003.jpg"],
    "test": ["cam04_day3_0001.jpg"],
}
print(leaked_groups(splits, key=lambda name: name.split("_")[0]))  # regrouper par caméra
['cam01']

cam01 apparaît à la fois dans les ensembles d’entraînement et de validation : cette partition présente donc une fuite. Déplace chaque image cam01 dans une seule partition et relance la vérification jusqu’à ce qu’elle affiche []. Pour medical-pills, où les images d’une même vidéo montrent à nouveau les mêmes vues, les noms de fichiers ne fournissent pas de clé de groupe fiable : filme de nouvelles vidéos de validation montrant d’autres plaquettes, ou regroupe les images selon leur empreinte de quasi-doublon et place chaque groupe entier dans une seule partition.

Et ensuite ?

Une fois les partitions fiables créées, tu vas maintenant lire les métriques qu’elles produisent.