Ошибка получения объяснения предсказания с использованием shap_values при использовании конвейера scikit-learn?

Вопрос или проблема

Я строю модель NLP для предсказания типа языка (C/C++/C#/Python…) для данного кода. Теперь мне нужно предоставить объяснение для предсказания моей модели. Например, следующий пользовательский ввод написан на Java, и модель это предсказывает, но мне нужно показать пользователям, почему она так предсказывает.

Я использую shap_values для достижения этой цели. По какой-то причине следующий код вызывает ошибку (я добавил ошибку внизу). Пожалуйста, посоветуйте, как я могу получить shap_values и графики для предсказаний моей модели.

Ссылка на данные: https://sharetext.me/bd68ryvzi0

Код:

import pandas as pd

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline

# Загрузка данных:
DATA_PATH = r"sample.csv"

data = pd.read_csv(DATA_PATH, dtype="object")
data = data.convert_dtypes()
data = data.dropna()
data = data.drop_duplicates()

# Разделение на обучающие и тестовые данные
X, y = data.content, data.language
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)

# Параметры модели для соответствия:
# 1. Имена переменных и модулей, слова в строке, ключевые слова: [A-Za-z_]\w*\b
# 2. Операторы: [!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+
# 3. Табуляции, пробелы и скобки: [ \t\(\),;\{\}\[\]`"']
# с помощью следующего регулярного выражения:
token_pattern = r"""(\b[A-Za-z_]\w*\b|[!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+|[ \t\(\),;\{\}\[\]`"'])"""

def preprocess(x):
 """ Удаляет имена переменных из одного символа или состоящие из последовательности одного и того же символа """
 return pd.Series(x).replace(r'\b([A-Za-z])\1+\b', '', regex=True)\
 .replace(r'\b[A-Za-z]\b', '', regex=True)

# Шаги пайплайна:
# Определить трансформер:
transformer = FunctionTransformer(preprocess)
# Выполняем TF-IDF векторизацию с нашим шаблоном токенов:
vectorizer = TfidfVectorizer(token_pattern=token_pattern, max_features=3000)
# Создаём классификатор случайного леса:
clf = RandomForestClassifier(n_jobs=4)

pipe_RF = Pipeline([
 ('preprocessing', transformer),
 ('vectorizer', vectorizer),
 ('clf', clf)]
)

# Установка лучших параметров (после выполнения GridSearchCV)
best_params = {
 'clf__criterion': 'gini',
 'clf__max_features': 'sqrt',
 'clf__min_samples_split': 3,
 'clf__n_estimators': 300
}

pipe_RF.set_params(**best_params)

# Обучение
pipe_RF.fit(X_train, y_train)

# Оценка
print(f'Точность: {pipe_RF.score(X_test, y_test)}')

user_input = [""" public class Fibonacci {

public static void main(String[] args) {

int n = 10;

System.out.println(fib(n));

}

public static int fib(int n) {

if (n <= 1) {

return n;

}

return fib(n - 1) + fib(n - 2);

}

} """]

import shap

shap.initjs()
explainer = shap.TreeExplainer(pipe_RF.named_steps['clf'])
observation = pipe_RF[:-1].transform(user_input).toarray()
shap_values = explainer.shap_values(observation)

Загрузите данные и выполните код, чтобы получить следующую ошибку:

ExplainerError: Проверка аддитивности не удалась в TreeExplainer! Пожалуйста,
убедитесь, что матрица данных, которую вы передали объяснителю, имеет ту же форму,
на которой была обучена модель. Если форма ваших данных верная, то
пожалуйста, сообщите об этом на GitHub. Рассмотрите возможность повторной попытки с
опцией feature_perturbation=’interventional’. Эта проверка не прошла,
потому что для одного из примеров сумма SHAP значений составила
46609069202029743624438153216.000000, в то время как вывод модели составил 0.004444. Если это различие приемлемо, вы можете установить check_additivity=False, чтобы отключить эту проверку.

Я разобрался, как это исправить, пост в помощь другим 🙂

import pandas as pd

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline

import re
from lime.lime_text import LimeTextExplainer

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

# Загрузка данных репозиториев GitHub, содержащих код и комментарии из 2.8 миллиона репозиториев GitHub:
DATA_PATH = r"/Users/stevesolun/Steves_Files/Data/github_repos_data.csv"

data = pd.read_csv(DATA_PATH, dtype="object")
data = data.convert_dtypes()
data = data.dropna()
data = data.drop_duplicates()

# Разделение на обучающие и тестовые данные
X, y = data.content, data.language
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)

# Параметры модели для соответствия:
# 1. Имена переменных и модулей, слова в строке, ключевые слова: [A-Za-z_]\w*\b
# 2. Операторы: [!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+
# 3. Табуляции, пробелы и скобки: [ \t\(\),;\{\}\[\]`"']
# с помощью следующего регулярного выражения:
token_pattern = r"""(\b[A-Za-z_]\w*\b|[!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+|[ \t\(\),;\{\}\[\]`"'])"""

def preprocess(x):
    """ Удаляет имена переменных из одного символа или состоящие из последовательности одного и того же символа """
    return pd.Series(x).replace(r'\b([A-Za-z])\1+\b', '', regex=True)\
        .replace(r'\b[A-Za-z]\b', '', regex=True)

# Шаги пайплайна:
# Определить трансформер:
transformer = FunctionTransformer(preprocess)
# Выполняем TF-IDF векторизацию с нашим шаблоном токенов:
vectorizer = TfidfVectorizer(token_pattern=token_pattern, max_features=1500)
# Создаём классификатор случайного леса:
clf = RandomForestClassifier(n_jobs=-1)

pipe_RF = Pipeline([
     ('preprocessing', transformer),
     ('vectorizer', vectorizer)]
    )

# Установка лучших параметров (после выполнения GridSearchCV)
best_params = {
    'criterion': 'gini',
    'max_features': 'sqrt',
    'min_samples_split': 3,
    'n_estimators': 300
}

clf.set_params(**best_params)
# Здесь я предобрабатываю данные:
X_train = pipe_RF.fit_transform(X_train).toarray()
X_test = pipe_RF.transform(X_test).toarray()

# Обучение модели вне пайплайна - не стесняйтесь показывать, возможно ли сделать это внутри пайплайна + выполнить fit_transform для обучающего и тестового наборов.
clf.fit(X_train, y_train)

# Оценка
print(f'Точность: {clf.score(X_test, y_test)}')

user_input = """ def fib(n):
                    a,b = 0,1
                    while a < n:
                        print(a, end=' ')
                    a,b = b, a+b
                    print()
                    fib(1000)

   """
clf.predict(pipe_RF.transform(user_input))[0]

prediction = clf.predict(pipe_RF.transform(user_input))[0]
predicted_class_idx = list(clf.classes_).index(prediction)

import shap

shap.initjs()
explainer = shap.TreeExplainer(clf, X_train)
observation = pipe_RF.transform(user_input).toarray()
shap_values = explainer.shap_values(observation)

shap.force_plot(explainer.expected_value[predicted_class_idx], shap_values[predicted_class_idx], feature_names=vectorizer.get_feature_names_out())

введите описание изображения здесь

Ответ или решение

Проблема с использованием SHAP для объяснения предсказания модели в scikit-learn Pipeline

Когда вы создаёте модель машинного обучения для анализа и классификации текста, такой как определение типа языка программирования по коду, может возникнуть необходимость в объяснении предсказаний модели. В данном случае, вы пытаетесь использовать библиотеку SHAP (SHapley Additive exPlanations) для этой цели, но сталкиваетесь с ошибкой, связанной с неправильной формой матрицы данных.

Ошибка и её причина

Ошибка, которую вы получаете, имеет следующий вид:

ExplainerError: Additivity check failed in TreeExplainer! Please ensure the data matrix you passed to the explainer is the same shape that the model was trained on.

Эта ошибка связана с тем, что передаваемая в TreeExplainer матрица данных не соответствует той, на которой была обучена модель. Данная проблема может возникнуть из-за неправильной обработки (преобразования) тестовых данных перед их передачей в объяснитель SHAP.

Решение проблемы

  1. Проверьте данные, вводимые в SHAP: Убедитесь, что вы используете правильный подход для получения и преобразования входных данных. В вашей реализации SHAP вы берёте данные из Pipeline, используя метод transform:

    observation = pipe_RF[:-1].transform(user_input).toarray()

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

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

    X_train = pipe_RF.fit_transform(X_train).toarray()
    clf.fit(X_train, y_train)
  3. Используйте правильные параметры для SHAP: При создании экземпляра TreeExplainer убедитесь, что вы передали полные данные, которые забирал класификатор. Это необходимо, чтобы объяснитель понимал контекст данных, на которых он будет производить оценку:

    explainer = shap.TreeExplainer(clf, X_train)
  4. Проверьте размерность входных данных: Убедитесь, что вы передаёте правильное количество признаков в shap_values. Данные, которые вы передаёте в explainer.shap_values, должны соответствовать количеству обученных признаков.

Пример исправленного кода

Вот исправленный код, который должен избежать ошибок:

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
import shap

# Загрузка данных
data = pd.read_csv(DATA_PATH)
data = data.convert_dtypes().dropna().drop_duplicates()

# Разделение данных
X, y = data.content, data.language
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)

# Определение трансформеров и классификатора
transformer = FunctionTransformer(preprocess)
vectorizer = TfidfVectorizer(token_pattern=token_pattern, max_features=3000)
clf = RandomForestClassifier(n_jobs=-1)

pipe_RF = Pipeline([
    ('preprocessing', transformer),
    ('vectorizer', vectorizer)
])

X_train = pipe_RF.fit_transform(X_train).toarray()
clf.fit(X_train, y_train)

# Передача новых данных
user_input = ["public class Fibonacci { ... }"]
observation = pipe_RF.transform(user_input).toarray()

# Получение SHAP значений
explainer = shap.TreeExplainer(clf, X_train)
shap_values = explainer.shap_values(observation)

shap.force_plot(explainer.expected_value, shap_values, feature_names=vectorizer.get_feature_names_out())

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...