Отрицательный спрос и скидка выше 100%: как проверять ограничения прогноза

Сервис планирования возвращает спрос −3 единицы. Другой расчёт предлагает скидку 117%. Тип данных правильный, JSON проходит проверку, средняя ошибка модели укладывается в прошлый отчёт. Но клиент приложения не может безопасно использовать такой ответ: одна задача требует неотрицательного количества, другая имеет верхнюю границу. Отдельно задайте точность хранения результата и единицы измерения: доля и процент должны передаваться разными согласованными полями.

У регрессии нет встроенного понимания допустимых действий бизнеса. Ограничение нужно задавать явно и оценивать вместе с моделью. Простое обрезание часто полезно, однако оно отвечает только на вопрос о границах значения. Систематическую ошибку, неверные единицы измерения и несовпадение цели обучения оно не устраняет.

Граница зависит от смысла поля

Сначала определите, что предсказывается. Число новых заказов не может быть отрицательным. Чистое изменение остатков, включающее возвраты и списания, может. Если целевой столбец смешивает оба понятия, обрезание уничтожит часть настоящих значений. Проверьте определение показателя и несколько отрицательных строк исходной истории до изменения сервиса.

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

Сравним сырой ответ и значение после ограничения

Для примера используем долю загрузки оборудования от нуля до единицы. Есть шесть измеренных фактов и шесть уже полученных предсказаний. Преобразование numpy.clip переносит значения за границей на ближайшую допустимую точку. Проверка конечности выполняется раньше: бесконечность не должна тихо стать полной загрузкой.

import numpy as np
actual = np.array([0., .1, .4, .7, .9, 1.])
raw = np.array([-.2, .2, .5, .6, 1.1, 1.3])
assert np.isfinite(actual).all() and np.isfinite(raw).all()
assert ((0 <= actual) & (actual <= 1)).all()
served = np.clip(raw, 0, 1)
changed = raw != served
print('raw_MAE', round(float(np.abs(actual-raw).mean()), 4))
print('served_MAE', round(float(np.abs(actual-served).mean()), 4))
print('changed_rows', np.flatnonzero(changed).tolist())
print('raw', raw.tolist(), 'served', served.tolist())
assert changed.sum() == 3
assert np.all(np.abs(actual-served) <= np.abs(actual-raw))
bad = np.array([float('nan'), float('inf')])
assert not np.isfinite(bad).all()

MAE уменьшается с 0,1667 до 0,0667. Это ожидаемый результат для абсолютной ошибки, когда факт действительно лежит в заданном интервале: перенос внешней точки к границе не увеличивает расстояние до факта. Такое улучшение полезно, но оно не доказывает, что модель научилась лучше понимать зависимость. Мы изменили правило формирования ответа.

Поэтому в отчёте нужны оба результата и доля изменённых строк. Если после обновления данных обрезается половина ответов, клиенты получают много одинаковых нулей и единиц. Итоговый MAE может выглядеть терпимо, а способность различать пограничные случаи исчезнет. Посмотрите распределение сырых ответов и примеры самых крупных поправок.

Ограничение должно жить в одном месте

Включите преобразование в общий inference pipeline, используемый и API, и пакетным расчётом. Если фронтенд обрезает ответ сам, а batch сохраняет исходное число, два потребителя начнут принимать разные решения по одной модели. Версию правила ограничения сохраняйте вместе с версией модели. При сравнении релизов учитывайте изменение обеих частей.

Порядок округления тоже имеет смысл. Прогноз среднего спроса 0,4 — допустимое ожидание, хотя отдельная покупка целая. Округление такого ожидания до нуля до агрегации занизит закупку по группе товаров. Сначала решите, потребителю нужен прогноз среднего, ожидаемый суммарный объём или целое количество к заказу. Последнее может требовать учёта упаковок и запаса, а не одного round.

При NaN, бесконечности или отсутствии нужной границы верните управляемую ошибку либо заранее согласованный fallback. Не подставляйте ноль автоматически: для оператора он выглядит как уверенный прогноз отсутствия нагрузки. В журнале различайте обычный ответ, ограниченный ответ и аварийную замену, чтобы мониторинг отражал источник результата.

Когда стоит поменять саму модель

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

Проверку ограничений удобно включить в приёмку релиза отдельными сценариями: нижняя граница, верхняя, значение между ними, отсутствующая граница и неконечный ответ. Кроме конечного числа сравнивайте статус обработки. Это защищает контракт при замене реализации модели. Для переменной мощности линии добавьте случай смены доступной мощности: один и тот же сырой прогноз должен ограничиваться по правилу конкретной смены, а журнал обязан сохранить использованное значение мощности.

Платный курс «ML Engineer / Data Scientist: табличные модели в продакшн» помогает пройти этот путь целиком: подготовить данные, сравнить модели, разобрать ошибки и собрать batch-инференс с простым REST API. Здесь полезен именно общий pipeline, в котором модель и подготовка ответа обновляются согласованно. Откройте программу курса и посмотрите, как выстроить такую практику в своём проекте.

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

  • ML Engineer / Data Scientist: табличные модели в продакшн ↗

    Практический курс для тех, кто хочет получить навыки ML Engineer / Data Scientist на реальных табличных задачах. В курсе Вы не просто обучите CatBoost/LightGBM, а соберёте полноценный production-style пайплайн: подготовка данных, feature engineering (генерация признаков), корректная валидация, защита от leakage (утечек), тюнинг через Optuna, интерпретация через SHAP, batch-инференс и REST API для предсказаний.