Jaykumardas commited on
Commit
ed919aa
·
verified ·
1 Parent(s): f76d21f

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +554 -0
  2. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, re, json, time, warnings, subprocess, signal
2
+ warnings.filterwarnings("ignore")
3
+
4
+ import numpy as np
5
+ import gradio as gr
6
+ import matplotlib
7
+ matplotlib.use("Agg")
8
+ import matplotlib.pyplot as plt
9
+ import torch
10
+ import torch.nn.functional as F
11
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
12
+
13
+ print("APP STARTED")
14
+
15
+ # ── Config ────────────────────────────────────────────────────────────────────
16
+
17
+ MODEL_PATH = ""
18
+ HF_MODEL_REPO = "Jaykumardas/Multilingual_News_Model"
19
+
20
+ # ── Load model ────────────────────────────────────────────────────────────────
21
+ def load_model_and_labels():
22
+ model_source = HF_MODEL_REPO if HF_MODEL_REPO else MODEL_PATH
23
+ print(f"[INFO] Loading from: {model_source}")
24
+
25
+ try:
26
+ tokenizer = AutoTokenizer.from_pretrained(model_source)
27
+ print("[INFO] Tokenizer loaded OK")
28
+ except Exception as e:
29
+ raise RuntimeError(f"Tokenizer load failed: {e}")
30
+
31
+ id2label = None
32
+ lmap = os.path.join(model_source, "label_map.json")
33
+ if os.path.exists(lmap):
34
+ with open(lmap, encoding="utf-8") as f:
35
+ lm = json.load(f)
36
+ id2label = {int(k): v for k, v in lm["id2label"].items()}
37
+ print(f"[INFO] id2label from label_map.json: {id2label}")
38
+
39
+ if id2label is None:
40
+ cfg_path = os.path.join(model_source, "config.json")
41
+ if os.path.isfile(cfg_path):
42
+ with open(cfg_path, encoding="utf-8") as f:
43
+ cfg = json.load(f)
44
+ if cfg.get("id2label"):
45
+ id2label = {int(k): v for k, v in cfg["id2label"].items()}
46
+ print(f"[INFO] id2label from config.json: {id2label}")
47
+
48
+ if id2label is None:
49
+ raise RuntimeError("label_map.json not found. Re-run your save cell in Kaggle.")
50
+
51
+ try:
52
+ model = AutoModelForSequenceClassification.from_pretrained(
53
+ model_source, num_labels=len(id2label), ignore_mismatched_sizes=True)
54
+ device = "cuda" if torch.cuda.is_available() else "cpu"
55
+ model.to(device).eval()
56
+ print(f"[INFO] Model OK — {len(id2label)} classes — {device.upper()}")
57
+ except Exception as e:
58
+ raise RuntimeError(f"Model load failed: {e}")
59
+
60
+ return model, tokenizer, id2label, device
61
+
62
+ try:
63
+ MODEL, TOKENIZER, ID2LABEL, DEVICE = load_model_and_labels()
64
+ CLASS_NAMES = [ID2LABEL[i] for i in sorted(ID2LABEL)]
65
+ NUM_CLASSES = len(CLASS_NAMES)
66
+ MODEL_LOADED = True
67
+ print(f"[INFO] Classes: {CLASS_NAMES}")
68
+ except Exception as e:
69
+ print(f"[ERROR] {e}")
70
+ MODEL_LOADED = False
71
+ CLASS_NAMES = ["Model not loaded"]
72
+ NUM_CLASSES = 1
73
+ ID2LABEL = {0: "Model not loaded"}
74
+ DEVICE = "cpu"
75
+
76
+ # ── Icons / metrics / samples ─────────────────────────────────────────────────
77
+ ICONS = {
78
+ "entertainment":"🎬","sports":"🏏","state":"🗺️","national":"🇮🇳",
79
+ "international":"🌏","business":"📈","technology":"💻","science":"🔬",
80
+ "health":"🏥","politics":"🏛️",
81
+ }
82
+ ICONS.update({k.title(): v for k, v in list(ICONS.items())})
83
+
84
+ def get_icon(label): return ICONS.get(label, "📰")
85
+
86
+ REAL_METRICS = {
87
+ "TF-IDF + LR": {"test_acc":83.84,"test_f1":77.85,"color":"#3b82f6","train_time":"< 2 min"},
88
+ "BiLSTM": {"test_acc":79.36,"test_f1":67.16,"color":"#8b5cf6","train_time":"~14 min"},
89
+ "XLM-RoBERTa": {"test_acc":86.12,"test_f1":78.75,"color":"#10b981","train_time":"~45 min"},
90
+ }
91
+
92
+ SAMPLES = {
93
+ "Telugu": "హైదరాబాద్‌లో క్రికెట్ టోర్నమెంట్ ప్రారంభమైంది; జిల్లా స్థాయి జట్లు పాల్గొంటున్నాయి.",
94
+ "Malayalam":"కേരളത്തിൽ ഇന്ന് കനത്ത മഴ; ഒൻപത് ജില്ലകളിൽ യെല്ലോ അലർട്ട് പ്രഖ്യാപിച്ചു.",
95
+ "Marathi": "मुंबई शेअर बाजारात आज मोठी तेजी; सेन्सेक्स ५०० अंकांनी वधारला.",
96
+ "Tamil": "தமிழ்நாட்டில் புதிய தொழில்நுட்ப பூங்கா திறப்பு; ஆயிரக்கணக்கான வேலை வாய்ப்புகள்.",
97
+ "Gujarati": "ગુજરાત ટીમ સ્ટેટ ક્રિકેટ ચેમ્પિયનશિપ જીતી; ખેલાડીઓ ઉત્સાહિત.",
98
+ }
99
+
100
+ # ── Preprocessing ─────────────────────────────────────────────────────────────
101
+ def clean_text(text):
102
+ if not isinstance(text, str): return ""
103
+ text = re.sub(r"https?://\S+|www\.\S+", " ", text)
104
+ text = re.sub(r"<[^>]+>", " ", text)
105
+ text = re.sub(r"[\u200b\u200c\u200d\ufeff\u00ad]", "", text)
106
+ text = re.sub(
107
+ r"[^\w\s\u0900-\u097F\u0C00-\u0C7F\u0D00-\u0D7F\u0B80-\u0BFF\u0A80-\u0AFF]",
108
+ " ", text)
109
+ return re.sub(r"\s+", " ", text).strip()
110
+
111
+ # ── Inference ─────────────────────────────────────────────────────────────────
112
+ def predict_text(text):
113
+ if not MODEL_LOADED:
114
+ return {c: 0.0 for c in CLASS_NAMES}, "Model not loaded", 0.0, 0
115
+ t_clean = clean_text(text)
116
+ if not t_clean:
117
+ return {c: 0.0 for c in CLASS_NAMES}, "Empty input", 0.0, 0
118
+ enc = TOKENIZER(t_clean, max_length=128, padding="max_length",
119
+ truncation=True, return_tensors="pt")
120
+ enc = {k: v.to(DEVICE) for k, v in enc.items()}
121
+ t0 = time.time()
122
+ with torch.no_grad():
123
+ logits = MODEL(**enc).logits
124
+ ms = int((time.time() - t0) * 1000)
125
+ probs = F.softmax(logits, dim=-1).squeeze().cpu().numpy()
126
+ idx = int(np.argmax(probs))
127
+ label = ID2LABEL.get(idx, f"class_{idx}")
128
+ return ({ID2LABEL.get(i, f"class_{i}"): float(probs[i]) for i in range(len(probs))},
129
+ label, float(probs[idx]), ms)
130
+
131
+ # ── Charts ────────────────────────────────────────────────────────────────────
132
+ def conf_chart(probs_dict, pred_label):
133
+ paired = sorted(zip(probs_dict.values(), probs_dict.keys()), reverse=True)
134
+ vals = [p[0]*100 for p in paired]
135
+ labs = [p[1] for p in paired]
136
+ colors = ["#10b981" if l == pred_label else "#6366f1" if v > 10 else "#334155"
137
+ for l, v in zip(labs, vals)]
138
+ fig, ax = plt.subplots(figsize=(9, max(4, len(labs)*0.5+1)))
139
+ fig.patch.set_facecolor("#0f172a"); ax.set_facecolor("#0f172a")
140
+ bars = ax.barh(labs[::-1], vals[::-1], color=colors[::-1], height=0.55, edgecolor="none")
141
+ for bar, v in zip(bars, vals[::-1]):
142
+ ax.text(bar.get_width()+0.5, bar.get_y()+bar.get_height()/2,
143
+ f"{v:.1f}%", va="center", ha="left", color="#e2e8f0", fontsize=10, fontweight="bold")
144
+ ax.set_xlim(0, 115)
145
+ ax.set_xlabel("Confidence (%)", color="#94a3b8", fontsize=11)
146
+ ax.set_title("Prediction Confidence", color="#f1f5f9", fontsize=13, fontweight="bold", pad=12)
147
+ ax.tick_params(colors="#94a3b8", labelsize=10)
148
+ for s in ax.spines.values(): s.set_visible(False)
149
+ ax.grid(axis="x", color="#1e293b", linewidth=0.8)
150
+ plt.tight_layout(pad=1.5)
151
+ return fig
152
+
153
+ def metrics_chart():
154
+ models = list(REAL_METRICS.keys())
155
+ accs = [REAL_METRICS[m]["test_acc"] for m in models]
156
+ f1s = [REAL_METRICS[m]["test_f1"] for m in models]
157
+ cols = [REAL_METRICS[m]["color"] for m in models]
158
+ x, w = np.arange(len(models)), 0.32
159
+ fig, ax = plt.subplots(figsize=(10, 5))
160
+ fig.patch.set_facecolor("#0f172a"); ax.set_facecolor("#0f172a")
161
+ b1 = ax.bar(x-w/2, accs, w, label="Test Accuracy (%)", color=[c+"cc" for c in cols], edgecolor="none")
162
+ b2 = ax.bar(x+w/2, f1s, w, label="Test F1 Macro (%)", color=cols, edgecolor="none", alpha=0.75)
163
+ for bars in [b1, b2]:
164
+ for bar in bars:
165
+ h = bar.get_height()
166
+ ax.text(bar.get_x()+bar.get_width()/2, h+0.5, f"{h:.1f}",
167
+ ha="center", va="bottom", color="#e2e8f0", fontsize=10, fontweight="bold")
168
+ ax.set_xticks(x); ax.set_xticklabels(models, color="#94a3b8", fontsize=11)
169
+ ax.set_ylim(0, 105); ax.set_ylabel("Score (%)", color="#94a3b8", fontsize=11)
170
+ ax.set_title("Model Comparison — Test Results", color="#f1f5f9", fontsize=13, fontweight="bold", pad=14)
171
+ ax.tick_params(colors="#94a3b8")
172
+ ax.legend(facecolor="#1e293b", edgecolor="none", labelcolor="#e2e8f0")
173
+ for s in ax.spines.values(): s.set_visible(False)
174
+ ax.grid(axis="y", color="#1e293b", linewidth=0.8)
175
+ plt.tight_layout(pad=1.5)
176
+ return fig
177
+
178
+ _METRICS_FIG = metrics_chart() # pre-render once
179
+
180
+ # ── Gradio handlers ───────────────────────────────────────────────────────────
181
+ def classify_single(text):
182
+ if not text or not text.strip():
183
+ return '<p style="color:#f87171;padding:20px;">Please enter a headline.</p>', None, None
184
+
185
+ pd, label, conf, ms = predict_text(text)
186
+ icon = get_icon(label)
187
+ pct = conf * 100
188
+ cc = "#10b981" if pct >= 70 else "#f59e0b" if pct >= 40 else "#ef4444"
189
+
190
+ html = f"""
191
+ <div style="background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid #334155;
192
+ border-radius:16px;padding:28px 32px;font-family:sans-serif;
193
+ box-shadow:0 8px 32px rgba(0,0,0,0.4);">
194
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:18px;">
195
+ <span style="font-size:44px;">{icon}</span>
196
+ <div>
197
+ <div style="font-size:11px;text-transform:uppercase;letter-spacing:2px;color:#64748b;font-weight:600;">
198
+ Predicted Category</div>
199
+ <div style="font-size:30px;font-weight:800;color:#f1f5f9;line-height:1.15;">{label.title()}</div>
200
+ </div>
201
+ </div>
202
+ <div style="display:flex;gap:32px;flex-wrap:wrap;">
203
+ <div>
204
+ <div style="font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;margin-bottom:4px;">Confidence</div>
205
+ <div style="font-size:38px;font-weight:900;color:{cc};">{pct:.1f}%</div>
206
+ </div>
207
+ <div>
208
+ <div style="font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;margin-bottom:4px;">Model</div>
209
+ <div style="font-size:16px;font-weight:600;color:#94a3b8;">XLM-RoBERTa</div>
210
+ </div>
211
+ <div>
212
+ <div style="font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;margin-bottom:4px;">Inference</div>
213
+ <div style="font-size:16px;font-weight:600;color:#94a3b8;">{ms} ms</div>
214
+ </div>
215
+ </div>
216
+ <hr style="border:none;border-top:1px solid #1e293b;margin:18px 0 10px;">
217
+ <div style="font-size:12px;color:#475569;">
218
+ IndicGLUE &nbsp;·&nbsp; 5 languages &nbsp;·&nbsp; {NUM_CLASSES} categories &nbsp;·&nbsp; Test acc: 86.12%
219
+ </div>
220
+ </div>"""
221
+ return html, conf_chart(pd, label), pd
222
+
223
+ def classify_batch(batch_text):
224
+ if not batch_text or not batch_text.strip():
225
+ return '<p style="color:#f87171;padding:20px;">Enter at least one headline.</p>', None
226
+ lines = [l.strip() for l in batch_text.strip().split("\n") if l.strip()][:50]
227
+ rows = ""
228
+ labels_list = []
229
+ for i, line in enumerate(lines, 1):
230
+ pd, label, conf, _ = predict_text(line)
231
+ icon = get_icon(label); pct = conf*100
232
+ cc = "#10b981" if pct >= 70 else "#f59e0b" if pct >= 40 else "#ef4444"
233
+ prev = (line[:80]+"…") if len(line) > 80 else line
234
+ labels_list.append(label)
235
+ rows += f"""<tr style="border-bottom:1px solid #1e293b;">
236
+ <td style="padding:10px 8px;color:#64748b;font-size:13px;">{i}</td>
237
+ <td style="padding:10px 8px;color:#cbd5e1;font-size:13px;max-width:340px;word-break:break-word;">{prev}</td>
238
+ <td style="padding:10px 8px;font-size:14px;color:#e2e8f0;">{icon} {label.title()}</td>
239
+ <td style="padding:10px 8px;font-weight:700;color:{cc};font-size:14px;">{pct:.1f}%</td>
240
+ </tr>"""
241
+ from collections import Counter
242
+ counts = Counter(labels_list)
243
+ summary = " · ".join(f"{get_icon(k)} {k.title()}: {v}" for k,v in counts.most_common(5))
244
+ table = f"""
245
+ <div style="background:#0f172a;border-radius:14px;padding:20px;
246
+ font-family:sans-serif;border:1px solid #1e293b;">
247
+ <div style="font-size:12px;color:#64748b;margin-bottom:14px;text-transform:uppercase;letter-spacing:1.5px;">
248
+ {len(lines)} headlines — {summary}</div>
249
+ <div style="overflow-x:auto;">
250
+ <table style="width:100%;border-collapse:collapse;">
251
+ <thead><tr style="border-bottom:2px solid #334155;">
252
+ <th style="padding:8px;color:#475569;font-size:11px;text-align:left;text-transform:uppercase;">#</th>
253
+ <th style="padding:8px;color:#475569;font-size:11px;text-align:left;text-transform:uppercase;">Headline</th>
254
+ <th style="padding:8px;color:#475569;font-size:11px;text-align:left;text-transform:uppercase;">Category</th>
255
+ <th style="padding:8px;color:#475569;font-size:11px;text-align:left;text-transform:uppercase;">Conf.</th>
256
+ </tr></thead>
257
+ <tbody style="color:#e2e8f0;">{rows}</tbody>
258
+ </table></div>
259
+ </div>"""
260
+ # Pie chart
261
+ fig, ax = plt.subplots(figsize=(7, 5))
262
+ fig.patch.set_facecolor("#0f172a"); ax.set_facecolor("#0f172a")
263
+ pal = ["#10b981","#6366f1","#f59e0b","#ef4444","#3b82f6","#8b5cf6","#ec4899","#14b8a6","#f97316","#84cc16"]
264
+ cd = dict(counts)
265
+ wedges, texts, ats = ax.pie(cd.values(), labels=[k.title() for k in cd],
266
+ autopct="%1.0f%%", colors=pal[:len(cd)], startangle=140,
267
+ wedgeprops={"edgecolor":"#0f172a","linewidth":2})
268
+ for t in texts: t.set_color("#94a3b8"); t.set_fontsize(10)
269
+ for at in ats: at.set_color("#0f172a"); at.set_fontweight("bold"); at.set_fontsize(9)
270
+ ax.set_title("Category Distribution", color="#f1f5f9", fontsize=13, fontweight="bold", pad=14)
271
+ plt.tight_layout()
272
+ return table, fig
273
+
274
+ # ── CSS ───────────────────────────────────────────────────────────────────────
275
+ # IMPORTANT: No @import (blocked in Kaggle). No body/html background override
276
+ # (breaks Kaggle iframe rendering). Only style our own named classes.
277
+ CSS = """
278
+ * { box-sizing: border-box; }
279
+ .gradio-container {
280
+ max-width: 1100px !important;
281
+ margin: 0 auto !important;
282
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
283
+ }
284
+ .app-header {
285
+ background: linear-gradient(135deg, #0f172a, #1e1b4b 50%, #0f172a);
286
+ border: 1px solid #1e293b; border-radius: 14px;
287
+ padding: 32px 40px 24px; text-align: center; margin-bottom: 8px;
288
+ }
289
+ .header-badge {
290
+ display: inline-block; background: linear-gradient(90deg, #6366f1, #8b5cf6);
291
+ color: white; font-size: 10px; font-weight: 700; letter-spacing: 2.5px;
292
+ text-transform: uppercase; padding: 4px 14px; border-radius: 20px; margin-bottom: 14px;
293
+ }
294
+ .header-title { font-size: 38px; font-weight: 800; color: #f1f5f9; line-height: 1.1; margin: 0 0 8px; }
295
+ .header-title span {
296
+ background: linear-gradient(90deg, #6366f1, #10b981);
297
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
298
+ }
299
+ .header-sub { font-size: 14px; color: #64748b; margin: 0; }
300
+ .header-stats { display: flex; justify-content: center; gap: 16px; margin-top: 20px; flex-wrap: wrap; }
301
+ .stat-pill {
302
+ background: #1e293b; border: 1px solid #334155; border-radius: 8px;
303
+ padding: 7px 16px; font-size: 12px; color: #94a3b8;
304
+ }
305
+ .stat-pill strong { color: #e2e8f0; }
306
+ .tab-nav { background: #0f172a !important; border-bottom: 1px solid #1e293b !important; }
307
+ .tab-nav button {
308
+ color: #64748b !important; font-weight: 600 !important; font-size: 13px !important;
309
+ padding: 12px 20px !important; border: none !important;
310
+ border-bottom: 2px solid transparent !important; background: transparent !important;
311
+ }
312
+ .tab-nav button.selected { color: #6366f1 !important; border-bottom-color: #6366f1 !important; }
313
+ textarea, input[type=text] {
314
+ background: #1e293b !important; border: 1px solid #334155 !important;
315
+ color: #e2e8f0 !important; border-radius: 10px !important; font-size: 14px !important;
316
+ }
317
+ label { color: #94a3b8 !important; font-size: 12px !important; text-transform: uppercase !important; }
318
+ button.primary {
319
+ background: linear-gradient(135deg, #6366f1, #8b5cf6) !important;
320
+ color: white !important; font-weight: 700 !important;
321
+ border: none !important; border-radius: 10px !important;
322
+ }
323
+ button.secondary {
324
+ background: #1e293b !important; color: #94a3b8 !important;
325
+ border: 1px solid #334155 !important; border-radius: 8px !important;
326
+ }
327
+ .app-footer {
328
+ background: #0f172a; border: 1px solid #1e293b; border-radius: 14px;
329
+ padding: 24px 40px; text-align: center; margin-top: 24px;
330
+ }
331
+ .footer-team { display: flex; justify-content: center; gap: 32px; flex-wrap: wrap; margin-bottom: 14px; }
332
+ .footer-member { display: flex; align-items: center; gap: 10px; }
333
+ .footer-avatar {
334
+ width: 32px; height: 32px; border-radius: 50%;
335
+ display: flex; align-items: center; justify-content: center;
336
+ font-weight: 800; font-size: 13px; color: white;
337
+ }
338
+ .footer-name { font-size: 13px; color: #94a3b8; }
339
+ .footer-roll { font-size: 11px; color: #475569; }
340
+ .footer-copy { font-size: 12px; color: #64748b; margin-top: 10px; }
341
+ footer { display: none !important; }
342
+ """
343
+
344
+ HEADER = """
345
+ <div class="app-header">
346
+ <div class="header-badge">Generative AI Assignment &middot; CBIT &middot; 2025-26</div>
347
+ <h1 class="header-title">Multilingual News<br><span>Classification</span></h1>
348
+ <p class="header-sub">Chaitanya Bharathi Institute of Technology &middot; Dept. of AI &amp; ML</p>
349
+ <div class="header-stats">
350
+ <div class="stat-pill">Model <strong>XLM-RoBERTa</strong></div>
351
+ <div class="stat-pill">Languages <strong>5 Indic</strong></div>
352
+ <div class="stat-pill">Dataset <strong>IndicGLUE</strong></div>
353
+ <div class="stat-pill">Test Acc <strong>86.12%</strong></div>
354
+ </div>
355
+ </div>
356
+ """
357
+
358
+ FOOTER = """
359
+ <div class="app-footer">
360
+ <div class="footer-team">
361
+ <div class="footer-member">
362
+ <div class="footer-avatar" style="background:linear-gradient(135deg,#6366f1,#8b5cf6);">J</div>
363
+ <div><div class="footer-name">Jay Kumar Das</div><div class="footer-roll">160123748035</div></div>
364
+ </div>
365
+ <div class="footer-member">
366
+ <div class="footer-avatar" style="background:linear-gradient(135deg,#10b981,#059669);">S</div>
367
+ <div><div class="footer-name">Siddhartha Dontula</div><div class="footer-roll">160123748036</div></div>
368
+ </div>
369
+ <div class="footer-member">
370
+ <div class="footer-avatar" style="background:linear-gradient(135deg,#f59e0b,#d97706);">P</div>
371
+ <div><div class="footer-name">Praneeth Reddy Ganta</div><div class="footer-roll">160123748037</div></div>
372
+ </div>
373
+ </div>
374
+ <div class="footer-copy">
375
+ &copy; 2025-26 &middot; Dept. of AI &amp; ML &middot; CBIT Hyderabad &middot;
376
+ Guided by <strong style="color:#64748b;">Mr. Panigrahi Srikanth</strong>
377
+ </div>
378
+ </div>
379
+ """
380
+
381
+ PROJECT_HTML = """
382
+ <div style="font-family:sans-serif;padding:8px 0;">
383
+ <div style="background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px 24px;margin-bottom:16px;">
384
+ <h3 style="color:#e2e8f0;margin:0 0 8px;">Problem Statement</h3>
385
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
386
+ A single unified model that reads Telugu, Malayalam, Marathi, Tamil, and Gujarati natively,
387
+ classifying news headlines into up to 10 categories — no translation required.
388
+ </p>
389
+ </div>
390
+ <div style="background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px 24px;margin-bottom:16px;">
391
+ <h3 style="color:#e2e8f0;margin:0 0 8px;">Dataset — IndicGLUE (ai4bharat/indic_glue)</h3>
392
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
393
+ iNLTK Headlines subsets &mdash; 37,069 labeled headlines across 5 languages.<br>
394
+ <strong style="color:#e2e8f0;">Split:</strong> Train 25,945 &middot; Val 3,707 &middot; Test 7,414
395
+ </p>
396
+ </div>
397
+ <div style="background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px 24px;">
398
+ <h3 style="color:#e2e8f0;margin:0 0 8px;">Results (Test Set)</h3>
399
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
400
+ <strong style="color:#3b82f6;">TF-IDF + LR:</strong> 83.84% &middot; F1 77.85%<br>
401
+ <strong style="color:#8b5cf6;">BiLSTM:</strong> 79.36% &middot; F1 67.16%<br>
402
+ <strong style="color:#10b981;">XLM-RoBERTa:</strong> 86.% &middot; F1 78.75%
403
+ </p>
404
+ </div>
405
+ </div>
406
+ """
407
+
408
+ TEAM_HTML = """
409
+ <div style="font-family:sans-serif;padding:8px 0;">
410
+ <div style="text-align:center;margin-bottom:24px;">
411
+ <div style="font-size:22px;font-weight:800;color:#f1f5f9;">Meet the Team</div>
412
+ <div style="font-size:13px;color:#64748b;margin-top:4px;">
413
+ Dept. of AI &amp; ML &middot; CBIT &middot; Guided by <strong style="color:#94a3b8;">Mr. Panigrahi Srikanth</strong>
414
+ </div>
415
+ </div>
416
+ <div style="background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid #334155;border-top:3px solid #6366f1;border-radius:14px;padding:22px 26px;margin-bottom:14px;">
417
+ <div style="display:flex;align-items:center;gap:14px;margin-bottom:12px;">
418
+ <div style="width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;color:white;">J</div>
419
+ <div>
420
+ <div style="font-size:17px;font-weight:700;color:#f1f5f9;">Jay Kumar Das</div>
421
+ <div style="font-size:11px;color:#6366f1;">160123748035 &middot; Phase 1 Lead</div>
422
+ </div>
423
+ </div>
424
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
425
+ IndicGLUE data loading, Unicode-safe preprocessing, TF-IDF baseline (84.95%), EDA.
426
+ </p>
427
+ </div>
428
+ <div style="background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid #334155;border-top:3px solid #10b981;border-radius:14px;padding:22px 26px;margin-bottom:14px;">
429
+ <div style="display:flex;align-items:center;gap:14px;margin-bottom:12px;">
430
+ <div style="width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#10b981,#059669);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;color:white;">S</div>
431
+ <div>
432
+ <div style="font-size:17px;font-weight:700;color:#f1f5f9;">Siddhartha Dontula</div>
433
+ <div style="font-size:11px;color:#10b981;">160123748036 &middot; Phase 2 Lead</div>
434
+ </div>
435
+ </div>
436
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
437
+ BiLSTM design (60k vocab, GlobalMaxPool), training curves, per-class evaluation (79.36%).
438
+ </p>
439
+ </div>
440
+ <div style="background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid #334155;border-top:3px solid #f59e0b;border-radius:14px;padding:22px 26px;">
441
+ <div style="display:flex;align-items:center;gap:14px;margin-bottom:12px;">
442
+ <div style="width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#f59e0b,#d97706);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;color:white;">P</div>
443
+ <div>
444
+ <div style="font-size:17px;font-weight:700;color:#f1f5f9;">Praneeth Reddy Ganta</div>
445
+ <div style="font-size:11px;color:#f59e0b;">160123748037 &middot; Phase 3 Lead</div>
446
+ </div>
447
+ </div>
448
+ <p style="color:#94a3b8;font-size:13px;line-height:1.6;margin:0;">
449
+ XLM-RoBERTa fine-tuning, full evaluation, Gradio UI deployment (86.12%).
450
+ </p>
451
+ </div>
452
+ </div>
453
+ """
454
+
455
+ # ── Build UI ──────────────────────────────────────────────────────────────────
456
+ # ONE with gr.Blocks() block. Nothing opens after it closes. No demo.load().
457
+ # The metrics chart uses gr.Plot(value=_METRICS_FIG) — renders immediately.
458
+
459
+ with gr.Blocks(css=CSS, title="Multilingual News Classification") as demo:
460
+
461
+ gr.HTML(HEADER)
462
+
463
+ with gr.Tabs():
464
+
465
+ with gr.Tab("Classify News"):
466
+ with gr.Row():
467
+ with gr.Column(scale=1):
468
+ txt_in = gr.Textbox(
469
+ placeholder="Paste a news headline in any of the 5 supported languages...",
470
+ lines=4, label="News Headline")
471
+ gr.HTML('<div style="font-size:11px;color:#475569;margin:8px 0 4px;text-transform:uppercase;letter-spacing:1px;">Load Sample</div>')
472
+ with gr.Row():
473
+ for lang in ["Telugu", "Malayalam", "Marathi"]:
474
+ b = gr.Button(lang, size="sm")
475
+ b.click(fn=lambda l=lang: SAMPLES.get(l,""), outputs=txt_in)
476
+ with gr.Row():
477
+ for lang in ["Tamil", "Gujarati"]:
478
+ b = gr.Button(lang, size="sm")
479
+ b.click(fn=lambda l=lang: SAMPLES.get(l,""), outputs=txt_in)
480
+ go_btn = gr.Button("Classify", variant="primary", size="lg")
481
+ with gr.Column(scale=1):
482
+ res_html = gr.HTML()
483
+ res_chart = gr.Plot()
484
+ res_json = gr.JSON(visible=False)
485
+ go_btn.click(fn=classify_single,
486
+ inputs=txt_in,
487
+ outputs=[res_html, res_chart, res_json])
488
+
489
+ with gr.Tab("Batch Classify"):
490
+ gr.HTML('<div style="background:#1e293b;border:1px solid #334155;border-radius:10px;padding:14px 18px;margin-bottom:12px;font-family:sans-serif;font-size:13px;color:#64748b;"><strong style="color:#e2e8f0;">Batch mode</strong> — one headline per line, max 50.</div>')
491
+ with gr.Row():
492
+ with gr.Column(scale=1):
493
+ batch_in = gr.Textbox(placeholder="One headline per line...",
494
+ lines=12, label="Headlines")
495
+ batch_btn = gr.Button("Classify All", variant="primary")
496
+ with gr.Column(scale=1):
497
+ batch_tbl = gr.HTML()
498
+ batch_chart = gr.Plot()
499
+ batch_btn.click(fn=classify_batch,
500
+ inputs=batch_in,
501
+ outputs=[batch_tbl, batch_chart])
502
+
503
+ with gr.Tab("Model Comparison"):
504
+ gr.Plot(value=_METRICS_FIG) # pre-rendered — no event needed
505
+ with gr.Row():
506
+ for mname, md in REAL_METRICS.items():
507
+ with gr.Column():
508
+ gr.HTML(f"""
509
+ <div style="background:#1e293b;border:1px solid {md['color']}40;border-top:3px solid {md['color']};border-radius:12px;padding:18px 20px;font-family:sans-serif;">
510
+ <div style="font-size:14px;font-weight:700;color:#f1f5f9;margin-bottom:12px;">{mname}</div>
511
+ <div style="font-size:22px;font-weight:800;color:{md['color']};">{md['test_acc']}%</div>
512
+ <div style="font-size:11px;color:#475569;text-transform:uppercase;">Test Accuracy</div>
513
+ <div style="font-size:22px;font-weight:800;color:{md['color']};margin-top:8px;">{md['test_f1']}%</div>
514
+ <div style="font-size:11px;color:#475569;text-transform:uppercase;">F1 Macro</div>
515
+ <div style="font-size:13px;color:#64748b;margin-top:10px;">{md['train_time']}</div>
516
+ </div>""")
517
+
518
+ with gr.Tab("Project Details"):
519
+ gr.HTML(PROJECT_HTML)
520
+
521
+ with gr.Tab("Team"):
522
+ gr.HTML(TEAM_HTML)
523
+
524
+ gr.HTML(FOOTER)
525
+
526
+ # ── Launch ────────────────────────────────────────────────────────────────────
527
+ # Kill any leftover Gradio server first (re-running a Kaggle cell leaves it alive)
528
+ def _free_ports():
529
+ for port in range(7860, 7871):
530
+ try:
531
+ r = subprocess.run(["lsof", "-ti", f"tcp:{port}"],
532
+ capture_output=True, text=True)
533
+ for pid in r.stdout.strip().split("\n"):
534
+ if pid:
535
+ os.kill(int(pid), signal.SIGKILL)
536
+ print(f"[INFO] Freed port {port} (killed PID {pid})")
537
+ except Exception:
538
+ pass
539
+
540
+ _free_ports()
541
+ try:
542
+ demo.close()
543
+ except Exception:
544
+ pass
545
+
546
+ import time as _t; _t.sleep(1)
547
+
548
+ demo.launch(
549
+ share=True, # Required in Kaggle — generates gradio.live public URL
550
+ server_port=7860, # Kaggle proxies this port to its output iframe
551
+ server_name="0.0.0.0",
552
+ show_error=True,
553
+ quiet=False,
554
+ )
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ torch
3
+ transformers
4
+ numpy
5
+ matplotlib