Pular para o conteúdo

Pré-rótulos e pontuações em itens reservados de um modelo que você já executa

Pontue um modelo servido por vLLM, Ollama, Hugging Face, Roboflow, CVAT ou um backend de ML do Label Studio nos rótulos do seu estudo e depois revise as respostas dele como pré-rótulos.

Novo na v2.14.0

Este tutorial é para equipes que já executam um modelo fora do Potato e querem saber o quanto o modelo concorda com os próprios anotadores da equipe antes de mostrar as respostas dele a alguém. O treinamento de modelos do Potato inclui cinco treinadores, chamados de pontes, que não treinam nada. Uma ponte envia os itens reservados do estudo a um modelo servido em outro lugar, pontua as respostas contra os rótulos do estudo e grava as respostas de volta como pré-rótulos para revisão, como faz uma execução de um modelo treinado. As etapas abaixo cobrem um modelo de visão e linguagem por trás de uma API compatível com OpenAI, um modelo do Roboflow, uma função serverless do CVAT, um modelo do Hugging Face e um backend de ML do Label Studio, com um config validado para cada um.

O que uma execução de ponte envia e pontua

Uma ponte é um treinador cuja etapa de ajuste apenas verifica se o serviço responde. A execução então envia ao serviço os itens das divisões val e test da execução e, com writeback.include_unlabeled: true, também os itens que ninguém rotulou. O Potato transforma as respostas nos formatos próprios do esquema, trechos ou escolhas, e pontua as respostas em itens reservados contra os rótulos humanos resolvidos, com as mesmas métricas em itens reservados que um modelo treinado recebe (mAP50 para caixas, F1 de trechos para trechos, F1 macro para escolhas únicas). A gravação de volta, a fila de revisão e o id de execução de cada pré-rótulo funcionam como após qualquer outra execução.

TreinadorEsquemasO modelo que ele chama
openai-vlm-boxesimage_annotationUm modelo de visão e linguagem por trás de uma API de chat compatível com OpenAI (vLLM, Ollama, LM Studio, o servidor do llama.cpp, OpenAI)
roboflowimage_annotationUm modelo de detecção ou de segmentação de instâncias do Roboflow, hospedado ou em um servidor Inference próprio
cvat-nuclioimage_annotationUma função serverless do CVAT executada no Nuclio
hf-inferenceradio, select, multiselect, span, image_annotationUm Hugging Face Inference Endpoint ou um modelo na API serverless HF Inference
label-studio-mlradio, select, multiselect, span, image_annotationUm backend de ML do Label Studio em execução

A divisão de um item depende apenas do id do item e de model_training.split_seed, então uma execução de ponte e a execução de um modelo treinado pontuam nos mesmos itens val e test enquanto a semente permanecer fixa. A tabela Runs em /admin/training então coloca um modelo que você já tem ao lado de um modelo treinado nos rótulos do estudo. Itens da divisão train não recebem predição de uma ponte, e a gravação de volta nunca pré-rotula um item da divisão train. Com a divisão padrão de 70/15/15, uma ponte portanto pontua em cerca de 30% dos itens rotulados.

Antes de começar

As quatro pontes que não são label-studio-ml precisam de requests e Pillow no servidor do Potato, e a página Training lista uma ponte como "not installed" até que os dois pacotes sejam importados. label-studio-ml não precisa de nada além do Potato. Nenhuma das cinco pontes instala um modelo, uma biblioteca de GPU ou o torch, porque o modelo executa no serviço.

As imagens precisam ser arquivos locais dentro do projeto, como em todo treinador de imagens. Uma ponte deixa de fora um item cuja imagem é uma URL http(s) remota, e um item com rótulo e sem predição conta como errado na pontuação em itens reservados. A execução informa a parcela de itens reservados que recebeu uma predição como val_coverage e test_coverage, então uma cobertura baixa aponta para imagens que a ponte não conseguiu enviar.

As chaves ficam em variáveis de ambiente

Uma chave nunca vai no config. Cada ponte que envia uma chave recebe um parâmetro que nomeia a variável de ambiente com a chave (api_key_env, token_env para o Hugging Face, ou os dois nomes basic_auth_*_env para o Label Studio), e a variável precisa estar definida no ambiente do servidor do Potato antes de o servidor iniciar. cvat-nuclio não envia chave e não recebe esse parâmetro. As quatro pontes que recebem uma chave recusam um valor que não seja um nome de variável, e o erro não repete o valor, já que um valor nesse lugar muito provavelmente é a própria chave. Quando o corpo de erro de um serviço ou um erro de conexão ecoa a requisição, as pontes substituem o valor da chave por [redacted] antes de mostrar o erro. Os corpos de erro do /predict de um backend de ML do Label Studio são limpos da mesma forma. Qualquer coisa com formato de chave em uma string de consulta ou em um cabeçalho Authorization também é substituída.

As execuções de hf-inference e roboflow são apenas locais. As duas pontes leem a chave no servidor do Potato, e uma execução em um backend ssh, SLURM ou de agente teria de enviar a chave a esse host, então o Potato recusa iniciar uma execução assim lá. openai-vlm-boxes, cvat-nuclio e label-studio-ml executam em qualquer backend que consiga alcançar o serviço, com a variável da chave definida no host desse backend.

Etapa 1: um estudo de imagens com três pontes

O config abaixo monta um estudo de fotos de obras em que os anotadores marcam com caixas cada capacete e cada cabeça descoberta. model_training.trainer_params contém padrões para vários treinadores ao mesmo tempo, então um único config carrega as configurações das três pontes de imagem, e cada execução usa o bloco do próprio treinador. O campo Parameters da página Training é preenchido a partir desses padrões quando você escolhe um treinador.

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}

Cada linha de data/photos.jsonl nomeia uma imagem em media/:

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

writeback.schemas é o que torna as caixas do modelo visíveis aos anotadores, e include_unlabeled envia ao serviço também as fotos que ninguém rotulou. Deixe as duas chaves de fora na primeira execução se você quiser a pontuação em itens reservados antes que alguém veja um pré-rótulo. label_map mapeia os nomes de classe do serviço para os rótulos do esquema, e uma classe mapeada para null é descartada. Uma classe sem entrada vai para o rótulo de mesmo nome, e a correspondência ignora maiúsculas e minúsculas. As caixas de qualquer outra classe ficam de fora, e o log da execução conta as caixas deixadas de fora.

Etapa 2: um modelo de visão e linguagem com openai-vlm-boxes

openai-vlm-boxes reduz cada imagem a no máximo 1.280 pixels no lado maior (max_image_side) e envia a imagem à rota de chat completions com um prompt que nomeia os rótulos do esquema. O Potato lê a resposta como caixas em JSON. Qualquer servidor com uma API compatível com OpenAI funciona. O comando abaixo serve o Qwen2.5-VL com o vLLM, cuja documentação conecta clientes em http://localhost:8000/v1:

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

Com o vLLM na porta 8000, inicie o Potato em outra porta:

bash
potato start config.yaml -p 9000

Os modelos de visão e linguagem respondem na própria convenção de coordenadas, qualquer que seja o pedido do prompt, e dois parâmetros dizem ao Potato como ler os números. coordinate_format é fraction (0 a 1 da imagem), per_mille (0 a 1.000) ou pixels da imagem como enviada. box_order é xyxy para [x0, y0, x1, y1] ou yxyx para [y0, x0, y1, x1]. O Qwen2.5-VL responde em pixels, então o config acima define coordinate_format: pixels. Com auto, o Potato adivinha o formato uma vez por resposta a partir do tamanho dos números. Valores até 1,05 são lidos como frações, valores acima do tamanho da imagem enviada são lidos como por mil, e todo o resto é lido como pixels. Uma caixa em por mil que cabe dentro de uma imagem grande portanto é lida como pixels, então nomeie o formato sempre que conhecer a convenção do modelo.

O Gemma e o Gemini respondem caixas em por mil com y primeiro, [ymin, xmin, ymax, xmax], mesmo quando o prompt pede frações. Com os dois parâmetros em auto, o Potato lê as caixas de um modelo cujo nome contém gemma ou gemini como por mil com y primeiro. Em um projeto de teste com o Gemma 4 12B com quadrados vermelhos e azuis, a equipe do Potato mediu mAP50 de 0,0 com as caixas lidas com x primeiro e 1,0 com as caixas lidas com y primeiro. O Ollama serve o Gemma 3 sob um nome que contém gemma, então auto se aplica:

bash
ollama run gemma3:12b

Substitua o bloco openai-vlm-boxes do config pelo bloco abaixo:

yaml
model_training:
  trainer_params:
    openai-vlm-boxes:
      url: http://localhost:11434/v1
      model: gemma3:12b
      json_mode: object

Um ajuste fino de Gemma ou Gemini servido sob um nome sem nenhuma das duas palavras recebe a leitura com x primeiro em auto. Defina coordinate_format: per_mille e box_order: yxyx para tal modelo. json_mode: object pede ao Ollama JSON com response_format: json_object. O padrão, schema, envia um esquema JSON com os rótulos como enumeração. O vLLM e a OpenAI respeitam o esquema. Uma execução em que nenhuma resposta é analisada como JSON falha, e o erro sugere json_mode: object ou um prompt mais simples (prompt, em que {labels} se torna os rótulos do esquema).

Um modelo de visão e linguagem não dá pontuação com as suas caixas, então as caixas não têm confiança. writeback.min_confidence deixa passar toda predição de openai-vlm-boxes, e a revisão de modelos não consegue ordenar as predições da menos para a mais confiante. A referência de openai-vlm-boxes lista api_key_env para uma API hospedada e as configurações de token e de tempo limite.

Etapa 3: inicie uma execução e leia a pontuação

Abra http://localhost:9000/admin/training, escolha openai-vlm-boxes e o esquema ppe e clique em Train now. A mesma execução começa pela 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"]}'

A etapa de ajuste lista os modelos do servidor por /models e registra qual modelo a execução pede e como a execução lê as caixas do modelo. A passagem de predição então envia uma imagem por requisição. Quando a execução termina, o mAP50 em itens reservados aparece na tabela Runs, e a página da execução lista val_map_50, test_map_50, os números de cobertura e um aviso "Not used" que conta respostas que não eram JSON, caixas de rótulos que o esquema não tem e caixas fora da imagem. Quando a pontuação está perto de 0 com cobertura alta e o aviso vazio, verifique primeiro coordinate_format e box_order.

Modelos do Roboflow

roboflow envia cada imagem por POST a <url>/<model_id>, em que model_id é o projeto e a versão como project/version. A chave vai por padrão em ROBOFLOW_API_KEY (api_key_env), e a execução se recusa a iniciar sem a chave:

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

A url padrão do Potato é https://detect.roboflow.com, e o config acima nomeia o padrão. Uma execução de segmentação de instâncias define url: https://outline.roboflow.com. url é um parâmetro, então uma execução também pode nomear https://serverless.roboflow.com, o host que a referência de detecção de objetos do Roboflow dá para a mesma rota project/version. A equipe do Potato não testou esse host com esta ponte. Um servidor Roboflow Inference próprio escuta na porta 9001 por padrão (url: http://localhost:9001).

A API do Roboflow recebe a chave na string de consulta. O Potato não grava a URL da requisição no log da execução nem em uma mensagem de erro. O Potato envia confidence e overlap (padrões 0,4 e 0,3) à API como porcentagens, e a referência do Roboflow documenta os dois em uma escala de 0 a 100. shapes: auto grava um contorno quando o modelo devolve um contorno e o esquema tem uma ferramenta de polígono, e uma caixa nos demais casos. A verificação de saúde envia uma imagem branca de 8 × 8, e o Roboflow conta a requisição no uso da conta.

Funções serverless do CVAT

cvat-nuclio usa um detector já implantado para o CVAT como uma função Nuclio, como as funções YOLOv7 ou Faster R-CNN em cvat/serverless. O Potato envia cada imagem por POST à URL própria da função como {"image": <base64>, "threshold": t}, sem passar pelo CVAT. nuctl get functions lista as funções implantadas com as suas portas, e o comando abaixo é a forma que a documentação do CVAT dá:

bash
nuctl get functions --platform local

A função responde com formas em pixels. Um rectangle se torna uma caixa, e um polygon se torna um polígono, ou a caixa delimitadora do polígono quando o esquema não tem ferramenta de polígono. Formas mask ficam de fora e são contadas. A função descarta formas com pontuação abaixo de threshold (padrão 0,5). O esquema não tem rótulo de pessoa, então label_map: {person: null} no config descarta as pessoas de um detector COCO.

Modelos do Hugging Face com hf-inference

hf-inference chama um modelo implantado no Hugging Face, seja um Inference Endpoint dedicado pela sua URL https:// (endpoint_url), seja um id de modelo na API serverless HF Inference (model). Defina endpoint_url ou model, e não ambos. A tarefa segue o tipo do esquema:

  • Classificação de texto para radio, select e multiselect. Um esquema de escolha única toma o rótulo principal do modelo. Quando esse rótulo não está entre os rótulos do esquema após label_map, o item não recebe predição em vez do segundo colocado. Um esquema multiselect toma todo rótulo com pontuação de pelo menos threshold (padrão 0,5).
  • Classificação de tokens para span, com pedaços de palavras agrupados em entidades por aggregation_strategy (simple, first, average ou max) e deslocamentos de caracteres tomados do modelo.
  • Detecção de objetos para image_annotation. Caixas com pontuação abaixo de threshold são descartadas.

O config abaixo pede trechos ao dslim/bert-base-NER. O cartão do modelo descreve um modelo BERT ajustado para entidades nomeadas no CoNLL-2003 em inglês. O HF Inference serve um subconjunto dos modelos do Hub, então verifique se a página do modelo lista o HF Inference antes de contar com a API serverless.

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}

As entidades PER, ORG e LOC do modelo são mapeadas para os rótulos do esquema, e MISC é descartada porque o esquema não tem tal rótulo. Quando o nome completo de uma entidade não corresponde a nada, o Potato descarta um prefixo B- ou I- e tenta de novo. Um classificador de sentimento que responde LABEL_0 e LABEL_1 precisa de label_map: {LABEL_0: negative, LABEL_1: positive} da mesma forma. A verificação de saúde é uma requisição real, uma frase curta ou uma imagem de 8 × 8, e wait_for_model: true (o padrão) espera enquanto um modelo serverless carrega em vez de falhar com um 503.

Um backend de ML do Label Studio

label-studio-ml usa um backend de ML do Label Studio em execução. Um backend iniciado a partir dos exemplos do repositório com docker-compose up serve um modelo por HTTP em http://localhost:9090. A execução verifica o GET /health do backend, envia os itens a POST /predict em lotes de 16 (batch_size) e lê as regiões devolvidas com o mesmo conversor que potato import -f label_studio usa. Os deslocamentos de trechos em UTF-16 do Label Studio se tornam pontos de código, e as coordenadas percentuais se tornam as do widget de imagem.

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

A única autenticação de um backend padrão é HTTP Basic, definida por BASIC_AUTH_USER e BASIC_AUTH_PASS no backend. basic_auth_user_env e basic_auth_pass_env nomeiam as variáveis no servidor do Potato que contêm esses mesmos dois valores, e os dois parâmetros andam juntos. api_key_env envia Authorization: Token <value> no lugar, para um backend atrás de um proxy que espera um token. Um backend padrão responde a esse cabeçalho com um 500. O Potato gera o config de rotulagem enviado a cada requisição a partir do esquema (Choices, Labels, RectangleLabels ou PolygonLabels), e label_config substitui o XML gerado pelo seu próprio.

Um backend de imagens precisa de mais uma configuração. Sem media_url_prefix, o Potato envia cada imagem como um caminho absoluto na máquina do Potato, e um backend construído sobre o SDK do Label Studio lê data.image como uma URL e falha com um caminho simples. Para um backend na mesma máquina, adicione o prefixo abaixo e inicie o backend com LOCAL_FILES_DOCUMENT_ROOT definido como o diretório da tarefa:

yaml
model_training:
  trainer_params:
    label-studio-ml:
      url: http://localhost:9090
      media_url_prefix: /data/local-files/?d=

Para um backend em outra máquina, sirva o diretório da tarefa por HTTP e dê a URL desse servidor como prefixo. Uma imagem fora do diretório da tarefa não tem caminho relativo ao diretório, então o Potato não envia a imagem, e o log da execução nomeia quantas ficaram de fora.

O Potato envia ao backend requisições de predição e nenhum evento de anotação, então o treinamento próprio do backend no seu manipulador /webhook não começa a partir do Potato. potato import -f label_studio move um projeto inteiro do Label Studio para o Potato com o config de rotulagem do projeto, as respostas dos anotadores e as predições armazenadas (Importando projetos). Migrando do Label Studio para o Potato trata das tags que o importador deixa de fora.

Revise os pré-rótulos

As predições de uma ponte passam pelas mesmas verificações de gravação de volta que as de um modelo treinado. A gravação de volta pula itens da divisão train e esquemas ausentes de writeback.schemas, pula pares de item e esquema que um revisor rejeitou e retém predições abaixo de writeback.min_confidence. Para caixas e trechos, a confiança de uma predição é a confiança da forma ou do trecho menos confiante, então o limite retém a predição inteira de um item quando qualquer caixa ou trecho do item pontua menos. O confidence do Roboflow e o threshold do CVAT atuam por caixa no serviço, e o threshold de hf-inference atua por caixa ou por rótulo de multiselect. O tutorial do modelo no ciclo trata da fila de revisão, dos vereditos de aceitar e rejeitar e do retreinamento. A fila e os vereditos funcionam da mesma forma para uma ponte.

Uma predição é mantida por item e esquema, então uma execução posterior de outra ponte ou de um modelo treinado substitui os pré-rótulos da execução anterior. Para retirar os pré-rótulos de uma execução, envie DELETE /admin/training/api/runs/<run_id>/predictions. A referência das pontes de inferência lista todos os parâmetros das quatro pontes, e a referência de label-studio-ml lista os parâmetros do Label Studio.

Leitura complementar