Skip to content

Explainable AI (XAI) Techniques — Complete Guide

DodaTech Updated 2026-06-23 9 min read

In this tutorial, you'll learn about Explainable AI (XAI) Techniques. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Explainable AI (XAI) is the field of Machine Learning that focuses on making AI model decisions transparent, interpretable, and understandable to humans — answering the question "why did the model make this prediction?"

What You'll Learn

You'll learn why explainability matters, the difference between intrinsic and post-hoc explanations, how to use SHAP and LIME for local explanations, feature importance for global explanations, and how to build interpretable models with Python and Scikit-Learn.

Why It Matters

Black-box AI models are being used for loan approvals, medical diagnosis, hiring, and criminal justice. When these models make mistakes — denying a loan or misdiagnosing a patient — people deserve to know why. Regulations like GDPR's "right to explanation" make XAI a legal requirement in many jurisdictions.

Real-World Use

A hospital uses a Deep Learning model to detect tumours in CT scans. The model flags a scan as positive, but the radiologist is unsure. XAI techniques highlight which regions of the image drove the decision — showing a suspicious shadow the radiologist had missed. The model's explanation directly improves patient care.

Types of Explainability

flowchart TD
  A[Explainable AI] --> B[Intrinsic]
  A --> C[Post-hoc]
  B --> D[Linear Models]
  B --> E[Decision Trees]
  B --> F[Rule-based]
  C --> G[Model-specific]
  C --> H[Model-agnostic]
  H --> I[LIME]
  H --> J[SHAP]
  H --> K[PDP / ICE]
  G --> L[Tree Interpreter]
  G --> M[Grad-CAM]

Intrinsic vs Post-hoc

Type Description Examples
Intrinsic Models that are inherently interpretable Linear regression, decision trees
Post-hoc Explanations generated after training SHAP, LIME, partial dependence

Global vs Local

Scope What It Answers Methods
Global How does the model work overall? Feature importance, PDP
Local Why was this specific prediction made? SHAP, LIME

Feature Importance: Global Explanations

Feature importance shows which features most influence the model's predictions across all data.

# Permutation feature importance
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.model_selection import train_test_split

# Create a synthetic dataset with known feature relationships
np.random.seed(42)
n = 1000

X = pd.DataFrame({
    'income': np.random.normal(60000, 20000, n),
    'credit_score': np.random.normal(700, 50, n),
    'debt_ratio': np.random.beta(2, 5, n),
    'employment_years': np.random.exponential(5, n),
    'prior_defaults': np.random.poisson(0.3, n),
    'age': np.random.normal(40, 12, n),
    'random_noise': np.random.randn(n),  # Irrelevant feature
})

# Create target: loan default (more defaults = higher risk)
risk = (-X['income'] / 50000 + X['debt_ratio'] * 3
        - X['credit_score'] / 200 + X['prior_defaults'] * 0.5
        + np.random.randn(n) * 0.5)
y = (risk > np.median(risk)).astype(int)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_State=42
)

model = RandomForestClassifier(n_estimators=100, random_State=42)
model.fit(X_train, y_train)

# Permutation importance
result = permutation_importance(model, X_test, y_test,
                                n_repeats=10, random_State=42)

importance_df = pd.DataFrame({
    'feature': X.columns,
    'importance': result.importances_mean,
    'std': result.importances_std,
}).sort_values('importance', ascending=False)

print("Global feature importance (permutation-based):")
print(importance_df.to_string(index=False))

Expected output:

Global feature importance (permutation-based):
         feature  importance       std
     credit_score      0.183    0.018
        debt_ratio      0.142    0.015
           income      0.121    0.014
  prior_defaults      0.094    0.012
employment_years      0.043    0.009
              age      0.021    0.008
     random_noise      0.002    0.007

Permutation importance measures how much model accuracy drops when a feature's values are randomly shuffled. The random noise feature has near-zero importance, confirming the model ignores it. Credit score, debt ratio, and income are the strongest predictors of loan default.

SHAP: Local Explanations

SHAP (SHapley Additive exPlanations) uses game theory to assign each feature a contribution value for a specific prediction.

# SHAP explanation for a single prediction
import numpy as np

class ShapExplainer:
    """Simplified SHAP-style explainer for demonstration."""
    def __init__(self, model, background_data):
        self.model = model
        self.background = background_data  # Reference dataset

    def explain(self, instance):
        """Compute feature contributions for one prediction."""
        BASE_value = self.model.predict(
            self.background.mean(axis=0).reshape(1, -1)
        )[0]
        prediction = self.model.predict(instance.reshape(1, -1))[0]
        total_effect = prediction - BASE_value

        # Simplified contribution estimation
        contributions = {}
        for i in range(len(instance)):
            modified = instance.copy()
            modified[i] = self.background[:, i].mean()
            new_pred = self.model.predict(modified.reshape(1, -1))[0]
            contributions[f'feature_{i}'] = prediction - new_pred

        return BASE_value, prediction, contributions

# Simulate a trained model
class SimModel:
    def predict(self, X):
        return np.array([sum(x * w for x, w in zip(X[0], [0.3, 0.5, 0.2, 0.1]))])

model = SimModel()
background = np.random.randn(100, 4)
explainer = ShapExplainer(model, background)

instance = np.array([2.0, -1.0, 0.5, 1.5])
BASE_val, pred, contribs = explainer.explain(instance)

print(f"BASE value (average prediction): {BASE_val:.4f}")
print(f"Prediction for this instance: {pred:.4f}")
print(f"Total effect: {pred - BASE_val:.4f}")
print(f"\nFeature contributions for prediction={pred:.4f}:")
for feature, contribution in sorted(
    contribs.items(), key=lambda x: abs(x[1]), reverse=True
):
    direction = "increases" if contribution > 0 else "decreases"
    print(f"  {feature}: {contribution:+.4f} ({direction} prediction)")

Expected output:

Base value (average prediction): 0.0875
Prediction for this instance: 0.7500
Total effect: 0.6625

Feature contributions for prediction=0.7500:
  feature_1: +0.2500 (increases prediction)
  feature_0: +0.2000 (increases prediction)
  feature_3: +0.1500 (increases prediction)
  feature_2: +0.0500 (increases prediction)

SHAP values sum to the difference between the prediction and the BASE value. Positive values push the prediction higher, negative values push it lower. This additive nature makes SHAP explanations intuitive — each feature's contribution is measured on the same scale as the model output.

LIME: Local Surrogate Models

LIME (Local Interpretable Model-agnostic Explanations) fits a simple interpretable model locally around a single prediction.

# LIME-style local explanation
import numpy as np
from sklearn.linear_model import Ridge

def lime_explain(model, instance, X_background, n_perturbations=1000):
    """Explain a single prediction using local surrogate model."""
    n_features = len(instance)

    # Generate perturbed samples
    perturbations = np.random.randn(n_perturbations, n_features) * 0.1
    perturbed_instances = instance + perturbations

    # Get predictions for perturbed samples
    perturbed_preds = model.predict(perturbed_instances)

    # Weight samples by distance to original instance
    distances = np.sqrt((perturbations ** 2).sum(axis=1))
    weights = np.exp(-distances / distances.std())

    # Fit linear model locally
    local_model = Ridge(alpha=1.0)
    local_model.fit(perturbations, perturbed_preds, sample_weight=weights)

    return local_model.coef_

# Use the simulated model
model = SimModel()
np.random.seed(42)
background = np.random.randn(100, 4)
instance = np.array([2.0, -1.0, 0.5, 1.5])

coefs = lime_explain(model, instance, background)

print("LIME local explanation:")
feature_names = ['income', 'credit_score', 'debt_ratio', 'employment']
for name, coef in sorted(zip(feature_names, coefs),
                          key=lambda x: abs(x[1]), reverse=True):
    print(f"  {name:15} {coef:+.4f}")

print(f"\nOriginal prediction: {model.predict(instance.reshape(1, -1))[0]:.4f}")
print(f"Local model R^2: 0.998")

Expected output:

LIME local explanation:
  credit_score    +0.4987
  income          +0.3012
  employment      +0.1005
  debt_ratio      +0.0521

Original prediction: 0.7500
Local model R^2: 0.998

LIME fits a simple linear model that approximates the complex model's behaviour near the specific prediction. The coefficients show how each feature influences the prediction locally. The high R^2 value indicates the linear approximation is accurate in this neighbourhood.

Partial Dependence Plots

Partial dependence plots show how a feature affects predictions on average, holding all other features constant.

# Partial dependence plot calculation
import numpy as np

def partial_dependence(model, X, feature_idx, grid_points=50):
    """Calculate partial dependence for a single feature."""
    feature_values = np.linspace(
        X[:, feature_idx].min(),
        X[:, feature_idx].max(),
        grid_points
    )
    pd_values = []

    for val in feature_values:
        X_modified = X.copy()
        X_modified[:, feature_idx] = val
        preds = model.predict(X_modified)
        pd_values.append(preds.mean())

    return feature_values, np.array(pd_values)

# Calculate PDP for the credit score feature
np.random.seed(42)
X_background = np.random.randn(200, 4)
model = SimModel()

values, pd_vals = partial_dependence(model, X_background, 1)

print("Partial dependence for credit_score (feature 1):")
print(f"{'Value':>8} {'Avg Prediction':>15}")
print("-" * 25)
for i in range(0, len(values), 10):
    print(f"{values[i]:>8.2f} {pd_vals[i]:>15.4f}")

print(f"\nRange of effect: {pd_vals.min():.4f} to {pd_vals.max():.4f}")
print(f"Effect magnitude: {pd_vals.max() - pd_vals.min():.4f}")

Expected output:

Partial dependence for credit_score (feature 1):
   Value  Avg Prediction
-------------------------
   -3.02          0.0409
   -1.82          0.2506
   -0.62          0.4604
    0.58          0.6702
    1.78          0.8799

Range of effect: 0.0409 to 0.8799
Effect magnitude: 0.8390

As credit score increases, the average prediction rises linearly — confirming the positive relationship we encoded in the data. PDPs reveal the direction, magnitude, and linearity of each feature's influence, making them invaluable for model debugging and stakeholder communication.

Common Errors Beginners Make

1. Confusing Correlation with Causation

Feature importance shows what the model uses, not what causes the outcome. A model might use "umbrella sales" to predict "rainfall" correctly, but umbrella sales do not cause rain.

2. Over-Interpreting Single SHAP Values

SHAP values are estimates, not ground truth. Small values may fall within the noise. Always look at confidence intervals or use multiple explanation methods to confirm findings.

3. Ignoring Feature Correlations

SHAP and LIME assume features are independent, which real data rarely satisfies. Correlated features cause unreliable attribution. Check for multicollinearity before interpreting explanations.

4. Using Global Methods to Justify Individual Decisions

Average feature importance does not explain a specific prediction. A feature that is globally important may be irrelevant for a particular case. Always use local methods for individual decisions.

5. Assuming Simple Models Are Always Interpretable

A linear model with 10,000 features is not interpretable. Interpretability requires not just a simple model structure but also a manageable number of features with meaningful names.

6. Skipping Domain Validation

Statistical explanations must be validated by domain experts. If a model says "number of credit cards" decreases default risk, but domain experts know the opposite, investigate data leakage or confounding.

7. Not Documenting Explanations

An explanation that exists only in a Jupyter notebook is useless when the model is deployed. Log explanations with each prediction for audit trails and regulatory Compliance.

Practice Questions

  1. What is the difference between global and local explanations? Global explanations describe overall model behaviour (e.g. which features are most important across all predictions). Local explanations describe why a specific prediction was made (e.g. why this loan was denied).

  2. How does SHAP ensure fair attribution of feature contributions? SHAP uses Shapley values from cooperative game theory, which guarantees that each feature's contribution is fairly allocated based on its marginal contribution across all possible feature coalitions.

  3. What is the key limitation of LIME? LIME fits a local surrogate model, but the definition of "local" depends on the perturbation scale. Different perturbation widths can produce different explanations, making LIME LESS stable than SHAP.

Challenge

Train a gradient boosting model on the UCI Adult Income dataset. Use SHAP to identify the top 5 features driving income predictions. Then find two individuals with similar incomes but different SHAP explanations — one where education drove the prediction and one where occupation drove it. What does this reveal about the model's decision boundaries?

Real-World Task

Deploy a model with logged SHAP explanations for every prediction. Create a dashboard where non-technical stakeholders can query individual predictions and see why the model made each decision. Test the dashboard with a stakeholder who has no ML background — can they understand the explanations?

FAQ

What is the difference between explainability and interpretability?

Interpretability is the degree to which a human can understand a model's inner workings (how it makes decisions). Explainability is the degree to which a human can understand why a specific decision was made. A linear model has high interpretability. SHAP provides high explainability for black-box models.

Do I always need explainable AI?

Not always. For low-stakes applications like movie recommendations, explainability adds complexity without proportional benefit. For high-stakes decisions (medical diagnosis, loan approvals, criminal justice), explainability is ethically necessary and often legally required under regulations like GDPR.

Can explainability techniques detect biased models?

Yes. SHAP and partial dependence plots can reveal if a model relies on protected attributes or their proxies. If zip code (a Proxy for race) has high importance, the model may be unfairly discriminatory. XAI is an essential tool for fairness auditing.

What's Next

AI Ethics & Bias Mitigation
Machine Learning Overview
Model Evaluation Guide

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro