XGBoost поддерживает прямой ввод функций в виде категорий, что очень полезно при наличии большого количества категориальных переменных. Кажется, это несовместимо с Shap:
import pandas as pd
import xgboost
import shap
# Test data
test_data = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']})
test_data['feature_2'] = test_data['feature_2'].astype('category')
# Fit xgboost
model = xgboost.XGBRegressor(enable_categorical=True,
tree_method='hist')
model.fit(test_data.drop('target', axis=1), test_data['target'] )
# Explain with Shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(test_data)
Выдает ошибку: ValueError: DataFrame.dtypes для данных должен быть int, float, bool или category.
Можно ли использовать шап в этой ситуации?
Выше есть пример. Преобразование категориальных данных в число с плавающей запятой не является хорошей идеей, данные не являются числовыми. Это не проблема классификации, это регрессия... но это не имеет значения. Вопрос специально для shap с категориальными входами
Я использовал GradientBoostingRegressor и преобразовал массив в 2 функции на элемент.
from sklearn.ensemble import GradientBoostingRegressor
import shap
import pandas as pd
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
import numpy as np
df = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']
})
df["feature_1"]=df["feature_1"].astype(int)
df["target"]=df["target"].astype(int)
encoder = preprocessing.LabelEncoder()
df["feature_2"]=encoder.fit_transform(df["feature_2"])
print(df)
SEED=42
model = GradientBoostingRegressor(n_estimators=300, max_depth=8, random_state=SEED)
scale= StandardScaler()
#X=df[["feature_1","feature_2"]]
columns=["feature_1","feature_2"]
n_features=len(columns)
X=np.array(scale.fit_transform(df[columns])).reshape(-1,n_features)
y=np.array(df["target"])
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=42)
model.fit(X_train,y_train)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(df)
print(shap_values)
y_pred=model.predict(X_test)
x=np.arange(len(X_test))
plt.bar(x,y_test)
plt.bar(x,y_pred,color='green')
plt.show()
выход:
target feature_1 feature_2
0 23 38 0
1 42 83 1
2 58 38 0
3 29 28 2
4 28 57 0
Shap values
[[-4.65720266 -3.00946401 0. ]
[ 2.32860133 -3.00946401 0. ]
[ 2.32860133 -3.00946401 0. ]
[-4.65720266 -3.00946401 0. ]
[-4.65720266 -3.00946401 0. ]]
или
df = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']
})
df["feature_1"]=df["feature_1"].astype(int)
df["target"]=df["target"].astype(int)
encoder = preprocessing.LabelEncoder()
df["feature_2"]=encoder.fit_transform(df["feature_2"])
SEED=42
#model = xgboost.XGBRegressor(enable_categorical=True,tree_method='hist')
model=xgboost.XGBRegressor(enable_categorical=True,tree_method='hist')
#model = GradientBoostingRegressor(n_estimators=100, max_depth=2, random_state=SEED)
scale= StandardScaler()
#X=df[["feature_1","feature_2"]]
columns=["feature_1","feature_2"]
n_features=len(columns)
X=np.array(scale.fit_transform(df[columns])).reshape(-1,n_features)
y=np.array(df["target"])
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.6,random_state=42)
model.fit(X_train,y_train)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
print(shap_values)
y_pred=model.predict(X_test)
x=np.arange(len(X_test))
plt.bar(x,y_test)
plt.bar(x,y_pred,color='green')
plt.show()
Спасибо за публикацию ответа. К сожалению, я не заинтересован в применении кодировщика меток к категориальным данным, поскольку это подразумевает, что данные должны быть в некотором порядке, чтобы они были эффективными. В случае со многими категориальными переменными это, вероятно, окажет значительное влияние на производительность. Целевое кодирование Я думаю, что это более вероятное решение
К сожалению, генерация значений формы с помощью xgboost с использованием категориальных переменных является открытой проблемой. См., например, https://github.com/slundberg/shap/issues/2662
Учитывая ваш конкретный пример, я запустил его, используя Dmatrix в качестве входных данных для shap (Dmatrix — это базовый ввод данных для моделей xgboost, см. Learning API. API sklearn, который вы используете, не нуждается в Dmatrix, по крайней мере для тренировки):
import pandas as pd
import xgboost as xgb
import shap
# Test data
test_data = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']})
test_data['feature_2'] = test_data['feature_2'].astype('category')
print(test_data.info())
# Fit xgboost
model = xgb.XGBRegressor(enable_categorical=True,
tree_method='hist')
model.fit(test_data.drop('target', axis=1), test_data['target'] )
# Explain with Shap
test_data_dm = xgb.DMatrix(data=test_data.drop('target', axis=1), label=test_data['target'], enable_categorical=True)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(test_data_dm)
print(shap_values)
Но возможность генерировать значения shap при наличии категориальных переменных очень нестабильна: например, если вы добавите другие параметры в xgboost, вы получите ошибку «Проверка не удалась: !HasCategoricalSplit()», на которую ссылается моя первая ссылка.
import pandas as pd
import xgboost as xgb
import shap
# Test data
test_data = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']})
test_data['feature_2'] = test_data['feature_2'].astype('category')
print(test_data.info())
# Fit xgboost
model = xgb.XGBRegressor(colsample_bylevel= 0.7,
enable_categorical=True,
tree_method='hist')
model.fit(test_data.drop('target', axis=1), test_data['target'] )
# Explain with Shap
test_data_dm = xgb.DMatrix(data=test_data.drop('target', axis=1), label=test_data['target'], enable_categorical=True)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(test_data_dm)
shap_values
Я искал решение в течение нескольких месяцев, но в заключение, насколько я понимаю, на самом деле пока невозможно генерировать значения shap с помощью xgboost и категориальных переменных (надеюсь, кто-то может возразить мне, с воспроизводимым примером). Я предлагаю вам попробовать Catboost
########################## РЕДАКТИРОВАТЬ ####################### #####
Пример с Catboost
import pandas as pd
import catboost as cb
import shap
# Test data
test_data = pd.DataFrame({'target':[23,42,58,29,28],
'feature_1' : [38, 83, 38, 28, 57],
'feature_2' : ['A', 'B', 'A', 'C','A']})
test_data['feature_2'] = test_data['feature_2'].astype('category')
print(test_data.info())
model = cb.CatBoostRegressor(iterations=100)
model.fit(test_data.drop('target', axis=1), test_data['target'],
cat_features=['feature_2'], verbose=False)
# Explain with Shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(test_data.drop('target', axis=1))
shap_values
print('shap values: \n',shap_values)
Спасибо. Значит, Catboost поддерживает категориальные входы, а также работает с Shap? У вас есть ссылка на это?
Конечно, я забыл опубликовать это в исходном вопросе, я внес изменения, дайте мне знать
тип приведения к чему?