File size: 3,314 Bytes
fe66586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2607de5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fe66586
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import os
import lime
import lime.lime_tabular
from . import config


def plot_rf_feature_importance(model, feature_names):
    """
    Plots and saves the Feature Importance for a Random Forest model.
    """
    if not hasattr(model, 'feature_importances_'):
        return None

    importances = model.feature_importances_
    indices = np.argsort(importances)[::-1]
    sorted_names = [feature_names[i] for i in indices]

    plt.figure(figsize=(14, 8))
    plt.title("Random Forest: Feature Importance (Global XAI)")
    plt.bar(range(len(importances)), importances[indices], align="center", color='teal')
    plt.xticks(range(len(importances)), sorted_names, rotation=90)
    plt.xlim([-1, len(importances)])
    plt.ylabel("Relative Importance")
    plt.tight_layout()

    save_path = os.path.join(config.MODEL_DIR, 'rf_feature_importance.png')
    plt.savefig(save_path)
    plt.close()
    print(f"Global XAI Plot saved to {save_path}")


def generate_lime_explanations(model, X_train, X_test, y_test, feature_names, class_names, model_name, num_samples=3):
    """
    Generates LIME (Local Interpretable Model-agnostic Explanations) for specific test instances.
    This works for ANY model (RF, SVM, etc.).
    """
    print(f"  Initializing LIME Explainer for {model_name}...")

    # 1. Initialize Explainer
    # LIME needs the training data to learn the distribution of features (mean, std, etc.)
    explainer = lime.lime_tabular.LimeTabularExplainer(
        training_data=np.array(X_train),
        feature_names=feature_names,
        class_names=class_names,
        mode='classification',
        verbose=False
    )

    # 2. Pick sample indices to explain
    # We pick evenly spaced samples from the test set to get a variety
    indices = np.linspace(0, len(X_test) - 1, num_samples, dtype=int)

    output_dir = os.path.join(config.MODEL_DIR, 'lime_explanations')
    os.makedirs(output_dir, exist_ok=True)

    for i in indices:
        # 3. Generate Explanation
        # LIME perturbs this specific instance and sees how the model's prediction changes
        exp = explainer.explain_instance(
            data_row=X_test[i],
            predict_fn=model.predict_proba,
            num_features=10,
            top_labels=1
        )

        # 4. Save Plot
        # FIX: We must explicitly tell pyplot which label to plot.
        # exp.local_exp keys are the class indices that were explained.
        # Since we used top_labels=1, there is only one key.
        available_labels = list(exp.local_exp.keys())
        if not available_labels:
            continue

        explained_label_idx = available_labels[0]

        # Get class name for title
        pred_label_name = class_names[explained_label_idx]
        true_label_name = class_names[y_test[i]]

        # Pass the specific label we computed to avoid KeyError
        fig = exp.as_pyplot_figure(label=explained_label_idx)
        plt.title(f"LIME ({model_name}): Test Inst {i}\nTrue: {true_label_name} | Pred: {pred_label_name}")
        plt.tight_layout()

        save_path = os.path.join(output_dir, f'{model_name}_inst_{i}_lime.png')
        plt.savefig(save_path)
        plt.close()

    print(f"  LIME explanations saved to {output_dir}")