Объясните любую модель черного ящика нетехническим людям

Мотивация

Сегодня вы не можете просто подойти к своему начальнику и сказать: «Вот моя лучшая модель. Запустим в производство и будем счастливы!». Нет, сейчас это так не работает. Компании и предприятия придирчиво относятся к внедрению решений ИИ из-за их характера «черного ящика». Они требуют объяснимости модели.

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

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

Что такое значения SHAP и Шепли?

SHAP (Shapley Additive exPlanations) — это пакет Python, основанный на документе NIPS 2016 года о значениях SHAP. Предпосылка этой статьи и значения Шепли исходят из подходов в теории игр.

Один из вопросов, который часто задают в играх, заключается в том, как разделить приз в группе из n игроков с разными наборами навыков, чтобы каждый получил справедливую долю в зависимости от набора навыков? В зависимости от количества игроков, времени их присоединения к игре и их различного вклада в результат, этот тип расчета может стать ужасно сложным.

Но какое отношение теория игр имеет к машинному обучению? Что ж, мы могли бы переформулировать приведенный выше вопрос так, чтобы он звучал так: «Учитывая прогноз, как нам наиболее точно измерить вклад каждой функции?» Да, это все равно, что спрашивать о важности функций модели, но ответ, который дают значения Шепли, гораздо сложнее.

В частности, значения Шепли могут помочь вам в следующем:

  1. Глобальная интерпретируемость модели. Представьте, что вы работаете в банке и создаете модель классификации для кредитных заявок. Ваш менеджер хочет, чтобы вы объяснили, какие (и как) различные факторы влияют на решения вашей модели. Используя значения SHAP, вы можете дать конкретный ответ с подробным описанием того, какие функции приводят к большему количеству кредитов, а какие — к большему количеству отказов. Вы радуете своего менеджера, потому что теперь он может составить базовые рекомендации для будущих клиентов банка, чтобы увеличить их шансы на получение кредита. Чем больше кредитов, тем больше денег, а значит, чем счастливее менеджер, тем выше ваша зарплата.

  1. Локальная интерпретируемость — ваша модель отклоняет одну из заявок, поданных в банк несколько дней назад. Клиент утверждает, что следовал всем рекомендациям и был уверен, что получит кредит в вашем банке. Теперь вы по закону обязаны объяснить, почему ваша модель отклонила этого конкретного кандидата. Используя значения Шепли, каждый случай можно анализировать независимо, не беспокоясь о его связях с другими выборками в данных. Другими словами, у вас есть локальная интерпретируемость. Вы извлекаете значения Shapley для жалующегося клиента и показываете ему, какие части его приложения вызвали отказ. Вы доказываете их неправоту с помощью такого сюжета:

Итак, как рассчитать могущественные значения Шепли? Вот где мы начинаем использовать пакет SHAP.

Как рассчитать значения Шепли с помощью SHAP?

Точные математические детали расчета значений Шепли заслуживают отдельной статьи. Поэтому пока я буду стоять на плече гигантов и отсылать вас к их постам. Они гарантированно укрепят ваше понимание понятий (1, 2Khuyen Tran).

На практике, однако, вы редко будете обращаться к математике, лежащей в основе значений Шепли. Причина в том, что все волшебные детали красиво упакованы внутри SHAP. Итак, давайте посмотрим на наш самый первый пример.

Используя набор данных Diamonds, встроенный в Seaborn, мы будем прогнозировать цены на бриллианты, используя несколько физических измерений. Я заранее обработал набор данных и разделил его на наборы для обучения и проверки. Вот тренировочный набор:

>>> X_train.shape, X_valid.shape
((45849, 9), (8091, 9))

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

В качестве основы мы подбираем модель XGBRegressor и оцениваем производительность с помощью среднеквадратичной ошибки:

Теперь давайте, наконец, заглянем за кулисы и рассчитаем значения Шепли для тренировочного набора.

Начнем с создания объекта объяснения для нашей модели:

TreeExplainer — это особый класс SHAP, оптимизированный для работы с любой древовидной моделью в Sklearn, XGBoost, LightGBM, CatBoost и так далее. Вы можете использовать KernelExplainer для любого другого типа модели, хотя это медленнее, чем объясняющие деревья.

Этот объяснитель дерева имеет много методов, один из которых shap_values:

Как я уже сказал, вычисление значений Шепли — сложный процесс, поэтому на ЦП ушло ~22 минуты всего на 45 тысяч наблюдений. Для больших современных наборов данных с сотнями признаков и миллионами выборок расчет может занять несколько дней. Итак, мы обращаемся к графическим процессорам для расчета значений SHAP.

На данный момент поддержка графического процессора в SHAP нестабильна, но у нас есть обходной путь. Метод predict базовой модели XGBoost имеет аргумент pred_contribs, который, если установлено значение True, вычисляет значения SHAP на графических процессорах:

Обратите внимание, что LightGBM также поддерживает GPU для значений SHAP в методе predict. В CatBoost это достигается путем вызова метода get_feature_importances для модели с параметром type, равным ShapValues.

После извлечения базовой модели бустера XGBoost потребовалось всего около секунды, чтобы вычислить значения Шепли для 45 000 образцов:

>>> shap_values_xgb.shape
(45849, 10)

Но подождите — значения Shap из объяснения дерева имели девять столбцов; у этого 10! Не волнуйся; мы можем спокойно игнорировать последний столбец, так как он просто содержит термин смещения, который XGBoost добавляет по умолчанию:

Мы получили значения Шепли; что теперь? Теперь мы получаем заговор.

Глобальное значение функций с SHAP

Давайте посмотрим, какие физические параметры бриллиантов являются наиболее важными при определении цены:

Карат выделяется как движущий фактор цены бриллианта. Читая заголовок оси ниже, мы видим, что важности — это просто средние абсолютные значения Шепли для функции. Мы можем проверить это ниже:

Но это не сильно отличается от графика важности функций, который вы получите от XGBoost:

>>> xgb.plot_importance(booster_xgb);

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

Напротив, важность признаков, полученная из значений Шепли, является последовательной и заслуживающей доверия.

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

Вот как интерпретировать приведенный выше сюжет:

  1. Левая вертикальная ось обозначает имена функций, упорядоченные по степени важности сверху вниз.
  2. Горизонтальная ось представляет величину значений SHAP для прогнозов.
  3. Вертикальная правая ось представляет фактическую величину объекта в том виде, в каком он появляется в наборе данных, и окрашивает точки.

Мы видим, что чем больше карат, тем больше его влияние на модель. То же самое относится и к функции y. Объекты x и z немного сложны из-за скопления смешанных точек вокруг центра.



Изучение каждой функции с помощью графиков зависимости

Мы можем получить более глубокое представление о влиянии каждой функции на весь набор данных с помощью графиков зависимости. Давайте посмотрим на пример и объясним его позже:

Этот сюжет совпадает с тем, что мы видели в сводном сюжете ранее. По мере увеличения карата его значение SHAP увеличивается. Изменив параметр interaction_index на auto, мы можем раскрасить точки с признаком, наиболее сильно взаимодействующим с каратом:

Кажется, что карат взаимодействует с чистотой бриллиантов гораздо сильнее, чем с другими характеристиками.

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

Этот сюжет также идет рука об руку с итоговым сюжетом. Последние цветовые категории негативно влияют на цены при взаимодействии с каратом.

Я позволю вам изучить графики зависимости для других функций ниже:

Взаимодействия функций со значениями Шепли

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

Мы можем пойти еще дальше и найти все взаимодействия функций, упорядоченные по их силе взаимодействия. Для этого нам нужен другой набор значений — значения взаимодействия SHAP.

Их можно вычислить с помощью shap_interaction_values объекта объяснения дерева следующим образом:

но это требует еще больше времени, чем обычные значения SHAP. Итак, мы снова обратимся к графическим процессорам с XGBoost:

Установив для pred_interactions значение True, мы получаем значения взаимодействия SHAP всего за 15 секунд. Это трехмерный массив, в котором последние оси столбцов являются элементами смещения:

Теперь у нас есть взаимодействия; что мы делаем? Откровенно говоря, даже в документации SHAP не описывается разумный вариант использования для взаимодействий, но мы получаем помощь от других. В частности, мы будем использовать функцию, которую я узнал от 4x Kaggle Grandmaster Bojan Tunguz, чтобы найти наиболее существенные взаимодействия функций в нашем наборе данных и построить их:

Теперь top_10_inter_feats содержит 10 самых сильных взаимодействий между всеми возможными парами признаков:

Мы можем создать еще одну функцию, которая отображает эти пары на основе их силы взаимодействия:

Как мы видим, взаимодействие между y и carat намного сильнее, чем у других. Хотя этот график ничего для нас не значит, эксперт в предметной области может расшифровать эту взаимосвязь и использовать ее для лучшей диагностики модели.

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

Локальная интерпретируемость

Наконец, мы добираемся до раздела локальной интерпретируемости. Все дело в объяснении, почему модель попала в то или иное решение для выборки.

Давайте выберем случайный бриллиант и его прогнозируемую цену, чтобы объяснить:

Хорошо, похоже, мы будем смотреть на 6559-й бриллиант в обучающих данных. Давайте начнем:

Сначала мы пересчитываем значения SHAP, используя объект объяснения. Это отличается от функции shap_values, потому что на этот раз значения Шепли возвращаются с несколькими дополнительными свойствами, которые нам нужны для локальной интерпретации:

>>> type(shap_explainer_values)
shap._explanation.Explanation

Теперь давайте объясним случайный алмаз, который мы выбрали, с помощью графика водопада:

>>> shap.waterfall_plot(shap_explainer_values[6559])

E[f(x)] = 3287.856 — это средний прогноз цен на бриллианты для поезда, т.е. preds.mean(). f(x) = 3214.05 — это прогнозируемая цена бриллианта.

Тонкая линия посередине обозначает средний прогноз. Вертикальная ось показывает значения характеристик 6559-го бриллианта. Столбцы показывают, как каждое свойство функции сместило цену от среднего прогноза. Красные полосы представляют положительные сдвиги; синие полосы представляют отрицательные сдвиги.

Давайте посмотрим на другой бриллиант для полноты картины:

>>> shap.waterfall_plot(shap_explainer_values[4652])

Этот бриллиант намного дешевле предыдущего, в основном потому, что его карат намного ниже, как видно выше.

Есть еще один сюжет, объясняющий локальную интерпретируемость. SHAP называет это силовым заговором, и выглядит это так:

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

>>> shap.force_plot(shap_explainer_values[6559])

Краткое содержание

Теперь вы можете подойти к своему начальнику и сказать: «Вот моя лучшая модель, и вот объяснение, почему она лучшая и как она работает!» Надеемся, что ответ, который вы получите, будет гораздо более положительным. Спасибо за чтение!

Понравилась эта статья и, скажем прямо, ее причудливый стиль написания? Представьте себе, что у вас есть доступ к десяткам таких же, написанных блестящим, обаятельным, остроумным автором (кстати, это я :).

Всего за 4,99 $ членства вы получите доступ не только к моим историям, но и к сокровищнице знаний от лучших и самых ярких умов на Medium. А если вы воспользуетесь моей реферальной ссылкой, то получите мою сверхновую благодарность и виртуальную пятерку за поддержку моей работы.