Модель и словарь категорий из разных релизов: как проверить комплект файлов

После обновления сервис загружает модель без ошибки, принимает запрос и возвращает число. Но категория «премиум» теперь кодируется как 2 вместо 0. Файл весов пришёл из нового релиза, а словарь категорий остался от предыдущего. Формы массивов совпадают, поэтому технического падения нет. Пользователь получает ответ модели, которой передали признаки с другим смыслом.

Проверять только существование model.bin недостаточно. Релиз включает комплект: веса, преобразования, словари, описание входных полей и необходимые настройки. Такой комплект нужно собирать, переносить и принимать как одно целое. Небольшой файл с перечнем частей и их контрольными суммами позволяет поймать смешение до первого запроса.

Опишем состав релиза заранее

Назовём этот файл манифестом. В нём указываются идентификатор релиза, список обязательных файлов и SHA-256 каждого файла. Контрольная сумма вычисляется из содержимого: одинаковое имя не спасёт подменённый словарь. Если сохраняется модель вместе с preprocessing, дополнительные внешние ресурсы всё равно нужно включить в перечень. Часто именно они остаются за пределами основного артефакта.

Манифест формируется после завершения сборки. Если вычислить суммы, а затем дописать файл, проверка справедливо отклонит комплект. Удобно складывать готовые релизы в отдельные каталоги и больше не менять их содержимое. Тогда имя release_id соответствует конкретному набору байтов и может использоваться в журнале предсказаний.

Поймаем словарь от другой модели

В примере файлы представлены байтовыми строками, чтобы результат можно было повторить без настоящего бинарного формата. Алгоритм hashlib.sha256 считает сумму одинаково для этих строк и для байтов, прочитанных с диска. Сначала проверяем перечень, затем содержимое каждого элемента.

from hashlib import sha256
bundle = {"model.bin": b"weights-v2", "categories.json": b'{"premium":0,"base":1}'}
manifest = {"release": "r2", "files": {
    name: sha256(data).hexdigest() for name, data in bundle.items()}}
def verify(files, spec):
    if set(files) != set(spec["files"]):
        raise ValueError("file list differs")
    bad = [name for name, digest in spec["files"].items()
           if sha256(files[name]).hexdigest() != digest]
    if bad:
        raise ValueError("checksum: " + ",".join(bad))
    return spec["release"]
print("accepted", verify(bundle, manifest))
mixed = dict(bundle)
mixed["categories.json"] = b'{"premium":1,"base":0}'
try:
    verify(mixed, manifest)
except ValueError as exc:
    print("rejected", exc)
else:
    raise AssertionError("mixed release passed")

Оригинал принимается как r2. Изменённый словарь отклоняется по имени файла. Проверка делает ошибку адресной: оператор понимает, какой элемент отличается от принятой сборки. При чтении больших файлов сумму можно считать последовательными блоками, не загружая всё содержимое в память. Сравнивать при этом нужно результат полного чтения, а не сумму первого блока.

Байты совпали — теперь проверим поведение

Совпадение сумм подтверждает комплектность относительно манифеста. Оно не говорит, что автор сборки выбрал правильные категории или что модель качественная. Поэтому следующим этапом загрузите проверенный комплект и выполните несколько контрольных запросов с известным преобразованием категорий. Включите неизвестную категорию, пропуск и допустимый обычный объект. Сохраните результаты вместе с отчётом приёмки релиза.

Также запишите версии библиотек и схему входа. При переносе между окружениями одинаковые файлы могут не загружаться из-за несовместимого формата сериализации. Не пытайтесь исправлять это случайным обновлением зависимости прямо на сервере: воспроизведите окружение сборки и проверьте загрузку там. Для отката храните предыдущий комплект полностью, включая его словари и настройки.

Если злоумышленник может заменить одновременно файлы и манифест, простые суммы не обнаружат подмену. Манифест должен поступать из доверенного канала выпуска; при необходимости применяют подпись и проверку происхождения. Это отдельная задача от случайного смешения релизов, ради которого мы ввели перечень. Внутри обычного процесса сборки уже полезно запретить ручное копирование отдельных элементов в активный каталог.

Проверьте не только повреждённый файл, но и отсутствующий, лишний и пустой. Удобнее завершить запуск при несовпадении, чем разрешить обслуживать часть запросов и искать последствия среди метрик качества. В журнале старта сохраните release_id и результат приёмки: это свяжет изменение ответов с конкретным комплектом.

В настройках развёртывания лучше указывать один release_id, чем отдельные пути к весам и словарю. Так сама конфигурация меньше располагает к случайному смешению. Новый процесс принимает комплект до подключения к трафику, а не откладывает проверку до первого пользовательского запроса.

Сборка воспроизводимого сервиса требует связать подготовку признаков, модель, API и автоматические проверки. В курсе этим этапам посвящён полный маршрут от данных до деплоя: следующий шаг после манифеста — включить проверку комплекта в приёмку релиза и CI/CD ML-проекта. Откройте программу платного курса «Data Scientist: от данных до деплоя моделей».

Почему при откате важен контракт признаков.

Продолжить на практике

  • Data Scientist: от данных до деплоя моделей ↗

    Практический курс по Data Science: от постановки ML-задачи и работы с данными до inference API, CI/CD и мониторинга моделей в продакшне. Вы пройдёте EDA, feature engineering, обучение и оценку моделей, experiment tracking, error analysis и deployment — чтобы собирать полноценный рабочий контур вокруг ML-решения.