rayshunp commited on
Commit
1ed4e28
·
verified ·
1 Parent(s): 01c7215

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +292 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import shap
3
+ import numpy as np
4
+ import torch
5
+ import matplotlib
6
+ matplotlib.use("Agg")
7
+ import matplotlib.pyplot as plt
8
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
9
+
10
+ # ── Model Setup ──────────────────────────────────────────────────────────────
11
+ MODEL_ID = "rayshunp/ADR2026Team5"
12
+
13
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
14
+ model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID)
15
+ model.eval()
16
+
17
+ # Hugging-Face pipeline used by SHAP
18
+ clf_pipeline = pipeline(
19
+ "text-classification",
20
+ model=model,
21
+ tokenizer=tokenizer,
22
+ return_all_scores=True, # needed so SHAP sees both class probabilities
23
+ device=0 if torch.cuda.is_available() else -1,
24
+ )
25
+
26
+ # Label map – adjust if your model uses different label names
27
+ LABELS = {0: "Non-Severe", 1: "Severe"}
28
+
29
+ # SHAP explainer (uses the HF pipeline directly)
30
+ explainer = shap.Explainer(clf_pipeline)
31
+
32
+ # --- Medical Information Extraction Model (NER) ---
33
+ # Token classification model for extracting medical entities
34
+ # (e.g. drugs, symptoms, body sites, dosages) from ADR text.
35
+ # Swap NER_MODEL_ID for any token-classification model on the Hub,
36
+ # for example:
37
+ # "allenai/scibert_scivocab_cased" – general biomedical
38
+ # "d4data/biomedical-ner-all" – multi-entity biomedical NER
39
+ # "pruas/BENT-PubMedBERT-NER-Disease" – disease/symptom focused
40
+
41
+ NER_MODEL_ID = "d4data/biomedical-ner-all" # ← replace with your chosen model
42
+
43
+ ner_pipe = pipeline(
44
+ "ner",
45
+ model=NER_MODEL_ID,
46
+ aggregation_strategy="simple", # merges subword tokens → whole words
47
+ device=0 if torch.cuda.is_available() else -1,
48
+ )
49
+
50
+
51
+ # ── Core prediction function ─────────────────────────────────────────────────
52
+ def predict(text: str):
53
+ if not text.strip():
54
+ return "⚠️ Please enter a reaction description.", None, None
55
+
56
+ # ── 1. Severity prediction ──
57
+ results = clf_pipeline(text)[0] # list of {label, score}
58
+ scores = {r["label"]: r["score"] for r in results}
59
+
60
+ # Normalise to expected label keys
61
+ # The model may emit "LABEL_0"/"LABEL_1" or "Non-Severe"/"Severe"
62
+ def get_score(idx):
63
+ for key in (LABELS[idx], f"LABEL_{idx}"):
64
+ if key in scores:
65
+ return scores[key]
66
+ # fallback: pick by position
67
+ return results[idx]["score"]
68
+
69
+ score_non_severe = get_score(0)
70
+ score_severe = get_score(1)
71
+ predicted_idx = int(score_severe > score_non_severe)
72
+ label = LABELS[predicted_idx]
73
+ confidence = score_severe if predicted_idx == 1 else score_non_severe
74
+
75
+ verdict_html = f"""
76
+ <div style="
77
+ font-family: 'Courier New', monospace;
78
+ border: 2px solid {'#e74c3c' if predicted_idx == 1 else '#2ecc71'};
79
+ border-radius: 10px;
80
+ padding: 18px 24px;
81
+ background: {'#2c0a0a' if predicted_idx == 1 else '#0a2c12'};
82
+ color: {'#ff6b6b' if predicted_idx == 1 else '#6bffaa'};
83
+ text-align: center;
84
+ ">
85
+ <div style="font-size:2rem; font-weight:900; letter-spacing:2px;">
86
+ {'🚨 SEVERE' if predicted_idx == 1 else '✅ NON-SEVERE'}
87
+ </div>
88
+ <div style="font-size:1rem; margin-top:8px; opacity:0.85;">
89
+ Confidence: <strong>{confidence:.1%}</strong>
90
+ </div>
91
+ <div style="margin-top:12px; display:flex; gap:8px; justify-content:center; flex-wrap:wrap;">
92
+ <span style="background:#333; border-radius:6px; padding:4px 12px;">
93
+ Non-Severe: {score_non_severe:.1%}
94
+ </span>
95
+ <span style="background:#333; border-radius:6px; padding:4px 12px;">
96
+ Severe: {score_severe:.1%}
97
+ </span>
98
+ </div>
99
+ </div>
100
+ """
101
+
102
+ # ── 2. SHAP values ──
103
+ shap_values = explainer([text])
104
+
105
+ # shap_values.values shape: (1, n_tokens, n_classes)
106
+ # We want the SHAP values for the SEVERE class (index 1)
107
+ tokens = shap_values.data[0] # list of token strings
108
+ vals_severe = shap_values.values[0, :, 1] # shape (n_tokens,)
109
+
110
+ # ── 3. Inline word-highlight HTML ──
111
+ word_html = _build_highlight_html(tokens, vals_severe)
112
+
113
+ # ── 4. SHAP bar/force plot image ──
114
+ fig = _build_shap_plot(tokens, vals_severe, text)
115
+
116
+ return verdict_html, word_html, fig
117
+
118
+
119
+ # ── HTML word highlights ──────────────────────────────────────────────────────
120
+ def _build_highlight_html(tokens, values):
121
+ max_abs = max(abs(values).max(), 1e-8)
122
+
123
+ parts = []
124
+ for token, val in zip(tokens, values):
125
+ # Skip special tokens
126
+ if token in ("[CLS]", "[SEP]", "<s>", "</s>", "<pad>"):
127
+ continue
128
+
129
+ intensity = abs(val) / max_abs # 0-1
130
+ alpha = 0.15 + 0.75 * intensity # 0.15-0.90
131
+
132
+ if val > 0: # pushes toward SEVERE → red
133
+ bg = f"rgba(231, 76, 60, {alpha:.2f})"
134
+ fg = "#fff" if intensity > 0.4 else "#111"
135
+ else: # pushes toward NON-SEVERE → green
136
+ bg = f"rgba(46, 204, 113, {alpha:.2f})"
137
+ fg = "#fff" if intensity > 0.4 else "#111"
138
+
139
+ # Strip leading ## from subword tokens for readability
140
+ display = token.lstrip("#")
141
+
142
+ parts.append(
143
+ f'<span style="background:{bg}; color:{fg}; '
144
+ f'padding:3px 5px; border-radius:4px; margin:2px; '
145
+ f'font-weight:{"700" if intensity > 0.5 else "400"}; '
146
+ f'display:inline-block;" '
147
+ f'title="SHAP: {val:+.4f}">{display}</span>'
148
+ )
149
+
150
+ html = f"""
151
+ <div style="
152
+ font-family: Georgia, serif;
153
+ font-size: 1.05rem;
154
+ line-height: 2.2;
155
+ padding: 16px;
156
+ background: #1a1a2e;
157
+ border-radius: 10px;
158
+ border: 1px solid #333;
159
+ color: #eee;
160
+ ">
161
+ <div style="font-size:0.78rem; color:#aaa; margin-bottom:10px;">
162
+ 🔴 Red = pushes toward <strong>Severe</strong> &nbsp;|&nbsp;
163
+ 🟢 Green = pushes toward <strong>Non-Severe</strong> &nbsp;|&nbsp;
164
+ Darker = higher importance
165
+ </div>
166
+ {''.join(parts)}
167
+ </div>
168
+ """
169
+ return html
170
+
171
+
172
+ # ── SHAP bar plot ─────────────────────────────────────────────────────────────
173
+ def _build_shap_plot(tokens, values, text):
174
+ # Filter special tokens
175
+ pairs = [
176
+ (t.lstrip("#"), v)
177
+ for t, v in zip(tokens, values)
178
+ if t not in ("[CLS]", "[SEP]", "<s>", "</s>", "<pad>")
179
+ ]
180
+ if not pairs:
181
+ return None
182
+
183
+ labels_plot, vals = zip(*pairs)
184
+
185
+ # Sort by absolute value descending, take top 20
186
+ order = np.argsort(np.abs(vals))[::-1][:20]
187
+ labels_plot = [labels_plot[i] for i in order]
188
+ vals = [vals[i] for i in order]
189
+
190
+ # Re-sort for display: positive first, then negative
191
+ combined = sorted(zip(vals, labels_plot), key=lambda x: x[0])
192
+ vals, labels_plot = zip(*combined)
193
+
194
+ colors = ["#e74c3c" if v > 0 else "#2ecc71" for v in vals]
195
+
196
+ fig, ax = plt.subplots(figsize=(8, max(4, len(vals) * 0.38)))
197
+ fig.patch.set_facecolor("#1a1a2e")
198
+ ax.set_facecolor("#1a1a2e")
199
+
200
+ bars = ax.barh(labels_plot, vals, color=colors, edgecolor="none", height=0.65)
201
+
202
+ ax.axvline(0, color="#555", linewidth=1)
203
+ ax.set_xlabel("SHAP value (impact on Severe class)", color="#ccc", fontsize=10)
204
+ ax.set_title(
205
+ f"Token SHAP Breakdown\n\"{text[:60]}{'…' if len(text)>60 else ''}\"",
206
+ color="#eee", fontsize=11, pad=12
207
+ )
208
+ ax.tick_params(colors="#ccc")
209
+ for spine in ax.spines.values():
210
+ spine.set_edgecolor("#444")
211
+
212
+ # Value labels on bars
213
+ for bar, val in zip(bars, vals):
214
+ ax.text(
215
+ val + (0.002 if val >= 0 else -0.002),
216
+ bar.get_y() + bar.get_height() / 2,
217
+ f"{val:+.3f}",
218
+ va="center",
219
+ ha="left" if val >= 0 else "right",
220
+ color="#eee",
221
+ fontsize=8,
222
+ )
223
+
224
+ plt.tight_layout()
225
+ return fig
226
+
227
+
228
+ # ── Gradio UI ─────────────────────────────────────────────────────────────────
229
+ EXAMPLES = [
230
+ ["I felt groggy and dizzy after taking Tylenol."],
231
+ ["I had a mild headache after the medication."],
232
+ ["I experienced severe chest pain and difficulty breathing after the injection."],
233
+ ["My stomach felt slightly upset after the pill."],
234
+ ["I had an anaphylactic reaction with hives and throat swelling after penicillin."],
235
+ ]
236
+
237
+ CSS = """
238
+ body { background: #0d0d1a !important; }
239
+ #title {
240
+ text-align: center;
241
+ font-family: 'Courier New', monospace;
242
+ color: #7eb8f7;
243
+ letter-spacing: 3px;
244
+ text-transform: uppercase;
245
+ margin-bottom: 4px;
246
+ }
247
+ #subtitle {
248
+ text-align: center;
249
+ color: #888;
250
+ font-size: 0.9rem;
251
+ margin-bottom: 20px;
252
+ font-family: Georgia, serif;
253
+ }
254
+ .gr-button-primary {
255
+ background: #3a5fc8 !important;
256
+ border: none !important;
257
+ font-weight: 700 !important;
258
+ letter-spacing: 1px !important;
259
+ }
260
+ """
261
+
262
+ with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
263
+ gr.HTML('<h1 id="title">⚕ ADR Severity Analyzer</h1>')
264
+ gr.HTML('<p id="subtitle">Adverse Drug Reaction · Severity Classification · SHAP Explainability</p>')
265
+
266
+ with gr.Row():
267
+ with gr.Column(scale=1):
268
+ text_input = gr.Textbox(
269
+ label="Describe your reaction",
270
+ placeholder="e.g. I felt groggy and dizzy after taking Tylenol.",
271
+ lines=3,
272
+ )
273
+ analyze_btn = gr.Button("🔍 Analyze Reaction", variant="primary")
274
+ gr.Examples(examples=EXAMPLES, inputs=text_input, label="Try an example")
275
+
276
+ with gr.Column(scale=1):
277
+ verdict_out = gr.HTML(label="Severity Verdict")
278
+
279
+ gr.Markdown("### 🎨 Word-Level SHAP Highlights")
280
+ highlight_out = gr.HTML(label="Token Highlights")
281
+
282
+ gr.Markdown("### 📊 SHAP Token Importance Plot")
283
+ plot_out = gr.Plot(label="SHAP Bar Chart")
284
+
285
+ analyze_btn.click(
286
+ fn=predict,
287
+ inputs=text_input,
288
+ outputs=[verdict_out, highlight_out, plot_out],
289
+ )
290
+
291
+ if __name__ == "__main__":
292
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ transformers>=4.38.0
3
+ torch>=2.0.0
4
+ shap>=0.44.0
5
+ matplotlib>=3.7.0
6
+ numpy>=1.24.0