Annotation de Datasets ML : Méthodes et Outils Pratiques
Vous êtes convaincu de la valeur d’un modèle IA entraîné sur vos données métier, mais vous sous-estimez peut-être la partie la plus chronophage du projet : l’annotation. Selon un rapport Cognilytica, 80 % du temps d’un projet machine learning est consacré à la préparation et à l’annotation des données, et non à l’entraînement du modèle lui-même. Pourtant, c’est l’étape la moins glamour, celle dont on parle le moins dans les articles techniques. Ce guide pratique démystifie l’annotation de datasets ML : méthodes, outils, stratégies pour travailler efficacement, et comment l’IA peut accélérer l’annotation elle-même.
Pourquoi l’annotation est la clé d’un modèle performant
Le modèle ML apprend à partir des exemples que vous lui fournissez. Si vos annotations sont incohérentes, incomplètes ou biaisées, votre modèle le sera aussi — c’est le principe fondamental “garbage in, garbage out”.
Un dataset annoté de qualité, c’est :
- Des labels cohérents : deux annotateurs différents doivent donner le même label au même exemple (mesurable avec l’accord inter-annotateurs, ou IAA)
- Des données représentatives : couvrir les cas rares et les cas limites, pas seulement les cas faciles
- Une définition claire des classes : des guidelines d’annotation précises qui éliminent l’ambiguïté
- Une bonne couverture : suffisamment d’exemples par classe pour que le modèle généralise
Types d’annotation selon le problème ML
Le type d’annotation dépend entièrement de votre objectif :
| Tâche ML | Type d’annotation | Complexité | Coût relatif |
|---|---|---|---|
| Classification d’images | Label par image | Faible | € |
| Détection d’objets | Bounding boxes | Moyen | €€ |
| Segmentation sémantique | Masques pixel par pixel | Élevé | €€€ |
| NER (entités nommées) | Spans de texte + label | Moyen | €€ |
| Classification de texte | Label par document | Faible | € |
| Résumé de texte | Paires texte/résumé | Élevé | €€€ |
| RLHF (alignment LLM) | Comparaisons de réponses | Très élevé | €€€€ |
Outils d’annotation : panorama
Label Studio — le plus polyvalent (open source)
Label Studio est l’outil de référence pour les équipes qui veulent garder le contrôle de leurs données. Il supporte tous les types d’annotation : texte, image, audio, vidéo, même des données structurées.
# Installation et démarrage
pip install label-studio
label-studio start
# Ou avec Docker pour une installation propre
docker run -it -p 8080:8080 -v $(pwd)/mydata:/label-studio/data
heartexlabs/label-studio:latest Configuration d’un projet de NER (Named Entity Recognition) via l’interface :
<!-- Template de labeling pour la reconnaissance d'entités nommées -->
<View>
<Labels name="label" toName="text">
<Label value="ORGANISATION" background="#FFA39E"/>
<Label value="PERSONNE" background="#D4380D"/>
<Label value="LIEU" background="#FFC069"/>
<Label value="DATE" background="#AD8B00"/>
<Label value="MONTANT" background="#7CB305"/>
</Labels>
<Text name="text" value="$text"/>
</View> Import/export via API :
import requests
LABEL_STUDIO_URL = "http://localhost:8080"
API_KEY = "your-api-key"
headers = {"Authorization": f"Token {API_KEY}"}
# Import de données à annoter
def import_texts_to_label_studio(project_id: int, texts: list[str]):
tasks = [{"data": {"text": t}} for t in texts]
response = requests.post(
f"{LABEL_STUDIO_URL}/api/projects/{project_id}/import",
headers=headers,
json=tasks
)
return response.json()
# Export des annotations
def export_annotations(project_id: int, export_format: str = "JSON") -> list[dict]:
response = requests.get(
f"{LABEL_STUDIO_URL}/api/projects/{project_id}/export",
headers=headers,
params={"exportType": export_format}
)
return response.json() CVAT — spécialisé vision par ordinateur
Pour les tâches de vision (bounding boxes, segmentation, keypoints), CVAT (Computer Vision Annotation Tool) de Intel est la référence :
# Installation avec Docker Compose
git clone https://github.com/opencv/cvat
cd cvat
docker compose up -d
# Interface disponible sur http://localhost:8080 Roboflow — annotation + augmentation + export
Roboflow va plus loin que l’annotation : il intègre l’augmentation de données et l’export dans tous les formats (YOLO, COCO, Pascal VOC, TFRecord).
from roboflow import Roboflow
rf = Roboflow(api_key="votre-api-key")
project = rf.workspace("votre-workspace").project("votre-projet")
# Télécharger le dataset dans le format YOLO
dataset = project.version(1).download("yolov8")
# Entraîner directement (exemple avec YOLOv8)
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
model.train(data=f"{dataset.location}/data.yaml", epochs=50) Annotation semi-automatisée : l’IA au service de l’annotation
L’approche la plus efficace aujourd’hui : utiliser un modèle IA pré-entraîné pour générer des annotations préliminaires, puis faire corriger par des humains. On passe ainsi de “annoter de zéro” à “valider et corriger”, ce qui est 3 à 5 fois plus rapide.
Pre-annotation avec un LLM (pour le texte)
import anthropic
import json
from typing import NamedTuple
client = anthropic.Anthropic()
class AnnotationCandidate(NamedTuple):
text: str
entities: list[dict] # [{"start": int, "end": int, "label": str, "text": str}]
confidence: float
def pre_annotate_ner(text: str, entity_types: list[str]) -> AnnotationCandidate:
"""
Génère des annotations NER préliminaires via LLM.
Un annotateur humain corrige ensuite.
"""
entity_list = ", ".join(entity_types)
response = client.messages.create(
model="claude-haiku-4-5", # Haiku pour économiser sur le coût d'annotation
max_tokens=800,
messages=[{
"role": "user",
"content": f"""Identifie les entités nommées dans ce texte.
Types d'entités à détecter : {entity_list}
Texte : "{text}"
Réponds avec un JSON :
{{
"entities": [
{{"text": "texte de l'entité", "label": "TYPE", "start": position_début, "end": position_fin}}
],
"confidence": score entre 0 et 1
}}
Les positions start/end sont des indices de caractères dans le texte original.
Réponds uniquement avec le JSON."""
}]
)
result = json.loads(response.content[0].text)
return AnnotationCandidate(
text=text,
entities=result.get("entities", []),
confidence=result.get("confidence", 0.5)
)
# Utilisation sur un batch
def pre_annotate_batch(texts: list[str], entity_types: list[str]) -> list[AnnotationCandidate]:
candidates = []
for text in texts:
candidate = pre_annotate_ner(text, entity_types)
candidates.append(candidate)
return candidates
# Filtrage par confiance — envoyer seulement les cas incertains à la révision humaine
def smart_routing(candidates: list[AnnotationCandidate]) -> tuple[list, list]:
auto_approved = [c for c in candidates if c.confidence >= 0.90]
needs_review = [c for c in candidates if c.confidence < 0.90]
print(f"Auto-approuvé : {len(auto_approved)} ({len(auto_approved)/len(candidates)*100:.0f}%)")
print(f"À réviser : {len(needs_review)}")
return auto_approved, needs_review Active Learning : annoter intelligemment
L’active learning est une stratégie qui maximise la valeur de chaque annotation humaine. Au lieu d’annoter aléatoirement, on sélectionne en priorité les exemples les plus informatifs pour le modèle.
import numpy as np
from sklearn.base import BaseEstimator
def uncertainty_sampling(model: BaseEstimator, unlabeled_data: np.ndarray,
n_samples: int = 100) -> np.ndarray:
"""
Sélectionne les exemples les plus incertains (entropie maximale).
Ces exemples apporteront le plus d'information au modèle.
"""
probabilities = model.predict_proba(unlabeled_data)
# Calcul de l'entropie pour chaque exemple
# Entropie maximale = modèle le plus incertain
entropy = -np.sum(probabilities * np.log(probabilities + 1e-10), axis=1)
# Indices des exemples les plus incertains
most_uncertain = np.argsort(entropy)[-n_samples:]
return most_uncertain
def diversity_sampling(embeddings: np.ndarray, already_annotated: np.ndarray,
n_samples: int = 100) -> np.ndarray:
"""
Sélectionne les exemples les plus différents du corpus déjà annoté.
Assure une bonne couverture de l'espace des données.
"""
from sklearn.metrics.pairwise import cosine_distances
# Distance minimale de chaque exemple non annoté aux exemples annotés
distances = cosine_distances(embeddings, already_annotated)
min_distances = distances.min(axis=1)
# On sélectionne les plus éloignés (les plus différents)
most_diverse = np.argsort(min_distances)[-n_samples:]
return most_diverse Contrôle qualité des annotations
Mesurer l’accord inter-annotateurs (IAA)
Si plusieurs annotateurs travaillent sur le même corpus, il faut mesurer leur cohérence :
from sklearn.metrics import cohen_kappa_score
import numpy as np
def calculate_iaa(annotations_a: list, annotations_b: list) -> dict:
"""
Calcule le kappa de Cohen entre deux annotateurs.
Score > 0.8 : accord excellent
Score 0.6-0.8 : bon accord
Score 0.4-0.6 : accord modéré — réviser les guidelines
Score < 0.4 : désaccord — formation nécessaire
"""
kappa = cohen_kappa_score(annotations_a, annotations_b)
if kappa > 0.8:
quality = "Excellent"
elif kappa > 0.6:
quality = "Bon"
elif kappa > 0.4:
quality = "Modéré — réviser les guidelines"
else:
quality = "Insuffisant — formation requise"
# Analyse des désaccords
disagreements = [
i for i, (a, b) in enumerate(zip(annotations_a, annotations_b))
if a != b
]
return {
"kappa": float(kappa),
"quality": quality,
"agreement_rate": 1 - len(disagreements) / len(annotations_a),
"disagreement_indices": disagreements[:10] # 10 premiers désaccords pour analyse
} Détection d’anomalies dans les annotations
from collections import Counter
def audit_annotations(annotations: list[dict]) -> dict:
"""Détecte les patterns suspects dans un corpus annoté."""
issues = []
# Distribution des labels
labels = [a["label"] for a in annotations]
label_counts = Counter(labels)
total = len(labels)
# Déséquilibre de classes extrême (< 2% d'une classe)
for label, count in label_counts.items():
percentage = count / total * 100
if percentage < 2:
issues.append({
"type": "class_imbalance",
"severity": "warning",
"message": f"Classe '{label}' très rare ({percentage:.1f}%) — risque de biais"
})
# Annotateur trop rapide (moins de 5 secondes par annotation)
fast_annotations = [
a for a in annotations
if a.get("annotation_time_ms", float('inf')) < 5000
]
if len(fast_annotations) / total > 0.3:
issues.append({
"type": "speed_anomaly",
"severity": "critical",
"message": f"{len(fast_annotations)/total*100:.0f}% d'annotations en moins de 5 secondes"
})
# Label toujours identique (annotateur fainéant)
if len(label_counts) == 1:
issues.append({
"type": "single_label",
"severity": "critical",
"message": "Tous les exemples ont le même label — annotation suspecte"
})
return {
"total_annotations": total,
"label_distribution": dict(label_counts),
"issues": issues,
"quality_score": max(0, 100 - len(issues) * 20)
} Guidelines d’annotation : la clé souvent négligée
Les guidelines sont le document qui définit ce que les annotateurs doivent faire dans chaque cas. Un mauvais guideline génère des données inutiles.
Structure minimale d’un bon guideline :
- Objectif du modèle : à quoi servira le modèle entraîné ? Cette question doit guider chaque décision d’annotation.
- Définition des classes : définition précise de chaque classe, avec exemples positifs ET négatifs.
- Cas limites traités : les 20 cas ambigus les plus fréquents, avec la règle de décision.
- Convention de tie-break : que faire en cas de doute ? (préférer la classe la plus spécifique, ou la plus générale ?)
- Exemples annotés : 10-20 exemples de référence, annotés par un expert.
Estimation du budget annotation
Pour une PME qui démarre un projet ML :
| Type de projet | Volume minimal viable | Coût annotation interne | Coût outsourcing |
|---|---|---|---|
| Classif. texte (2-5 classes) | 2 000 exemples | 20-30h | 500-1 500 € |
| NER (5-10 entités) | 5 000 phrases | 50-80h | 2 000-5 000 € |
| Classif. images | 3 000 images | 15-25h | 300-1 000 € |
| Détection objets | 2 000 images | 40-80h | 2 000-6 000 € |
Avec la pre-annotation LLM, divisez ces estimations par 3 à 5.
Ce qu’il faut retenir
L’annotation de données n’est pas une tâche à sous-traiter et oublier — c’est un investissement stratégique dans la qualité de votre IA. Trois principes guident les projets réussis :
- Des guidelines béton avant de commencer : 2 heures de rédaction de guidelines évitent 20 heures de réannotation.
- Mesurer l’IAA systématiquement : un kappa en dessous de 0.6 signale un problème à régler avant de continuer.
- L’IA accélère l’annotation, pas la remplace : la pre-annotation LLM + validation humaine est le rapport qualité/vitesse optimal aujourd’hui.
Vous lancez un projet ML et avez besoin d’accompagnement sur la stratégie de données ? Brio Novia vous aide à concevoir votre pipeline d’annotation, choisir les bons outils, et définir des guidelines efficaces. Contactez-nous à contact@brio-novia.eu pour démarrer du bon pied.