Author: Yasindu Kaveesha Degree: B.Sc. (Hons) Data Science / CS, SLIIT Parent system: TinySteps multi-modal ASD screening Version: 2.0 — revised after deep read of all sector1/2/3 notebooks, plots, and cell outputs
Before planning extensions, here's exactly what XAI-adjacent work already exists in the codebase:
| Artifact | Location | What it is | Reusable? |
|---|---|---|---|
GradCAM_ASD class |
sector1/Stage_4/stage4_final_model_evaluation.ipynb §5 (cells 21-23) |
Full Grad-CAM implementation targeting conv2d_14, with nested-backbone handling and JET overlay |
Yes — port to backend as-is |
gradcam_autistic.png + gradcam_non_autistic.png |
sector1/Stage_4/plots/ |
4 correctly-classified ASD + 4 Non-ASD examples with heatmaps overlaid | For thesis figures |
| SHAP on XGBoost | sector2/Stage_2/qchat_model_training.ipynb |
shap.Explainer(xgb_final, X) — XGBoost-2.x compatible API; saves beeswarm + bar plots |
Yes — pattern directly reusable |
shap_summary_qchat.png |
sector2/Stage_2/plots/shap_summary_qchat.png |
Beeswarm with feature contributions | Thesis figure |
shap_bar_qchat.png |
sector2/Stage_2/plots/shap_bar_qchat.png |
Mean |SHAP| bar chart | Thesis figure |
| Calibration analysis | sector2/Stage_2/qchat_model_training.ipynb + sector1/Stage_4/stage4_advanced_evaluation.ipynb |
Brier + ECE (10-bin uniform) implemented for both streams | Reusable |
| Bootstrap 95% CI | sector1/Stage_4/stage4_advanced_evaluation.ipynb |
2000 resamples for AUC/Recall/Precision/F1 | Reusable |
| t-SNE + silhouette | sector1/Stage_4/tsne_embedding_visualization.ipynb |
Perplexity sweep (10/30/50), silhouette scores 256-D vs 2-D | Reusable |
| LogReg probe + coefficients | sector1/Stage_4/save_logreg_probe.py |
logreg_probe_{model,scaler}.pkl, OOF AUC 0.8593 |
logreg.coef_ is a free linear-attribution tool — not yet used as XAI |
| Monte-Carlo fusion | sector3/late_fusion_simulation.ipynb |
500 iter × 300 pairs, α-sweep, complementarity aggregate | Aggregate only — no per-sample tracking |
What does NOT exist anywhere in the codebase:
- LIME (any modality)
- Counterfactual explanations
- Per-sample fusion attribution (sector3 discards sample-level data inside the MC loop)
- Cross-modal agreement analysis (per-sample P_facial vs P_qchat)
- Live XAI served through the backend (
asd_router.pyhas no explain endpoints) - Any XAI UI on the frontend
These absences define the novelty space for this extension.
Our trained multi-modal pipeline achieves strong aggregate performance (fused AUC ≈ 0.9994, joint-FN 0.23% in the MC simulation) but operates as a black box at inference time. Two concrete gaps:
- Training-time XAI was produced, but never served. Grad-CAM plots exist in the notebook folder; SHAP plots exist in sector2. Neither is reachable from the app.
- Fusion-level XAI does not exist at all. Sector 3 reports aggregate AUC/complementarity across 500 Monte-Carlo iterations but discards sample-level probabilities inside the loop. There is no per-prediction explanation for why the fused score landed where it did.
- RQ1 — Can the existing offline
GradCAM_ASDclass andshap.Explainerpatterns be lifted into live FastAPI endpoints without distorting their semantics? - RQ2 — At inference time, how should the fused score
P = αP_facial + (1-α)P_qchatbe decomposed into per-sample modality contributions, and what information does that decomposition carry? - RQ3 — Does cross-modal agreement (|P_facial − P_qchat|) correlate with prediction correctness on held-out data, and can it be used as a confidence signal that flags cases for clinical review?
- RQ4 — Do different XAI methods on the Q-CHAT model (SHAP, a newly-added LIME comparison, one-flip counterfactuals) converge on the same top drivers, or disagree?
Per-prediction modality attribution with cross-modal-agreement-as-confidence, coupled with a clinical-translation layer. Sector 3 already establishes aggregate complementarity (0.23% joint FN rate). This extension pushes that from population-level to patient-level, which directly enables:
- A deployment policy ("flag low-agreement cases for clinician review")
- A human-interpretable explanation per prediction
This is a modest but genuine SLIIT-level contribution: it is not trivially available in the existing literature for multi-modal ASD screening, and it requires the sector-3 simulation to be extended with per-sample tracking — work that is not yet done.
| Method | Canonical ref | Used on | Status in our project |
|---|---|---|---|
SHAP Explainer |
Lundberg & Lee, NeurIPS 2017 | Q-CHAT XGBoost | Already implemented offline |
| Grad-CAM | Selvaraju et al., ICCV 2017 | VGG-Face CNN | Already implemented offline (conv2d_14 target) |
| Counterfactual (one-flip) | Wachter et al., 2017 | Q-CHAT binary features | To add |
| LIME | Ribeiro et al., KDD 2016 | Comparison baseline for Q-CHAT | To add |
| Per-sample modality attribution | This work | Fusion layer | To add (novel in our pipeline) |
Published multi-modal ASD fusion papers typically report aggregate AUC and, when present, SHAP on one branch. The combination of (a) per-sample modality decomposition + (b) cross-modal agreement correlated with correctness + (c) patient-facing translation is uncommon. That combination is the contribution.
Image ──► MTCNN ──► VGG-Face (conv2d_14 block) ──► asd_feature_vector_256
│
├──► sigmoid head ──► P_facial (fold_5)
│
└──► logreg_probe (used in production) ──► P_facial'
QChat 12-feature vector ──► xgboost_qchat_stage2 ──► P_qchat
P_fused = 0.15·P_facial + 0.85·P_qchat ──► risk label
Image ──► ... (same) ──► P_facial ┐
│ │
└──► GradCAM_ASD.generate_heatmap ──► base64 overlay
└──► logreg_probe.coef_ · embedding ──► per-dim contributions
QChat ──► ... (same) ──► P_qchat │
│ │
└──► shap.Explainer(xgb)(x) ──► per-feature SHAP values
└──► one_flip_counterfactuals(x) ──► Δ per feature
└──► LIME explain_instance (optional comparison) ──► top-K features
│
▼
Modality Attribution Module
{ facial_contribution = α·P_facial
qchat_contribution = (1-α)·P_qchat
dominant_modality, modality_share,
|P_facial − P_qchat|, agreement_bucket,
both_above_threshold, both_below_threshold }
│
▼
Clinical Translator
(SHAP → readable sentences with A-codes mapped to
clinical behaviour names and DSM-5 criterion labels;
disagreement → "flag for clinician review" copy)
│
▼
Frontend cards
| Endpoint | Method | Input | Output |
|---|---|---|---|
/explain-qchat |
POST | 12-feature payload (same as predict-qchat) | SHAP values, counterfactual Δ, optional LIME, plain-language summary |
/explain-face |
POST | image file | Grad-CAM heatmap (base64 PNG), probe-coefficient contributions on top-K embedding dims, caveat text |
/explain-fused |
POST | {p_facial, p_qchat} (or full features to recompute) |
Modality shares, |
/explain-all |
POST | image + 12-feature payload | Single aggregate response (one call for the UI) |
# Q-CHAT response (Pydantic)
class QChatContribution(BaseModel):
feature: str # e.g. "A4"
behaviour_name: str # "Protodeclarative pointing (joint attention)"
raw_answer: int # 0-4
shap: float # e.g. 1.57
direction: Literal["increases_risk", "decreases_risk", "neutral"]
percent_of_abs_total: float # e.g. 0.15
class QChatCounterfactual(BaseModel):
feature: str
flipped_answer: int
new_probability: float
delta: float # new - original
class QChatExplanation(BaseModel):
base_value: float
shap_values: list[float]
feature_names: list[str]
contributions: list[QChatContribution]
top_drivers: list[QChatContribution] # top 3 by |shap|
counterfactuals: list[QChatCounterfactual] # one per answered feature
plain_language: str# Facial response
class ProbeDimContribution(BaseModel):
embedding_dim: int # 0..255
value: float # embedding[dim]
coef: float # logreg.coef_[0, dim]
contribution: float # value * coef
class FaceExplanation(BaseModel):
gradcam_overlay_base64: str
attention_region_summary: str # "midface" | "periocular" | "diffuse" | ...
top_probe_dims: list[ProbeDimContribution] # top 10 by |contribution|
prediction: float
threshold_used: float
disclaimer: str # the confound caveat, verbatim# Fused response
class FusedExplanation(BaseModel):
p_facial: float
p_qchat: float
alpha: float # 0.15
facial_contribution: float # alpha * p_facial
qchat_contribution: float # (1-alpha) * p_qchat
p_fused: float
modality_share: dict[str, float] # normalized
dominant_modality: Literal["facial", "qchat"]
disagreement: float # |p_facial - p_qchat|
agreement_bucket: Literal["high", "medium", "low"]
clinician_review_recommended: bool
clinical_message: strGiven α = 0.15:
C_facial = α · P_facialC_qchat = (1 − α) · P_qchatshare_m = C_m / (C_facial + C_qchat)(normalised)dominant = argmax_m C_mdisagreement = |P_facial − P_qchat|- Agreement bucket:
highifdisagreement < 0.15mediumif0.15 ≤ disagreement < 0.35lowifdisagreement ≥ 0.35
H0: Agreement bucket is independent of prediction correctness. H1 (our hypothesis): Low-agreement cases have measurably lower correctness than high-agreement cases.
Protocol (new notebook sector4_xai/02_agreement_vs_correctness.ipynb):
- Construct a paired dataset for this analysis. Because sector3 simulation pairs by class label only (the two streams have no patient overlap), we will run this on the synthetically paired blind-test samples — fixed pairing, not MC resampled. Use sector1 Fold-5 blind test (392 images) and a stratified sample of sector2 Stage-2 test rows (368 rows). For each class, shuffle once with a fixed seed and pair index-wise. This gives a reproducible 392/368 → 368 pair set.
- Compute
P_facialandP_qchatfor every pair, thenP_fused, then correctness at the fusion threshold 0.35. - Bin by agreement bucket.
- Report: accuracy, precision, recall, and 95% bootstrap CIs per bucket.
- Statistical test: χ² on (correct, incorrect) × (high, medium, low). Report p-value.
- Threshold sweep: τ_disagree ∈ {0.15, 0.20, 0.25, 0.30, 0.35, 0.40}; for each, report recall of correct-classification among cases that would be auto-approved (agreement ≥ τ) vs flagged (agreement < τ).
Decision rule for deployment: if flagged-bucket correctness is meaningfully lower than auto-approved correctness at some τ, adopt τ as the "recommend-clinician" threshold. This becomes a deployment artefact, not just an analysis.
A policy-grade XAI artefact: "When the two streams disagree by more than τ, we do not make a unilateral prediction; we recommend clinical follow-up." That is a meaningful, testable claim that goes beyond naïve fusion.
| Metric | Target | Notebook |
|---|---|---|
| Grad-CAM AUC-Deletion (mask top-K pixels → measure prob drop) | Steeper drop = more faithful | sector4_xai/03_gradcam_faithfulness.ipynb |
| Grad-CAM AUC-Insertion (start blur, insert top-K) | Faster rise = more faithful | same |
| SHAP stability (1000-row bootstrap, feature-rank Spearman) | ρ > 0.9 | sector4_xai/01_shap_analysis.ipynb |
| SHAP vs LIME top-5 rank agreement | ρ > 0.7 expected | sector4_xai/04_shap_vs_lime.ipynb |
| Counterfactual validity (flipping top feature actually moves prediction) | ≥ 95 % | sector4_xai/01_shap_analysis.ipynb |
Note on sample size for Grad-CAM faithfulness: the blind test has 195 ASD + 197 Non-ASD = 392 images; run on the full set. For SHAP stability, use the 1,838-row parent-only Stage-2 set.
Pick and render all plots + plain-language text for:
- High-risk, both streams agree, true ASD
- Low-risk, both streams agree, true Non-ASD
- High-risk fused but facial low, Q-CHAT high (Q-CHAT drives, confound-resistant)
- High-risk fused, facial high, Q-CHAT low (disagreement → review)
- False positive — system flags but ground truth is Non-ASD (what features drove the error?)
- False negative — system misses a true ASD case (which modality failed?)
Each case shows: inputs, all four panels (SHAP bar, SHAP waterfall or force, Grad-CAM overlay, modality bar), plain-language paragraph, retrospective commentary paragraph.
- SHAP vs LIME on 300 test rows: rank agreement, runtime
- Grad-CAM target-layer sensitivity:
conv2d_14vsconv2d_13— faithfulness difference - α-perturbation sensitivity on fusion attribution: does the dominant-modality designation flip under α ∈ {0.10, 0.15, 0.20}?
- 3 rendered case studies, 5-10 non-clinical parents
- 3 Likert items: understandability, trust, what's missing
- Report N, medians, and a qualitative theme pass. Do not over-claim.
backEnd/
xai/ NEW module
__init__.py
schemas.py Pydantic response models (section 3.4)
qchat_shap.py shap.Explainer + counterfactual + LIME
facial_gradcam.py Port of GradCAM_ASD class from notebook
facial_probe.py Linear probe coefficient attribution
fusion_attribution.py Section 4.1 math + agreement bucket
clinical_translator.py SHAP → plain English, with A-code → behaviour map
qchat_feature_map.py 12-feature → DSM-5 criterion + behaviour name table
figure_render.py base64 PNG encoding helpers
routers/
asd_router.py Add 4 new endpoints; load explainers at module init
All explainers load once at module import, same as current xgb_final and fold_5_best loading in asd_router.py:
# backEnd/xai/__init__.py
from xai.qchat_shap import build_shap_explainer
from xai.facial_gradcam import GradCAM_ASD
SHAP_EXPLAINER = build_shap_explainer(xgb_final) # one-time
GRADCAM = GradCAM_ASD(fold5_model, target_conv_layer='conv2d_14')No new heavy dependencies. shap, matplotlib, opencv-python are already in requirements.txt. Only lime is new; add lime==0.2.0.1 to requirements.
The GradCAM_ASD class in stage4_final_model_evaluation.ipynb §5 works on a loaded model in-place. Copy it into backEnd/xai/facial_gradcam.py with a single change: accept a numpy RGB array from /predict-face instead of reading from disk. Preserve VGG mean subtraction and conv2d_14 target exactly.
# backEnd/xai/qchat_shap.py
import shap
def build_shap_explainer(xgb_model):
# pass a small background set if available; otherwise Explainer will handle it
return shap.Explainer(xgb_model) # XGBoost 2.x compatible
def explain_row(explainer, feature_vector, feature_names):
exp = explainer(feature_vector[None, :]) # (1, n_features)
values = exp.values[0]
base = float(exp.base_values[0])
return base, valuesThis replicates the pattern already proven in sector2/Stage_2/qchat_model_training.ipynb.
Derived directly from Q-CHAT-10 and the Stage-2 SHAP ranking we have:
| Code | Behaviour | DSM-5 criterion | Mean |SHAP| (Stage-2) |
|---|---|---|---|
| A4 | Protodeclarative pointing (joint attention) | A2 – nonverbal communication | 1.57 |
| A7 | Empathic response | A1 – social-emotional reciprocity | 1.53 |
| A8 | Typicality of first words | B – restricted/repetitive | 1.21 |
| A1 | Response to name | A1 – social-emotional reciprocity | 1.14 |
| A9 | Use of simple gestures | A2 – nonverbal communication | 1.13 |
| A10 | Visual fixation / unusual staring | B – restricted/repetitive | 1.08 |
| A2 | Eye contact quality | A1 – social-emotional reciprocity | 0.93 |
| A6 | Gaze following | A2 – nonverbal communication | 0.88 |
| A5 | Pretend play | A3 – peer relationships | 0.87 |
| A3 | Protoimperative pointing | A2 – nonverbal communication | 0.41 |
| Sex_M | Sex (male) | — | 0.34 |
| Family_mem_with_ASD_Yes | Family history | — | 0.13 |
These names + DSM-5 labels go in backEnd/xai/qchat_feature_map.py as a dict. The translator uses them to emit sentences like: "The strongest driver of this score was A4 (pointing to show shared interest, a nonverbal communication indicator), which contributed 31% of the absolute SHAP magnitude."
frontEnd/components/xai/
ShapContributionsChart.tsx horizontal bar; green = decreases risk, red = increases
ShapWaterfall.tsx optional; alternative view
CounterfactualTable.tsx "if A4 were 0, score drops to 45% (-23pp)"
GradcamOverlay.tsx renders base64 PNG with toggle and caveat tooltip
ModalityAttributionBar.tsx stacked bar showing facial vs qchat contributions
AgreementBadge.tsx 3-colour state badge + tooltip
PlainLanguagePanel.tsx expandable text card
Wire ShapContributionsChart + CounterfactualTable + PlainLanguagePanel into asd-qchat-result.tsx.
Wire ModalityAttributionBar + AgreementBadge + GradcamOverlay (toggle) + PlainLanguagePanel into asd-result.tsx.
mlModels/autisumDetect/sector4_xai/
01_shap_analysis.ipynb full SHAP; per-prediction values; stability; top-driver profile
02_agreement_vs_correctness.ipynb section 4.2 experiment
03_gradcam_faithfulness.ipynb AUC-Del / AUC-Ins on full blind test
04_shap_vs_lime.ipynb method comparison on 300 rows
05_case_studies.ipynb the 6 canonical cases rendered end-to-end
results/
agreement_bucket_metrics.json
gradcam_faithfulness.json
shap_stability.json
plots/
agreement_vs_correctness.png
gradcam_deletion_insertion_curves.png
shap_vs_lime_rank_agreement.png
case_study_*.png (6 files)
| Week | Deliverables |
|---|---|
| 1 | xai/schemas.py, qchat_shap.py, facial_probe.py, fusion_attribution.py, clinical_translator.py. /explain-qchat + /explain-fused endpoints functional. Notebook 01. |
| 2 | facial_gradcam.py (ported class) and /explain-face + /explain-all. Frontend components wired into both result screens. Notebook 02 (agreement experiment). |
| 3 | Notebook 03 (faithfulness), 04 (SHAP vs LIME), 05 (case studies). Thesis chapter draft. Optional user study. |
Chapter 6 — Explainability for Multi-Modal ASD Screening
- 6.1 Motivation (references § 1)
- 6.2 Related work (§ 2)
- 6.3 Method
- 6.3.1 SHAP on the Q-CHAT XGBoost (extends existing offline work to live inference)
- 6.3.2 Grad-CAM on the facial CNN (ported class, target
conv2d_14) - 6.3.3 Linear-probe coefficient attribution as a complement to Grad-CAM
- 6.3.4 One-flip counterfactuals for Q-CHAT
- 6.3.5 Per-sample fusion attribution + agreement-as-confidence (§ 4)
- 6.3.6 Clinical translator (§ 6.5)
- 6.4 Evaluation
- 6.4.1 Faithfulness (AUC-Del / AUC-Ins, SHAP stability)
- 6.4.2 Cross-method agreement (SHAP vs LIME)
- 6.4.3 Agreement-vs-correctness experiment and deployment threshold
- 6.4.4 Six case studies
- 6.5 Discussion
- Does XAI fix the GradCAM-exposed confound? (Honest answer: no — but it surfaces it to the user)
- Limits of SHAP on a model that reconstructs a scoring rule (the A1-A10 beeswarm is honest, not a discovery)
- 6.6 Summary
- Chapter 3 (Background): ~3 pages on SHAP, Grad-CAM, LIME, counterfactuals
- Chapter 4 (System Design): updated architecture figure with XAI layer
- Chapter 7 (Conclusion): explainability listed as distinct contribution
- Abstract + intro strengthened with one sentence each
Realistic: IEEE ICIIS or IEEE SCSE (both SLIIT-adjacent, student-friendly short paper). Stretch: IEEE EMBC if a clinician co-author can be added.
- XAI does not fix the dataset confound. Grad-CAM still shows the model attending to age/photo-style. XAI surfaces this problem rather than resolves it — argue in the thesis that surfacing it to the end user is itself valuable.
- SHAP on the Q-CHAT XGBoost essentially re-derives the clinically hand-crafted Q-CHAT rule (all A1-A10 dominate, demographics near-zero). We cannot claim novel clinical insight from this — only that the model's behaviour is consistent with the instrument.
- No clinician-in-the-loop validation. We can claim internal faithfulness (AUC-Del/Ins), not clinical utility.
- Fusion attribution assumes linear fusion (α·p + (1-α)·p). Extends trivially to any linear combination. Non-linear fusion would require a different decomposition.
- Agreement-vs-correctness is computed on synthetically paired blind-test samples because the two streams have no true patient overlap. State this plainly. It is the same limitation sector3 already acknowledges.
- The optional user study (N≈5-10) is formative, not confirmatory.
Addressing limitations honestly increases the score; do not hide them.
| Dimension | Current | After Tier 1 (qchat + fusion explain) | After Tier 1 + 2 (+ Grad-CAM, faithfulness, agreement experiment) |
|---|---|---|---|
| Problem relevance | 8/10 | 9/10 | 9/10 |
| Methodology depth | 9/10 | 9/10 | 9.5/10 |
| Technical implementation | 8/10 | 8.5/10 | 8.5/10 |
| Dataset quality | 5/10 | 5/10 | 5/10 |
| Results honesty | 9/10 | 9/10 | 9.5/10 |
| Novelty | 7/10 | 8/10 | 8.5/10 |
| End-to-end integration | 7/10 | 8/10 | 8.5/10 |
| Documentation | 7/10 | 8/10 | 8.5/10 |
| Weighted average | 7.5/10 | 8.2/10 | 8.7/10 |
- All 4
/api/asd/explain-*endpoints returning valid Pydantic responses -
backEnd/xai/module with unit tests per component - 5 notebooks in
mlModels/autisumDetect/sector4_xai/, each with results + plots saved -
asd-qchat-result.tsxshows SHAP bar + counterfactual + plain language -
asd-result.tsxshows modality bar + agreement badge + Grad-CAM toggle + plain language - Agreement-vs-correctness experiment produces a defensible τ threshold
- Grad-CAM faithfulness table in the thesis (AUC-Del, AUC-Ins per class)
- SHAP stability table (Spearman ≥ 0.9 target)
- Chapter 6 drafted (first pass)
- Paper outline (5-8 pages) for ICIIS/SCSE
- Create empty scaffold under
backEnd/xai/. - Port
shap.Explainerpattern first (cheapest win, highest research value) + fusion attribution math. - Wire
/api/asd/explain-qchatand/api/asd/explain-fused. - Build
ShapContributionsChart+ModalityAttributionBar+AgreementBadgeand drop them into the result screens. - Run
sector4_xai/01_shap_analysis.ipynband02_agreement_vs_correctness.ipynb. - Supervisor checkpoint before moving to Grad-CAM porting.
This document replaces the earlier version and is grounded in the actual contents of sector1/2/3 notebooks and plots. Update it in-place as scope changes; do not fork it.