Okay, let's put theory into practice. You've learned about LIME and SHAP individually. Now, we'll generate explanations for the same model prediction using both techniques and compare the results side-by-side. This comparison is valuable for understanding how each method attributes influence and what nuances might arise from their different approaches.
Imagine we have trained a RandomForestClassifier
on the familiar Iris dataset to predict the species of iris based on sepal and petal measurements. We'll assume this model is already trained and available as model
. Our features are 'sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', and 'petal width (cm)'.
Let's select a specific iris flower from our test set for which the model predicts the species 'virginica' (class index 2). Suppose this instance has the following features:
Our goal is to explain why the model predicted 'virginica' for this specific flower using both LIME and SHAP.
First, we use LIME. As covered in Chapter 2, LIME works by creating perturbations of the instance in question and fitting a simpler, interpretable model (like linear regression) to understand the black-box model's behavior locally around that instance.
We'll use the LimeTabularExplainer
for this task.
import lime
import lime.lime_tabular
import numpy as np
# Assume:
# X_train: training data (numpy array or pandas DataFrame)
# feature_names: list of feature names
# class_names: list of target class names
# model: trained scikit-learn classifier with predict_proba method
# instance_to_explain: numpy array for the single instance (e.g., [6.5, 3.0, 5.8, 2.2])
explainer_lime = lime.lime_tabular.LimeTabularExplainer(
training_data=X_train,
feature_names=feature_names,
class_names=class_names,
mode='classification'
)
# Explain the prediction for the specific instance (predicting class index 2)
explanation_lime = explainer_lime.explain_instance(
data_row=instance_to_explain,
predict_fn=model.predict_proba,
num_features=4, # Show importance for all features
top_labels=1 # Focus on the predicted class
)
# Displaying the explanation (conceptual representation)
# The actual output might be visualized or accessed via explanation_lime.as_list(label=2)
print("LIME Explanation for class 'virginica':")
# Example Output:
# [('petal width (cm) > 1.70', 0.35),
# ('petal length (cm) > 4.90', 0.25),
# ('sepal length (cm) <= 6.80', -0.08),
# ('sepal width (cm) <= 3.10', 0.05)]
Interpreting LIME: LIME often presents explanations as a list of feature contributions (weights) derived from its local surrogate model. The output above suggests that having a 'petal width' greater than 1.7 cm and a 'petal length' greater than 4.9 cm strongly pushed the prediction towards 'virginica' for this instance. The sepal measurements had a smaller, mixed impact according to LIME's local approximation. Note that LIME often discretizes continuous features for interpretability, resulting in rules like 'petal width (cm) > 1.70'.
Next, we apply SHAP. Since we have a RandomForest model, we can use the efficient TreeExplainer
. If we didn't know the model type or it wasn't tree-based, we could use KernelExplainer
. SHAP calculates Shapley values, representing the average marginal contribution of each feature across all possible feature combinations.
import shap
# Assume:
# model: trained tree-based model (like RandomForestClassifier)
# X_train: background data for the explainer (often the training set)
# instance_to_explain_df: pandas DataFrame for the single instance
explainer_shap = shap.TreeExplainer(model, data=X_train) # Use KernelExplainer for non-tree models
# Calculate SHAP values for the instance
# Note: SHAP explainers often expect DataFrame input if trained on one
shap_values = explainer_shap.shap_values(instance_to_explain_df)
# SHAP values are often returned per class. We need the values for the predicted class ('virginica', index 2)
shap_values_for_class_2 = shap_values[2][0] # [class_index][instance_index]
# Displaying SHAP values (conceptual representation)
print("\nSHAP Values for class 'virginica':")
# Example Output:
# {'sepal length (cm)': -0.15,
# 'sepal width (cm)': 0.05,
# 'petal length (cm)': 0.40,
# 'petal width (cm)': 0.60}
# Visualizing with a force plot
# shap.force_plot(explainer_shap.expected_value[2], shap_values_for_class_2, instance_to_explain_df)
Interpreting SHAP: The SHAP values represent the contribution of each feature value towards pushing the model output away from the base value (average prediction over the training set) towards the final prediction for this specific instance. A positive SHAP value increases the prediction for 'virginica', while a negative value decreases it. Here, 'petal width' (0.60) and 'petal length' (0.40) are the primary drivers pushing the prediction towards 'virginica'. 'Sepal width' has a small positive contribution, while 'sepal length' has a moderate negative contribution.
Now, let's compare the insights from both methods for our specific iris flower prediction:
Let's visualize the comparison using hypothetical importance values derived from the examples above.
Comparison of LIME weights and SHAP values for the features contributing to the 'virginica' prediction for the selected instance. While absolute values differ, the relative importance of petal features is clear in both.
TreeExplainer
(though TreeExplainer
is highly optimized). If we had used KernelSHAP
, the computation would have been significantly more intensive than LIME, as it also relies on perturbation but requires many more samples for theoretical grounding.By running both methods on the same instance, you gain a richer understanding of why your model made a specific prediction, appreciating the nuances and potential differences between these two popular interpretability techniques. This comparative approach helps validate findings and provides a more comprehensive picture of feature influence.
© 2025 ApX Machine Learning