Pré-étiquettes et scores sur items réservés à partir d'un modèle que vous exécutez déjà
Évaluez sur les étiquettes de votre étude un modèle servi par vLLM, Ollama, Hugging Face, Roboflow, CVAT ou un backend ML de Label Studio, puis relisez ses réponses sous forme de pré-étiquettes.
Nouveauté de la v2.14.0
Ce tutoriel s'adresse aux équipes qui exécutent déjà un modèle hors de Potato et veulent savoir dans quelle mesure ce modèle s'accorde avec leurs propres annotateurs avant de montrer ses réponses à qui que ce soit. L'entraînement de modèles de Potato comprend cinq modules d'entraînement, appelés passerelles, qui n'entraînent rien. Une passerelle envoie les items réservés de l'étude à un modèle servi ailleurs, évalue les réponses d'après les étiquettes de l'étude et les écrit en retour sous forme de pré-étiquettes à relire, comme le fait l'exécution d'un modèle entraîné. Les étapes ci-dessous couvrent un modèle vision-langage derrière une API compatible OpenAI, un modèle Roboflow, une fonction serverless CVAT, un modèle Hugging Face et un backend ML de Label Studio, avec une configuration validée pour chacun.
Ce qu'une exécution de passerelle envoie et évalue
Une passerelle est un module d'entraînement dont l'étape d'ajustement se borne à vérifier que le service répond. L'exécution envoie ensuite au service les items des partitions val et test de l'exécution, et avec writeback.include_unlabeled: true aussi les items que personne n'a étiquetés. Potato transforme les réponses en formes propres au schéma, empans ou choix, et les évalue sur les items réservés d'après les étiquettes humaines résolues, avec les mêmes métriques sur items réservés que celles d'un modèle entraîné (mAP50 pour les boîtes, F1 d'empans pour les empans, F1 macro pour les choix uniques). L'écriture en retour, la file de relecture et l'identifiant d'exécution de chaque pré-étiquette fonctionnent comme après toute autre exécution.
| Module | Schémas | Le modèle qu'il appelle |
|---|---|---|
openai-vlm-boxes | image_annotation | Un modèle vision-langage derrière une API de chat compatible OpenAI (vLLM, Ollama, LM Studio, le serveur de llama.cpp, OpenAI) |
roboflow | image_annotation | Un modèle Roboflow de détection ou de segmentation d'instances, hébergé ou sur un serveur Inference auto-hébergé |
cvat-nuclio | image_annotation | Une fonction serverless CVAT qui tourne sur Nuclio |
hf-inference | radio, select, multiselect, span, image_annotation | Un Inference Endpoint Hugging Face ou un modèle sur l'API HF Inference serverless |
label-studio-ml | radio, select, multiselect, span, image_annotation | Un backend ML de Label Studio en cours d'exécution |
La partition d'un item ne dépend que de l'identifiant de l'item et de model_training.split_seed, de sorte qu'une exécution de passerelle et l'exécution d'un modèle entraîné sont évaluées sur les mêmes items val et test tant que la graine reste fixe. Le tableau Runs de /admin/training place alors un modèle que vous avez déjà à côté d'un modèle entraîné sur les étiquettes de l'étude. Les items de la partition train ne reçoivent aucune prédiction d'une passerelle, et l'écriture en retour ne pré-étiquette jamais un item de la partition train. Avec la partition par défaut de 70/15/15, une passerelle est donc évaluée sur environ 30 % des items étiquetés.
Avant de commencer
Les quatre passerelles autres que label-studio-ml demandent requests et Pillow sur le serveur Potato, et la page Training liste une passerelle comme « not installed » tant que les deux paquets ne s'importent pas. label-studio-ml ne demande rien de plus que Potato. Aucune des cinq passerelles n'installe de modèle, de bibliothèque GPU ni torch, car le modèle tourne dans le service.
Les images doivent être des fichiers locaux dans le projet, comme pour tout module d'entraînement sur les images. Une passerelle exclut un item dont l'image est une URL http(s) distante, et un item qui a une étiquette et pas de prédiction compte comme faux dans le score sur items réservés. L'exécution rapporte la part des items réservés qui ont reçu une prédiction sous les noms val_coverage et test_coverage, de sorte qu'une faible couverture indique des images que la passerelle n'a pas pu envoyer.
Les clés restent dans des variables d'environnement
Une clé ne figure jamais dans la configuration. Chaque passerelle qui envoie une clé prend un paramètre qui nomme la variable d'environnement contenant la clé (api_key_env, token_env pour Hugging Face, ou les deux noms basic_auth_*_env pour Label Studio), et la variable doit être définie dans l'environnement du serveur Potato avant le démarrage du serveur. cvat-nuclio n'envoie aucune clé et ne prend pas de tel paramètre. Les quatre passerelles qui prennent une clé refusent une valeur qui n'est pas un nom de variable, et l'erreur ne répète pas la valeur, car une valeur à cet endroit est le plus probablement la clé elle-même. Lorsque le corps d'erreur d'un service ou une erreur de connexion répète la requête, les passerelles remplacent la valeur de la clé par [redacted] avant d'afficher l'erreur. Les corps d'erreur du /predict d'un backend ML de Label Studio sont nettoyés de la même façon. Tout ce qui a la forme d'une clé dans une chaîne de requête ou dans un en-tête Authorization est également remplacé.
Les exécutions hf-inference et roboflow sont uniquement locales. Les deux passerelles lisent la clé sur le serveur Potato, et une exécution sur un backend ssh, SLURM ou agent devrait envoyer la clé à cet hôte, de sorte que Potato refuse de lancer une telle exécution à cet endroit. openai-vlm-boxes, cvat-nuclio et label-studio-ml s'exécutent sur tout backend qui peut joindre le service, avec la variable de la clé définie sur l'hôte de ce backend.
Étape 1 : une étude d'images avec trois passerelles
La configuration ci-dessous met en place une étude de photos de chantier dans laquelle les annotateurs encadrent chaque casque de chantier et chaque tête nue. model_training.trainer_params contient les valeurs par défaut de plusieurs modules à la fois, de sorte qu'une seule configuration porte les réglages des trois passerelles d'images, et chaque exécution utilise le bloc de son propre module. Le champ Parameters de la page Training se remplit à partir de ces valeurs par défaut lorsque vous choisissez un module.
annotation_task_name: "Hard hats on site photos"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
admin_api_key: "replace-with-a-long-random-string"
data_files:
- data/photos.jsonl
item_properties:
id_key: id
text_key: image_url
annotation_schemes:
- annotation_type: image_annotation
name: ppe
description: "Box every hard hat and every bare head."
source_field: image_url
tools: [bbox, polygon]
labels:
- name: helmet
- name: head
model_training:
enabled: true
writeback:
schemas: [ppe]
include_unlabeled: true
trainer_params:
openai-vlm-boxes:
url: http://localhost:8000/v1
model: Qwen/Qwen2.5-VL-7B-Instruct
coordinate_format: pixels
roboflow:
model_id: site-safety/3 # your Roboflow project and version
url: https://detect.roboflow.com
api_key_env: ROBOFLOW_API_KEY
confidence: 0.4
label_map: {hardhat: helmet, no-hardhat: head} # the model's class names on the left
cvat-nuclio:
url: http://localhost:32768
threshold: 0.5
label_map: {person: null}Chaque ligne de data/photos.jsonl nomme une image sous media/ :
{"id": "site_001", "image_url": "media/site_001.jpg"}writeback.schemas est ce qui rend les boîtes du modèle visibles aux annotateurs, et include_unlabeled envoie aussi au service les photos que personne n'a étiquetées. Omettez les deux clés pour la première exécution si vous voulez le score sur items réservés avant que quiconque voie une pré-étiquette. label_map fait correspondre les noms de classes du service aux étiquettes du schéma, et une classe associée à null est écartée. Une classe sans entrée va vers l'étiquette du même nom, et la correspondance ignore la casse. Les boîtes de toute autre classe sont exclues, et le journal de l'exécution compte les boîtes exclues.
Étape 2 : un modèle vision-langage avec openai-vlm-boxes
openai-vlm-boxes réduit chaque image à 1 280 pixels au plus sur le côté le plus long (max_image_side) et envoie l'image à la route des complétions de chat avec une consigne qui nomme les étiquettes du schéma. Potato lit la réponse comme des boîtes en JSON. Tout serveur doté d'une API compatible OpenAI convient. La commande ci-dessous sert Qwen2.5-VL avec vLLM, dont la documentation connecte les clients à http://localhost:8000/v1 :
vllm serve Qwen/Qwen2.5-VL-7B-InstructAvec vLLM sur le port 8000, démarrez Potato sur un autre port :
potato start config.yaml -p 9000Les modèles vision-langage répondent dans leur propre convention de coordonnées quelle que soit la demande de la consigne, et deux paramètres indiquent à Potato comment lire les nombres. coordinate_format vaut fraction (0 à 1 de l'image), per_mille (0 à 1 000) ou pixels de l'image telle qu'envoyée. box_order vaut xyxy pour [x0, y0, x1, y1] ou yxyx pour [y0, x0, y1, x1]. Qwen2.5-VL répond en pixels, de sorte que la configuration ci-dessus fixe coordinate_format: pixels. Avec auto, Potato devine le format une fois par réponse d'après la taille des nombres. Les valeurs jusqu'à 1,05 se lisent comme des fractions, les valeurs au-delà de la taille de l'image envoyée se lisent comme des pour-mille, et tout le reste se lit comme des pixels. Une boîte en pour-mille qui tient dans une grande image se lit donc comme des pixels, de sorte qu'il faut nommer le format dès que vous connaissez la convention du modèle.
Gemma et Gemini répondent par des boîtes en pour-mille avec y en premier, [ymin, xmin, ymax, xmax], même lorsque la consigne demande des fractions. Avec les deux paramètres sur auto, Potato lit les boîtes d'un modèle dont le nom contient gemma ou gemini comme des pour-mille avec y en premier. Sur un projet de test Gemma 4 12B de carrés rouges et bleus, l'équipe de Potato a mesuré un mAP50 de 0,0 avec les boîtes lues x en premier et de 1,0 avec les boîtes lues y en premier. Ollama sert Gemma 3 sous un nom qui contient gemma, de sorte que auto s'applique :
ollama run gemma3:12bRemplacez le bloc openai-vlm-boxes de la configuration par le bloc ci-dessous :
model_training:
trainer_params:
openai-vlm-boxes:
url: http://localhost:11434/v1
model: gemma3:12b
json_mode: objectUn affinage de Gemma ou de Gemini servi sous un nom sans aucun de ces mots reçoit la lecture x en premier avec auto. Fixez coordinate_format: per_mille et box_order: yxyx pour un tel modèle. json_mode: object demande à Ollama du JSON avec response_format: json_object. La valeur par défaut, schema, envoie un schéma JSON avec les étiquettes comme énumération. vLLM et OpenAI respectent le schéma. Une exécution dans laquelle aucune réponse ne s'analyse comme du JSON échoue, et l'erreur suggère json_mode: object ou une consigne plus simple (prompt, où {labels} devient les étiquettes du schéma).
Un modèle vision-langage ne donne aucun score avec ses boîtes, de sorte que les boîtes n'ont aucune confiance. writeback.min_confidence laisse passer toutes les prédictions d'openai-vlm-boxes, et la relecture des modèles ne peut pas ordonner les prédictions de la moins sûre à la plus sûre. La référence d'openai-vlm-boxes liste api_key_env pour une API hébergée ainsi que les réglages de jeton et de délai.
Étape 3 : lancer une exécution et lire le score
Ouvrez http://localhost:9000/admin/training, choisissez openai-vlm-boxes et le schéma ppe, puis cliquez sur Train now. La même exécution se lance depuis l'API :
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
http://localhost:9000/admin/training/api/runs \
-d '{"trainer": "openai-vlm-boxes", "schemas": ["ppe"]}'L'étape d'ajustement liste les modèles du serveur par /models et consigne quel modèle l'exécution interroge et comment l'exécution lit les boîtes du modèle. La passe de prédiction envoie ensuite une image par requête. Lorsque l'exécution se termine, le mAP50 sur items réservés apparaît dans le tableau Runs, et la page de l'exécution liste val_map_50, test_map_50, les chiffres de couverture et un avertissement « Not used » qui compte les réponses qui n'étaient pas du JSON, les boîtes sous des étiquettes que le schéma n'a pas et les boîtes hors de l'image. Lorsque le score est proche de 0 alors que la couverture est élevée et que l'avertissement est vide, vérifiez d'abord coordinate_format et box_order.
Modèles Roboflow
roboflow envoie chaque image par POST à <url>/<model_id>, où model_id est le projet et la version sous la forme project/version. La clé se place par défaut dans ROBOFLOW_API_KEY (api_key_env), et l'exécution refuse de démarrer sans la clé :
export ROBOFLOW_API_KEY=... # in the shell that starts Potato
potato start config.yaml -p 9000L'url par défaut de Potato est https://detect.roboflow.com, et la configuration ci-dessus nomme la valeur par défaut. Une exécution de segmentation d'instances définit url: https://outline.roboflow.com. url est un paramètre, de sorte qu'une exécution peut aussi nommer https://serverless.roboflow.com, l'hôte que donne la référence de détection d'objets de Roboflow pour la même route project/version. L'équipe de Potato n'a pas testé cet hôte avec cette passerelle. Un serveur Roboflow Inference auto-hébergé écoute par défaut sur le port 9001 (url: http://localhost:9001).
L'API de Roboflow prend la clé dans la chaîne de requête. Potato n'écrit pas l'URL de la requête dans le journal de l'exécution ni dans un message d'erreur. Potato envoie confidence et overlap (valeurs par défaut 0,4 et 0,3) à l'API sous forme de pourcentages, et la référence de Roboflow documente les deux sur une échelle de 0 à 100. shapes: auto écrit un contour lorsque le modèle renvoie un contour et que le schéma a un outil polygone, et une boîte dans le cas contraire. La vérification d'état envoie une image blanche de 8 × 8, et Roboflow compte la requête dans l'utilisation du compte.
Fonctions serverless CVAT
cvat-nuclio utilise un détecteur déjà déployé pour CVAT comme fonction Nuclio, comme les fonctions YOLOv7 ou Faster R-CNN de cvat/serverless. Potato envoie chaque image à l'URL propre de la fonction sous la forme {"image": <base64>, "threshold": t}, sans passer par CVAT. nuctl get functions liste les fonctions déployées avec leurs ports, et la commande ci-dessous est la forme que donne la documentation de CVAT :
nuctl get functions --platform localLa fonction répond avec des formes en pixels. Un rectangle devient une boîte, et un polygon devient un polygone, ou la boîte englobante du polygone lorsque le schéma n'a pas d'outil polygone. Les formes mask sont exclues et comptées. La fonction écarte les formes dont le score est inférieur à threshold (0,5 par défaut). Le schéma n'a pas d'étiquette de personne, de sorte que label_map: {person: null} dans la configuration écarte les personnes d'un détecteur COCO.
Modèles Hugging Face avec hf-inference
hf-inference appelle un modèle déployé sur Hugging Face, soit un Inference Endpoint dédié par son URL https:// (endpoint_url), soit un identifiant de modèle sur l'API HF Inference serverless (model). Définissez endpoint_url ou model, mais pas les deux. La tâche suit le type du schéma :
- Classification de texte pour
radio,selectetmultiselect. Un schéma à choix unique prend l'étiquette de tête du modèle. Lorsque cette étiquette ne figure pas parmi les étiquettes du schéma aprèslabel_map, l'item ne reçoit aucune prédiction plutôt que la deuxième. Un schémamultiselectprend chaque étiquette dont le score atteint au moinsthreshold(0,5 par défaut). - Classification de jetons pour
span, avec les morceaux de mots regroupés en entités paraggregation_strategy(simple,first,averageoumax) et les décalages de caractères pris au modèle. - Détection d'objets pour
image_annotation. Les boîtes dont le score est inférieur àthresholdsont écartées.
La configuration ci-dessous interroge dslim/bert-base-NER pour des empans. La fiche du modèle le décrit comme un modèle BERT affiné pour les entités nommées sur le CoNLL-2003 en anglais. HF Inference sert un sous-ensemble des modèles du Hub, de sorte qu'il faut vérifier que la page du modèle liste HF Inference avant de compter sur l'API serverless.
annotation_task_name: "Named entities in news sentences"
task_dir: .
output_annotation_dir: annotation_output
admin_api_key: "replace-with-a-long-random-string"
data_files:
- data/sentences.jsonl
item_properties:
id_key: id
text_key: text
annotation_schemes:
- annotation_type: span
name: entities
description: "Highlight every person, organization, and place."
labels: [person, organization, location]
model_training:
enabled: true
writeback:
schemas: [entities]
include_unlabeled: true
trainer_params:
hf-inference:
model: dslim/bert-base-NER
token_env: HF_TOKEN
aggregation_strategy: simple
label_map: {PER: person, ORG: organization, LOC: location, MISC: null}Les entités PER, ORG et LOC du modèle correspondent aux étiquettes du schéma, et MISC est écartée car le schéma n'a pas une telle étiquette. Lorsque le nom complet d'une entité ne correspond à rien, Potato retire un préfixe B- ou I- et réessaie. Un classifieur de sentiment qui répond LABEL_0 et LABEL_1 demande de la même façon label_map: {LABEL_0: negative, LABEL_1: positive}. La vérification d'état est une vraie requête, une courte phrase ou une image de 8 × 8, et wait_for_model: true (la valeur par défaut) attend pendant le chargement d'un modèle serverless au lieu d'échouer avec une erreur 503.
Un backend ML de Label Studio
label-studio-ml utilise un backend ML de Label Studio en cours d'exécution. Un backend démarré à partir des exemples du dépôt avec docker-compose up sert un modèle en HTTP à http://localhost:9090. L'exécution vérifie le GET /health du backend, envoie les items à POST /predict par lots de 16 (batch_size) et lit les régions renvoyées avec le même convertisseur que celui de potato import -f label_studio. Les décalages d'empans en UTF-16 de Label Studio deviennent des points de code, et les coordonnées en pourcentage deviennent celles du widget d'image.
annotation_task_name: "Review sentiment"
task_dir: .
output_annotation_dir: annotation_output
admin_api_key: "replace-with-a-long-random-string"
data_files:
- data/reviews.jsonl
item_properties:
id_key: id
text_key: text
annotation_schemes:
- annotation_type: radio
name: sentiment
description: "Is this review positive or negative?"
labels: [positive, negative]
model_training:
enabled: true
writeback:
schemas: [sentiment]
include_unlabeled: true
min_confidence: 0.6
trainer_params:
label-studio-ml:
url: http://localhost:9090
basic_auth_user_env: LS_ML_USER
basic_auth_pass_env: LS_ML_PASSLa seule authentification d'un backend standard est HTTP Basic, définie par BASIC_AUTH_USER et BASIC_AUTH_PASS sur le backend. basic_auth_user_env et basic_auth_pass_env nomment les variables du serveur Potato qui contiennent ces deux mêmes valeurs, et les deux paramètres vont ensemble. api_key_env envoie plutôt Authorization: Token <value>, pour un backend derrière un proxy qui attend un jeton. Un backend standard répond à cet en-tête par une erreur 500. Potato génère la configuration d'étiquetage envoyée avec chaque requête à partir du schéma (Choices, Labels, RectangleLabels ou PolygonLabels), et label_config remplace le XML généré par le vôtre.
Un backend d'images demande un réglage de plus. Sans media_url_prefix, Potato envoie chaque image comme un chemin absolu sur la machine de Potato, et un backend construit sur le SDK de Label Studio lit data.image comme une URL et échoue sur un chemin nu. Pour un backend sur la même machine, ajoutez le préfixe ci-dessous et démarrez le backend avec LOCAL_FILES_DOCUMENT_ROOT défini sur le répertoire de la tâche :
model_training:
trainer_params:
label-studio-ml:
url: http://localhost:9090
media_url_prefix: /data/local-files/?d=Pour un backend sur une autre machine, servez le répertoire de la tâche en HTTP et donnez l'URL de ce serveur comme préfixe. Une image hors du répertoire de la tâche n'a pas de chemin relatif au répertoire, de sorte que Potato n'envoie pas l'image, et le journal de l'exécution indique combien ont été exclues.
Potato envoie au backend des requêtes de prédiction et aucun événement d'annotation, de sorte que l'entraînement propre au backend dans son gestionnaire /webhook ne démarre pas depuis Potato. potato import -f label_studio déplace tout un projet Label Studio dans Potato avec la configuration d'étiquetage du projet, les réponses des annotateurs et les prédictions stockées (Importer des projets). Migrer de Label Studio vers Potato traite des balises que l'importateur laisse de côté.
Relire les pré-étiquettes
Les prédictions d'une passerelle passent par les mêmes vérifications d'écriture en retour que celles d'un modèle entraîné. L'écriture en retour ignore les items de la partition train et les schémas absents de writeback.schemas, ignore les paires item-schéma qu'un relecteur a rejetées et retient les prédictions sous writeback.min_confidence. Pour les boîtes et les empans, la confiance d'une prédiction est celle de la forme ou de l'empan le moins sûr, de sorte que le seuil retient toute la prédiction d'un item lorsqu'une seule boîte ou un seul empan de l'item obtient un score plus bas. Le confidence de Roboflow et le threshold de CVAT agissent plutôt par boîte, au niveau du service, et le threshold de hf-inference agit par boîte ou par étiquette multiselect. Le tutoriel du modèle dans la boucle traite de la file de relecture, des verdicts d'acceptation et de rejet et du réentraînement. La file et les verdicts fonctionnent de la même façon pour une passerelle.
Une seule prédiction est conservée par item et par schéma, de sorte que l'exécution ultérieure d'une autre passerelle ou d'un modèle entraîné remplace les pré-étiquettes de l'exécution précédente. Pour retirer les pré-étiquettes d'une exécution, envoyez DELETE /admin/training/api/runs/<run_id>/predictions. La référence des passerelles d'inférence liste chaque paramètre des quatre passerelles, et la référence de label-studio-ml liste les paramètres de Label Studio.
Pour aller plus loin
- Entraînement de modèles présente la boucle et la page Training.
- Modules d'entraînement liste les 38 modules par tâche, pour comparer un modèle entraîné sur les étiquettes de l'étude avec le score d'une passerelle.
- Configuration documente les partitions, l'écriture en retour et les routes.
- Annotation d'images et annotation d'empans documentent les schémas que remplissent les passerelles.
- IA visuelle montre à la demande les suggestions d'un modèle de vision à un annotateur, sans score sur items réservés.