1

We are working with a regression problem, where we want to apply log transform to target variable. After model training, we want to obtain SHAP values in native units, that they would be suitable for end-user.

When trying to create an explainer by using SHAP library, we receive the following error:

shap.utils._exceptions.InvalidModelError: Model type not yet supported by TreeExplainer: <class 'sklearn.compose._target.TransformedTargetRegressor'>

I can manually apply reverse transformation to obtain final prediction correctly, however, I want to receive each SHAP value for individual features in native units. How could I do it?

LightGBM prediction with target transform: [24.23973864] SHAP explainer estimated value with manual reverse_tranform of final value: 24.23973864452016 SHAP explainer estimated value with manual reverse_tranform of intermediate values: 21.04059855102976

Reproducible example:

import numpy as np
from lightgbm import LGBMRegressor
import shap
from sklearn.pipeline import make_pipeline
from sklearn.compose import TransformedTargetRegressor

#get data set
X, y = shap.datasets.boston()

#Create model with target transform
pipeline = make_pipeline(
    LGBMRegressor()
)

model_with_target_transform = TransformedTargetRegressor(regressor=pipeline, func=np.log1p, inverse_func=np.expm1)
model_with_target_transform.fit(X, y)

#Create model without target transform, but with transform
y = np.log1p(y)
model_without_target_trasnsform = LGBMRegressor()
model_without_target_trasnsform.fit(X, y)

#Select observation for testing purpose
test = X.iloc[:1]

# Calculate prediction with target transform
lightgbm_prediction_with_target_transform = model_with_target_transform.predict(test)

# Create explainer
# Explainer with target transform is not supported
# explainer = shap.TreeExplainer(model_with_target_transform)
# shap.utils._exceptions.InvalidModelError: Model type not yet supported by TreeExplainer: <class 'sklearn.compose._target.TransformedTargetRegressor'>

explainer = shap.TreeExplainer(model_without_target_trasnsform)
explained = explainer(test)

# Manualy inverse transform shap values
shap_prediction_all_transform = np.expm1(explained.base_values[0] + sum(explained.values[0]))
shap_prediction_seperate_transform = np.expm1(explained.base_values[0]) + sum(np.expm1(explained.values[0]))

print(f"LightGBM prediction with target transform: {lightgbm_prediction_with_target_transform}")

# Manual conversion of shap values to estimation, reverse transformation of shap values + base values is correct.
# Seperate values reverse transformation and than sum is incorrect
print(f"SHAP explainer estimated value with manual reverse_tranform of final value: {shap_prediction_all_transform}")
print(f"SHAP explainer estimated value with manual reverse_tranform of intermediate values: {shap_prediction_seperate_transform}")
Alexander L. Hayes
  • 3,892
  • 4
  • 13
  • 34
  • Ideally, have shap implement it; but I see you've already raised an Issue: https://github.com/slundberg/shap/issues/2719 – Ben Reiniger Oct 11 '22 at 14:28
  • @BenReiniger Yes, I did, however, I think the implementation is not so easy. I found a separate issue for classification problems: https://github.com/slundberg/shap/pull/1041#issuecomment-726147674 I tried to implement a similar approach, however, I see two issues: 1. Shap values can be negative, thus sum is not suitable, it should be abs, power, or some similar approach so that individual influence would not get canceled. 2. Recalculation based on ratios is not suitable, since the ratio between numbers and their log is not the same i.e. the transformation is non-linear. – Valentas Gruzauskas Oct 11 '22 at 14:50
  • The model is not supported. Why not `KernelExplainer` then? – Sergey Bushmanov Oct 11 '22 at 15:13
  • @SergeyBushmanov We want to apply SHAP to a very large number of observations, thus computational time is important for us. We want to use a GPU tree for this purpose: https://shap.readthedocs.io/en/latest/generated/shap.explainers.GPUTree.html As I understand KernelExplainer is computationally expensive, I will try it out just in case. Thank you for the recommendation. – Valentas Gruzauskas Oct 11 '22 at 15:39
  • The problem is if somebody volanteers to solve your problem the clever SHAP way, assuming it's possible which I doubt. Or, you go ahead with KernelExplainer. "Large number of observations" is not interesting here. Try 100 observations. That's enough, as this is about convergance at the rate of 1/(n)**2 – Sergey Bushmanov Oct 11 '22 at 15:54
  • @SergeyBushmanov Thank you, I will try. I found KernelExplainer GPU version, maybe it will be a suitable alternative. https://docs.rapids.ai/api/cuml/stable/api.html#model-explainability – Valentas Gruzauskas Oct 11 '22 at 16:13

0 Answers0