"""
LIME (Local Interpretable Model-agnostic Explanations) implementation for XAI-Lib.
This module provides LIME explainers for tabular, image, and text data.
LIME is a popular explanation method that approximates the behavior of
a black-box model locally using an interpretable surrogate model.
LIME works by:
1. Generating a neighborhood of perturbed samples around the instance to explain
2. Getting predictions for these samples from the black-box model
3. Training an interpretable model (e.g., linear model) on the neighborhood
4. Using the interpretable model to explain the prediction
Classes:
LimeXAITabularExplanation: Explanation class for LIME tabular explanations.
LimeXAITabularExplainer: LIME explainer for tabular data.
LimeXAIImageExplainer: LIME explainer for image data.
LimeXAITextExplainer: LIME explainer for text data.
References:
Ribeiro, M. T., Singh, S., & Guestrin, C. (2016).
"Why Should I Trust You?": Explaining the Predictions of Any Classifier.
KDD 2016.
Example:
Using LIME for tabular data::
from xailib.explainers.lime_explainer import LimeXAITabularExplainer
from xailib.models.sklearn_classifier_wrapper import sklearn_classifier_wrapper
bb = sklearn_classifier_wrapper(trained_model)
explainer = LimeXAITabularExplainer(bb)
explainer.fit(df, 'target', config={'discretize_continuous': True})
explanation = explainer.explain(instance)
explanation.plot_features_importance()
See Also:
:mod:`lime`: The underlying LIME library.
:class:`xailib.explainers.shap_explainer_tab.ShapXAITabularExplainer`: Alternative explanation method.
"""
import pandas as pd
from skimage.segmentation import mark_boundaries
import matplotlib.pyplot as plt
import numpy as np
from xailib.models.bbox import AbstractBBox
from xailib.xailib_tabular import TabularExplainer, TabularExplanation
from xailib.xailib_image import ImageExplainer
from xailib.xailib_text import TextExplainer
from lore_explainer.datamanager import prepare_dataset
from lime.lime_tabular import LimeTabularExplainer
from lime.lime_image import LimeImageExplainer
from lime.lime_text import LimeTextExplainer
[docs]
class LimeXAITabularExplanation(TabularExplanation):
"""
Explanation class for LIME tabular explanations.
This class wraps the LIME explanation result and provides methods
to access feature importance and visualize the explanation.
Args:
lime_exp: The raw LIME explanation object.
Attributes:
exp: The underlying LIME explanation object.
Example:
>>> explanation = explainer.explain(instance)
>>> importance = explanation.getFeaturesImportance()
>>> explanation.plot_features_importance()
"""
def __init__(self, lime_exp):
"""
Initialize the LIME explanation.
Args:
lime_exp: Raw explanation from LimeTabularExplainer.
"""
super().__init__()
self.exp = lime_exp
[docs]
def getFeaturesImportance(self):
"""
Get feature importance as a list of (feature, weight) tuples.
Returns:
list: List of tuples (feature_description, weight) showing
how each feature contributed to the prediction.
"""
return self.exp.as_list()
[docs]
def getExemplars(self):
"""
Get exemplar instances.
Note:
LIME does not provide exemplars.
Returns:
None: Not available for LIME.
"""
return None
[docs]
def getCounterExemplars(self):
"""
Get counter-exemplar instances.
Note:
LIME does not provide counter-exemplars.
Returns:
None: Not available for LIME.
"""
return None
[docs]
def getRules(self):
"""
Get decision rules.
Note:
LIME provides feature weights, not explicit rules.
Use getFeaturesImportance() instead.
Returns:
None: Not available for LIME.
"""
return None
[docs]
def getCounterfactualRules(self):
"""
Get counterfactual rules.
Note:
LIME does not provide counterfactual rules.
Returns:
None: Not available for LIME.
"""
return None
[docs]
def plot_features_importance(self, fontDimension=10):
"""
Plot an interactive visualization of feature importance.
Creates an Altair chart showing feature weights with an
interactive slider to filter by importance threshold.
Args:
fontDimension (int, optional): Base font size for the chart.
Defaults to 10.
Returns:
None. Displays the chart using IPython display.
"""
dataToPlot = pd.DataFrame(self.exp.as_list(), columns=['name', 'value'])
dataToPlot['value'] = dataToPlot['value'].astype('float64')
super().plot_features_importance_from(dataToPlot, fontDimension)
[docs]
class LimeXAITabularExplainer(TabularExplainer):
"""
LIME explainer for tabular data.
This explainer uses LIME (Local Interpretable Model-agnostic Explanations)
to explain predictions on tabular data by training a local linear model.
Args:
bb (AbstractBBox): The black-box model wrapper to explain.
Attributes:
bb: The black-box model wrapper.
lime_explainer: The underlying LimeTabularExplainer (after fitting).
Example:
>>> explainer = LimeXAITabularExplainer(model_wrapper)
>>> explainer.fit(df, 'target', config={
... 'discretize_continuous': True,
... 'feature_selection': 'auto'
... })
>>> explanation = explainer.explain(instance, num_samples=1000)
"""
lime_explainer = None
def __init__(self, bb: AbstractBBox):
"""
Initialize the LIME tabular explainer.
Args:
bb (AbstractBBox): Black-box model wrapper to explain.
"""
super().__init__()
self.bb = bb
[docs]
def fit(self, _df: pd.DataFrame, class_name, config):
"""
Fit the LIME explainer to the training data.
Args:
_df (pd.DataFrame): Training DataFrame with features and target.
class_name (str): Name of the target column.
config (dict): Configuration dictionary with optional parameters:
- 'feature_selection': Feature selection method ('auto', 'forward', etc.)
- 'discretize_continuous' (bool): Whether to discretize continuous features
- 'discretizer': Discretizer type ('quartile' or 'decile')
- 'sample_around_instance' (bool): Sampling strategy
- 'kernel_width': Width of the kernel for weighting
- 'kernel': Custom kernel function
Returns:
None. The explainer is fitted in-place.
"""
df, feature_names, class_values, numeric_columns, \
_, _, _ = prepare_dataset(_df, class_name)
feature_selection = config['feature_selection'] if 'feature_selection' in config else None
discretize_continuous = config['discretize_continuous'] if 'discretize_continuous' in config else False
discretizer = config['discretizer'] if 'discretizer' in config else 'quartile'
sample_around_instance = config['sample_around_instance'] if 'sample_around_instance' in config else False
kernel_width = config['kernel_width'] if 'kernel_width' in config else None
kernel = config['kernel'] if 'kernel' in config else None
self.lime_explainer = LimeTabularExplainer(
df[feature_names].values, feature_names=feature_names,
class_names=class_values, feature_selection=feature_selection,
discretize_continuous=discretize_continuous, discretizer=discretizer,
sample_around_instance=sample_around_instance, kernel_width=kernel_width,
kernel=kernel
)
[docs]
def explain(self, x, classifier_fn=None, num_samples=1000, top_labels=5):
"""
Generate a LIME explanation for a tabular instance.
Args:
x: Instance to explain as a numpy array.
classifier_fn (callable, optional): Custom prediction function.
If None, uses the black-box model's predict_proba.
num_samples (int): Number of samples in the neighborhood.
Defaults to 1000.
top_labels (int): Number of top labels to explain.
Defaults to 5.
Returns:
LimeXAITabularExplanation: Explanation object with feature weights.
"""
if classifier_fn:
self.classifier_fn = classifier_fn
else:
self.classifier_fn = self.bb.predict_proba
exp = self.lime_explainer.explain_instance(
x,
self.classifier_fn,
num_samples=num_samples,
top_labels=top_labels
)
return LimeXAITabularExplanation(exp)
[docs]
def plot_lime_values(self, exp, range_start, range_end):
"""
Plot LIME feature importance values as a bar chart.
Args:
exp: LIME explanation (list of (feature, weight) tuples).
range_start (int): Start index for features to display.
range_end (int): End index for features to display.
Returns:
None. Displays the matplotlib figure.
"""
feature_names = [a_tuple[0] for a_tuple in exp]
exp = [a_tuple[1] for a_tuple in exp]
plt.rcParams.update({'font.size': 20})
plt.figure(figsize=(10, 8))
plt.bar(feature_names[range_start:range_end], exp[range_start:range_end], facecolor='lightblue', width=0.5)
plt.xticks(feature_names[range_start:range_end], rotation='vertical')
plt.margins(0.1)
plt.subplots_adjust(bottom=0.25)
plt.show()
[docs]
class LimeXAIImageExplainer(ImageExplainer):
"""
LIME explainer for image data.
This explainer uses LIME to explain image classification predictions
by segmenting the image and determining which segments are most
important for the prediction.
Args:
bb (AbstractBBox): The black-box model wrapper to explain.
Attributes:
bb: The black-box model wrapper.
lime_explainer: The underlying LimeImageExplainer (after fitting).
"""
lime_explainer = None
def __init__(self, bb: AbstractBBox):
"""
Initialize the LIME image explainer.
Args:
bb (AbstractBBox): Black-box model wrapper to explain.
"""
super().__init__()
self.bb = bb
[docs]
def fit(self, verbose=False):
"""
Initialize the LIME image explainer.
Args:
verbose (bool): Whether to print verbose output.
Defaults to False.
Returns:
None. The explainer is initialized in-place.
"""
self.lime_explainer = LimeImageExplainer(verbose=False)
[docs]
def explain(self, image, classifier_fn=None, segmentation_fn=None, top_labels=5, num_samples=1000):
"""
Generate a LIME explanation for an image.
Args:
image: Query image to explain as a numpy array.
classifier_fn (callable, optional): Function that takes images and
returns predictions. If None, uses black_box.predict.
segmentation_fn (callable, optional): Function to segment the image.
If None, uses quickshift segmentation.
top_labels (int): Number of top labels to explain. Defaults to 5.
num_samples (int): Number of perturbed images to generate.
Defaults to 1000.
Returns:
LIME ImageExplanation object with superpixel importance values.
"""
if classifier_fn:
self.classifier_fn = classifier_fn
else:
self.classifier_fn = self.bb.predict
exp = self.lime_explainer.explain_instance(
image,
self.classifier_fn,
segmentation_fn=segmentation_fn,
top_labels=top_labels,
hide_color=0,
num_samples=num_samples
)
return exp
[docs]
def plot_lime_values(self, image, explanation, figsize=(15, 5)):
"""
Plot a visualization of the LIME image explanation.
Creates a three-panel figure showing:
1. The original query image
2. A heatmap of superpixel importance
3. An overlay of the heatmap on the original image
Args:
image: The original image used in the explain function.
explanation: LIME explanation object returned by explain().
figsize (tuple): Figure size as (width, height). Defaults to (15, 5).
Returns:
None. Displays the matplotlib figure.
"""
F, ax = plt.subplots(1, 3, figsize=figsize)
ax[0].imshow(image)
ax[0].axis('off')
ax[0].set_title('Query Image')
# plot heatmap
ind = explanation.top_labels[0]
dict_heatmap = dict(explanation.local_exp[ind])
heatmap = np.vectorize(dict_heatmap.get)(explanation.segments)
ax[1].imshow(heatmap, cmap='coolwarm', vmin=-heatmap.max(), vmax=heatmap.max())
ax[1].axis('off')
ax[1].set_title('Super Pixel Heatmap Explanation')
# plot overlap
ax[2].imshow(image)
ax[2].imshow(heatmap, alpha=0.5, cmap='coolwarm')
ax[2].axis('off')
ax[2].set_title('Overlap of Query Image and Heatmap')
[docs]
class LimeXAITextExplainer(TextExplainer):
"""
LIME explainer for text data.
This explainer uses LIME to explain text classification predictions
by perturbing words in the text and measuring their impact on predictions.
Args:
bb (AbstractBBox): The black-box model wrapper to explain.
Attributes:
bb: The black-box model wrapper.
lime_explainer: The underlying LimeTextExplainer (after fitting).
"""
lime_explainer = None
def __init__(self, bb: AbstractBBox):
"""
Initialize the LIME text explainer.
Args:
bb (AbstractBBox): Black-box model wrapper to explain.
"""
super().__init__()
self.bb = bb
[docs]
def fit(self, class_names=None, verbose=False):
"""
Initialize the LIME text explainer.
Args:
class_names (list, optional): List of class names ordered
according to the classifier output. If None, class names
will be '0', '1', etc.
verbose (bool): Whether to print verbose output.
Defaults to False.
Returns:
None. The explainer is initialized in-place.
"""
self.lime_explainer = LimeTextExplainer(class_names=class_names, verbose=False)
[docs]
def explain(self, sentence, classifier_fn=None, num_samples=1000, plot=False):
"""
Generate a LIME explanation for a text sentence.
Args:
sentence (str): Query text to explain.
classifier_fn (callable, optional): Function that takes text
and returns predictions. If None, uses black_box.predict.
num_samples (int): Number of perturbed texts to generate.
Defaults to 1000.
plot (bool): Whether to display a matplotlib plot of the
explanation. Defaults to False.
Returns:
LIME TextExplanation object with word importance values.
"""
if classifier_fn:
self.classifier_fn = classifier_fn
else:
self.classifier_fn = self.bb.predict
exp = self.lime_explainer.explain_instance(sentence,
self.classifier_fn,
num_samples=num_samples)
if plot:
exp.as_pyplot_figure()
return exp