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, у їхньому нативному dtype. Виходи з плаваючою комою надходять як є; цілочисельні виходи надходять квантованими.

7.15.3. Квантована арифметика

Усі вбудовані декодери використовують ті самі допоміжні функції з ml.utils, і власний декодер зазвичай потребує того самого шаблону: quantize() підіймає поріг з плаваючою комою до квантованого простору моделі, threshold() фільтрує без деквантизації всього тензора, а dequantize() запускається один раз на відібраних значеннях. sigmoid() та logit() доступні для мереж, у яких вихідні канали є логітами до сигмоїди (детектори MediaPipe – канонічний випадок).

Для моделей з виходами у плаваючій комі – регресійні голови, моделі з фінальним шаром деквантизації – допоміжні функції квантування проходять без змін, тому той самий код постпроцесора працює з будь-яким dtype без спеціальних умов.

7.15.4. Повернуте значення

Те, що повертає виклик, є тим, що повертає predict(). Для декодерів, що видають рамки, прийнято передавати кандидатів через NMS і повертати його списки по класах – форма виклику, яку документує немаксимальне пригнічення і яку будує розбір YOLOv8 у контексті. Для всього іншого повертайте те, що є зручним для застосунку: один ndarray, рядок мітки, кортеж (class, score, embedding), словник.