Vorlabels und Holdout-Scores von einem Modell, das du bereits betreibst
Ein Modell, das über vLLM, Ollama, Hugging Face, Roboflow, CVAT oder ein Label-Studio-ML-Backend läuft, auf den Labels deiner Studie bewerten und seine Antworten dann als Vorlabels prüfen.
Neu in v2.14.0
Dieses Tutorial richtet sich an Teams, die bereits ein Modell außerhalb von Potato betreiben und wissen wollen, wie gut das Modell mit den eigenen Annotatoren des Teams übereinstimmt, bevor die Antworten des Modells jemandem gezeigt werden. Das Modelltraining von Potato enthält fünf Trainer, Bridges genannt, die nichts trainieren. Eine Bridge sendet die zurückgehaltenen Items der Studie an ein anderswo betriebenes Modell, bewertet die Antworten an den Labels der Studie und schreibt die Antworten als Vorlabels zur Prüfung zurück, wie es der Lauf eines trainierten Modells tut. Die folgenden Schritte behandeln ein Vision-Language-Modell hinter einer OpenAI-kompatiblen API, ein Roboflow-Modell, eine serverlose CVAT-Funktion, ein Hugging-Face-Modell und ein Label-Studio-ML-Backend, jeweils mit einer validierten Konfiguration.
Was ein Bridge-Lauf sendet und bewertet
Eine Bridge ist ein Trainer, dessen Fit-Schritt nur prüft, dass der Dienst antwortet. Der Lauf sendet dann die Items in den Splits val und test des Laufs an den Dienst, und mit writeback.include_unlabeled: true auch die Items, die noch niemand gelabelt hat. Potato macht aus den Antworten die eigenen Formen des Schemas, also Spans oder Auswahlen, und bewertet die Antworten auf zurückgehaltenen Items an den aufgelösten menschlichen Labels, mit denselben Holdout-Metriken, die ein trainiertes Modell erhält (mAP50 für Boxen, Span-F1 für Spans, Macro-F1 für Einzelauswahlen). Das Rückschreiben, die Prüf-Warteschlange und die Lauf-ID jedes Vorlabels funktionieren wie nach jedem anderen Lauf.
| Trainer | Schemas | Das Modell, das er aufruft |
|---|---|---|
openai-vlm-boxes | image_annotation | Ein Vision-Language-Modell hinter einer OpenAI-kompatiblen Chat-API (vLLM, Ollama, LM Studio, der Server von llama.cpp, OpenAI) |
roboflow | image_annotation | Ein Roboflow-Modell für Detection oder Instanzsegmentierung, gehostet oder auf einem selbst betriebenen Inference-Server |
cvat-nuclio | image_annotation | Eine serverlose CVAT-Funktion, die auf Nuclio läuft |
hf-inference | radio, select, multiselect, span, image_annotation | Ein Hugging Face Inference Endpoint oder ein Modell auf der serverlosen HF-Inference-API |
label-studio-ml | radio, select, multiselect, span, image_annotation | Ein laufendes Label-Studio-ML-Backend |
Der Split eines Items hängt nur von der ID des Items und von model_training.split_seed ab, deshalb bewerten ein Bridge-Lauf und der Lauf eines trainierten Modells dieselben val- und test-Items, solange der Seed gleich bleibt. Die Runs-Tabelle auf /admin/training stellt dann ein Modell, das du schon hast, neben ein Modell, das auf den Labels der Studie trainiert wurde. Items im Split train erhalten keine Vorhersage von einer Bridge, und das Rückschreiben vorlabelt nie ein Item aus dem Train-Split. Beim Standard-Split von 70/15/15 bewertet eine Bridge deshalb auf etwa 30 % der gelabelten Items.
Bevor es losgeht
Die vier Bridges außer label-studio-ml brauchen requests und Pillow auf dem Potato-Server, und die Training-Seite führt eine Bridge als „not installed“ auf, bis beide Pakete importierbar sind. label-studio-ml braucht nichts über Potato hinaus. Keine der fünf Bridges installiert ein Modell, eine GPU-Bibliothek oder torch, weil das Modell im Dienst läuft.
Bilder müssen wie bei jedem Bild-Trainer lokale Dateien innerhalb des Projekts sein. Eine Bridge lässt ein Item aus, dessen Bild eine entfernte http(s)-URL ist, und ein Item mit einem Label und ohne Vorhersage zählt im Holdout-Score als falsch. Der Lauf meldet den Anteil der zurückgehaltenen Items, die eine Vorhersage erhalten haben, als val_coverage und test_coverage, deshalb weist eine niedrige Coverage auf Bilder hin, die die Bridge nicht senden konnte.
Schlüssel bleiben in Umgebungsvariablen
Ein Schlüssel gehört nie in die Konfiguration. Jede Bridge, die einen Schlüssel sendet, hat einen Parameter, der die Umgebungsvariable mit dem Schlüssel benennt (api_key_env, token_env für Hugging Face oder die beiden Namen basic_auth_*_env für Label Studio), und die Variable muss in der Umgebung des Potato-Servers gesetzt sein, bevor der Server startet. cvat-nuclio sendet keinen Schlüssel und hat keinen solchen Parameter. Die vier Bridges, die einen Schlüssel nehmen, weisen einen Wert zurück, der kein Variablenname ist, und der Fehler wiederholt den Wert nicht, weil ein Wert an dieser Stelle höchstwahrscheinlich der Schlüssel selbst ist. Wenn der Fehlertext eines Dienstes oder ein Verbindungsfehler die Anfrage wiedergibt, ersetzen die Bridges den Wert des Schlüssels durch [redacted], bevor der Fehler angezeigt wird. Fehlertexte von /predict eines Label-Studio-ML-Backends werden auf dieselbe Weise bereinigt. Alles, was in einem Query-String oder einem Authorization-Header wie ein Schlüssel aussieht, wird ebenfalls ersetzt.
Läufe von hf-inference und roboflow sind nur lokal möglich. Beide Bridges lesen den Schlüssel auf dem Potato-Server, und ein Lauf auf einem ssh-, SLURM- oder Agent-Backend müsste den Schlüssel an diesen Host schicken, deshalb weigert sich Potato, dort einen solchen Lauf zu starten. openai-vlm-boxes, cvat-nuclio und label-studio-ml laufen auf jedem Backend, das den Dienst erreichen kann, mit der Variable des Schlüssels auf dem Host dieses Backends.
Schritt 1: Eine Bildstudie mit drei Bridges
Die folgende Konfiguration richtet eine Studie mit Baustellenfotos ein, in der Annotatoren jeden Schutzhelm und jeden unbedeckten Kopf mit einer Box markieren. model_training.trainer_params hält Standardwerte für mehrere Trainer zugleich, deshalb trägt die eine Konfiguration die Einstellungen für alle drei Bild-Bridges, und jeder Lauf verwendet den Block seines eigenen Trainers. Das Feld Parameters auf der Training-Seite füllt sich aus diesen Standardwerten, wenn ein Trainer gewählt wird.
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}Jede Zeile von data/photos.jsonl nennt ein Bild unter media/:
{"id": "site_001", "image_url": "media/site_001.jpg"}writeback.schemas macht die Boxen des Modells für Annotatoren sichtbar, und include_unlabeled sendet auch die Fotos, die noch niemand gelabelt hat, an den Dienst. Lass beide Schlüssel beim ersten Lauf weg, wenn der Holdout-Score vorliegen soll, bevor jemand ein Vorlabel sieht. label_map ordnet die Klassennamen des Dienstes den Labels des Schemas zu, und eine auf null abgebildete Klasse wird verworfen. Eine Klasse ohne Eintrag geht an das Label gleichen Namens, und der Abgleich ignoriert die Groß- und Kleinschreibung. Boxen unter jeder anderen Klasse werden ausgelassen, und das Lauf-Log zählt die ausgelassenen Boxen.
Schritt 2: Ein Vision-Language-Modell mit openai-vlm-boxes
openai-vlm-boxes verkleinert jedes Bild auf höchstens 1.280 Pixel an der längeren Seite (max_image_side) und sendet das Bild mit einem Prompt, der die Labels des Schemas nennt, an die Chat-Completions-Route. Potato liest die Antwort als JSON-Boxen. Jeder Server mit einer OpenAI-kompatiblen API funktioniert. Der folgende Befehl stellt Qwen2.5-VL mit vLLM bereit, dessen Dokumentation Clients unter http://localhost:8000/v1 verbindet:
vllm serve Qwen/Qwen2.5-VL-7B-InstructStarte Potato mit vLLM auf Port 8000 auf einem anderen Port:
potato start config.yaml -p 9000Vision-Language-Modelle antworten in ihrer eigenen Koordinatenkonvention, was auch immer der Prompt verlangt, und zwei Parameter sagen Potato, wie die Zahlen zu lesen sind. coordinate_format ist fraction (0 bis 1 des Bildes), per_mille (0 bis 1.000) oder pixels des Bildes, wie es gesendet wurde. box_order ist xyxy für [x0, y0, x1, y1] oder yxyx für [y0, x0, y1, x1]. Qwen2.5-VL antwortet in Pixeln, deshalb setzt die obige Konfiguration coordinate_format: pixels. Unter auto rät Potato das Format einmal pro Antwort anhand der Größe der Zahlen. Werte bis 1,05 gelten als Bruchteile, Werte über der Größe des gesendeten Bildes gelten als Promille, und alles andere gilt als Pixel. Eine Promille-Box, die in ein großes Bild passt, wird deshalb als Pixel gelesen, also benenne das Format, sobald die Konvention des Modells bekannt ist.
Gemma und Gemini antworten mit Promille-Boxen, y zuerst, [ymin, xmin, ymax, xmax], auch wenn der Prompt Bruchteile verlangt. Wenn beide Parameter auf auto stehen, liest Potato die Boxen eines Modells, dessen Name gemma oder gemini enthält, als Promille mit y zuerst. In einem Testprojekt mit roten und blauen Quadraten für Gemma 4 12B maß das Potato-Team mAP50 von 0,0, wenn die Boxen mit x zuerst gelesen wurden, und von 1,0, wenn sie mit y zuerst gelesen wurden. Ollama stellt Gemma 3 unter einem Namen bereit, der gemma enthält, deshalb greift auto:
ollama run gemma3:12bErsetze den Block openai-vlm-boxes in der Konfiguration durch den folgenden Block:
model_training:
trainer_params:
openai-vlm-boxes:
url: http://localhost:11434/v1
model: gemma3:12b
json_mode: objectEin Fine-Tune von Gemma oder Gemini, der unter einem Namen ohne eines der beiden Wörter bereitgestellt wird, erhält unter auto die Lesart mit x zuerst. Setze für ein solches Modell coordinate_format: per_mille und box_order: yxyx. json_mode: object bittet Ollama mit response_format: json_object um JSON. Der Standard, schema, sendet ein JSON-Schema mit den Labels als Enum. vLLM und OpenAI beachten das Schema. Ein Lauf, in dem keine Antwort als JSON geparst werden kann, scheitert, und der Fehler schlägt json_mode: object oder einen schlichteren Prompt vor (prompt, wobei {labels} zu den Labels des Schemas wird).
Ein Vision-Language-Modell gibt mit seinen Boxen keinen Score aus, deshalb tragen die Boxen keine Konfidenz. writeback.min_confidence lässt jede Vorhersage von openai-vlm-boxes passieren, und die Modellprüfung kann die Vorhersagen nicht von der geringsten zur höchsten Konfidenz ordnen. Die Referenz zu openai-vlm-boxes führt api_key_env für eine gehostete API sowie die Token- und Timeout-Einstellungen auf.
Schritt 3: Einen Lauf starten und den Score lesen
Öffne http://localhost:9000/admin/training, wähle openai-vlm-boxes und das Schema ppe und klicke auf Train now. Derselbe Lauf startet über die 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"]}'Der Fit-Schritt listet die Modelle des Servers über /models auf und protokolliert, welches Modell der Lauf anfragt und wie der Lauf die Boxen des Modells liest. Der Vorhersagedurchlauf sendet dann ein Bild pro Anfrage. Wenn der Lauf endet, erscheint das mAP50 auf zurückgehaltenen Items in der Runs-Tabelle, und die Lauf-Seite listet val_map_50, test_map_50, die Coverage-Werte und eine „Not used“-Warnung, die Antworten zählt, die kein JSON waren, Boxen unter Labels, die dem Schema fehlen, und Boxen außerhalb des Bildes. Wenn der Score nahe 0 liegt, während die Coverage hoch und die Warnung leer ist, prüfe zuerst coordinate_format und box_order.
Roboflow-Modelle
roboflow sendet jedes Bild per POST an <url>/<model_id>, wobei model_id das Projekt und die Version als project/version ist. Der Schlüssel steht standardmäßig in ROBOFLOW_API_KEY (api_key_env), und der Lauf weigert sich ohne den Schlüssel zu starten:
export ROBOFLOW_API_KEY=... # in the shell that starts Potato
potato start config.yaml -p 9000Der Standard-url von Potato ist https://detect.roboflow.com, und die obige Konfiguration nennt den Standard. Ein Lauf für Instanzsegmentierung setzt url: https://outline.roboflow.com. url ist ein Parameter, deshalb kann ein Lauf auch https://serverless.roboflow.com nennen, den Host, den die Referenz zur Objekterkennung von Roboflow für dieselbe Route project/version angibt. Das Potato-Team hat diesen Host mit dieser Bridge nicht getestet. Ein selbst betriebener Roboflow-Inference-Server lauscht standardmäßig auf Port 9001 (url: http://localhost:9001).
Die API von Roboflow nimmt den Schlüssel im Query-String entgegen. Potato schreibt die Anfrage-URL weder in das Lauf-Log noch in eine Fehlermeldung. Potato sendet confidence und overlap (Standardwerte 0,4 und 0,3) als Prozentwerte an die API, und die Referenz von Roboflow dokumentiert beide auf einer Skala von 0 bis 100. shapes: auto schreibt eine Kontur, wo das Modell eine Kontur liefert und das Schema ein Polygon-Tool hat, und sonst eine Box. Die Gesundheitsprüfung sendet ein weißes 8 × 8-Bild, und Roboflow rechnet die Anfrage auf die Nutzung des Kontos an.
Serverlose CVAT-Funktionen
cvat-nuclio verwendet einen für CVAT bereits bereitgestellten Detektor als Nuclio-Funktion, etwa die Funktionen für YOLOv7 oder Faster R-CNN in cvat/serverless. Potato sendet jedes Bild als {"image": <base64>, "threshold": t} an die eigene URL der Funktion, ohne den Weg über CVAT. nuctl get functions listet die bereitgestellten Funktionen mit ihren Ports auf, und der folgende Befehl ist die Form, die die Dokumentation von CVAT angibt:
nuctl get functions --platform localDie Funktion antwortet mit Formen in Pixeln. Ein rectangle wird zu einer Box, und ein polygon wird zu einem Polygon oder zur Bounding Box des Polygons, wenn das Schema kein Polygon-Tool hat. mask-Formen werden ausgelassen und gezählt. Die Funktion verwirft Formen mit einem Score unter threshold (Standard 0,5). Das Schema hat kein Label für Personen, deshalb verwirft label_map: {person: null} in der Konfiguration die Personen eines COCO-Detektors.
Hugging-Face-Modelle mit hf-inference
hf-inference ruft ein auf Hugging Face bereitgestelltes Modell auf, entweder einen dedizierten Inference Endpoint über seine https://-URL (endpoint_url) oder eine Modell-ID auf der serverlosen HF-Inference-API (model). Setze endpoint_url oder model, nicht beides. Die Aufgabe folgt dem Typ des Schemas:
- Textklassifikation für
radio,selectundmultiselect. Ein Einzelauswahl-Schema nimmt das oberste Label des Modells. Ist dieses Label nachlabel_mapkeines der Labels des Schemas, erhält das Item keine Vorhersage statt der zweitplatzierten. Einmultiselect-Schema nimmt jedes Label mit einem Score von mindestensthreshold(Standard 0,5). - Token-Klassifikation für
span, wobei Wortstücke durchaggregation_strategy(simple,first,averageodermax) zu Entitäten gruppiert werden und die Zeichen-Offsets vom Modell stammen. - Objekterkennung für
image_annotation. Boxen mit einem Score unterthresholdwerden verworfen.
Die folgende Konfiguration fragt dslim/bert-base-NER nach Spans. Die Modellkarte beschreibt ein BERT-Modell, das auf dem englischen CoNLL-2003 für Named Entities fine-getunt wurde. HF Inference stellt nur einen Teil der Modelle des Hubs bereit, prüfe also, dass die Seite des Modells HF Inference aufführt, bevor die serverlose API verwendet wird.
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}Die Entitäten PER, ORG und LOC des Modells werden den Labels des Schemas zugeordnet, und MISC wird verworfen, weil das Schema kein solches Label hat. Passt der volle Name einer Entität auf nichts, entfernt Potato ein Präfix B- oder I- und versucht es erneut. Ein Sentiment-Klassifikator, der LABEL_0 und LABEL_1 antwortet, braucht auf dieselbe Weise label_map: {LABEL_0: negative, LABEL_1: positive}. Die Gesundheitsprüfung ist eine echte Anfrage, ein kurzer Satz oder ein 8 × 8-Bild, und wait_for_model: true (der Standard) wartet, während ein serverloses Modell lädt, statt mit einem 503 zu scheitern.
Ein Label-Studio-ML-Backend
label-studio-ml verwendet ein laufendes Label-Studio-ML-Backend. Ein Backend, das aus den Beispielen des Repositorys mit docker-compose up gestartet wird, stellt ein Modell über HTTP unter http://localhost:9090 bereit. Der Lauf prüft GET /health des Backends, sendet die Items in Batches von 16 (batch_size) an POST /predict und liest die zurückgegebenen Regionen mit demselben Konverter, den potato import -f label_studio verwendet. Die UTF-16-Span-Offsets von Label Studio werden zu Codepoints, und Prozentkoordinaten werden zu denen des Bild-Widgets.
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_PASSDie einzige Authentifizierung eines Standard-Backends ist HTTP Basic, gesetzt durch BASIC_AUTH_USER und BASIC_AUTH_PASS am Backend. basic_auth_user_env und basic_auth_pass_env benennen die Variablen auf dem Potato-Server, die dieselben beiden Werte halten, und die beiden Parameter gehören zusammen. api_key_env sendet stattdessen Authorization: Token <value>, für ein Backend hinter einem Proxy, der ein Token erwartet. Ein Standard-Backend beantwortet diesen Header mit einem 500. Potato erzeugt die mit jeder Anfrage gesendete Labeling-Konfiguration aus dem Schema (Choices, Labels, RectangleLabels oder PolygonLabels), und label_config ersetzt das erzeugte XML durch ein eigenes.
Ein Bild-Backend braucht eine weitere Einstellung. Ohne media_url_prefix sendet Potato jedes Bild als absoluten Pfad auf der Potato-Maschine, und ein auf dem Label-Studio-SDK aufbauendes Backend liest data.image als URL und scheitert an einem bloßen Pfad. Füge für ein Backend auf derselben Maschine das folgende Präfix hinzu und starte das Backend mit LOCAL_FILES_DOCUMENT_ROOT, gesetzt auf das Aufgabenverzeichnis:
model_training:
trainer_params:
label-studio-ml:
url: http://localhost:9090
media_url_prefix: /data/local-files/?d=Für ein Backend auf einer anderen Maschine stelle das Aufgabenverzeichnis über HTTP bereit und gib die URL dieses Servers als Präfix an. Ein Bild außerhalb des Aufgabenverzeichnisses hat keinen relativen Pfad zum Verzeichnis, deshalb sendet Potato das Bild nicht, und das Lauf-Log nennt, wie viele ausgelassen wurden.
Potato sendet dem Backend Vorhersageanfragen und keine Annotationsereignisse, deshalb startet das eigene Training des Backends in seinem /webhook-Handler nicht von Potato aus. potato import -f label_studio bringt ein ganzes Label-Studio-Projekt mit der Labeling-Konfiguration des Projekts, den Antworten der Annotatoren und den gespeicherten Vorhersagen nach Potato (Projekte importieren). Von Label Studio zu Potato migrieren behandelt die Tags, die der Importer auslässt.
Die Vorlabels prüfen
Die Vorhersagen einer Bridge durchlaufen dieselben Rückschreibe-Prüfungen wie die eines trainierten Modells. Das Rückschreiben überspringt Items aus dem Train-Split und Schemas, die in writeback.schemas fehlen, überspringt Paare aus Item und Schema, die ein Reviewer abgelehnt hat, und hält Vorhersagen unter writeback.min_confidence zurück. Bei Boxen und Spans ist die Konfidenz einer Vorhersage die Konfidenz der unsichersten Form oder des unsichersten Spans, deshalb hält die Untergrenze die ganze Vorhersage eines Items zurück, wenn irgendeine Box oder irgendein Span des Items niedriger bewertet ist. Das confidence von Roboflow und das threshold von CVAT wirken stattdessen pro Box, am Dienst, und das threshold von hf-inference wirkt pro Box oder pro multiselect-Label. Das Tutorial zum Modell im Kreislauf behandelt die Prüf-Warteschlange, die Urteile Annehmen und Ablehnen und das erneute Training. Die Warteschlange und die Urteile funktionieren für eine Bridge auf dieselbe Weise.
Pro Item und Schema wird eine Vorhersage behalten, deshalb ersetzt ein späterer Lauf einer anderen Bridge oder eines trainierten Modells die Vorlabels des früheren Laufs. Um die Vorlabels eines Laufs zurückzuziehen, sende DELETE /admin/training/api/runs/<run_id>/predictions. Die Referenz zu den Inference-Bridges listet jeden Parameter der vier Bridges auf, und die Referenz zu label-studio-ml listet die Label-Studio-Parameter auf.
Weiterführende Links
- Modelltraining behandelt den Kreislauf und die Training-Seite.
- Trainer listet alle 38 Trainer nach Aufgabe auf, für ein auf den Labels der Studie trainiertes Modell, das mit dem Score einer Bridge verglichen werden soll.
- Konfiguration dokumentiert Splits, das Rückschreiben und die Routen.
- Bildannotation und Span-Annotation dokumentieren die Schemas, die die Bridges füllen.
- Visuelle KI-Unterstützung zeigt einem Annotator auf Anfrage die Vorschläge eines Vision-Modells, ohne Holdout-Score.