ML API возвращает вероятность: как зафиксировать класс, версию и ошибку входа
Сервис отдаёт клиенту JSON {"probability": 0.8}. Через месяц модель переобучили, а клиентская команда выясняет, что не знает: 0,8 — вероятность возврата или успешной доставки? Можно ли сравнивать её с прошлым порогом? Как отличить ошибку входных данных от низкой вероятности? Число само по себе не является достаточным контрактом решения.
Именуйте событие и версию
В ответе нужны имя класса или события, числовая вероятность, версия модели и id запроса для трассировки. Если клиент применяет порог, договоритесь, принадлежит ли порог сервису или клиенту и где хранится его версия. predict_proba в scikit-learn возвращает столбцы в порядке classes_; индекс положительного класса выбирайте по метке, а не по предположению «второй столбец всегда нужный». Для неверного или неполного входа возвращайте явную ошибку валидации, а не вероятность 0.
Ниже функция строит форму ответа поверх игрушечной модели. Она проверяет вход, получает позицию класса returned из classes_ и прикрепляет версию. Числа вероятности здесь демонстрационные.
import math
import numpy as np
class ToyModel:
classes_ = np.array(["delivered", "returned"])
def predict_proba(self, rows):
return np.array([[0.2, 0.8] for _ in rows])
def predict_response(payload, model, version):
amount = payload.get("amount")
if isinstance(amount, bool) or not isinstance(amount, (int, float)) or not math.isfinite(amount) or amount < 0:
raise ValueError("amount must be a finite nonnegative number")
if not isinstance(payload.get("request_id"), str) or not payload["request_id"]:
raise ValueError("request_id is required")
event = "returned"
class_index = list(model.classes_).index(event)
probability = float(model.predict_proba([[payload["amount"]]])[0, class_index])
return {"request_id": payload["request_id"], "event": event,
"probability": probability, "model_version": version}
model = ToyModel()
answer = predict_response({"request_id": "r-17", "amount": 1250}, model, "v4")
print(answer)
assert answer == {"request_id": "r-17", "event": "returned", "probability": 0.8,
"model_version": "v4"}
try:
predict_response({"request_id": "r-18", "amount": -1}, model, "v4")
except ValueError as exc:
assert "amount" in str(exc)
else:
raise AssertionError("invalid input was accepted")
try:
predict_response({"amount": 1250}, model, "v4")
except ValueError as exc:
assert "request_id" in str(exc)
else:
raise AssertionError("missing request_id was accepted")Клиент получает вероятность 0,8 именно для события returned из версии v4; отрицательная сумма отклонена. В настоящем HTTP-сервисе исключение следует преобразовать в согласованный статус 4xx с машинным кодом ошибки и полем, которое её вызвало. Ошибка валидации не должна превращаться в 500, а проблема модели не должна маскироваться под некорректный запрос.
При мультиклассовой задаче одно поле probability становится особенно опасным. Договоритесь, возвращаете ли вероятности всех классов объектом «метка → число» или только вероятность конкретного бизнес-события. Если возвращаете все, проверьте совпадение набора меток с classes_ и сумму вероятностей с единицей в пределах численной погрешности. У клиента не должно быть необходимости угадывать соответствие по позиции массива. Для бинарного примера выше именованное событие решает ту же задачу.
Пример ответа — часть договора
Зафиксируйте JSON Schema или аналогичный контракт для успешного ответа и ошибок. Добавьте тесты на отсутствующее поле, строку вместо числа, пустой batch и неизвестную версию. При смене модели проверьте семантику события и калибровку вероятности; одинаковое имя поля не означает, что старый порог остаётся подходящим. Если ответ включает принятое решение, укажите и версию порога.
Для разбора самого порядка столбцов вероятностей есть отдельный пример по classes_. Здесь задача шире: превратить выход модели в стабильный договор с клиентом и понятный путь отказа.
У вероятности есть ещё практический срок жизни. Клиент может кешировать ответ или повторно отправить запрос после таймаута. Если решение зависит от быстро меняющихся признаков, укажите время расчёта и правила повторения; request_id помогает отследить дубликат. Для аудита сохраняйте событие, версию модели и порог решения вместе с ответом. Иначе позже невозможно объяснить, почему клиент получил предложение, даже если модельный артефакт сохранился.
В курсе «Data Scientist: от данных до деплоя моделей» есть inference API, тестирование API-контракта и работа с quality gates перед деплоем. Откройте программу курса, если хотите пройти весь путь от predict_proba до проверенного ответа сервиса.
Продолжить на практике
- Data Scientist: от данных до деплоя моделей ↗
Практический курс по Data Science: от постановки ML-задачи и работы с данными до inference API, CI/CD и мониторинга моделей в продакшне. Вы пройдёте EDA, feature engineering, обучение и оценку моделей, experiment tracking, error analysis и deployment — чтобы собирать полноценный рабочий контур вокруг ML-решения.
