SHAP отвечает на один конкретный вопрос: почему модель выдала именно такое предсказание для именно этого объекта. Он берёт разницу между предсказанием для объекта и средним предсказанием по всей выборке и раскладывает эту разницу по признакам — сколько «накинул» возраст клиента, сколько отняла давность последнего заказа, сколько добавили три обращения в поддержку. Раскладка не на глаз, а по значениям Шепли из кооперативной теории игр: это единственный способ поделить общий вклад между признаками так, чтобы дележ удовлетворял набору условий справедливости. Ниже — зачем это нужно аналитику, как читать графики и в каком месте SHAP тихо начинает врать.
Зачем объяснять модель — себе и бизнесу?
У объяснимости два заказчика, и оба реальны.
Первый — бизнес. Модель помечает клиента как «уйдёт в ближайший месяц», и менеджер по удержанию тут же спрашивает: почему он? Без ответа прогноз бесполезен — непонятно, что делать: звонить, давать скидку или не трогать. В скоринге и антифроде всё жёстче: отказ по кредиту или блокировку счёта нужно уметь обосновать, иначе к вам придут и клиент, и регулятор. «Так решила модель» — не обоснование.
Второй заказчик — вы сами. Прежде чем катить модель в прод, хочется понять, на что она вообще опирается. У меня был случай: градиентный бустинг показывал подозрительно высокий, почти идеальный ROC-AUC на, казалось бы, сложной задаче оттока. SHAP за минуту показал, что почти весь сигнал держится на одном признаке — а он, как выяснилось, считался уже после факта оттока. Классическая утечка. Без разложения по признакам я бы выкатил в прод модель, которая на новых данных развалилась бы в ноль. Объяснимость — это ещё и способ поймать себя за руку. Похожие разборы моделей регулярно всплывают в реальных кейсах аналитиков.
Что такое SHAP и при чём тут теория игр?
Представьте, что признаки — это игроки, которые сообща играют в игру, а «выигрыш» — это предсказание модели, точнее его отклонение от базового уровня (среднего предсказания). Вопрос: как честно поделить общий выигрыш между игроками, учитывая, что вклад каждого зависит от того, с кем он играет в команде?
Ровно эту задачу в начале 1950-х решил Ллойд Шепли. Он предложил единственное распределение, которое удовлетворяет нескольким естественным условиям: сумма долей равна общему выигрышу; два игрока с одинаковым вкладом получают поровну; игрок, который ничего не добавляет, получает ноль; вклады складываются между независимыми играми. Доля каждого игрока называется значением Шепли.
SHAP (SHapley Additive exPlanations) переносит это на машинное обучение один в один: игроки — признаки, выигрыш — насколько предсказание для объекта отклонилось от среднего. Значение SHAP признака — это его честная доля в этом отклонении. Положительная толкает предсказание вверх, отрицательная — вниз.
Как SHAP считает «справедливый» вклад?
Идея — маржинальный вклад. Берём признак, скажем «число обращений в поддержку», и смотрим, насколько меняется предсказание, если добавить этот признак к какому-то набору уже известных. Загвоздка в том, что вклад зависит от того, что мы уже знаем: если модель уже видела «давность заказа» и «сумму чека», добавка от обращений будет одна; если стартуем с нуля — другая.
Поэтому значение Шепли усредняет маржинальный вклад по всем возможным порядкам добавления признаков. Это честно, но дорого: для n признаков нужно перебрать порядка 2 в степени n подмножеств. Для десятка признаков ещё терпимо, для сотни — нет. На практике используют быстрые приближения: TreeSHAP считает значения точно и быстро для деревьев и их ансамблей (случайный лес, бустинги), KernelSHAP приближённо оценивает любую модель, включая нейросети.
Одно свойство стоит запомнить намертво, потому что на нём держится всё чтение графиков: базовое значение плюс сумма всех SHAP-вкладов в точности равны выходу модели для объекта. Это и есть «аддитивность» из названия — разложение ничего не теряет и не придумывает.
Чем SHAP лучше обычной feature importance?
Встроенная важность (feature_importances_ в sklearn, gain в бустингах) — это одно число на признак на всю модель. У неё три беды. Она глобальная: не скажет, почему конкретный клиент получил высокий риск. Она без знака: вы знаете, что признак важен, но не знаете, толкает большое значение прогноз вверх или вниз. И она смещена — деревья охотнее «важничают» на признаках с большим числом уникальных значений (непрерывные, id-подобные), даже если реального сигнала там немного.
Permutation importance честнее: перемешиваем колонку, смотрим, насколько просела метрика. Но она тоже глобальная и заметно врёт, когда признаки скоррелированы.
SHAP закрывает обе дырки сразу. Он даёт значение на каждый объект (локально) и агрегируется до глобальной картины. Он со знаком — видно направление. И он согласован: если поменять модель так, что признак реально стал важнее, его SHAP не может уменьшиться (для встроенной важности это, как ни странно, не гарантировано). По сути один метод отвечает и на вопрос «на что модель опирается вообще», и на «почему вот этот объект».
Глобальная важность или локальное объяснение — когда что нужно?
Глобальный взгляд (summary plot, beeswarm, бар со средним модулем SHAP) отвечает на вопрос «какова логика модели в целом». Он нужен на валидации, при отборе признаков и в разговоре со стейкхолдерами: показать, что модель оттока опирается на давность заказов и активность, а не на цвет аватарки.
Локальное объяснение (waterfall, force plot) отвечает на «почему вот этот». Оно нужно там, где решение касается одного человека: спорный отказ, ручная модерация, карточка клиента у менеджера. Пример из практики: глобально модель оттока держится на recency, но для пользователя 42 главным драйвером оказался всплеск обращений в поддержку за неделю — и это меняет сценарий удержания. Глобальная картина этого не покажет, а локальная — сразу.
Красота SHAP в том, что оба взгляда — из одного расчёта: усредните модуль локальных значений по объектам и получите глобальную важность.
Как посчитать SHAP на Python?
Возьмём модель оттока на табличке пользователей. Признаки — обычные: давность последнего заказа, число заказов за 30 дней, средний чек, обращения в поддержку, доля покупок по скидке.
import shap
from sklearn.ensemble import GradientBoostingClassifier
features = ['days_since_last_order', 'orders_30d', 'avg_check',
'support_tickets', 'discount_share']
X = df[features]
y = df['churned']
model = GradientBoostingClassifier().fit(X, y)
explainer = shap.TreeExplainer(model)
sv = explainer(X) # объект Explanation: вклады для каждого объекта
shap.plots.beeswarm(sv) # глобальная картина по всем пользователям
shap.plots.waterfall(sv[42]) # разбор одного пользователя
Проверить аддитивность — то самое свойство — можно руками:
row = 42
print(sv.base_values[row] + sv.values[row].sum())
print(model.decision_function(X.iloc[[row]])[0])
# оба числа совпадут: базовый уровень + сумма вкладов = raw-выход модели
# (для классификатора это логиты, а не вероятность — не пугайтесь)
Пять строк до первого графика. Модель менять не нужно — SHAP надстраивается сверху над уже обученной. Погонять этот код вживую удобно в Python-песочнице, а синтаксис pandas и sklearn под рукой — в справочнике по Python.
Как читать summary plot и waterfall?
Beeswarm (он же summary plot) — самый плотный по смыслу график. Каждая точка — один объект. По вертикали признаки, отсортированные по важности сверху вниз. По горизонтали — значение SHAP: точка левее нуля тянет предсказание вниз, правее — вверх. Цвет точки — величина самого признака: красный высокий, синий низкий. Отсюда читается сразу три вещи: какой признак важен, в какую сторону он двигает прогноз и как это зависит от его значения. Если красные точки «давности заказа» кучкуются справа — значит, чем дольше клиент не заказывал, тем выше риск оттока. Ровно то, что ждёшь.
Waterfall — про один объект. Он стартует от базового значения (среднего прогноза) и по одному прибавляет вклады признаков: красные бары толкают вверх, синие вниз, пока не приходим к финальному предсказанию. Читается как история: «база низкая, обращения в поддержку сильно вверх, редкие заказы ещё вверх, высокий чек чуть вниз — итого высокий риск». Это ровно тот текст, который можно отдать менеджеру без перевода.
Почему SHAP может обмануть на коррелированных признаках?
Здесь начинается взрослая часть. SHAP не волшебный, и у него есть три места, где легко сделать неверный вывод.
Первое — скоррелированные признаки делят вклад между собой. Если «средний чек» и «сумма последнего заказа» несут почти один сигнал, SHAP размажет заслугу на двоих, и каждый по отдельности будет выглядеть слабее, чем реальный сигнал под ними. Легко недооценить фактор, который на самом деле решающий, просто потому что у него есть двойник.
Второе тоньше. Чтобы посчитать маржинальный вклад, SHAP вынужден спрашивать модель о комбинациях признаков, которых в реальности не бывает: например, «высокий доход при нулевых тратах», когда в данных эти вещи жёстко связаны. На таких несуществующих сочетаниях модель выдаёт что попало — она их не видела, — и это просачивается в объяснение. У TreeSHAP есть два режима: path-dependent (по умолчанию, учитывает корреляции внутри дерева) и interventional (нужен фоновый набор данных). Они дают разные значения на скоррелированных данных, и это не баг — это два разных вопроса. Если объяснения выглядят странно, первым делом проверьте, какой режим включён.
# interventional-режим: явно задаём фон, по которому усредняем
explainer = shap.TreeExplainer(
model, data=X.sample(200), feature_perturbation="interventional")
Третье, и самое важное для разговора с бизнесом: SHAP не про причинность. Большой положительный вклад «доли покупок по скидке» не значит, что скидки удерживают клиентов. Это ассоциация внутри модели, а не утверждение о мире: возможно, скидками пользуются как раз лояльные, а не наоборот. Как только со стола звучит «а давайте раздадим скидки, SHAP же говорит», нужно останавливаться и идти в честный эксперимент или причинный вывод. SHAP объясняет модель, а не реальность.
С чего начать, если хочется применять SHAP в работе
Минимальный путь: возьмите любую свою обученную табличную модель, поставьте shap, постройте beeswarm и один waterfall на спорном объекте. Уже это ловит утечки и даёт язык для разговора с продуктом. Дальше — научитесь читать взаимодействия признаков (dependence plot) и разберитесь с interventional-режимом на своих коррелированных данных.
Умение объяснять модель, а не только тренировать её, — это ровно та граница, за которой аналитик становится дороже; на страницах вилок и грейдов интерпретируемость всё чаще пишут прямо в требованиях к middle+. Такие вопросы любят задавать на собеседованиях — потренироваться можно на подборке вопросов для аналитиков, в практических задачах, а перед конкретным собесом собрать план по гайдам подготовки.
Если хочется решать такие задачи каждый день с проверкой в один клик — в Pro открыты все Python-задачи, кейсы и разборы. Но SHAP на своей модели можно попробовать бесплатно уже сегодня: пять строк кода — и вы впервые увидите, что у модели внутри.
Связанная тема — Feature engineering для аналитика: примеры признаков, которые работают.