Как рассчитать глобальную информацию для ваших объектов и использовать ее для процедур независимого выбора объектовx

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

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

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

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

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

Все коды из этого поста доступны в блокноте на Kaggle, а также на моем Github.

Краткий обзор SHAP

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

Когда мы получаем значения SHAP из пары модель + набор данных, мы получаем матрицу NxM, где N — количество экземпляров вашего набора данных, а M — количество признаков. У нас та же форма, что и у исходного набора данных.

Каждая запись i,j в этой матрице обозначает влияние функции j на прогноз экземпляра i. Это позволяет нам объяснить на локальном уровне, как наша модель делает свои прогнозы на основе признаков.

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

Это базовая структура SHAP, которую мы собираемся использовать.

Другой очень важной структурой являются векторы взаимодействия SHAP.

Векторы взаимодействия SHAP

Вектор взаимодействия SHAP между двумя функциями определяет взаимодействие между этими функциями в прогнозах. Чтобы вычислить его, значение SHAP для признака i вычисляется, когда j присутствует и когда j отсутствует. Перестановка его для всех возможностей генерирует вектор взаимодействия.

Если мы определим вектор SHAP для признака i как вектор, содержащий значения SHAP признака i для каждой выборки как pi, то мы знаем, что:

Где pij — вектор взаимодействия SHAP между функциями i и j.

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

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

Синергия

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

Геометрическая интерпретация этого заключается в том, что мы можем создать вектор синергии с проекцией вектора pi на вектор pij, что можно перевести как уравнение:

Визуально получаем:

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

И поскольку мы определили этот вектор, мы, по сути, говорим: учитывая предсказательную силу моего признака i, какая часть этого исходит от взаимодействия с признаком j? Что ж, если мы ответим на этот вопрос, то поймем, какая часть предсказательной силы исходит не из этого.

Имея это в виду, мы можем определить вектор автономии между этими двумя функциями как вычитание вектора:

Геометрически мы можем видеть, что сумма синергии (то, что происходит от взаимодействия) и автономии (то, что не происходит) будет суммироваться с исходным вектором признаков:

Избыточность

Интуитивно понятно, что избыточность представляет собой количество информации от функции i, которая реплицируется на функцию j. Две совершенно избыточные функции будут иметь абсолютно одинаковую информацию. Одним из примеров этого является температура в градусах Кельвина и Цельсия.

Мы можем измерить, сколько информации совместно используется, спроецировав вектор автономии от i до j в вектор автономии от j до i. Это переводится алгебраически как:

Визуально имеем следующую картину. Обратите внимание, что вектор aji принадлежит другой плоскости по отношению к векторам aij и pij:

И затем, как мы сделали с синергией, мы можем сгенерировать одно значение из этого вектора, рассчитав длину проекции:

Независимость

Наконец, мы определим последнюю часть информации: учитывая информацию в i, которая не является синергетической или избыточной с информацией в признаке j, мы получаем независимость от признака i.

Это можно рассчитать как разницу между автономностью и избыточностью между функциями:

И, как мы сделали с другими функциями, мы можем вычислить скалярное значение, вычислив длину проекции этого вектора на число пи:

Кодирование функций

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

В этом примере мы будем использовать набор данных Wine из репозитория UCI, который можно использовать бесплатно, взяв его из функции в пакете sklearn. Затем мы применим классификатор случайного леса.

Во-первых, давайте импортируем необходимые библиотеки:

import shap
import numpy as np
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier

Теперь давайте подгоним классификатор к нашему набору данных:

# Get the dataset and fit a Random Forest on it
X, y = load_wine(return_X_y=True, as_frame=True)
rf = RandomForestClassifier()
rf.fit(X, y)

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

# Runs the explainer on the model and the dataset to grab the Shap Values
explainer = shap.Explainer(rf)
shap_values = explainer(X)

Библиотека SHAP возвращает три матрицы, когда мы выполняем приведенный выше код, поэтому мы выберем матрицу SHAP:

# The return of the explainer has three matrices, we will get the shap values one
shap_values = shap_values.values[:, :, 0]

Теперь давайте сгенерируем значения взаимодействия для генерации векторов взаимодействия SHAP:

shap_interaction_values = explainer.shap_interaction_values(X)[0]

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

# Define matrices to be filled
    s = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
    a = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
    r = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
    i_ = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
S = np.zeros((shap_values.shape[1], shap_values.shape[1]))
R = np.zeros((shap_values.shape[1], shap_values.shape[1]))
I = np.zeros((shap_values.shape[1], shap_values.shape[1]))

Мы определили матрицу для каждого из наших векторов и одну матрицу для каждого из наших скалярных значений. Теперь давайте пройдемся по каждому из значений SHAP (представьте двойной цикл for для i и j) и выберем векторы, которые мы собираемся использовать:

# Selects the p_i vector -> Shap Values vector for feature i
pi = shap_values[:, i]
# Selects pij -> SHAP interaction vector between features i and j
pij = shap_interaction_values[:, i, j]
            
# Other required vectors
pji = shap_interaction_values[:, j, i]
pj = shap_values[:, j]

Имея это в наших руках, легко рассчитать следующие векторы, просто следуя приведенным выше уравнениям:

# Synergy vector
s[i, j] = (np.inner(pi, pij) / np.linalg.norm(pij)**2) * pij
s[j, i] = (np.inner(pj, pji) / np.linalg.norm(pji)**2) * pji
# Autonomy vector
a[i,j] = pi - s[i, j]
a[j,i] = pj - s[j, i]
# Redundancy vector
r[i,j] = (np.inner(a[i, j], a[j, i]) / np.linalg.norm(a[j, i])**2) * a[j, i]
r[j,i] = (np.inner(a[j, i], a[i, j]) / np.linalg.norm(a[i, j])**2) * a[i, j]
# Independece vector
i_[i, j] = a[i, j] - r[i, j]
i_[j, i] = a[j, i] - r[j, i]

Затем, используя уравнение расчета длины, получаем окончательные скалярные значения:

# Synergy value
S[i, j] = np.linalg.norm(s[i, j])**2 / np.linalg.norm(pi)**2
# Redundancy value
R[i, j] = np.linalg.norm(r[i, j])**2 / np.linalg.norm(pi)**2
# Independence value
I[i, j] = np.linalg.norm(i_[i, j])**2 / np.linalg.norm(pi)**2

Также существует реализация этих методов с открытым исходным кодом, предоставленная авторами статьи на Facet Library.

Предложение модели выбора функций

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

Предложение метода может быть:

  • Учитывая обученную модель в вашем наборе данных, возьмите из нее информацию SHAP.
  • Запустите расчет S-I-R
  • Если пара функций имеет избыточность больше порогового значения, отметьте их для удаления.
  • Возьмите функцию из пары, которая имеет наименьшую синергию с остальной частью набора данных, и удалите ее. Для этого вы можете использовать среднюю синергию или другую метрику.

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

[1] Иттнер и др., Синергия функций, избыточность и независимость в объяснениях глобальной модели с использованием векторной декомпозиции SHAP (2021 г.),
arXiv:2107.12436
[cs.LG]