すでに運用しているモデルから事前ラベルとホールドアウトスコアを得る
vLLM、Ollama、Hugging Face、Roboflow、CVAT、Label Studio MLバックエンドで提供しているモデルを研究のラベルでスコアリングし、その答えを事前ラベルとしてレビューします。
v2.14.0の新機能
このチュートリアルは、すでにPotatoの外でモデルを運用していて、モデルの答えをだれかに見せる前に、チーム自身のアノテーターとどれだけ一致するかを知りたいチーム向けです。Potatoのモデル学習には、何も学習しない、ブリッジと呼ぶ5つのトレーナーがあります。ブリッジは、研究のホールドアウトした項目を、別の場所で提供されているモデルに送り、答えを研究のラベルに照らしてスコアリングし、学習したモデルの実行と同じように、答えを事前ラベルとして書き戻してレビューできるようにします。以下の手順では、OpenAI互換APIの背後にある視覚言語モデル、Roboflowのモデル、CVATのサーバーレス関数、Hugging Faceのモデル、Label Studio MLバックエンドを取り上げ、それぞれに検証済みの設定を示します。
ブリッジの実行が送るものとスコアリングするもの
ブリッジは、学習のステップがサービスの応答を確認するだけのトレーナーです。その後、実行は、実行のval分割とtest分割の項目をサービスに送り、writeback.include_unlabeled: trueの場合は、だれもラベルを付けていない項目も送ります。Potatoは、答えをスキーム自身の形(スパン、選択肢、図形)に変換し、ホールドアウトした項目で、統合した人間のラベルと照らし合わせてスコアリングします。このとき、学習したモデルと同じホールドアウト指標(ボックスにはmAP50、スパンにはスパンF1、単一選択にはマクロF1)を使います。書き戻し、レビューキュー、各事前ラベルの実行IDは、ほかの実行の後と同じように働きます。
| トレーナー | スキーム | 呼び出すモデル |
|---|---|---|
openai-vlm-boxes | image_annotation | OpenAI互換のチャットAPIの背後にある視覚言語モデル(vLLM、Ollama、LM Studio、llama.cppのサーバー、OpenAI) |
roboflow | image_annotation | Roboflowの物体検出またはインスタンスセグメンテーションのモデル(ホスト型、またはセルフホストのInferenceサーバー) |
cvat-nuclio | image_annotation | NuclioのCVATサーバーレス関数 |
hf-inference | radio、select、multiselect、span、image_annotation | Hugging Face Inference Endpoint、またはサーバーレスのHF Inference APIのモデル |
label-studio-ml | radio、select、multiselect、span、image_annotation | 動作中のLabel Studio MLバックエンド |
項目の分割は、項目のIDとmodel_training.split_seedだけで決まるため、シードが固定されているかぎり、ブリッジの実行と学習したモデルの実行は、同じvalとtestの項目でスコアリングされます。そのため、/admin/trainingの実行テーブルには、すでに持っているモデルと、研究のラベルで学習したモデルが並びます。train分割の項目には、ブリッジからの予測が付かず、書き戻しも、train分割の項目に事前ラベルを付けることはありません。したがって、既定の70/15/15の分割では、ブリッジがスコアリングするのは、ラベル付きの項目の約30%です。
始める前に
label-studio-ml以外の4つのブリッジには、PotatoサーバーにrequestsとPillowが必要で、Trainingページは、両方のパッケージがインポートできるようになるまで、ブリッジを「not installed」と表示します。label-studio-mlには、Potato以外に何も必要ありません。5つのブリッジのいずれも、モデル、GPUライブラリ、torchをインストールしません。モデルはサービスの中で動くためです。
画像は、すべての画像トレーナーと同じく、プロジェクト内のローカルファイルでなければなりません。ブリッジは、画像がリモートのhttp(s)のURLである項目を除外し、ラベルがあって予測がない項目は、ホールドアウトスコアで不正解と数えられます。実行は、予測が付いたホールドアウトの項目の割合をval_coverageとtest_coverageとして報告するため、カバレッジの値が低ければ、ブリッジが送れなかった画像があることを示します。
キーは環境変数に置く
キーは、設定には決して書きません。キーを送る各ブリッジは、キーを保持する環境変数の名前を指定するパラメーターを受け取り(api_key_env、Hugging Faceのtoken_env、Label Studioの2つのbasic_auth_*_env)、その変数は、サーバーを起動する前に、Potatoサーバーの環境に設定されていなければなりません。cvat-nuclioはキーを送らず、このようなパラメーターも受け取りません。キーを受け取る4つのブリッジは、変数名ではない値を拒否し、エラーにはその値を繰り返しません。その場所にある値は、キーそのものである可能性が高いためです。サービスのエラー本文や接続エラーがリクエストを繰り返し返す場合、ブリッジは、エラーを表示する前にキーの値を[redacted]に置き換えます。Label Studio MLバックエンドの/predictのエラー本文も、同じように処理されます。クエリ文字列やAuthorizationヘッダーにあるキーのような形のものも、置き換えられます。
hf-inferenceとroboflowの実行は、ローカルのみです。どちらのブリッジも、Potatoサーバーでキーを読み込むため、ssh、SLURM、エージェントのバックエンドでの実行では、キーをそのホストに送る必要が生じてしまい、Potatoはそこでの実行の開始を拒否します。openai-vlm-boxes、cvat-nuclio、label-studio-mlは、サービスに到達できる任意のバックエンドで実行でき、キーの変数は、そのバックエンドのホストに設定します。
ステップ1:3つのブリッジを使う画像の研究
次の設定は、現場の写真の研究を設定し、アノテーターがすべてのヘルメットとすべてのヘルメットなしの頭にボックスを付けます。model_training.trainer_paramsは、複数のトレーナーの既定値を一度に保持するため、1つの設定に3つの画像ブリッジすべての設定が入り、各実行は、自分のトレーナーのブロックを使います。トレーナーを選ぶと、TrainingページのParameters欄が、これらの既定値から入力されます。
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}data/photos.jsonlの各行が、media/の下の1枚の画像を指します。
{"id": "site_001", "image_url": "media/site_001.jpg"}モデルのボックスをアノテーターに見えるようにするのはwriteback.schemasで、include_unlabeledは、だれもラベルを付けていない写真もサービスに送ります。事前ラベルをだれも見る前にホールドアウトスコアがほしい場合は、最初の実行では両方のキーを省いてください。label_mapは、サービスのクラス名をスキームのラベルに対応づけ、nullに対応づけたクラスは捨てられます。エントリのないクラスは、大文字小文字を区別せずに、同じ名前のラベルに対応づけられます。それ以外のクラスのボックスは除外され、実行ログには、除外したボックスの数が記録されます。
ステップ2:openai-vlm-boxesで視覚言語モデルを使う
openai-vlm-boxesは、各画像を長辺が最大1,280ピクセルになるように縮小し(max_image_side)、スキームのラベルを示すプロンプトを付けて、チャット補完のルートに送ります。Potatoは、返答をJSONのボックスとして読み取ります。OpenAI互換のAPIを持つサーバーであれば、どれでも使えます。次のコマンドは、Qwen2.5-VLをvLLMで提供します。vLLMのドキュメントでは、クライアントはhttp://localhost:8000/v1に接続します。
vllm serve Qwen/Qwen2.5-VL-7B-InstructvLLMをポート8000で動かした状態で、Potatoを別のポートで起動します。
potato start config.yaml -p 9000視覚言語モデルは、プロンプトでどう頼んでも、モデル独自の座標の規約で答えます。2つのパラメーターが、Potatoに数値の読み方を伝えます。coordinate_formatは、fraction(画像の0から1)、per_mille(0から1,000)、または送信した画像のpixelsです。box_orderは、[x0, y0, x1, y1]ならxyxy、[y0, x0, y1, x1]ならyxyxです。Qwen2.5-VLはピクセルで答えるため、上の設定ではcoordinate_format: pixelsを指定しています。autoでは、Potatoは、返答ごとに一度、数値の大きさから形式を推測します。1.05までの値は割合として、送信した画像の大きさを超える値は千分率として、それ以外はピクセルとして読み取られます。そのため、大きな画像の内側に収まる千分率のボックスは、ピクセルとして読み取られてしまいます。モデルの規約がわかっている場合は、必ず形式を指定してください。
GemmaとGeminiは、プロンプトで割合を求めても、yを先にした千分率のボックス[ymin, xmin, ymax, xmax]で答えます。両方のパラメーターがautoのとき、Potatoは、名前にgemmaまたはgeminiを含むモデルのボックスを、yを先にした千分率として読み取ります。赤と青の正方形からなるGemma 4 12Bのテストプロジェクトで、Potatoのチームは、ボックスをxを先にして読むとmAP50が0.0、yを先にして読むと1.0になることを測定しました。Ollamaは、Gemma 3をgemmaを含む名前で提供するため、autoが適用されます。
ollama run gemma3:12b設定のopenai-vlm-boxesブロックを、次のブロックに置き換えます。
model_training:
trainer_params:
openai-vlm-boxes:
url: http://localhost:11434/v1
model: gemma3:12b
json_mode: objectどちらの語も含まない名前で提供されているGemmaやGeminiのファインチューニングは、autoではxを先にした読み方になります。そのようなモデルには、coordinate_format: per_milleとbox_order: yxyxを設定してください。json_mode: objectは、Ollamaにresponse_format: json_objectでJSONを求めます。既定のschemaは、ラベルを列挙型にしたJSONスキーマを送ります。vLLMとOpenAIは、このスキーマに従います。どの返答もJSONとして解析できない実行は失敗し、エラーは、json_mode: objectか、より単純なプロンプトを提案します(promptでは、{labels}がスキームのラベルに置き換わります)。
視覚言語モデルは、ボックスにスコアを付けないため、ボックスに信頼度はありません。writeback.min_confidenceは、openai-vlm-boxesのすべての予測を通し、モデルレビューは、予測を信頼度の低い順に並べられません。openai-vlm-boxesのリファレンスには、ホスト型APIのためのapi_key_envと、トークンとタイムアウトの設定が載っています。
ステップ3:実行を開始してスコアを読む
http://localhost:9000/admin/trainingを開き、openai-vlm-boxesとppeスキームを選んで、Train nowをクリックします。同じ実行は、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"]}'学習のステップは、/modelsを通してサーバーのモデルを一覧し、実行がどのモデルを求めるかと、モデルのボックスをどう読むかをログに記録します。その後の予測の工程は、1回のリクエストにつき1枚の画像を送ります。実行が終了すると、ホールドアウトのmAP50が実行テーブルに表示され、実行のページには、val_map_50、test_map_50、カバレッジの値、そして、JSONでなかった返答、スキームにないラベルのボックス、画像の外にあるボックスを数える「Not used」の警告が表示されます。カバレッジが高く、警告が空なのにスコアが0に近いときは、まずcoordinate_formatとbox_orderを確認してください。
Roboflowのモデル
roboflowは、各画像を<url>/<model_id>にPOSTします。model_idは、project/versionの形のプロジェクトとバージョンです。キーは、既定ではROBOFLOW_API_KEYに置き(api_key_env)、キーがないと、実行は開始を拒否します。
export ROBOFLOW_API_KEY=... # in the shell that starts Potato
potato start config.yaml -p 9000Potatoの既定のurlはhttps://detect.roboflow.comで、上の設定は既定の値を指定しています。インスタンスセグメンテーションの実行ではurl: https://outline.roboflow.comを設定します。urlはパラメーターなので、実行ではhttps://serverless.roboflow.comを指定することもできます。これは、Roboflowの物体検出のリファレンスが、同じproject/versionのルートに対して示すホストです。Potatoのチームは、このホストをこのブリッジでテストしていません。セルフホストのRoboflow Inferenceサーバーは、既定ではポート9001で待ち受けます(url: http://localhost:9001)。
RoboflowのAPIは、キーをクエリ文字列で受け取ります。Potatoは、リクエストのURLを、実行ログにもエラーメッセージにも書きません。Potatoは、confidenceとoverlap(既定値は0.4と0.3)を、パーセントとしてAPIに送ります。Roboflowのリファレンスは、どちらも0から100のスケールで記載しています。shapes: autoは、モデルが輪郭を返し、スキームにポリゴンのツールがある場合は輪郭を書き込み、それ以外の場合はボックスを書き込みます。ヘルスチェックは、8 × 8の白い画像をPOSTし、Roboflowは、そのリクエストをアカウントの使用量に数えます。
CVATのサーバーレス関数
cvat-nuclioは、Nuclio関数としてCVAT向けにすでにデプロイされた検出器、たとえばcvat/serverlessにあるYOLOv7やFaster R-CNNの関数を使います。Potatoは、CVATを経由せずに、各画像を関数自身のURLに{"image": <base64>, "threshold": t}としてPOSTします。nuctl get functionsは、デプロイされた関数をポートとともに一覧し、次のコマンドは、CVATのドキュメントが示す形式です。
nuctl get functions --platform local関数は、ピクセル単位の図形で答えます。rectangleはボックスになり、polygonはポリゴンになるか、スキームにポリゴンのツールがない場合はポリゴンの外接ボックスになります。maskの図形は除外され、数えられます。関数は、threshold(既定は0.5)を下回るスコアの図形を捨てます。このスキームにはpersonのラベルがないため、設定のlabel_map: {person: null}が、COCOの検出器が検出した人を捨てます。
hf-inferenceでHugging Faceのモデルを使う
hf-inferenceは、Hugging Faceにデプロイされたモデルを呼び出します。専用のInference Endpointをhttps://のURL(endpoint_url)で指定するか、サーバーレスのHF Inference APIのモデルIDを指定します(model)。endpoint_urlまたはmodelのどちらか一方を設定し、両方は設定しないでください。タスクは、スキームの種類に従って決まります。
- テキスト分類は、
radio、select、multiselect向けです。単一選択のスキームは、モデルの最上位のラベルを取ります。そのラベルが、label_mapの後でスキームのラベルのいずれでもない場合、項目には、次点のラベルではなく、予測なしが付きます。multiselectのスキームは、threshold(既定は0.5)以上のスコアのラベルをすべて取ります。 - トークン分類は、
span向けで、ワードピースはaggregation_strategy(simple、first、average、またはmax)でエンティティにまとめられ、文字のオフセットはモデルから取られます。 - 物体検出は、
image_annotation向けです。thresholdを下回るスコアのボックスは捨てられます。
次の設定は、dslim/bert-base-NERにスパンを求めます。モデルカードは、英語のCoNLL-2003で固有表現向けにファインチューニングしたBERTモデルだと説明しています。HF InferenceはHubのモデルの一部を提供しているため、サーバーレスAPIを当てにする前に、モデルのページにHF Inferenceが載っていることを確認してください。
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}モデルのPER、ORG、LOCのエンティティは、スキームのラベルに対応づけられ、スキームにそのようなラベルがないため、MISCは捨てられます。エンティティの完全な名前がどれにも一致しない場合、PotatoはB-またはI-の接頭辞を外して、もう一度試します。LABEL_0とLABEL_1で答える感情分類器には、同じようにlabel_map: {LABEL_0: negative, LABEL_1: positive}が必要です。ヘルスチェックは、短い文または8 × 8の画像による実際の1回のリクエストで、wait_for_model: true(既定)は、サーバーレスのモデルが読み込まれるあいだ、503で失敗せずに待ちます。
Label Studio MLバックエンド
label-studio-mlは、動作中のLabel Studio MLバックエンドを使います。リポジトリの例からdocker-compose upで起動したバックエンドは、http://localhost:9090で、モデルをHTTPで提供します。実行は、バックエンドのGET /healthを確認し、項目を16件ずつのバッチで(batch_size)POST /predictに送り、返された領域を、potato import -f label_studioが使うのと同じコンバーターで読み取ります。Label StudioのUTF-16のスパンのオフセットはコードポイントに変換され、パーセントの座標は、画像ウィジェットの座標に変換されます。
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_PASS標準のバックエンドの唯一の認証は、バックエンドのBASIC_AUTH_USERとBASIC_AUTH_PASSで設定するHTTP Basicです。basic_auth_user_envとbasic_auth_pass_envは、同じ2つの値を保持する、Potatoサーバー上の変数を指定し、この2つのパラメーターは、セットで指定します。api_key_envは、代わりにAuthorization: Token <value>を送ります。これは、トークンを期待するプロキシの背後にあるバックエンド向けです。標準のバックエンドは、このヘッダーに500で答えます。Potatoは、各リクエストとともに送るラベリングの設定を、スキーム(Choices、Labels、RectangleLabels、またはPolygonLabels)から生成し、label_configは、生成されたXMLをご自身のXMLに置き換えます。
画像のバックエンドには、もう1つ設定が必要です。media_url_prefixがない場合、Potatoは各画像を、Potatoのマシン上の絶対パスとして送り、Label Studio SDKで作られたバックエンドは、data.imageをURLとして読み取るため、素のパスでは失敗します。同じマシン上のバックエンドでは、次のプレフィックスを追加し、バックエンドを、LOCAL_FILES_DOCUMENT_ROOTをタスクディレクトリに設定して起動します。
model_training:
trainer_params:
label-studio-ml:
url: http://localhost:9090
media_url_prefix: /data/local-files/?d=別のマシン上のバックエンドでは、タスクディレクトリをHTTPで提供し、そのサーバーのURLをプレフィックスとして指定します。タスクディレクトリの外にある画像には、ディレクトリからの相対パスがないため、Potatoは画像を送らず、実行ログには、除外した数が記録されます。
Potatoがバックエンドに送るのは予測のリクエストだけで、アノテーションのイベントは送らないため、バックエンド自身の/webhookハンドラーでの学習は、Potatoからは始まりません。potato import -f label_studioは、Label Studioのプロジェクト全体を、ラベリングの設定、アノテーターの答え、保存済みの予測とともにPotatoに移します(プロジェクトのインポート)。インポーターが除外するタグについては、Migrating from Label Studio to Potatoで説明しています。
事前ラベルをレビューする
ブリッジの予測は、学習したモデルの予測と同じ書き戻しの検査を通ります。書き戻しは、train分割の項目と、writeback.schemasにないスキームを飛ばし、レビュー担当者が却下した項目とスキームの組を飛ばし、writeback.min_confidenceを下回る予測を保留します。ボックスとスパンでは、予測の信頼度は、最も信頼度の低い図形またはスパンの信頼度なので、項目上のいずれか1つのボックスまたはスパンのスコアが低いと、下限によって、その項目の予測全体が保留されます。RoboflowのconfidenceとCVATのthresholdは、代わりにサービス側でボックスごとに働き、hf-inferenceのthresholdは、ボックスごと、またはmultiselectのラベルごとに働きます。モデルインザループのチュートリアルでは、レビューキュー、承認と却下の判定、再学習を説明しています。キューと判定は、ブリッジでも同じように働きます。
1つの項目とスキームにつき、予測は1つだけ保持されるため、別のブリッジまたは学習したモデルの後の実行が、前の実行の事前ラベルを置き換えます。ある実行の事前ラベルを取り下げるには、DELETE /admin/training/api/runs/<run_id>/predictionsを送ります。推論ブリッジのリファレンスには、4つのブリッジのすべてのパラメーターが載っており、label-studio-mlのリファレンスには、Label Studioのパラメーターが載っています。
関連資料
- モデル学習は、ループとTrainingページを説明しています。
- トレーナーは、ブリッジのスコアと比較するための、研究のラベルで学習するモデル向けに、38のトレーナーをタスク別に一覧しています。
- 設定は、分割、書き戻し、ルートを説明しています。
- 画像アノテーションとスパンアノテーションは、ブリッジが埋めるスキームを説明しています。
- Visual AI Supportは、ホールドアウトスコアなしで、リクエストに応じて視覚モデルの提案をアノテーターに表示します。