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

Мотивация
Объяснимость в ИИ имеет решающее значение для повышения доверия к прогнозам модели и крайне важна для повышения её устойчивости. Хорошая объяснимость часто выступает в качестве инструмента отладки, выявляя недостатки в процессе обучения модели. Хотя значения Шапли стали отраслевым стандартом для этой задачи, мы должны задаться вопросом: всегда ли они работают? И, что особенно важно, где они дают сбой?
Чтобы понять, в чём заключаются ошибки значений Шапли, наилучший подход — это контроль над исходными данными. Мы начнём с простой линейной модели, а затем систематически разберём объяснение. Наблюдая за тем, как значения Шапли реагируют на эти контролируемые изменения, мы сможем точно определить, где они приводят к вводящим в заблуждение результатам и как это исправить.
Игрушечная модель
Начнём с модели, включающей 100 равномерно распределенных случайных величин.
import numpy as np from sklearn.linear_model import LinearRegression import shap def get_shapley_values_linear_independent_variables( weights: np.ndarray, data: np.ndarray ) -> np.ndarray: return weights * data # Для сравнения теоретических результатов с пакетом shap def get_shap(weights: np.ndarray, data: np.ndarray): model = LinearRegression() model.coef_ = weights # Вставляем ваши веса model.intercept_ = 0 background = np.zeros((1, weights.shape[0])) explainer = shap.LinearExplainer(model, background) # Предполагает независимость всех признаков results = explainer.shap_values(data) return results DIM_SPACE = 100 np.random.seed(42) # Генерируем случайные веса и данные weights = np.random.rand(DIM_SPACE) data = np.random.rand(1, DIM_SPACE) # Устанавливаем определенные значения для проверки нашей интуиции # Признак 0: Высокий вес (10), Признак 1: Нулевой вес weights[0] = 10 weights[1] = 0 # Устанавливаем максимальное значение для первых двух признаков data[0, 0:2] = 1 shap_res = get_shapley_values_linear_independent_variables(weights, data) shap_res_pacakge = get_shap(weights, data) idx_max = shap_res.argmax() idx_min = shap_res.argmin() print( f»Ожидаемое значение: idx_max 0, idx_min 1nФактическое значение: idx_max {idx_max}, idx_min: {idx_min}» ) print(abs(shap_res_package — shap_res).max()) # Нет разницы
В этом простом примере, где все переменные независимы, расчет значительно упрощается.
Напомним, что формула Шапли основана на предельном вкладе каждого признака, то есть на разнице в результатах модели, когда переменная добавляется к совокупности известных признаков по сравнению с ситуацией, когда она отсутствует.
[ V(S∪{i}) – V(S)
[
Поскольку переменные независимы, конкретная комбинация предварительно выбранных признаков (S) не влияет на вклад признака i. Влияние предварительно выбранных и невыбранных признаков взаимно компенсируется при вычитании, не оказывая влияния на влияние признака i. Таким образом, расчет сводится к измерению предельного эффекта признака i непосредственно на выход модели:
[ W_i · X_i ]
Результат интуитивно понятен и работает как ожидалось. Поскольку отсутствует влияние других признаков, вклад зависит исключительно от веса признака и его текущего значения. Следовательно, признак с наибольшей комбинацией веса и значения является наиболее значимым. В нашем случае признак с индексом 0 имеет вес 10 и значение 1.
Давайте ломать вещи
Теперь мы рассмотрим зависимости, чтобы понять, где значения Шапли начинают давать сбои.
В этом сценарии мы искусственно создадим идеальную корреляцию, продублировав наиболее влиятельный признак (индекс 0) 100 раз. В результате получится новая модель с 200 признаками, где 100 признаков являются идентичными копиями нашего исходного наиболее значимого признака и независимы от остальных 99 признаков. Для завершения настройки мы присваиваем нулевой вес всем этим добавленным дубликатам признаков. Это гарантирует, что предсказания модели останутся неизменными. Мы изменяем только структуру входных данных, а не выходных. Хотя эта настройка кажется экстремальной, она отражает распространенный сценарий в реальном мире: взять известный важный сигнал и создать несколько производных признаков (таких как скользящие средние, запаздывания или математические преобразования), чтобы лучше уловить его информацию.
Однако, поскольку исходный объект Feature 0 и его новые копии являются идеально зависимыми, расчет Шапли изменяется.
Исходя из аксиомы симметрии: если две характеристики вносят одинаковый вклад в модель (в данном случае, неся одну и ту же информацию), то им должна быть присуждена одинаковая оценка.
Интуитивно понятно, что знание ценности любого отдельного клона раскрывает полную информацию о группе. В результате огромный вклад, который мы ранее наблюдали для отдельного признака, теперь равномерно распределяется между ним и его 100 клонами. «Сигнал» размывается , из-за чего основной фактор, определяющий работу модели, кажется гораздо менее важным, чем он есть на самом деле.
Вот соответствующий код:
import numpy as np from sklearn.linear_model import LinearRegression import shap def get_shapley_values_linear_correlated( weights: np.ndarray, data: np.ndarray ) -> np.ndarray: res = weights * data duplicated_indices = np.array( [0] + list(range(data.shape[1] — DUPLICATE_FACTOR, data.shape[1])) ) # мы суммируем эти вклады и распределяем вклад между ними full_contrib = np.sum(res[:, duplicated_indices], axis=1) duplicate_feature_factor = np.ones(data.shape[1]) duplicate_feature_factor[duplicated_indices] = 1 / (DUPLICATE_FACTOR + 1) full_contrib = np.tile(full_contrib, (DUPLICATE_FACTOR+1, 1)).T res[:, duplicated_indices] = full_contrib res *= duplicate_feature_factor return res def get_shap(weights: np.ndarray, data: np.ndarray): model = LinearRegression() model.coef_ = weights # Введите свои веса model.intercept_ = 0 explainer = shap.LinearExplainer(model, data, feature_perturbation=»correlation_dependent») results = explainer.shap_values(data) return results DIM_SPACE = 100 DUPLICATE_FACTOR = 100 np.random.seed(42) weights = np.random.rand(DIM_SPACE) weights[0] = 10 weights[1] = 0 data = np.random.rand(10000, DIM_SPACE) data[0, 0:2] = 1 # Дублируем признак 0 100 раз: dup_data = np.tile(data[:, 0], (DUPLICATE_FACTOR, 1)).T data = np.concatenate((data, dup_data), axis=1) # Мы присвоим нулевой вес всем добавленным признакам: weights = np.concatenate((weights, np.tile(0, (DUPLICATE_FACTOR)))) shap_res = get_shapley_values_linear_correlated(weights, data) shap_res = shap_res[0, :] # Берем первую запись для проверки результатов idx_max = shap_res.argmax() idx_min = shap_res.argmin() print(f»Ожидаемое значение: idx_max 0, idx_min 1nФактическое значение: idx_max {idx_max}, idx_min: {idx_min}»)
Это явно не то, что мы хотели, и не дает хорошего объяснения поведению модели. В идеале мы хотим, чтобы объяснение отражало истинную картину: признак 0 является основным фактором (с весом 10), в то время как дублированные признаки (индексы 101–200) являются просто избыточными копиями с нулевым весом. Вместо того чтобы размывать сигнал по всем копиям, мы бы явно предпочли такое объяснение, которое бы подчеркивало истинный источник сигнала.
Примечание: Если вы запустите этот код с помощью пакета Python shap, вы можете заметить, что результаты похожи, но не идентичны нашим ручным расчетам. Это связано с тем, что вычисление значений Шапли вычислительно нецелесообразно. Поэтому такие библиотеки, как shap, используют методы аппроксимации, которые вносят незначительную погрешность.

Мы можем это исправить?
Поскольку корреляция и зависимости между признаками встречаются крайне часто, мы не можем игнорировать эту проблему.
С одной стороны, значения Шапли действительно учитывают эти зависимости. Признак с коэффициентом 0 в линейной модели, не оказывающий прямого влияния на выходные данные, получает ненулевой вклад, поскольку содержит информацию, общую с другими признаками. Однако такое поведение, обусловленное аксиомой симметрии, не всегда является тем, что нам нужно для практической объяснимости. Хотя «справедливое» распределение заслуг между коррелированными признаками математически обосновано, оно часто скрывает истинные движущие силы модели.
Для решения этой задачи существует несколько методов, и мы их рассмотрим.
Функции группировки
Этот подход особенно важен для моделей с многомерным пространством признаков, где корреляция признаков неизбежна. В таких условиях попытка приписать конкретный вклад каждой отдельной переменной часто приводит к шуму и вычислительной нестабильности. Вместо этого мы можем объединить похожие признаки, представляющие одно и то же понятие, в одну группу. Полезная аналогия — из классификации изображений: если мы хотим объяснить, почему модель предсказывает «кошку», а не «собаку», изучение отдельных пикселей не имеет смысла. Однако, если мы сгруппируем пиксели в «фрагменты» (например, уши, хвост), объяснение сразу становится понятным. Применяя ту же логику к табличным данным, мы можем рассчитать вклад группы, а не произвольно разделять ее между компонентами.
Этого можно достичь двумя способами: простым суммированием значений Шапли внутри каждой группы или прямым вычислением вклада группы. При прямом методе мы рассматриваем группу как единое целое. Вместо переключения отдельных признаков мы рассматриваем наличие и отсутствие группы как одновременное наличие или отсутствие всех признаков внутри нее. Это уменьшает размерность задачи, делая оценку быстрее, точнее и стабильнее.

Победитель забирает всё
Хотя группировка эффективна, она имеет ограничения. Она требует предварительного определения групп и часто игнорирует корреляции между этими группами.
Это приводит к «избыточности объяснений». Возвращаясь к нашему примеру, если 101 клонированный признак не сгруппирован предварительно, выходные данные будут повторять эти 101 признак с одинаковым вкладом 101 раз. Это чрезмерно, повторяющеся и функционально бесполезно. Эффективная объяснимость должна уменьшать избыточность и каждый раз показывать пользователю что-то новое.
Для достижения этой цели мы можем создать жадный итеративный процесс . Вместо того чтобы вычислять все значения сразу, мы можем выбирать признаки шаг за шагом:
- Выберите «Победителя» : определите отдельную функцию (или группу функций) с наибольшим индивидуальным вкладом.
- Условие следующего шага : Переоцените оставшиеся признаки, предполагая, что признаки из предыдущего шага уже известны. Мы будем включать их в подмножество предварительно отобранных признаков S в значение Шапли каждый раз.
- Повторите : Спросите модель: «Учитывая, что пользователь уже знает о признаках A, B, C, какой из оставшихся признаков предоставляет больше всего информации?»
Пересчитывая значения Шапли (или предельные вклады) с учетом предварительно выбранных признаков, мы обеспечиваем, чтобы избыточные признаки фактически сводились к нулю. Если признак A и признак B идентичны, и признак A выбран первым, признак B больше не предоставляет новой информации. Он автоматически отфильтровывается, оставляя чистый, краткий список различных факторов.

Примечание : Реализацию этого прямого группового и жадного итеративного вычисления можно найти в нашем пакете Python medpython.
Сразу оговорюсь: я являюсь соавтором этого пакета с открытым исходным кодом.
Проверка в реальных условиях
Хотя эта упрощенная модель демонстрирует математические недостатки метода значений Шапли, как он работает в реальных жизненных ситуациях?
Мы применили методы группового анализа Шапли с использованием алгоритма «Победитель получает всё», а также другие методы (которые выходят за рамки этой статьи, возможно, в следующий раз) в сложных клинических условиях, используемых в здравоохранении. Наши модели используют сотни признаков с сильной корреляцией, которые были сгруппированы в десятки концепций.
Этот метод был проверен на нескольких моделях в условиях слепого эксперимента, когда наши врачи не знали, какой метод они проверяют, и превзошел стандартные значения Шапли по своим показателям. Каждая методика показала результаты выше, чем в предыдущем эксперименте, в многоэтапном исследовании. Кроме того, наша команда использовала эти улучшения в плане объяснимости в рамках нашей заявки на участие в конкурсе CMS Health AI Challenge , где мы были выбраны победителями.

Заключение
Значения Шапли являются золотым стандартом для оценки объяснимости модели, предоставляя математически строгий способ определения заслуг.
Однако, как мы уже убедились, математическая «корректность» не всегда приводит к эффективной объяснимости.
Когда признаки сильно коррелированы, сигнал может ослабевать, скрывая истинные факторы, определяющие работу вашей модели, за слоем избыточности.
Мы рассмотрели два способа решения этой проблемы:
- Группировка : Объединение характеристик в единую концепцию.
- Итеративный отбор : обусловливание уже представленных концепций с целью извлечения только новой информации, эффективно устраняя избыточность.
Признавая эти ограничения, мы можем гарантировать, что наши объяснения будут содержательными и полезными.
Если эта информация оказалась вам полезной, давайте свяжемся в LinkedIn.
Источник: towardsdatascience.com



























