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), словарь.