| """ |
| Model explainability for RF baseline. |
| - Computes feature importances |
| - Aggregates by frequency band and channel |
| - Saves csv/json summaries |
| """ |
| from pathlib import Path |
| import json |
| import numpy as np |
| import pandas as pd |
| import joblib |
|
|
| BANDS = ["delta", "theta", "alpha", "beta"] |
|
|
|
|
| def main(model_path="outputs/model_rf_real.joblib", max_channels=8): |
| model = joblib.load(model_path) |
| rf = model.named_steps.get("rf") or model.named_steps.get("clf") |
| if rf is None or not hasattr(rf, "feature_importances_"): |
| raise RuntimeError("Loaded model does not expose feature_importances_") |
|
|
| imp = rf.feature_importances_ |
| n_features = len(imp) |
| |
| if n_features % 4 != 0: |
| raise RuntimeError(f"Unexpected feature dim {n_features}, not divisible by 4") |
|
|
| n_channels = n_features // 4 |
| rows = [] |
| for ch in range(n_channels): |
| for bi, b in enumerate(BANDS): |
| idx = ch * 4 + bi |
| rows.append({"channel": ch, "band": b, "importance": float(imp[idx])}) |
|
|
| df = pd.DataFrame(rows) |
| by_band = df.groupby("band", as_index=False)["importance"].sum().sort_values("importance", ascending=False) |
| by_channel = df.groupby("channel", as_index=False)["importance"].sum().sort_values("importance", ascending=False) |
|
|
| out = Path("outputs") |
| out.mkdir(exist_ok=True) |
| df.to_csv(out / "feature_importance_detailed.csv", index=False) |
| by_band.to_csv(out / "feature_importance_by_band.csv", index=False) |
| by_channel.to_csv(out / "feature_importance_by_channel.csv", index=False) |
|
|
| summary = { |
| "n_features": int(n_features), |
| "n_channels": int(n_channels), |
| "top_band": by_band.iloc[0].to_dict() if len(by_band) else None, |
| "top_channel": by_channel.iloc[0].to_dict() if len(by_channel) else None, |
| } |
| (out / "explainability_summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") |
|
|
| print("Top bands:") |
| print(by_band.to_string(index=False)) |
| print("\nTop channels:") |
| print(by_channel.head(10).to_string(index=False)) |
| print("\nSaved explainability artifacts in outputs/") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|