Машинное обучение

Благодаря SHAP модель машинного обучения больше не черный ящик

Пошаговое руководство по Python, чтобы показать, как ваша модель машинного обучения работает внутри.

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

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

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

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

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

В этой статье я представлю значения SHAP, которые являются одним из самых популярных методов объяснения модели. Я также рассмотрю пример, чтобы показать, как использовать значения SHAP для получения информации.

Статья организована следующим образом:

  • Обзор SHAP
  • Практический пример на Python

1 Обзор SHAP

SHAP расшифровывается как «Аддитивные объяснения Шепли». Значения Шепли — это широко используемый подход теории кооперативных игр.

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

Алгоритм SHAP был впервые опубликован в 2017 году Лундбергом и Ли в статье под названием Унифицированный подход к интерпретации модельных прогнозов (статья имеет почти 5500 цитат, учитывая ее важность).

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

Чтобы иметь дело со значениями SHAP в Python, вы можете установить пакет shap:

pip3 install shap

Значения SHAP можно рассчитать для различных библиотек Python, включая Scikit-learn, XGBoost, LightGBM, CatBoost и Pyspark. Полная документация пакета shap доступна по этой ссылке.

2 Практический пример на Python

В качестве практического примера я использую хорошо известный набор данных о диабете, предоставленный пакетом scikit-learn. Описание набора данных доступно по этой ссылке. Я тестирую следующие алгоритмы:

  • DummyRegressor
  • LinearRegressor
  • SGDRegressor.

Для каждой тестируемой модели я создаю модель, обучаю ее и предсказываю новые значения, заданные тестовым набором. Затем я вычисляю среднеквадратичную ошибку (MSE), чтобы проверить его производительность. Наконец, я вычисляю и наношу значения SHAP.

2.1 Загрузить набор данных

Во-первых, я загружаю набор данных о диабете:

from sklearn.datasets import load_diabetes
data = load_diabetes(as_frame=True)
X = data.data
y = data.target

и я разделил его на тренировочный и тестовый наборы:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)

Целью этого сценария является вычисление значения уровня глюкозы в крови (значение y) по некоторым входным параметрам, включая индекс массы тела (ИМТ), давление тела (АД) и другие подобные параметры. Входные функции уже нормализованы. Это типичная проблема регрессии.

2.2 Фиктивный регрессор

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

Dummy Regressor можно использовать для сравнения, т. е. проверить, улучшает ли модель машинного обучения производительность по отношению к ней.

from sklearn.dummy import DummyRegressor
model = DummyRegressor()
model.fit(X_train, y_train)

Я рассчитываю MSE для модели:

y_pred = model.predict(X_test)
print("Mean squared error: %.2f" % mean_squared_error(y_test, y_pred))

выход 5755,47.

Теперь я могу рассчитать значение SHAP для этой базовой модели. Я создаю общий Explainer с моделью и обучающим набором, а затем вычисляю значения SHAP для набора данных, которые могут отличаться от обучающего набора. В моем примере я рассчитываю значения SHAP для тренировочного набора.

import shap
explainer = shap.Explainer(model.predict, X_train)
shap_values = explainer(X_train)

Обратите внимание, что Explainer может получать на вход саму модель или функцию model.predict, в зависимости от типа модели.

Библиотека shap предоставляет различные функции для построения значений SHAP, в том числе следующие:

  • summary_plot() — показать вклад каждого признака в значения SHAP;
  • scatter() — показать график разброса значений SHAP по каждому входному объекту;
  • plots.force() — интерактивный график для всех наборов данных;
  • plots.waterfall() — показать, как строится значение SHAP для отдельных данных.

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

shap.initjs()

Во-первых, я рисую точечную диаграмму:

shap.plots.scatter(shap_values, color=shap_values)

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

Теперь я рисую сводную диаграмму:

shap.summary_plot(shap_values, X_train, plot_type='bar')

Интересно отметить, что в манекене самым влиятельным фактором является возраст.

Наконец, я строю график force(). Следующий рисунок является статическим, но если вы начертите его в блокноте, он станет интерактивным:

Очевидно, что для каждой выборки в наборе данных (ось x) каждый признак дает одинаковый вклад в прогнозируемое значение (ось y).

2.3 Линейный регрессор

Dummy Regressor не кажется лучшей моделью, потому что он достигает MSE = 5755,47. Я пробую линейный регрессор в надежде, что он даст лучшую производительность.

from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("Mean squared error: %.2f" % mean_squared_error(y_test, y_pred))

Линейный регрессор имеет MSE = 2817,80, что лучше, чем у фиктивной модели.

Я создаю Explainer, который получает модель в качестве входных данных:

import shap
explainer = shap.Explainer(model, X_train)
shap_values = explainer(X_train)

Теперь я строю сводный сюжет:

shap.summary_plot(shap_values, X_train, plot_type='bar')

В данном случае наиболее влиятельным признаком является s1 (в фиктивной модели наиболее влиятельным признаком был возраст). Сводный график можно построить и без указания параметра plot_type:

shap.summary_plot(shap_values, X_train)

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

Я рисую каскадный график для выборки 0 в тренировочном наборе:

shap.plots.waterfall(shap_values[2])

На предыдущем графике показано, как окончательный прогноз f(x) строится на основе базового значения E[f(x)]. Красные столбцы дают положительный вклад, синие – отрицательный.

Могу показать предыдущий график для всех образцов, через force() график:

Что касается того же графика, построенного для фиктивного регрессора (который является плоским), для линейного регрессора вклад каждой функции в конечный результат зависит от отдельной выборки.

Наконец, я строю график рассеяния:

shap.plots.scatter(shap_values, color=shap_values)

2.4 Регрессор SGD

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

from sklearn.model_selection import GridSearchCV
parameters = {
    'penalty' : ('l2', 'l1', 'elasticnet')
}
model = SGDRegressor(max_iter=100000)
clf = GridSearchCV(model, parameters)
clf.fit(X_train, y_train)

Я рассчитываю MSE:

model = clf.best_estimator_
y_pred = model.predict(X_test)
print("Mean squared error: %.2f" % mean_squared_error(y_test, y_pred))

Модель достигает MSE = 2784,27, что лучше, чем у предыдущих моделей.

На следующем рисунке показан сводный график:

На предыдущем графике показано, что функция ИМТ является наиболее влиятельным параметром для классификатора SGD.

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

Поздравляем! Вы только что узнали, как изменить модель машинного обучения с помощью значения SHAP! Значение SHAP указывает, как каждая функция влияет на итоговое прогнозируемое значение.

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

Рекомендации

Статьи по Теме