Перейти к содержимому

Предварительные метки и оценки на отложенных объектах от модели, которую вы уже запускаете

Оцените модель, которую обслуживает vLLM, Ollama, Hugging Face, Roboflow, CVAT или бэкенд ML Label Studio, на метках вашего исследования, а затем проверьте её ответы как предварительные метки.

Новое в v2.14.0

Этот учебник предназначен для команд, которые уже запускают модель вне Potato и хотят узнать, насколько модель согласуется с собственными разметчиками команды, прежде чем показывать её ответы кому-либо. В обучение моделей Potato входят пять тренеров, называемых мостами, которые ничего не обучают. Мост отправляет отложенные объекты исследования модели, обслуживаемой в другом месте, оценивает ответы по меткам исследования и записывает ответы обратно как предварительные метки для проверки, как это делает запуск обученной модели. Шаги ниже охватывают визуально-языковую модель за API, совместимым с OpenAI, модель Roboflow, бессерверную функцию CVAT, модель Hugging Face и бэкенд ML Label Studio, с проверенной конфигурацией для каждого случая.

Что отправляет и оценивает запуск моста

Мост это тренер, чей шаг обучения лишь проверяет, что сервис отвечает. Затем запуск отправляет сервису объекты из выборок val и test этого запуска, а при writeback.include_unlabeled: true ещё и объекты, которые никто не размечал. Potato превращает ответы в собственные формы схемы, то есть отрезки или варианты выбора, и оценивает ответы на отложенных объектах по сведённым меткам людей теми же метриками на отложенных объектах, что и обученную модель (mAP50 для рамок, F1 отрезков для отрезков, макро-F1 для одиночного выбора). Обратная запись, очередь проверки и идентификатор запуска каждой предварительной метки работают так же, как после любого другого запуска.

ТренерСхемыМодель, которую он вызывает
openai-vlm-boxesimage_annotationВизуально-языковая модель за API чата, совместимым с OpenAI (vLLM, Ollama, LM Studio, сервер llama.cpp, OpenAI)
roboflowimage_annotationМодель Roboflow для детекции или сегментации экземпляров, размещённая в облаке или на собственном сервере Inference
cvat-nuclioimage_annotationБессерверная функция CVAT, работающая на Nuclio
hf-inferenceradio, select, multiselect, span, image_annotationКонечная точка Hugging Face Inference Endpoint или модель в бессерверном API HF Inference
label-studio-mlradio, select, multiselect, span, image_annotationРаботающий бэкенд ML Label Studio

Выборка объекта зависит только от идентификатора объекта и model_training.split_seed, поэтому запуск моста и запуск обученной модели оцениваются на одних и тех же объектах val и test, пока значение seed остаётся неизменным. Тогда таблица Runs на /admin/training ставит модель, которая у вас уже есть, рядом с моделью, обученной на метках исследования. Объекты из выборки train не получают предсказаний от моста, а обратная запись никогда не ставит предварительные метки на объекты из выборки train. При разбиении по умолчанию 70/15/15 мост поэтому оценивается примерно на 30% размеченных объектов.

Перед началом

Четырём мостам, кроме label-studio-ml, нужны requests и Pillow на сервере Potato, а страница Training показывает мост как "not installed", пока оба пакета не импортируются. label-studio-ml не требует ничего сверх Potato. Ни один из пяти мостов не устанавливает ни модель, ни библиотеку для GPU, ни torch, потому что модель работает в сервисе.

Изображения должны быть локальными файлами внутри проекта, как для любого тренера изображений. Мост исключает объект, изображение которого задано удалённым URL http(s), а объект с меткой и без предсказания считается в оценке на отложенных объектах ошибочным. Запуск сообщает долю отложенных объектов, получивших предсказание, как val_coverage и test_coverage, поэтому низкое покрытие указывает на изображения, которые мост не смог отправить.

Ключи остаются в переменных окружения

Ключ никогда не попадает в конфигурацию. Каждый мост, который отправляет ключ, принимает параметр, который называет переменную окружения с ключом (api_key_env, token_env для Hugging Face или два имени basic_auth_*_env для Label Studio), и эта переменная должна быть задана в окружении сервера Potato до его запуска. cvat-nuclio не отправляет ключ и такого параметра не принимает. Четыре моста, которые принимают ключ, отвергают значение, которое не является именем переменной, а ошибка не повторяет это значение, поскольку значение на этом месте скорее всего и есть сам ключ. Когда тело ошибки сервиса или ошибка соединения повторяет запрос, мосты заменяют значение ключа на [redacted] перед показом ошибки. Тела ошибок из /predict бэкенда ML Label Studio очищаются так же. Всё, что похоже на ключ в строке запроса или в заголовке Authorization, тоже заменяется.

Запуски hf-inference и roboflow только локальные. Оба моста читают ключ на сервере Potato, а запуск на бэкенде ssh, SLURM или агента должен был бы отправить ключ на ту машину, поэтому Potato отказывается начинать там такой запуск. openai-vlm-boxes, cvat-nuclio и label-studio-ml работают на любом бэкенде, который достаёт до сервиса, если переменная ключа задана на машине этого бэкенда.

Шаг 1. Исследование изображений с тремя мостами

Конфигурация ниже задаёт исследование фотографий со строительной площадки, на которых разметчики обводят рамкой каждую каску и каждую непокрытую голову. model_training.trainer_params хранит значения по умолчанию сразу для нескольких тренеров, поэтому одна конфигурация несёт настройки для всех трёх мостов изображений, а каждый запуск использует блок своего тренера. Поле Parameters на странице Training заполняется из этих значений по умолчанию, когда вы выбираете тренер.

yaml
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/:

json
{"id": "site_001", "image_url": "media/site_001.jpg"}

writeback.schemas делает рамки модели видимыми для разметчиков, а include_unlabeled отправляет сервису и фотографии, которые никто не размечал. Для первого запуска опустите оба ключа, если хотите получить оценку на отложенных объектах до того, как кто-либо увидит предварительную метку. label_map сопоставляет названия классов сервиса с метками схемы, а класс, отображённый в null, отбрасывается. Класс без записи уходит в метку с тем же названием, причём сопоставление не учитывает регистр. Рамки с любым другим классом исключаются, а журнал запуска считает исключённые рамки.

Шаг 2. Визуально-языковая модель с openai-vlm-boxes

openai-vlm-boxes уменьшает каждое изображение до не более чем 1280 пикселей по длинной стороне (max_image_side) и отправляет изображение на маршрут chat completions с запросом, называющим метки схемы. Potato читает ответ как рамки в JSON. Подходит любой сервер с API, совместимым с OpenAI. Команда ниже запускает Qwen2.5-VL через vLLM, чья документация подключает клиентов по адресу http://localhost:8000/v1:

bash
vllm serve Qwen/Qwen2.5-VL-7B-Instruct

Если vLLM работает на порту 8000, запустите Potato на другом порту:

bash
potato start config.yaml -p 9000

Визуально-языковые модели отвечают в собственной системе координат независимо от того, что просит запрос, и два параметра сообщают Potato, как читать числа. coordinate_format принимает fraction (от 0 до 1 от изображения), per_mille (от 0 до 1000) или pixels изображения в том виде, в каком оно отправлено. box_order принимает xyxy для [x0, y0, x1, y1] или yxyx для [y0, x0, y1, x1]. 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 измерила mAP50, равную 0.0 при чтении рамок с x на первом месте и 1.0 при чтении с y на первом месте. Ollama обслуживает Gemma 3 под названием, содержащим gemma, поэтому применяется auto:

bash
ollama run gemma3:12b

Замените блок openai-vlm-boxes в конфигурации блоком ниже:

yaml
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 JSON через response_format: json_object. Значение по умолчанию, schema, отправляет схему JSON с метками как перечислением. vLLM и OpenAI соблюдают схему. Запуск, в котором ни один ответ не разбирается как JSON, завершается ошибкой, и сообщение предлагает json_mode: object или более простой запрос (prompt, где {labels} заменяется метками схемы).

Визуально-языковая модель не даёт оценки вместе с рамками, поэтому у рамок нет уверенности. writeback.min_confidence пропускает каждое предсказание openai-vlm-boxes, а проверка модели не может упорядочить предсказания от наименее к наиболее уверенным. Справочник по openai-vlm-boxes перечисляет api_key_env для размещённого API, а также настройки токена и тайм-аута.

Шаг 3. Начало запуска и чтение оценки

Откройте http://localhost:9000/admin/training, выберите openai-vlm-boxes и схему ppe и нажмите Train now. Тот же запуск начинается и из API:

bash
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), а без ключа запуск отказывается начинаться:

bash
export ROBOFLOW_API_KEY=...   # in the shell that starts Potato
potato start config.yaml -p 9000

url по умолчанию в Potato это 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).

API Roboflow принимает ключ в строке запроса. Potato не записывает URL запроса ни в журнал запуска, ни в сообщение об ошибке. Potato отправляет confidence и overlap (по умолчанию 0.4 и 0.3) в API как проценты, а справочник Roboflow описывает оба параметра на шкале от 0 до 100. shapes: auto записывает контур там, где модель возвращает контур и у схемы есть инструмент многоугольника, а в остальных случаях рамку. Проверка работоспособности отправляет белое изображение 8 × 8, и Roboflow засчитывает запрос в использование аккаунта.

Бессерверные функции CVAT

cvat-nuclio использует детектор, уже развёрнутый для CVAT как функция Nuclio, например функции YOLOv7 или Faster R-CNN из cvat/serverless. Potato отправляет каждое изображение на собственный URL функции как {"image": <base64>, "threshold": t}, минуя CVAT. nuctl get functions перечисляет развёрнутые функции с их портами, а команда ниже это форма, которую даёт документация CVAT:

bash
nuctl get functions --platform local

Функция отвечает фигурами в пикселях. rectangle становится рамкой, а polygon становится многоугольником либо охватывающей рамкой многоугольника, когда у схемы нет инструмента многоугольника. Фигуры mask исключаются и учитываются. Функция отбрасывает фигуры с оценкой ниже threshold (по умолчанию 0.5). В схеме нет метки person, поэтому label_map: {person: null} в конфигурации отбрасывает людей, найденных детектором COCO.

Модели Hugging Face с hf-inference

hf-inference вызывает модель, развёрнутую на Hugging Face, либо выделенную конечную точку Inference Endpoint по её URL https:// (endpoint_url), либо идентификатор модели в бессерверном API HF Inference (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 отрезки. Карточка модели описывает модель BERT, дообученную на именованных сущностях на английском CoNLL-2003. HF Inference обслуживает подмножество моделей Hub, поэтому проверьте, что на странице модели указан HF Inference, прежде чем полагаться на бессерверный API.

yaml
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.

Бэкенд ML Label Studio

label-studio-ml использует работающий бэкенд ML Label Studio. Бэкенд, запущенный из примеров репозитория командой docker-compose up, обслуживает модель по HTTP на http://localhost:9090. Запуск проверяет GET /health бэкенда, отправляет объекты на POST /predict пакетами по 16 (batch_size) и читает возвращённые области тем же конвертером, который использует potato import -f label_studio. Смещения отрезков Label Studio в UTF-16 становятся кодовыми точками, а процентные координаты становятся координатами виджета изображения.

yaml
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, а бэкенд, построенный на SDK Label Studio, читает data.image как URL и терпит неудачу на голом пути. Для бэкенда на той же машине добавьте префикс ниже и запустите бэкенд с LOCAL_FILES_DOCUMENT_ROOT, равным папке задачи:

yaml
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 переносит в Potato целый проект Label Studio вместе с его конфигурацией разметки, ответами разметчиков и сохранёнными предсказаниями (Импорт проектов). Миграция из Label Studio в Potato рассматривает теги, которые импортёр исключает.

Проверка предварительных меток

Предсказания моста проходят через те же проверки обратной записи, что и предсказания обученной модели. Обратная запись пропускает объекты из выборки train и схемы, отсутствующие в writeback.schemas, пропускает пары объекта и схемы, которые рецензент отклонил, и задерживает предсказания ниже writeback.min_confidence. Для рамок и отрезков уверенность предсказания это уверенность наименее уверенной фигуры или отрезка, поэтому порог задерживает предсказание объекта целиком, если какая-либо одна рамка или отрезок на объекте оценены ниже. confidence у Roboflow и threshold у CVAT действуют вместо этого на каждую рамку, на стороне сервиса, а threshold у hf-inference действует на каждую рамку или каждую метку multiselect. Учебник по модели в цикле рассматривает очередь проверки, вердикты принятия и отклонения и переобучение. Очередь и вердикты работают для моста так же.

Для каждого объекта и схемы хранится одно предсказание, поэтому более поздний запуск другого моста или обученной модели заменяет предварительные метки предыдущего запуска. Чтобы отозвать предварительные метки запуска, отправьте DELETE /admin/training/api/runs/<run_id>/predictions. Справочник по мостам к моделям перечисляет все параметры четырёх мостов, а справочник по label-studio-ml перечисляет параметры Label Studio.

Дополнительные материалы