来自你已在运行的模型的预标签和留出得分
在研究的标签上给由 vLLM、Ollama、Hugging Face、Roboflow、CVAT 或 Label Studio ML 后端提供服务的模型评分,再把它的答案作为预标签来审核。
v2.14.0 新增
本教程面向已经在 Potato 之外运行某个模型的团队,他们想在把模型的答案展示给任何人之前,先了解该模型与团队自己的标注者有多一致。Potato 的模型训练包含五个称为桥接器的训练器,它们什么也不训练。桥接器把研究的留出条目发送给在别处提供服务的模型,对照研究的标签给答案评分,并把答案作为预标签回写供审核,与训练出的模型的一次运行相同。下面的步骤涵盖 OpenAI 兼容 API 背后的视觉语言模型、Roboflow 模型、CVAT 无服务器函数、Hugging Face 模型和 Label Studio ML 后端,每一种都附有经过验证的配置。
桥接器运行发送和评分的内容
桥接器是一种训练器,它的拟合步骤只检查服务是否应答。随后运行把该次运行 val 和 test 划分中的条目发送给服务,在 writeback.include_unlabeled: true 时,还包括没有人标注过的条目。Potato 把答案转换为方案自己的形状、片段或选项,并对照合并后的人工标签,在留出条目上给答案评分,使用的留出指标与训练出的模型相同(框用 mAP50,片段用 span F1,单选用 macro-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 上的 Runs 表可以把你已有的模型与在研究标签上训练的模型并排比较。train 划分中的条目不会从桥接器得到预测,回写也永远不会给 train 划分的条目加预标签。因此在默认的 70/15/15 划分下,桥接器大约在 30% 的已标注条目上评分。
开始之前
除 label-studio-ml 之外的四个桥接器需要 Potato 服务器上有 requests 和 Pillow,在这两个包都能导入之前,训练页面会把桥接器列为“not installed”。label-studio-ml 除 Potato 之外什么都不需要。五个桥接器都不会安装模型、GPU 库或 torch,因为模型运行在服务里。
与每个图像训练器一样,图像必须是项目内的本地文件。桥接器会排除图像为远程 http(s) URL 的条目,有标签而没有预测的条目在留出得分中算作错误。运行把得到预测的留出条目所占的比例报告为 val_coverage 和 test_coverage,所以覆盖率偏低说明有桥接器无法发送的图像。
密钥保存在环境变量中
密钥永远不放在配置里。每个发送密钥的桥接器都有一个参数,用来指明保存密钥的环境变量(api_key_env,Hugging Face 用 token_env,Label Studio 用两个 basic_auth_*_env 名称),该变量必须在服务器启动之前设置在 Potato 服务器的环境中。cvat-nuclio 不发送密钥,也没有这样的参数。接受密钥的四个桥接器会拒绝不是变量名的值,错误信息不会重复该值,因为出现在这里的值很可能就是密钥本身。服务的错误正文或连接错误回显了请求时,桥接器在显示错误之前,会把密钥的值替换为 [redacted]。Label Studio ML 后端 /predict 的错误正文也以同样方式清理。查询字符串或 Authorization 头中任何形似密钥的内容同样会被替换。
hf-inference 和 roboflow 的运行只能在本地进行。这两个桥接器都在 Potato 服务器上读取密钥,而在 ssh、SLURM 或 agent 后端上运行就必须把密钥送到那台主机,所以 Potato 拒绝在那里启动这样的运行。openai-vlm-boxes、cvat-nuclio 和 label-studio-ml 可以在任何能访问该服务的后端上运行,密钥的变量设在该后端的主机上。
步骤 1:用三个桥接器处理图像研究
下面的配置建立一项现场照片研究,标注者为每个安全帽和每个没戴帽的头画框。model_training.trainer_params 同时保存多个训练器的默认值,所以这一份配置携带全部三个图像桥接器的设置,每次运行使用自己训练器的那一块。选择训练器时,训练页面的 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/ 下的一张图像:
{"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),并把图像连同点出方案标签的提示发送到 chat completions 路由。Potato 把回复读作 JSON 框。任何带 OpenAI 兼容 API 的服务器都可以使用。下面的命令用 vLLM 为 Qwen2.5-VL 提供服务,其文档让客户端连接 http://localhost:8000/v1:
vllm serve Qwen/Qwen2.5-VL-7B-InstructvLLM 使用 8000 端口时,请在另一个端口启动 Potato:
potato start config.yaml -p 9000无论提示怎么要求,视觉语言模型都按自己的坐标约定作答,有两个参数告诉 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 的名称提供 Gemma 3,所以 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 以 response_format: json_object 向 Ollama 请求 JSON。默认的 schema 会发送以标签为枚举的 JSON schema。vLLM 和 OpenAI 遵守该 schema。没有任何回复能解析为 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 列出服务器的模型,并记录该次运行请求哪个模型,以及如何读取模型的框。随后预测阶段每次请求发送一张图像。运行结束后,留出的 mAP50 显示在 Runs 表中,运行页面列出 val_map_50、test_map_50、覆盖率数字,以及一条“Not used”警告,统计不是 JSON 的回复、属于方案没有的标签的框,以及超出图像的框。得分接近 0、覆盖率很高而警告为空时,请先检查 coordinate_format 和 box_order。
Roboflow 模型
roboflow 把每张图像 POST 到 <url>/<model_id>,其中 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 以百分比向 API 发送 confidence 和 overlap(默认 0.4 和 0.3),Roboflow 的参考文档说明这两者的范围是 0 到 100。shapes: auto 在模型返回轮廓而方案有多边形工具时写出轮廓,否则写出框。健康检查会 POST 一张 8 × 8 的白色图像,Roboflow 会把这次请求计入账户的用量。
CVAT 无服务器函数
cvat-nuclio 使用已经作为 Nuclio 函数部署给 CVAT 的检测器,例如 cvat/serverless 中的 YOLOv7 或 Faster R-CNN 函数。Potato 不经过 CVAT,把每张图像以 {"image": <base64>, "threshold": t} 的形式 POST 到函数自己的 URL。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 上的模型,可以通过 https:// URL(endpoint_url)调用专用的 Inference Endpoint,也可以通过模型 id(model)调用无服务器的 HF Inference API。请设置 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 的图像,wait_for_model: true(默认)会在无服务器模型加载时等待,而不是以 503 失败。
Label Studio ML 后端
label-studio-ml 使用正在运行的 Label Studio ML 后端。用仓库示例中的 docker-compose up 启动的后端,通过 HTTP 在 http://localhost:9090 提供模型服务。运行会检查后端的 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标准后端唯一的认证是 HTTP Basic,由后端上的 BASIC_AUTH_USER 和 BASIC_AUTH_PASS 设置。basic_auth_user_env 和 basic_auth_pass_env 指明 Potato 服务器上保存这两个值的变量,这两个参数要一起使用。api_key_env 改为发送 Authorization: Token <value>,用于位于期望令牌的代理之后的后端。标准后端对这个头会返回 500。Potato 根据方案生成随每次请求发送的标注配置(Choices、Labels、RectangleLabels 或 PolygonLabels),label_config 会把生成的 XML 替换为你自己的。
图像后端还需要多一项设置。没有 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(导入项目)。从 Label Studio 迁移到 Potato 介绍导入器不处理的标签。
审核预标签
桥接器的预测与训练出的模型的预测经过同样的回写检查。回写会跳过 train 划分的条目和不在 writeback.schemas 中的方案,跳过审核者拒绝过的条目与方案的组合,并扣下低于 writeback.min_confidence 的预测。对框和片段,一个预测的置信度是其中置信度最低的形状或片段的置信度,所以只要条目上任何一个框或片段的得分更低,下限就会扣下该条目的整个预测。Roboflow 的 confidence 和 CVAT 的 threshold 则在服务端按框起作用,hf-inference 的 threshold 按框或按 multiselect 标签起作用。模型在环教程介绍审核队列、接受和拒绝的判定,以及重新训练。队列和判定对桥接器的工作方式相同。
每个条目和方案只保留一个预测,所以另一个桥接器或训练出的模型的后续运行会替换之前那次运行的预标签。要撤回一次运行的预标签,请发送 DELETE /admin/training/api/runs/<run_id>/predictions。推理桥接器参考列出四个桥接器的全部参数,label-studio-ml 参考列出 Label Studio 的参数。