Classificação
A tarefa Classifier aceita qualquer classificador ONNX com saída de formato
(1, num_classes) (estilo torchvision). Ela faz preprocessamento, normalização,
softmax opcional e resolução de rótulos por você, devolvendo um envelope
ClassificationResults.
Construindo o classificador
from ort_vision_sdk import Classifier
clf = Classifier(
"resnet50.onnx",
labels="imagenet_labels.txt", # ver "Rótulos" abaixo
input_size=(224, 224), # (largura, altura) padrão
normalization="auto", # lê os metadados do modelo e escolhe
apply_softmax=True, # False se o modelo já emite probabilidades
)
Parâmetros adicionais: providers (lista de execution providers) e
session_options (uma ort.SessionOptions).
import { Classifier } from "@mauriciobenjamin700/ort-vision-sdk-web";
const clf = await Classifier.create("/models/resnet50.onnx", {
labels: ["tench", "goldfish", /* ... */], // ou null + numClasses
inputSize: [224, 224], // padrão
normalization: "auto", // lê os metadados e escolhe
applySoftmax: true, // padrão
providers: ["webgpu", "wasm"], // ordem padrão
});
Quando labels é null, informe numClasses para o SDK gerar
class_0, class_1, ...
Normalização: o modelo decide
O recorte chega ao classificador em [0, 1]. O que tem que acontecer depois
depende inteiramente de como o seu modelo foi treinado — e as duas famílias
mais comuns discordam:
| Família | Espera | Preset |
|---|---|---|
| torchvision / timm | tensor normalizado com média e desvio do ImageNet | "imagenet" |
Ultralytics (YOLO(...).export()) |
[0, 1] cru, sem normalização nenhuma |
"ultralytics" |
Por isso o padrão é normalization="auto": o SDK lê os metadados do próprio
modelo e escolhe. Todo export do Ultralytics carrega author: Ultralytics e
task: classify no .onnx — o mesmo bloco de onde os nomes de classe já vinham.
O modo de falha que isto elimina
Até a 0.8.0 o padrão era ImageNet para todo mundo. Carregar um classificador Ultralytics com esse padrão o alimenta com um tensor que ele nunca viu no treino — e nada reclama: sem exceção, sem aviso, com a predição saindo na forma certa e simplesmente pior.
Para escolher à mão, ou fugir dos dois presets:
Classifier("modelo.onnx", normalization="imagenet")
Classifier("modelo.onnx", normalization="ultralytics")
Classifier("modelo.onnx", normalization="none")
Classifier("modelo.onnx", mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
print(clf.normalization) # "ultralytics" | "imagenet" | "none" | "custom"
await Classifier.create(url, { normalization: "imagenet" });
await Classifier.create(url, { mean: [0.5, 0.5, 0.5], std: [0.5, 0.5, 0.5] });
console.log(clf.normalization);
mean e std são independentes
Passar só um dos dois deixa o outro no valor do preset que o auto
escolheria. Passar mean/std junto com normalization é erro
(ValueError no Python, RangeError no web): são duas respostas para a
mesma pergunta.
Pedir uma normalização não-identidade para um modelo Ultralytics ainda funciona, mas emite aviso — o SDK não decide por você, só se recusa a ficar calado.
Softmax: uma vez, não duas
A mesma família de modelo traz a mesma armadilha do outro lado da inferência.
Todo export de classificação do Ultralytics termina num nó Softmax —
conferido em v8, v11 e v26 — então a saída já é uma distribuição de
probabilidade. Aplicar softmax de novo em cima dela é monotônico: a classe
predita não muda, e todas as confianças mudam.
Por isso apply_softmax (applySoftmax no web) tem default None/indefinido,
que lê os metadados e responde False para essa família:
clf = Classifier("yolo11n-cls.onnx")
print(clf.applies_softmax) # False — o grafo já termina em Softmax
const clf = await Classifier.create("/models/yolo11n-cls.onnx");
console.log(clf.appliesSoftmax); // false
Por que isso passa despercebido
O softmax duplo preserva a ordenação, então o top-1 continua certo e
qualquer verificação que só olhe a classe predita passa. Medido num
yolo11n-cls real contra o pipeline do próprio Ultralytics: a concordância
de top-1 era 100 % nos dois casos, mas o erro absoluto de probabilidade
ia de 7,6·10⁻⁷ (correto) para 0,82. Uma confiança de 0,99 chega como
0,73 — e um limiar de confiança lê exatamente esse número.
A detecção cobre a família Ultralytics. Para qualquer outro modelo que já emita
probabilidades, passe apply_softmax=False explicitamente.
Predizendo
predict() devolve uma lista de comprimento 1 — use [0].
r = clf.predict("dog.jpg")[0]
print(r.cls, r.conf, r.name) # top-1 (índice, confiança, rótulo)
print(r.probs.top1) # índice top-1
print(r.probs.top5) # array com os 5 índices mais prováveis
print(r.probs.top1conf) # confiança do top-1
print(r.probs.top5conf) # confianças do top-5
print(r.probabilities[:5]) # tupla de dataclasses ClassProbability
const r = (await clf.predict("/images/dog.jpg", { topK: 5 }))[0];
console.log(r.cls, r.conf, r.name); // top-1
console.log(r.probs.top1, r.probs.top5);
console.log(r.probs.top1conf, r.probs.top5conf);
console.log(r.probabilities); // ClassProbability[]
topK controla quantas probabilidades por classe são materializadas em
probabilities.
A visão Probs
A visão em massa probs espelha a interface Probs do Ultralytics:
top1, top5, top1conf, top5conf, data. As dataclasses/objetos
ClassProbability carregam os campos verbosos (class_id/classId,
class_name/className, probability) e expõem aliases estilo Ultralytics
(cls, name).
Rótulos
Veja a seção Rótulos no guia Python (Python) e
Rótulos no guia Web (Web) — as duas plataformas aceitam
preset ("coco"), lista/tupla, dict esparso, caminho de arquivo (Python) ou
null para auto-gerar.