7.15. Написание собственного¶
Когда каталог не покрывает модель – исследовательская сеть с уникальной компоновкой выхода, доработка существующей архитектуры, тензор, семантическая интерпретация которого специфична для приложения – приложение предоставляет свой собственный постпроцессор. Протокол прост: вызываемый объект, который принимает (model, inputs, outputs) и возвращает то, что приложение ожидает от predict().
Класс с __call__ – общепринятая форма:
class MyPostprocessor:
def __init__(self, threshold=0.5):
self.threshold = threshold
def __call__(self, model, inputs, outputs):
...
return result
Обычная функция тоже работает – движок лишь проверяет, что объект вызываемый.
7.15.1. Подключение¶
Две точки подключения. Именованный аргумент postprocess= в конструкторе привязывает вызываемый объект для каждого вызова predict() у модели:
model = ml.Model("/rom/my_model.tflite",
postprocess=MyPostprocessor())
Чтобы переопределить привязку для единственного вызова – сменить декодеры без перезагрузки модели – передайте callback= непосредственно в predict:
result = model.predict([img], callback=MyOtherPostprocessor())
Сигнатура вызываемого объекта одинакова в обоих случаях.
7.15.2. Что получает вызываемый объект¶
model– экземплярModel, полезный для параметров квантования (output_scale,output_zero_point,output_dtype) и размерностей входа (input_shape).inputs– список входов, который приложение передало вpredict(). Первый элемент обычно – привязанный экземплярNormalization; его атрибутroi– это то, чтоNMSожидает для пересчёта рамок обратно в исходное изображение.outputs– сырые выходные тензоры в виде списка объектовndarrayв их родном типе данных. Вещественные выходы приходят как есть; целочисленные выходы приходят квантованными.
7.15.3. Квантованная арифметика¶
Все поставляемые декодеры обращаются к одним и тем же вспомогательным функциям в ml.utils, и пользовательскому обычно нужен тот же шаблон: quantize() переводит вещественный порог в квантованное пространство модели, threshold() фильтрует без деквантования всего тензора, а dequantize() выполняется один раз над уцелевшими. sigmoid() и logit() доступны для сетей, выходные каналы которых являются логитами до сигмоиды (детекторы MediaPipe – канонический случай).
Для моделей с вещественными выходами – регрессионные головы, модели с встроенным финальным слоем деквантования – вспомогательные функции квантования пропускают данные без изменений, поэтому один и тот же код постпроцессора работает с любым типом данных без особой обработки.
7.15.4. Возвращаемое значение¶
То, что возвращает вызываемый объект, и есть то, что возвращает predict(). Для декодеров, выдающих рамки, принято прогонять кандидатов через NMS и возвращать его списки по классам – ту форму вызова, которую документирует подавление немаксимумов и которую в контексте строит разбор YOLOv8. Для всего остального возвращайте то, что приложению удобно: одиночный ndarray, строку-метку, кортеж (class, score, embedding), словарь.