lime-audit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. lime_audit-0.1.0/.gitignore +27 -0
  2. lime_audit-0.1.0/LICENSE +21 -0
  3. lime_audit-0.1.0/Makefile +20 -0
  4. lime_audit-0.1.0/PKG-INFO +248 -0
  5. lime_audit-0.1.0/README.md +218 -0
  6. lime_audit-0.1.0/article.md +103 -0
  7. lime_audit-0.1.0/audit/__init__.py +0 -0
  8. lime_audit-0.1.0/audit/analyse_results.py +343 -0
  9. lime_audit-0.1.0/audit/config.py +24 -0
  10. lime_audit-0.1.0/audit/failure_memo.md +119 -0
  11. lime_audit-0.1.0/audit/generate_charts.py +209 -0
  12. lime_audit-0.1.0/audit/metrics.py +94 -0
  13. lime_audit-0.1.0/audit/protocol.md +37 -0
  14. lime_audit-0.1.0/audit/requirements.txt +7 -0
  15. lime_audit-0.1.0/audit/results/chart_faithfulness_bars.png +0 -0
  16. lime_audit-0.1.0/audit/results/chart_faithfulness_scatter.png +0 -0
  17. lime_audit-0.1.0/audit/results/chart_jaccard_by_input.png +0 -0
  18. lime_audit-0.1.0/audit/results/deletion_faithfulness.csv +29 -0
  19. lime_audit-0.1.0/audit/results/environment.json +23 -0
  20. lime_audit-0.1.0/audit/results/raw_attributions.csv +146 -0
  21. lime_audit-0.1.0/audit/results/stability_metrics.csv +30 -0
  22. lime_audit-0.1.0/audit/results/summary.json +107 -0
  23. lime_audit-0.1.0/audit/runner.py +506 -0
  24. lime_audit-0.1.0/audit/test_set.json +187 -0
  25. lime_audit-0.1.0/audit/verify.py +71 -0
  26. lime_audit-0.1.0/backend/.dockerignore +8 -0
  27. lime_audit-0.1.0/backend/Dockerfile +16 -0
  28. lime_audit-0.1.0/backend/analyser.py +155 -0
  29. lime_audit-0.1.0/backend/main.py +145 -0
  30. lime_audit-0.1.0/backend/requirements.txt +9 -0
  31. lime_audit-0.1.0/frontend/.gitignore +41 -0
  32. lime_audit-0.1.0/frontend/app/audit/page.js +297 -0
  33. lime_audit-0.1.0/frontend/app/favicon.ico +0 -0
  34. lime_audit-0.1.0/frontend/app/globals.css +26 -0
  35. lime_audit-0.1.0/frontend/app/layout.js +38 -0
  36. lime_audit-0.1.0/frontend/app/page.js +1022 -0
  37. lime_audit-0.1.0/frontend/eslint.config.mjs +16 -0
  38. lime_audit-0.1.0/frontend/jsconfig.json +7 -0
  39. lime_audit-0.1.0/frontend/next.config.mjs +6 -0
  40. lime_audit-0.1.0/frontend/package-lock.json +6581 -0
  41. lime_audit-0.1.0/frontend/package.json +23 -0
  42. lime_audit-0.1.0/frontend/postcss.config.mjs +7 -0
  43. lime_audit-0.1.0/lime_audit_results/chart_faithfulness_bars.png +0 -0
  44. lime_audit-0.1.0/lime_audit_results/chart_jaccard_by_input.png +0 -0
  45. lime_audit-0.1.0/lime_audit_results/deletion_faithfulness.csv +30 -0
  46. lime_audit-0.1.0/lime_audit_results/environment.json +29 -0
  47. lime_audit-0.1.0/lime_audit_results/raw_attributions.csv +146 -0
  48. lime_audit-0.1.0/lime_audit_results/stability_metrics.csv +30 -0
  49. lime_audit-0.1.0/lime_audit_results/summary.json +178 -0
  50. lime_audit-0.1.0/public/image-1.png +0 -0
  51. lime_audit-0.1.0/public/image-2.png +0 -0
  52. lime_audit-0.1.0/public/image-3.png +0 -0
  53. lime_audit-0.1.0/public/image-4.png +0 -0
  54. lime_audit-0.1.0/public/image-5.png +0 -0
  55. lime_audit-0.1.0/public/image.png +0 -0
  56. lime_audit-0.1.0/pyproject.toml +48 -0
  57. lime_audit-0.1.0/scripts/benchmark_latency.py +80 -0
  58. lime_audit-0.1.0/src/lime_audit/__init__.py +3 -0
  59. lime_audit-0.1.0/src/lime_audit/analyse.py +290 -0
  60. lime_audit-0.1.0/src/lime_audit/charts.py +179 -0
  61. lime_audit-0.1.0/src/lime_audit/cli.py +75 -0
  62. lime_audit-0.1.0/src/lime_audit/config.py +14 -0
  63. lime_audit-0.1.0/src/lime_audit/default_test_set.json +36 -0
  64. lime_audit-0.1.0/src/lime_audit/metrics.py +96 -0
  65. lime_audit-0.1.0/src/lime_audit/runner.py +297 -0
  66. lime_audit-0.1.0/tests/__init__.py +0 -0
  67. lime_audit-0.1.0/tests/test_regression.py +34 -0
  68. lime_audit-0.1.0/xai_forensics_explained.md +232 -0
@@ -0,0 +1,27 @@
1
+ .env
2
+ .env.local
3
+ audit/__pycache__
4
+ __pycache__/
5
+ skills-lock.json
6
+ *.pyc
7
+ *.pyo
8
+ REVIEW.md
9
+ venv/
10
+ .venv/
11
+ backend/venv/
12
+ backend/.venv/
13
+
14
+ node_modules/
15
+ .next/
16
+ out/
17
+
18
+ *.egg-info/
19
+ dist/
20
+ build/
21
+
22
+ .cache/
23
+ .DS_Store
24
+
25
+ .agents
26
+ Decisions.md
27
+ Rules.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Parshvi Jain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ .PHONY: audit analyse charts verify clean
2
+
3
+ PYTHON ?= .venv/Scripts/python
4
+
5
+ audit:
6
+ $(PYTHON) -m audit.runner
7
+ $(PYTHON) -m audit.analyse_results
8
+ $(PYTHON) -m audit.generate_charts
9
+
10
+ analyse:
11
+ $(PYTHON) -m audit.analyse_results
12
+
13
+ charts:
14
+ $(PYTHON) -m audit.generate_charts
15
+
16
+ verify:
17
+ $(PYTHON) -c "from audit.verify import verify_reproducibility; verify_reproducibility()"
18
+
19
+ clean:
20
+ $(PYTHON) -c "import glob, os; [os.remove(f) for f in glob.glob('audit/results/*.csv') + glob.glob('audit/results/*.json') + glob.glob('audit/results/*.png')]"
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.5
2
+ Name: lime-audit
3
+ Version: 0.1.0
4
+ Summary: Audit LIME explanations for stability and faithfulness on any HuggingFace text classifier
5
+ Project-URL: Homepage, https://github.com/parshvi1508/XAI_Forensic
6
+ Project-URL: Documentation, https://github.com/parshvi1508/XAI_Forensic#readme
7
+ Project-URL: Issues, https://github.com/parshvi1508/XAI_Forensic/issues
8
+ Author-email: Parshvi Jain <parshvijain1508@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: LIME,NLP,XAI,audit,explainability,transformers
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: lime>=0.2
19
+ Requires-Dist: matplotlib>=3.7
20
+ Requires-Dist: numpy>=1.24
21
+ Requires-Dist: scikit-learn>=1.2
22
+ Requires-Dist: scipy>=1.10
23
+ Requires-Dist: torch>=2.0
24
+ Requires-Dist: transformers>=4.30
25
+ Provides-Extra: dev
26
+ Requires-Dist: build; extra == 'dev'
27
+ Requires-Dist: pytest; extra == 'dev'
28
+ Requires-Dist: twine; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # XAI Forensics
32
+
33
+ A diagnostic tool for evaluating when LIME token attributions are trustworthy on transformer sentiment classifiers. Runs three independent forensic checks (attribution stability, counterfactual faithfulness, cross-model agreement) and validates results against a 30-input pre-registered audit.
34
+
35
+ **Key finding:** high-confidence predictions (>95%) produce the least stable attributions (mean Jaccard 0.46 on strong baselines), while lexical-shortcut inputs are perfectly faithful (Jaccard 1.0, 100% flip rate). This tool shows you when LIME is signal vs. noise.
36
+
37
+ Sentiment classification is the controlled test task. The project evaluates the explanation method, not the classifier output.
38
+
39
+
40
+
41
+ ## Live Demo
42
+
43
+ | Component | Link |
44
+ |-----------|------|
45
+ | Frontend | [xai-forensic.vercel.app](https://xai-forensic.vercel.app/) |
46
+ | Backend API | [jainparshvi-xai-forensics-backend.hf.space](https://jainparshvi-xai-forensics-backend.hf.space) |
47
+ | API Docs (Swagger) | [/docs](https://jainparshvi-xai-forensics-backend.hf.space/docs) |
48
+
49
+ **Demo input:** `I am not entirely unhappy with this result.`
50
+
51
+ This sentence contains a double negation that causes the two models to genuinely disagree.
52
+
53
+ > The backend runs on Hugging Face Spaces free tier. If the Space has been idle, the first request triggers a cold start (30-60 seconds) while models download. Subsequent requests are faster.
54
+
55
+
56
+
57
+ ![alt text](public/image.png)
58
+ ![alt text](public/image-1.png)
59
+ ![alt text](public/image-2.png)
60
+ ![alt text](public/image-3.png)
61
+ ![alt text](public/image-4.png)
62
+ ![alt text](public/image-5.png)
63
+
64
+ ## What This Does
65
+
66
+ XAI Forensics runs three independent diagnostic checks on any short English text:
67
+
68
+ 1. **WHY (Attribution Stability)** - Tests whether LIME can produce a stable, reproducible token ranking for a given prediction. Seeds the random state for deterministic output.
69
+ 2. **FLIP (Counterfactual Faithfulness)** - Tests whether the tokens LIME identifies as important are actually causally influential. Removes the top-attributed word and measures the real confidence shift.
70
+ 3. **DISAGREE (Cross-Model Consistency)** - Tests whether the prediction itself is domain-stable enough to warrant attribution analysis. Compares DistilBERT-SST2 against Twitter-RoBERTa.
71
+
72
+ Each check targets a different failure mode of LIME: instability under re-sampling, unfaithfulness to the model's actual reasoning, and domain sensitivity of the underlying prediction.
73
+
74
+ ### Audit Results
75
+
76
+ A pre-registered 30-input audit (5 seeds each, 150 total LIME runs) found:
77
+ - Overall mean Jaccard stability: **0.81** (passes 0.6 threshold)
78
+ - Deletion faithfulness direction correct: **89.3%** (passes 70% threshold)
79
+ - Strong baselines category (high confidence): **Jaccard 0.67** (lowest category, driven by redundant evidence)
80
+ - Lexical shortcuts category: **100% label flip rate** (highest faithfulness)
81
+
82
+ Full interactive results available at [/audit](https://xai-forensic.vercel.app/audit).
83
+
84
+ ## How It Works
85
+
86
+ ### WHY: LIME Token Attribution
87
+
88
+ Uses [LIME](https://arxiv.org/abs/1602.04938) (Ribeiro et al., 2016) for model-agnostic local explanations. LIME generates perturbed versions of the input text, reruns the classifier on each perturbation, and fits a local linear model to estimate which tokens most influenced the prediction.
89
+
90
+ - Runs 300 perturbation samples per explanation
91
+ - Returns the top 10 tokens with signed weights (positive = pushes toward positive class)
92
+ - SHAP was considered and rejected: slower for transformers, expensive on free-tier CPU
93
+ - Attention weights were rejected as explanations per Jain and Wallace (2019)
94
+
95
+ ### FLIP: Counterfactual Word Removal
96
+
97
+ Removes each word one at a time, reruns inference, and finds the word whose removal causes the largest confidence shift. Shows the full before-and-after comparison: original label, modified label, confidence delta, and whether the verdict changed.
98
+
99
+ - Greedy O(n) search over words in the input
100
+ - Deterministic, no generation model required
101
+ - Word removal can create ungrammatical text (documented limitation)
102
+ - Does not always flip the label on highly confident predictions
103
+
104
+ ### DISAGREE: Dual-Model Divergence
105
+
106
+ Runs the same text through both models and computes the absolute difference in their positive-class confidence scores. A high divergence score means the models have different views on the same text, which reveals linguistic ambiguity across training domains.
107
+
108
+ - Divergence = abs(positive_score_A - positive_score_B)
109
+ - This is an interpretable confidence delta, not a formal divergence metric like KL divergence
110
+ - KL divergence was considered and rejected: harder to interpret for non-technical audiences
111
+
112
+
113
+ ## Models Used
114
+
115
+ | Model | Training Data | Strength |
116
+ |-------|---------------|----------|
117
+ | [distilbert-base-uncased-finetuned-sst-2-english](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english) | SST-2 movie reviews | Formal, structured English |
118
+ | [cardiffnlp/twitter-roberta-base-sentiment-latest](https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment-latest) | 124M tweets | Sarcasm, slang, informal tone |
119
+
120
+ These two models are chosen because they genuinely disagree on informal or ambiguous language. DistilBERT expects clean, formal text. Twitter-RoBERTa handles internet language better. This domain mismatch makes the DISAGREE panel meaningful rather than artificial.
121
+
122
+ ## Architecture
123
+
124
+ ```
125
+ Frontend (Vercel) Backend (HF Spaces Docker)
126
+ Next.js + Tailwind FastAPI + PyTorch
127
+ | |
128
+ |--- POST /why ------------->|--- LIME (300 perturbations)
129
+ |--- POST /flip ------------->|--- Greedy word removal
130
+ |--- POST /disagree ---------->|--- Dual model inference
131
+ | |
132
+ |<---- JSON responses ---------|
133
+ ```
134
+
135
+ - Frontend and backend are fully decoupled
136
+ - All endpoints accept `{"text": "..."}` and return structured JSON
137
+ - Frontend calls all three endpoints in parallel using `Promise.allSettled`
138
+ - Models load once at container startup, not per request
139
+
140
+ ## Run it Locally
141
+
142
+ ### Backend
143
+
144
+ ```bash
145
+ cd backend
146
+ python -m venv venv
147
+ source venv/bin/activate # Windows: venv\Scripts\activate
148
+ pip install -r requirements.txt
149
+ uvicorn main:app --reload --port 8000
150
+ ```
151
+
152
+ First run downloads models (~500MB total). Subsequent starts use cached weights.
153
+
154
+ ### Frontend
155
+
156
+ ```bash
157
+ cd frontend
158
+ npm install
159
+ npm run dev
160
+ ```
161
+
162
+ Frontend runs at `http://localhost:3000` and calls the backend at `http://localhost:8000` by default. To change the backend URL, set `NEXT_PUBLIC_API_URL` in `frontend/.env.local`.
163
+
164
+
165
+ ## API Endpoints
166
+
167
+ All endpoints accept POST with `Content-Type: application/json` and a body of `{"text": "your input"}`.
168
+
169
+ | Endpoint | Returns |
170
+ |----------|---------|
171
+ | `POST /why` | Predicted label, confidence, and top 10 token weights from LIME |
172
+ | `POST /flip` | Original and modified labels, removed word, confidence delta, verdict changed status |
173
+ | `POST /disagree` | Both model predictions, divergence score, agreement status |
174
+ | `GET /` | Health check: `{"status": "ok"}` |
175
+ | `GET /docs` | Interactive Swagger API documentation |
176
+
177
+ Input is limited to 1000 characters. Requests with longer text return HTTP 400.
178
+
179
+
180
+ ## Latency Notes
181
+
182
+ | Endpoint | Approx. CPU time | Why |
183
+ |----------|-------------------|-----|
184
+ | `/why` | 15-45 seconds | LIME runs 300 model inference calls per explanation |
185
+ | `/flip` | 2-10 seconds | One model call per word in the input |
186
+ | `/disagree` | Under 1 second | Two forward passes |
187
+
188
+ LIME is the main latency source. CPU inference is used because deployment is on the Hugging Face free tier, which does not guarantee GPU availability. This is acceptable for a demo tool with short text inputs.
189
+
190
+
191
+ ## Latency Benchmark
192
+
193
+ The backend runs on Hugging Face Spaces free-tier CPU. The table below reports median endpoint runtime across 3 runs after one warmup request. Timings are approximate because free-tier CPU performance varies.
194
+
195
+ | Words | WHY median | FLIP median | DISAGREE median |
196
+ |---:|---:|---:|---:|
197
+ | 5 | 4.5s | 414ms | 98ms |
198
+ | 10 | 5.8s | 270ms | 75ms |
199
+ | 20 | 6.1s | 475ms | 72ms |
200
+ | 50 | 8.3s | 4.2s | 104ms |
201
+
202
+ WHY is slowest because LIME generates perturbed versions of the input and reruns model inference many times. FLIP scales with word count because it removes candidate words one at a time and reruns inference. DISAGREE is fastest because it only runs two model forward passes.
203
+
204
+ The benchmark can be reproduced with `scripts/benchmark_latency.py`.
205
+
206
+
207
+ ## Security and Cost
208
+
209
+ - No paid APIs. Both models are public Hugging Face models.
210
+ - No database. Nothing is stored.
211
+ - No authentication. This is a public demo tool.
212
+ - No user data is collected, logged, or persisted.
213
+ - CORS allows all origins (appropriate for a public demo with no sensitive operations).
214
+ - Input capped at 1000 characters to prevent LIME timeouts on free-tier CPU.
215
+
216
+
217
+ ## Known Behavior and Limitations
218
+
219
+ Single-word inputs are not ideal for this tool. LIME works by perturbing parts of the input and observing prediction changes. With only one word, there is very little structure to perturb, so the explanation can be unstable or uninformative. In testing, short inputs such as "Fine." can produce domain-sensitive behavior because different models interpret minimal context differently.
220
+
221
+ Highly confident predictions may not flip after one-word removal. For example, strongly positive sentences such as "I absolutely love this, it is the best thing ever." often remain positive after removing one word. This does not mean every word is irrelevant. It means the model found enough evidence across the sentence that removing one token did not change the final verdict.
222
+
223
+ Counterfactual removal can create ungrammatical text because the method deletes a word rather than rewriting the sentence. This is a deliberate MVP tradeoff. The FLIP panel is a fragility test, not a full natural-language counterfactual generator. For example, removing a key word from "I am not entirely unhappy with this result." can flip the verdict, but the modified sentence may not always be natural English.
224
+
225
+ Additional limitations:
226
+
227
+ - LIME explanation takes 15-45 seconds on CPU for short text
228
+ - Input limited to 1000 characters
229
+ - Hugging Face free tier sleeps after inactivity; first request after sleep has a 30-60 second cold start
230
+ - Only tested on English text
231
+ - Sentiment-specific; adapting to other tasks would require different models and possibly different XAI methods
232
+
233
+
234
+ ## Tech Stack
235
+
236
+ | Layer | Technology |
237
+ |-------|-----------|
238
+ | Backend | Python, FastAPI, PyTorch, Hugging Face Transformers, LIME |
239
+ | Frontend | Next.js, React, Tailwind CSS |
240
+ | Backend deployment | Hugging Face Spaces (Docker) |
241
+ | Frontend deployment | Vercel |
242
+ | ML models | DistilBERT-SST2, Twitter-RoBERTa |
243
+ | Infrastructure cost | Zero (free tier only) |
244
+
245
+
246
+ ## License
247
+
248
+ MIT
@@ -0,0 +1,218 @@
1
+ # XAI Forensics
2
+
3
+ A diagnostic tool for evaluating when LIME token attributions are trustworthy on transformer sentiment classifiers. Runs three independent forensic checks (attribution stability, counterfactual faithfulness, cross-model agreement) and validates results against a 30-input pre-registered audit.
4
+
5
+ **Key finding:** high-confidence predictions (>95%) produce the least stable attributions (mean Jaccard 0.46 on strong baselines), while lexical-shortcut inputs are perfectly faithful (Jaccard 1.0, 100% flip rate). This tool shows you when LIME is signal vs. noise.
6
+
7
+ Sentiment classification is the controlled test task. The project evaluates the explanation method, not the classifier output.
8
+
9
+
10
+
11
+ ## Live Demo
12
+
13
+ | Component | Link |
14
+ |-----------|------|
15
+ | Frontend | [xai-forensic.vercel.app](https://xai-forensic.vercel.app/) |
16
+ | Backend API | [jainparshvi-xai-forensics-backend.hf.space](https://jainparshvi-xai-forensics-backend.hf.space) |
17
+ | API Docs (Swagger) | [/docs](https://jainparshvi-xai-forensics-backend.hf.space/docs) |
18
+
19
+ **Demo input:** `I am not entirely unhappy with this result.`
20
+
21
+ This sentence contains a double negation that causes the two models to genuinely disagree.
22
+
23
+ > The backend runs on Hugging Face Spaces free tier. If the Space has been idle, the first request triggers a cold start (30-60 seconds) while models download. Subsequent requests are faster.
24
+
25
+
26
+
27
+ ![alt text](public/image.png)
28
+ ![alt text](public/image-1.png)
29
+ ![alt text](public/image-2.png)
30
+ ![alt text](public/image-3.png)
31
+ ![alt text](public/image-4.png)
32
+ ![alt text](public/image-5.png)
33
+
34
+ ## What This Does
35
+
36
+ XAI Forensics runs three independent diagnostic checks on any short English text:
37
+
38
+ 1. **WHY (Attribution Stability)** - Tests whether LIME can produce a stable, reproducible token ranking for a given prediction. Seeds the random state for deterministic output.
39
+ 2. **FLIP (Counterfactual Faithfulness)** - Tests whether the tokens LIME identifies as important are actually causally influential. Removes the top-attributed word and measures the real confidence shift.
40
+ 3. **DISAGREE (Cross-Model Consistency)** - Tests whether the prediction itself is domain-stable enough to warrant attribution analysis. Compares DistilBERT-SST2 against Twitter-RoBERTa.
41
+
42
+ Each check targets a different failure mode of LIME: instability under re-sampling, unfaithfulness to the model's actual reasoning, and domain sensitivity of the underlying prediction.
43
+
44
+ ### Audit Results
45
+
46
+ A pre-registered 30-input audit (5 seeds each, 150 total LIME runs) found:
47
+ - Overall mean Jaccard stability: **0.81** (passes 0.6 threshold)
48
+ - Deletion faithfulness direction correct: **89.3%** (passes 70% threshold)
49
+ - Strong baselines category (high confidence): **Jaccard 0.67** (lowest category, driven by redundant evidence)
50
+ - Lexical shortcuts category: **100% label flip rate** (highest faithfulness)
51
+
52
+ Full interactive results available at [/audit](https://xai-forensic.vercel.app/audit).
53
+
54
+ ## How It Works
55
+
56
+ ### WHY: LIME Token Attribution
57
+
58
+ Uses [LIME](https://arxiv.org/abs/1602.04938) (Ribeiro et al., 2016) for model-agnostic local explanations. LIME generates perturbed versions of the input text, reruns the classifier on each perturbation, and fits a local linear model to estimate which tokens most influenced the prediction.
59
+
60
+ - Runs 300 perturbation samples per explanation
61
+ - Returns the top 10 tokens with signed weights (positive = pushes toward positive class)
62
+ - SHAP was considered and rejected: slower for transformers, expensive on free-tier CPU
63
+ - Attention weights were rejected as explanations per Jain and Wallace (2019)
64
+
65
+ ### FLIP: Counterfactual Word Removal
66
+
67
+ Removes each word one at a time, reruns inference, and finds the word whose removal causes the largest confidence shift. Shows the full before-and-after comparison: original label, modified label, confidence delta, and whether the verdict changed.
68
+
69
+ - Greedy O(n) search over words in the input
70
+ - Deterministic, no generation model required
71
+ - Word removal can create ungrammatical text (documented limitation)
72
+ - Does not always flip the label on highly confident predictions
73
+
74
+ ### DISAGREE: Dual-Model Divergence
75
+
76
+ Runs the same text through both models and computes the absolute difference in their positive-class confidence scores. A high divergence score means the models have different views on the same text, which reveals linguistic ambiguity across training domains.
77
+
78
+ - Divergence = abs(positive_score_A - positive_score_B)
79
+ - This is an interpretable confidence delta, not a formal divergence metric like KL divergence
80
+ - KL divergence was considered and rejected: harder to interpret for non-technical audiences
81
+
82
+
83
+ ## Models Used
84
+
85
+ | Model | Training Data | Strength |
86
+ |-------|---------------|----------|
87
+ | [distilbert-base-uncased-finetuned-sst-2-english](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english) | SST-2 movie reviews | Formal, structured English |
88
+ | [cardiffnlp/twitter-roberta-base-sentiment-latest](https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment-latest) | 124M tweets | Sarcasm, slang, informal tone |
89
+
90
+ These two models are chosen because they genuinely disagree on informal or ambiguous language. DistilBERT expects clean, formal text. Twitter-RoBERTa handles internet language better. This domain mismatch makes the DISAGREE panel meaningful rather than artificial.
91
+
92
+ ## Architecture
93
+
94
+ ```
95
+ Frontend (Vercel) Backend (HF Spaces Docker)
96
+ Next.js + Tailwind FastAPI + PyTorch
97
+ | |
98
+ |--- POST /why ------------->|--- LIME (300 perturbations)
99
+ |--- POST /flip ------------->|--- Greedy word removal
100
+ |--- POST /disagree ---------->|--- Dual model inference
101
+ | |
102
+ |<---- JSON responses ---------|
103
+ ```
104
+
105
+ - Frontend and backend are fully decoupled
106
+ - All endpoints accept `{"text": "..."}` and return structured JSON
107
+ - Frontend calls all three endpoints in parallel using `Promise.allSettled`
108
+ - Models load once at container startup, not per request
109
+
110
+ ## Run it Locally
111
+
112
+ ### Backend
113
+
114
+ ```bash
115
+ cd backend
116
+ python -m venv venv
117
+ source venv/bin/activate # Windows: venv\Scripts\activate
118
+ pip install -r requirements.txt
119
+ uvicorn main:app --reload --port 8000
120
+ ```
121
+
122
+ First run downloads models (~500MB total). Subsequent starts use cached weights.
123
+
124
+ ### Frontend
125
+
126
+ ```bash
127
+ cd frontend
128
+ npm install
129
+ npm run dev
130
+ ```
131
+
132
+ Frontend runs at `http://localhost:3000` and calls the backend at `http://localhost:8000` by default. To change the backend URL, set `NEXT_PUBLIC_API_URL` in `frontend/.env.local`.
133
+
134
+
135
+ ## API Endpoints
136
+
137
+ All endpoints accept POST with `Content-Type: application/json` and a body of `{"text": "your input"}`.
138
+
139
+ | Endpoint | Returns |
140
+ |----------|---------|
141
+ | `POST /why` | Predicted label, confidence, and top 10 token weights from LIME |
142
+ | `POST /flip` | Original and modified labels, removed word, confidence delta, verdict changed status |
143
+ | `POST /disagree` | Both model predictions, divergence score, agreement status |
144
+ | `GET /` | Health check: `{"status": "ok"}` |
145
+ | `GET /docs` | Interactive Swagger API documentation |
146
+
147
+ Input is limited to 1000 characters. Requests with longer text return HTTP 400.
148
+
149
+
150
+ ## Latency Notes
151
+
152
+ | Endpoint | Approx. CPU time | Why |
153
+ |----------|-------------------|-----|
154
+ | `/why` | 15-45 seconds | LIME runs 300 model inference calls per explanation |
155
+ | `/flip` | 2-10 seconds | One model call per word in the input |
156
+ | `/disagree` | Under 1 second | Two forward passes |
157
+
158
+ LIME is the main latency source. CPU inference is used because deployment is on the Hugging Face free tier, which does not guarantee GPU availability. This is acceptable for a demo tool with short text inputs.
159
+
160
+
161
+ ## Latency Benchmark
162
+
163
+ The backend runs on Hugging Face Spaces free-tier CPU. The table below reports median endpoint runtime across 3 runs after one warmup request. Timings are approximate because free-tier CPU performance varies.
164
+
165
+ | Words | WHY median | FLIP median | DISAGREE median |
166
+ |---:|---:|---:|---:|
167
+ | 5 | 4.5s | 414ms | 98ms |
168
+ | 10 | 5.8s | 270ms | 75ms |
169
+ | 20 | 6.1s | 475ms | 72ms |
170
+ | 50 | 8.3s | 4.2s | 104ms |
171
+
172
+ WHY is slowest because LIME generates perturbed versions of the input and reruns model inference many times. FLIP scales with word count because it removes candidate words one at a time and reruns inference. DISAGREE is fastest because it only runs two model forward passes.
173
+
174
+ The benchmark can be reproduced with `scripts/benchmark_latency.py`.
175
+
176
+
177
+ ## Security and Cost
178
+
179
+ - No paid APIs. Both models are public Hugging Face models.
180
+ - No database. Nothing is stored.
181
+ - No authentication. This is a public demo tool.
182
+ - No user data is collected, logged, or persisted.
183
+ - CORS allows all origins (appropriate for a public demo with no sensitive operations).
184
+ - Input capped at 1000 characters to prevent LIME timeouts on free-tier CPU.
185
+
186
+
187
+ ## Known Behavior and Limitations
188
+
189
+ Single-word inputs are not ideal for this tool. LIME works by perturbing parts of the input and observing prediction changes. With only one word, there is very little structure to perturb, so the explanation can be unstable or uninformative. In testing, short inputs such as "Fine." can produce domain-sensitive behavior because different models interpret minimal context differently.
190
+
191
+ Highly confident predictions may not flip after one-word removal. For example, strongly positive sentences such as "I absolutely love this, it is the best thing ever." often remain positive after removing one word. This does not mean every word is irrelevant. It means the model found enough evidence across the sentence that removing one token did not change the final verdict.
192
+
193
+ Counterfactual removal can create ungrammatical text because the method deletes a word rather than rewriting the sentence. This is a deliberate MVP tradeoff. The FLIP panel is a fragility test, not a full natural-language counterfactual generator. For example, removing a key word from "I am not entirely unhappy with this result." can flip the verdict, but the modified sentence may not always be natural English.
194
+
195
+ Additional limitations:
196
+
197
+ - LIME explanation takes 15-45 seconds on CPU for short text
198
+ - Input limited to 1000 characters
199
+ - Hugging Face free tier sleeps after inactivity; first request after sleep has a 30-60 second cold start
200
+ - Only tested on English text
201
+ - Sentiment-specific; adapting to other tasks would require different models and possibly different XAI methods
202
+
203
+
204
+ ## Tech Stack
205
+
206
+ | Layer | Technology |
207
+ |-------|-----------|
208
+ | Backend | Python, FastAPI, PyTorch, Hugging Face Transformers, LIME |
209
+ | Frontend | Next.js, React, Tailwind CSS |
210
+ | Backend deployment | Hugging Face Spaces (Docker) |
211
+ | Frontend deployment | Vercel |
212
+ | ML models | DistilBERT-SST2, Twitter-RoBERTa |
213
+ | Infrastructure cost | Zero (free tier only) |
214
+
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,103 @@
1
+ # I Audited LIME on 30 Inputs and Found It Fails on the Easiest Ones
2
+
3
+ LIME is one of the most cited explanation methods in machine learning. It tells you which words in a sentence drove a classifier's prediction. But does it give you the same answer twice?
4
+
5
+ I ran LIME on 30 carefully chosen inputs across 5 random seeds, measured whether the explanations stayed stable, and tested whether removing the "most important" token actually changed the model's output. The input with the worst stability was not the hardest or most ambiguous one. It was "This is the best product I have ever purchased."
6
+
7
+ ## The Setup
8
+
9
+ I froze a test set of 30 inputs before looking at any results. The inputs span six categories designed to stress-test LIME: negation and minimal pairs ("This is not good" vs. "This is good"), lexical shortcuts where surface cues contradict the true sentiment, ambiguous sentences near the decision boundary, distribution shifts from domains like clinical notes and financial reports, strong baselines that any competent model should handle easily, and edge cases like repeated tokens or alternating contradictions.
10
+
11
+ The model is DistilBERT fine-tuned on SST-2 (the standard sentiment benchmark), pinned to an exact revision hash. LIME runs with 300 perturbation samples and returns the top 10 tokens ranked by attribution weight.
12
+
13
+ I measure two things:
14
+
15
+ **Stability**: For each input, I run LIME with five different random seeds and compute the Jaccard overlap of the top-5 attributed tokens across all 10 seed pairs. A Jaccard of 1.0 means every seed returned the same five tokens. A Jaccard of 0.25 means the explanations barely overlap.
16
+
17
+ **Deletion faithfulness**: Using the canonical seed, I identify the token LIME considers most important and remove it from the input. If LIME says a token has positive weight, removing it should decrease the model's positive score. If the score barely moves, the "most important" token was not actually important.
18
+
19
+ All thresholds, metrics, and the 30 inputs were frozen in a pre-registration protocol before seeing any results. The raw data, every CSV, and the runner code are public.
20
+
21
+ ## The Surprise: Easy Inputs Are the Least Stable
22
+
23
+ The overall mean Jaccard is 0.81 [0.74, 0.87] (95% bootstrap CI), comfortably above the 0.60 threshold. That headline number looks fine. But the per-input distribution tells a different story.
24
+
25
+ ![LIME Attribution Stability Across 5 Random Seeds](audit/results/chart_jaccard_by_input.png)
26
+
27
+ The leftmost bar in the chart, the input with the worst stability of all 29 tested inputs, is Input 21: "This is the best product I have ever purchased." Its mean Jaccard is 0.46, with a minimum of 0.25 across seed pairs. This is an unambiguously positive sentence that any sentiment model handles with near-perfect confidence (0.9999).
28
+
29
+ Why does the easiest input have the worst explanation stability?
30
+
31
+ The sentence has nine whitespace tokens: "This", "is", "the", "best", "product", "I", "have", "ever", "purchased". Only one or two of those carry real sentiment signal ("best", maybe "purchased"). The remaining seven are function words with near-zero LIME weights. Across seeds, LIME assigns slightly different random weights to "is", "the", "I", "have", and "ever", causing them to shuffle positions in the top-5 ranking. The top-1 token is not even consistent, because "best" receives a weight of only 0.008, barely above the noise floor.
32
+
33
+ The instability is not about hard cases. It is about the signal-to-noise ratio between meaningful tokens and filler. Longer sentences with clear sentiment have more function words competing for top-5 slots with near-zero weights, making the ranking fragile.
34
+
35
+ The per-category breakdown confirms this. Strong baselines, the easiest category, have the second-worst mean Jaccard (0.67). Ambiguity, a category you would expect to be unstable, actually scores 0.97 because ambiguous sentences are short and nearly every token carries some signal.
36
+
37
+ | Category | Mean Jaccard | 95% CI |
38
+ |----------|-------------|--------|
39
+ | Ambiguity | 0.97 | [0.93, 1.00] |
40
+ | Edge cases | 0.94 | [0.86, 1.00] |
41
+ | Negation | 0.80 | [0.66, 0.93] |
42
+ | Lexical shortcuts | 0.77 | [0.62, 0.92] |
43
+ | Distribution shift | 0.72 | [0.61, 0.84] |
44
+ | Strong baselines | 0.67 | [0.52, 0.82] |
45
+
46
+ ## LIME Says "This Token Matters." Deletion Says "Nothing Matters."
47
+
48
+ The deletion faithfulness test reveals a second, independent problem. Overall, 89% of deletions are directionally correct: if LIME says a token is positive, removing it decreases the score. That passes the 0.70 threshold. But the magnitude tells a different story.
49
+
50
+ ![LIME Deletion Faithfulness: Confidence vs. Impact](audit/results/chart_faithfulness_scatter.png)
51
+
52
+ The chart shows two distinct clusters. In the upper-left corner, lexical shortcuts: inputs like "The movie was terrible but I loved every minute of it." Removing the decisive token ("loved") causes a confidence swing of 0.987 and a full label flip. LIME works perfectly here because one token genuinely dominates the prediction.
53
+
54
+ In the lower-right corner, strong baselines: inputs where the model is 0.9999 confident. Removing the "most important" token changes the score by 0.0002. Not a single strong baseline input flips its label when the top token is removed. LIME is directionally correct but the deletion test is powerless. When a model outputs 0.9999, removing one word drops it to 0.9995. The direction is right, but the magnitude is meaningless.
55
+
56
+ The per-category faithfulness numbers make this concrete:
57
+
58
+ | Category | Direction Correct | Flip Rate | Mean |Delta| |
59
+ |----------|------------------|-----------|------------|
60
+ | Lexical shortcuts | 100% | 100% | 0.995 |
61
+ | Ambiguity | 100% | 40% | 0.408 |
62
+ | Distribution shift | 100% | 20% | 0.230 |
63
+ | Negation | 80% | 60% | 0.590 |
64
+ | Strong baselines | 80% | 0% | 0.0002 |
65
+ | Edge cases | 75% | 25% | 0.162 |
66
+
67
+ LIME is faithful in direction but blind to magnitude. For high-confidence predictions, the standard one-token deletion test cannot distinguish a genuinely important token from a random one.
68
+
69
+ ## The Tokenizer Problem Nobody Talks About
70
+
71
+ LIME splits text on whitespace. The model uses WordPiece tokenization. These are not the same thing.
72
+
73
+ When LIME sees "I'm", it treats that as one token. When DistilBERT sees "I'm", it splits it into three subword pieces: "i", "'", "m". LIME's perturbation masks whole whitespace tokens, but the model processes subwords. This means LIME's attribution is to whitespace tokens, not to the features the model actually uses. The mismatch is fundamental and cannot be fixed without replacing LIME's perturbation strategy.
74
+
75
+ Across the 29 tested inputs, the model's WordPiece tokenizer produces more tokens than LIME's whitespace splitter in the majority of cases. The worst mismatch occurs on longer sentences where compound words, contractions, and punctuation create divergent tokenizations. LIME attributes importance to a unit of text that does not correspond to how the model reads it.
76
+
77
+ ## When to Trust LIME
78
+
79
+ LIME is not useless. It works well under specific conditions:
80
+
81
+ **Trust LIME when one token dominates.** Lexical shortcuts score 100% direction-correct and 100% label flip. When a sentence has a single decisive word that, if removed, reverses the prediction, LIME finds it reliably.
82
+
83
+ **Trust LIME on short inputs.** Shorter sentences have fewer near-zero-weight function words competing for top-5 slots. "This is good" (3 tokens) has perfect Jaccard across all seeds. "The quality exceeded all my expectations and I am thrilled" (11 tokens) drops to 0.68.
84
+
85
+ **Do not trust LIME when model confidence exceeds 0.99.** The deletion test becomes powerless. LIME will tell you a token is important, but removing it changes nothing measurable.
86
+
87
+ **Do not trust the ranking below rank 1.** The top-1 token is unanimous across seeds for 66% of inputs. Ranks 2 through 5 are where the instability lives. If you are reporting LIME results, report the top token and its weight. Do not treat the full ranking as meaningful.
88
+
89
+ **Be skeptical of LIME on long sentences with clear sentiment.** Counterintuitively, these are where LIME is least stable, because function words create ranking noise.
90
+
91
+ ## Limitations
92
+
93
+ This audit tests 30 inputs, giving 4 to 5 data points per category. Per-category statistics have wide confidence intervals, which is why I report bootstrap CIs rather than point estimates. Only one model (DistilBERT-SST2) is audited. The findings may not generalize to other architectures, though the tokenizer mismatch problem is architectural and affects any model that does not split on whitespace. The deletion test uses 300 perturbation samples, which is low. Higher sample counts would tighten attributions but would not fix the signal-to-noise problem on function-word-heavy sentences. Input 30 (a whitespace-only string) was intentionally included to test pipeline robustness. The pipeline correctly refused it, and it is logged as skipped rather than silently excluded.
94
+
95
+ ## Try It Yourself
96
+
97
+ The live demo is at [xai-forensic.vercel.app](https://xai-forensic.vercel.app). Type a sentence and see the LIME attribution, counterfactual word removal, and dual-model divergence in real time.
98
+
99
+ The full audit code, pre-registration protocol, raw CSVs, and this article's charts are at [github.com/parshvi1508/XAI_Forensic](https://github.com/parshvi1508/XAI_Forensic). Every number in this article can be reproduced with `make audit`.
100
+
101
+ ---
102
+
103
+ *Parshvi Jain is a computer science student at ABES Engineering College and IIT Madras (BS Data Science), researching multimodal sentiment analysis and explainable AI. She deliberately chose LIME over attention weights as an explanation method (Jain and Wallace, 2019, showed attention is unreliable for explanation) and built XAI Forensics to make model explanations auditable.*
File without changes