Интерпретируемость в продакшене красива ровно до тех пор, пока ты не считаешь SHAP на batch из тысячи объектов и не видишь latency за десятки миллисекунд. При 10k+ RPS и бюджете времени меньше 10 мс пакетный SHAP или LIME просто не влезают. Главная ошибка — пытаться посчитать полные атрибуции для каждого запроса, не думая о компромиссах между точностью и скоростью.
Три подхода к online-атрибуции
Первый — TreeSHAP. Самый точный, но по сложности O(T*D*L). Для CatBoost с 500 деревьями и глубиной 8 это уже 100-200 мкс на объект. Можно кэшировать path-dependent градиенты, но это всё ещё тяжело.
Второй — Fast SHAP approximation через expected gradients или Gradient SHAP. Работает за O(T*D) — на порядок быстрее. Теряешь в точности, но для большинства production задач разница не принципиальна.
Третий — surrogate LIME. Строишь линейную модель на лету вокруг запроса на выборке из 100-200 объектов. Время O(k*T*D), можно распараллелить по строкам.
Как контролировать latency
Самый надёжный способ — адаптивный таймаут:
class OnlineAttributor:
def __init__(self, model, latency_budget_ms=5):
self.model = model
self.budget = latency_budget_ms / 1000
async def get_attribution(self, features):
shap_values = await asyncio.to_thread(
self._tree_shap, features, timeout=self.budget
)
if shap_values is None:
shap_values = await asyncio.to_thread(
self._global_importance, features
)
return shap_values
Также полезно:
* batching — группируешь запросы по 10-50 штук и считаешь SHAP векторно. Latency per item падает в разы.
* precomputed SHAP для стримовых запросов — делаешь offline-атрибуцию раз в 10 минут и кешируешь. Если фичи не дрифтят резко, этого хватает.
Типичные ошибки и trade-offs
Линейные модели вроде LIME плохо работают на нелинейностях бустинга. Для CatBoost или LightGBM лучше использовать встроенный TreeSHAP через
predict с pred_contrib=True — он дешевле и точнее.Другая распространённая ошибка — не учитывать распределение latency. При high-throughput среднее может быть 1 мс, но 95-й перцентиль — 50 мс из-за сложных объектов. Нужно закладывать таймаут на 99-й перцентиль и падать на global importance.
По моему опыту, комбинация Fast TreeSHAP с pruning, adaptive timeout и fallback на global importance даёт типичное время меньше 2 мс на объект при 100 деревьях. Без этого интерпретация на production становится узким горлышком.
Вывод: Для online-атрибуции в градиентном бустинге при high-throughput инференсе используйте Fast TreeSHAP с адаптивным таймаутом и кэшированием, а не полный SHAP на каждый запрос — это даёт баланс между точностью и latency.