Files
2026-07-13 13:22:52 +08:00

98 lines
3.7 KiB
Python

"""Tests for Explainer class."""
import pytest
import sklearn
import shap
def test_explainer_to_permutationexplainer():
"""Checks that Explainer maps to PermutationExplainer as expected."""
X_train, X_test, y_train, _ = sklearn.model_selection.train_test_split(
*shap.datasets.adult(), test_size=0.1, random_state=0
)
lr = sklearn.linear_model.LogisticRegression(solver="liblinear")
lr.fit(X_train, y_train)
explainer = shap.Explainer(lr.predict_proba, masker=X_train)
assert isinstance(explainer, shap.PermutationExplainer)
# ensures a proper error message is raised if a masker is not provided (GH #3310)
with pytest.raises(
ValueError,
match=r"masker cannot be None",
):
explainer = shap.Explainer(lr.predict_proba)
_ = explainer(X_test)
def test_wrapping_for_text_to_text_teacher_forcing_model():
"""This tests using the Explainer class to auto wrap a masker in a text to text scenario."""
pytest.importorskip("torch")
transformers = pytest.importorskip("transformers")
def f(x):
pass
name = "hf-internal-testing/tiny-random-BartForCausalLM"
tokenizer = transformers.AutoTokenizer.from_pretrained(name)
model = transformers.AutoModelForCausalLM.from_pretrained(name)
wrapped_model = shap.models.TeacherForcing(f, similarity_model=model, similarity_tokenizer=tokenizer)
masker = shap.maskers.Text(tokenizer, mask_token="...")
explainer = shap.Explainer(wrapped_model, masker, seed=1)
assert shap.utils.safe_isinstance(explainer.masker, "shap.maskers.OutputComposite")
def test_transformers_label_to_id_mapping_enforces_ints():
"""This tests that when we construct our TransformersPipeline, we enforce that label2id values are ints."""
pytest.importorskip("torch")
transformers = pytest.importorskip("transformers")
name = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
pipe = transformers.pipeline("text-classification", name)
# Make the model label2id mapping have str values
# to test that our TransformersPipeline converts them to int
pipe.model.config.label2id = {k: str(v) for k, v in pipe.model.config.label2id.items()}
# Finish constructing the Explainer
explainer = shap.Explainer(pipe, seed=1)
# Check that the label2id values are all ints after construction
assert isinstance(explainer.model, shap.models.TransformersPipeline)
assert all(isinstance(v, int) for v in explainer.model.label2id.values())
def test_wrapping_for_topk_lm_model():
"""This tests using the Explainer class to auto wrap a masker in a language modelling scenario."""
pytest.importorskip("torch")
transformers = pytest.importorskip("transformers")
name = "hf-internal-testing/tiny-random-BartForCausalLM"
tokenizer = transformers.AutoTokenizer.from_pretrained(name)
model = transformers.AutoModelForCausalLM.from_pretrained(name)
wrapped_model = shap.models.TopKLM(model, tokenizer)
masker = shap.maskers.Text(tokenizer, mask_token="...")
explainer = shap.Explainer(wrapped_model, masker, seed=1)
assert shap.utils.safe_isinstance(explainer.masker, "shap.maskers.FixedComposite")
def test_explainer_xgboost():
"""Check the explainer class wraps a TreeExplainer as expected"""
# train an XGBoost model
xgboost = pytest.importorskip("xgboost")
X, y = shap.datasets.california(n_points=500)
model = xgboost.XGBRegressor().fit(X, y)
# explain the model's predictions
explainer = shap.Explainer(model)
explanation = explainer(X)
# check the properties of Explanation object
assert explanation.values.shape == (*X.shape,) # type: ignore[union-attr]
assert explanation.base_values.shape == (len(X),) # type: ignore[union-attr]