Sam Austin on October 9, 2026

LIME Tutorial: Explain Individual Predictions from Any Model

LIME Tutorial: Explain Individual Predictions from Any Model
Contents

Medical scan readings and charts on a screen, the kind of single prediction a clinician asks about

Figure 1: One prediction, one question — which measurements drove that call?

You have a trained model that predicts a tumor is malignant, and a clinician asks which measurements drove that call. LIME (Local Interpretable Model-agnostic Explanations) answers that question for one prediction at a time, and it works on any model that can output predictions, whether that's a random forest, a neural network, or a pipeline you didn't write. This tutorial walks through how it works, a complete working example, the settings that matter, and the ways it misleads.

How LIME Works in Four Steps

LIME doesn't try to explain the whole model. It explains the neighborhood around one instance:

  • Perturb: generate many slightly altered copies of the instance you're explaining.
  • Predict: ask your black-box model what it predicts for each copy.
  • Weight: give copies closer to the original instance more influence.
  • Fit: train a simple, interpretable model (a sparse linear model) on the weighted copies and read its coefficients as the explanation.

In the library's own description, it generates neighborhood data by randomly perturbing features from the instance, then learns locally weighted linear models on that neighborhood to explain each class in an interpretable way. The key idea is that a model can be wildly nonlinear globally while behaving roughly linearly in a tiny region, so a linear surrogate is a reasonable local description.

Installation and a Complete Example

pip install lime scikit-learn

This example explains one prediction from a random forest trained on scikit-learn's bundled breast cancer dataset:

import numpy as np
from lime.lime_tabular import LimeTabularExplainer
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, random_state=0
)

model = RandomForestClassifier(n_estimators=200, random_state=0)
model.fit(X_train, y_train)

explainer = LimeTabularExplainer(
    training_data=X_train,
    feature_names=data.feature_names,
    class_names=data.target_names,
    mode="classification",
    random_state=0,
)

exp = explainer.explain_instance(
    X_test[0],
    model.predict_proba,
    num_features=8,
    num_samples=5000,
)

print(exp.as_list())
exp.save_to_file("lime_explanation.html")   # or exp.show_in_notebook()

Two details matter immediately. The explainer receives your training data, which it uses to learn feature statistics for sampling realistic perturbations. And explain_instance takes a prediction function, not the model itself: predict_proba for classifiers, or predict for regressors with mode="regression". That function is what makes LIME model-agnostic, since anything callable that maps arrays to predictions works.

Reading the Output

exp.as_list() returns pairs like ("worst radius <= 13.0", 0.21). Read them this way:

  • The sign and size of the weight show how that condition pushed the prediction toward or away from the explained class, within this local linear model.
  • The condition is a range, not a raw value. By default LIME discretizes continuous features into bins (quartiles by default), which is why you see ranges instead of exact numbers. Set discretize_continuous=False if you'd rather have coefficients on the raw features, though the explanations then read less naturally.
  • exp.score reports how well the local linear model fits the neighborhood predictions (an R² value). A low score means the surrogate describes the local behavior poorly, so don't trust that explanation much.

For multiclass problems, use top_labels=3 to explain the three most likely classes, or pass labels= to pick specific ones.

The Settings That Matter

num_samples controls how many perturbed points LIME generates. The default is 5,000. More samples give more stable explanations but cost more predictions, which matters if your model is slow.

num_features caps how many features appear in the explanation (default 10). Keep it small, since the entire value of LIME is a short, readable answer.

kernel_width defines what "nearby" means, and it's the most underappreciated parameter. A narrow kernel makes the explanation more local but noisier. A wide kernel smooths it out but may stop describing the instance you care about. The default is a heuristic, not a truth, so test sensitivity to it.

random_state makes runs reproducible, which you'll want for audits and tests.

Handling Categorical Features

LIME's tabular explainer expects numeric arrays, so categorical columns need integer encoding, and you tell the explainer which columns are categorical:

explainer = LimeTabularExplainer(
    training_data=X_train_encoded,
    feature_names=feature_names,
    categorical_features=[2, 5],
    categorical_names={2: ["red", "green", "blue"], 5: ["low", "high"]},
    class_names=["no", "yes"],
    mode="classification",
)

If your model sits inside a scikit-learn pipeline that does its own preprocessing, wrap it so the prediction function accepts the same encoded array the explainer produces, and decodes it internally. Mismatches between what LIME perturbs and what your model expects are a common source of confusing errors.

Text and Images

LIME also has explainers for text and images, built on the same perturb-and-fit idea. For text, it removes words and watches how predictions change:

from lime.lime_text import LimeTextExplainer

explainer = LimeTextExplainer(class_names=["negative", "positive"])
exp = explainer.explain_instance(
    review_text,
    classifier_fn=text_pipeline.predict_proba,
    num_features=6,
)
print(exp.as_list())

For images, it hides regions called superpixels. Use LimeImageExplainer with a classifier_fn mapping a batch of images to class probabilities, then visualize the result with get_image_and_mask. Image explanations are the slowest, since each perturbed image needs a forward pass, so budget accordingly.

Check Stability Before You Trust an Explanation

LIME is stochastic, so run it several times and see whether the answer holds:

top_sets = []
for seed in range(10):
    explainer = LimeTabularExplainer(
        X_train, feature_names=data.feature_names,
        class_names=data.target_names, mode="classification",
        random_state=seed,
    )
    exp = explainer.explain_instance(X_test[0], model.predict_proba, num_features=5)
    top_sets.append({name for name, _ in exp.as_list()})

overlap = set.intersection(*top_sets)
print(f"Features appearing in all 10 runs: {len(overlap)} of 5")

If the top features shift substantially across seeds, raise num_samples, reconsider kernel_width, or treat that explanation as unreliable. Reporting a single LIME run as definitive is one of the most common mistakes.

Where LIME Misleads

  • Instability. Different random samples can produce different explanations for the same prediction, as covered in the beginner's guide in this series.
  • Arbitrary locality. What counts as "nearby" depends on the kernel width, and different choices can change the story.
  • Unrealistic perturbations. LIME samples feature values semi-independently, so perturbed points can be combinations that never occur in real data, such as an impossible age and income pairing. The model's behavior on those off-manifold points may have little to do with how it treats real instances.
  • Correlated features. When features overlap in information, the linear surrogate may arbitrarily favor one, hiding the others.
  • Manipulability. Research has shown that models can be built to behave differently on LIME's perturbed samples than on real data, making a biased model look innocuous in explanations. That matters if explanations are used for fairness audits or adversarial settings, so don't treat LIME output as a standalone compliance artifact.
  • Association, not causation. A high weight means the model responds to that feature locally, not that the feature causes the real-world outcome.

LIME or SHAP?

LIME is quick, intuitive, and fine for exploration and for explaining non-tabular models. SHAP is more consistent and deterministic, and it supports global summaries — the SHAP values article covers that side in depth. A pragmatic pattern: use LIME while debugging, switch to SHAP for anything that needs to be reproducible, and sanity-check them against each other when stakes are high. If the two disagree sharply on the same instance, investigate before explaining anything to anyone. The explainability tools comparison places both in the wider tool landscape.

A Note on Maintenance

LIME is widely used and its core API has stayed stable, but development has slowed. Its release notes show a history of incremental fixes, and the issue tracker has accumulated reports from 2023 and 2024 about compatibility problems, such as an import error with newer SciPy versions and an unexpected progress_bar argument in the image explainer. None of that makes LIME unusable, but it means you should pin your dependency versions, test in a clean environment, and check recent issue activity before building anything long-lived around it. If you hit a version conflict, an alternative is to use a more actively maintained library that offers LIME-style explanations.

A Practical Checklist

Choose the instances you're explaining deliberately, including surprising and borderline cases, not only easy ones. Fix random_state and record your settings. Check exp.score to see whether the local model fits. Run several seeds and compare top features. Vary kernel_width to test sensitivity. Review explanations with someone who knows the domain. And store the explanation with the prediction and the settings used, so you can reproduce it later.

Common Pitfalls

  • Passing the model instead of its prediction function, or passing predict when the explainer expects probabilities.
  • Forgetting to align encoding between LIME's perturbations and your pipeline's preprocessing.
  • Reporting one run as the explanation without checking variance.
  • Reading binned conditions as exact thresholds that the model uses.
  • Ignoring a low local fit score and presenting the explanation anyway.
  • Using LIME alone as evidence in a fairness or compliance review.
CoverBookDescriptionGet it
Cover of “Interpretable Machine Learning” Interpretable Machine Learningby Christoph Molnar the model-agnostic reference that covers LIME's assumptions and its relationship to other explainers properly. View on Amazon
Cover of “Interpretable Machine Learning with Python” Interpretable Machine Learning with Python hands-on companion matching the code above. View on Amazon
Cover of “Explainable AI: Interpreting, Explaining and Visualizing Deep Learning” Explainable AI: Interpreting, Explaining and Visualizing Deep Learning edited by Samek, Montavon & Müller — deeper treatment of why sanity checks matter for any attribution method. View on Amazon

Unlock AI That Actually Works

Get lifetime access to GPT-6 Astra, Claude Fable 5.1, Gemini 3.5, Grok 4.5, and more — all in one platform. Build websites, apps, videos, content, and digital products from a single command. No monthly fees. No tool-hopping.

Click here to get GPTAstra Max now — one-time payment, lifetime access.

Frequently Asked Questions

How does LIME explain a prediction?

It explains the neighborhood around one instance rather than the whole model: generate many slightly perturbed copies of that instance, ask your black-box model to predict on each copy, weight copies closer to the original more heavily, then fit a simple sparse linear model on those weighted copies. The coefficients of that local surrogate, read as signed weights against feature conditions, are the explanation.

What is the difference between LIME and SHAP?

LIME is quick, intuitive, model-agnostic, and fine for exploration and for explaining text or image models; it produces one local surrogate per instance and is stochastic. SHAP is deterministic, carries consistency guarantees, supports global summaries when you aggregate across a dataset, and is the better default for anything reproducible. A pragmatic pattern is to use LIME while debugging, switch to SHAP for anything that has to be defended later, and check them against each other when the stakes are high.

Why does LIME give different explanations each run?

Because it samples perturbations randomly, different runs can put different features in the top slot. Fix random_state for reproducibility, raise num_samples above the 5,000 default if the model can afford it, and check sensitivity to kernel_width, which controls how local the explanation is. Run several seeds and compare the top features: if they shift substantially, the explanation is not reliable and should not be reported as definitive.

What do the values in exp.as_list() mean?

Each entry is a feature condition paired with a weight from the local linear model — for example ('worst radius <= 13.0', 0.21) — where the sign and size show how that condition pushed the prediction toward or away from the explained class within this neighborhood. Conditions are ranges because LIME discretizes continuous features into bins by default; set discretize_continuous=False for raw-feature coefficients. exp.score reports the local model's R² against the neighborhood predictions, and a low score means the surrogate describes local behavior poorly.

Wrapping This Up

LIME explains one prediction by perturbing the instance, querying your model, and fitting a weighted linear surrogate around it. A few lines of code give you readable explanations for tabular, text, and image models. The cost is that those explanations are approximations that depend on sampling and kernel choices, so they need stability checks, a look at local fit, and a healthy suspicion of anything that changes when you rerun it.

Is LIME the right tool for every explanation job? No, but for fast, model-agnostic, per-prediction insight it's hard to beat, as long as you treat its output as a hypothesis to test rather than a verdict. Run it on a handful of real predictions from your own model, rerun with different seeds, and see how much of the story survives.

What are You Looking For?

esc