Модели машинного обучения и их интерпретация#
Материалы для самостоятельного изучения Деревья решений (Decision Trees) и Ансамблевые методы: Градиентный бустинг, случайные леса, бэггинг, метод голосования, стекинг, Как интерпретировать предсказания моделей в SHAP
Что такое SHAP#
SHAP (SHapley Additive exPlanations) — это теоретико-игровой подход к объяснению результатов работы любой модели машинного обучения. Он связывает оптимальное распределение кредитов с локальными объяснениями, используя классические значения Шепли из теории игр и связанные с ними расширения.
В библиотеке SHAP для оценки важности фичей рассчитываются значения Шэпли (по имени американского математика и названа библиотека).
Для оценки важности фичи происходит оценка предсказаний модели с и без данной фичи.
SHAP можно установить из PyPI
pip install shap
Пример загрузки пользовательской древовидной модели в SHAP#
В этом разделе показано, как передать пользовательскую древовидную модель в SHAP для объяснения.
import graphviz
import numpy as np
import scipy
import sklearn
import shap
Простая регрессионная древовидная модель#
Здесь мы определяем простое дерево регрессии, а затем загружаем его в SHAP как пользовательскую модель.
X, y = shap.datasets.adult()
orig_model = sklearn.tree.DecisionTreeRegressor(max_depth=2)
orig_model.fit(X, y)
dot_data = sklearn.tree.export_graphviz(orig_model, out_file=None, filled=True, rounded=True, special_characters=True)
graph = graphviz.Source(dot_data)
graph
Дополнительную информацию о том, что именно означают эти атрибуты, можно найти в документации scikit-learn
# Извлекаем массивы, описывающие структуру дерева
children_left = orig_model.tree_.children_left
children_right = orig_model.tree_.children_right
children_default = children_right.copy() # в scikit-learn отсутствует обработка пропущенных значений, поэтому используем правое поддерево по умолчанию
features = orig_model.tree_.feature
thresholds = orig_model.tree_.threshold
values = orig_model.tree_.value.reshape(orig_model.tree_.value.shape[0], 1)
node_sample_weight = orig_model.tree_.weighted_n_node_samples
print(" children_left", children_left) # отрицательное значение означает, что узел является листом
print(" children_right", children_right)
print(" children_default", children_default)
print(" features", features)
print(" thresholds", thresholds.round(3)) # значение -2 указывает, что узел является листом
print(" values", values.round(3))
print("node_sample_weight", node_sample_weight)
# Определение пользовательской модели дерева в виде словаря
tree_dict = {
"children_left": children_left, # индексы левых дочерних узлов
"children_right": children_right, # индексы правых дочерних узлов
"children_default": children_default, # индексы дочерних узлов по умолчанию (в sklearn равны правым, так как отсутствуют пропущенные значения)
"features": features, # индекс признака, по которому происходит разбиение в каждом узле
"thresholds": thresholds, # пороговое значение для разбиения
"values": values, # значения, предсказываемые в узлах (например, средние значения по классам)
"node_sample_weight": node_sample_weight, # количество (взвешенных) объектов, попавших в каждый узел
}
# Представление модели в виде словаря, содержащего одно дерево
model = {"trees": [tree_dict]}
explainer = shap.TreeExplainer(model)
# Убедимся, что интерпретатор SHAP (модель TreeEnsemble) выдаёт те же предсказания,
# что и исходная модель
assert np.abs(explainer.model.predict(X) - orig_model.predict(X)).max() < 1e-4
# Убедимся, что сумма SHAP-значений даёт точное значение прогноза модели
# (свойство локальной точности: сумма вкладов признаков плюс базовое значение равна предсказанию)
assert np.abs(explainer.expected_value + explainer.shap_values(X).sum(1) - orig_model.predict(X)).max()
Объяснение потери древовидной модели#
Интерпретация функции потерь модели может оказаться весьма полезной как для отладки, так и для мониторинга качества обучения.
В этом блокноте приводится простой пример того, как работает такой подход.
Обратите внимание: объяснение функции потерь требует передачи истинных меток (
labels
) и поддерживается только при использовании параметраfeature_perturbation="independent"
вTreeExplainer
.
Полное описание метода будет представлено позже, вместе с более подробным примером.
import numpy as np
import xgboost
import shap
Обучение классификатор XGBoost#
X, y = shap.datasets.adult()
model = xgboost.XGBClassifier()
model.fit(X, y)
model_loss = -np.log(model.predict_proba(X)[:, 1]) * y + -np.log(model.predict_proba(X)[:, 0]) * (1 - y)
model_loss[:10]
Объясние логарифмической потери модели с помощью TreeExplainer#
Обратите внимание:
expected_value
для функции потерь модели зависит от значения метки (label
),
поэтому вместо одного числового значения это теперь функция, возвращающая значение в зависимости от конкретной метки.
explainer = shap.TreeExplainer(model, X, feature_perturbation="interventional", model_output="log_loss")
explainer.shap_values(X.iloc[:10, :], y[:10]).sum(1) + np.array([explainer.expected_value(v) for v in y[:10]])
Вывод
array([8.43887488e-04, 2.47898585e-01, 1.17997435e-02, 7.11527711e-02,
6.41849874e-01, 1.76084475e+00, 5.70285151e-03, 8.60033255e-01,
4.78233521e-04, 6.43796897e-03])
Сводная гистограмма показывает общую значимость каждого признака#
shap_values = shap.TreeExplainer(model).shap_values(X_test)
shap_interaction_values = shap.TreeExplainer(model).shap_interaction_values(X_test)
shap.summary_plot(shap_values, X_test, plot_type="bar")
Сводный график показывает глобальную значимость каждой характеристики и распределение величин эффекта#
Сводный график показывает глобальную значимость каждой характеристики и распределение величин эффекта
shap.summary_plot(shap_values, X_test)

Еще несколько примеров визуализации#
Код из примера на главной странице с использованием XGBoost
.
import xgboost
import shap
# Загрузка данных по ценам на жильё в Калифорнии
X, y = shap.datasets.california()
# Обучение модели XGBoost на этих данных
model = xgboost.XGBRegressor().fit(X, y)
# Построение интерпретатора SHAP для модели
# (тот же синтаксис работает для моделей LightGBM, CatBoost, scikit-learn, HuggingFace, Spark и др.)
explainer = shap.Explainer(model)
# Расчёт SHAP-значений для всех объектов в выборке
shap_values = explainer(X)
# Визуализация объяснения для первого объекта (waterfall-график)
shap.plots.waterfall(shap_values[0])
shap.plots.initjs()
# визуализируем объяснение первого прогноза
shap.plots.force(shap_values[0])
# визуализируем все прогнозы по обучающей выборке
shap.plots.force(shap_values[:500])

# создаём точечную диаграмму зависимости, чтобы показать влияние одного признака на весь набор данных
shap.plots.scatter(shap_values[:, "Latitude"], color=shap_values)

#суммируем влияние всех признаков
shap.plots.beeswarm(shap_values)

shap.plots.bar(shap_values)