isha0110 commited on
Commit
c1db3b1
·
verified ·
1 Parent(s): be78d6f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +204 -661
app.py CHANGED
@@ -3,714 +3,257 @@ import torch
3
  import torch.nn as nn
4
  from transformers import AutoTokenizer, AutoModel
5
  import numpy as np
6
- import os
7
- from typing import List, Dict, Tuple
8
- import json
9
- from datetime import datetime
10
- import plotly.graph_objects as go
11
- from collections import Counter
12
- import time
13
-
14
- print("🎭 EmotiScan Initializing...")
15
 
16
  # Configuration
17
- CONFIG = {
18
- "model": "roberta-base",
19
- "emotions": ["anger", "fear", "joy", "sadness", "surprise"],
20
- "thresholds": [0.24722222, 0.61666667, 0.59722222, 0.44166667, 0.46111111],
21
- "max_length": 200,
22
- "weights_path": "roberta.pth"
23
- }
24
-
25
- # Emotion metadata with unique styling
26
- EMOTION_META = {
27
- "anger": {
28
- "emoji": "😠", "color": "#E74C3C", "gradient": ["#E74C3C", "#C0392B"],
29
- "description": "Hostile or irritated state", "intensity_labels": ["Mild", "Moderate", "High", "Extreme"]
30
- },
31
- "fear": {
32
- "emoji": "😨", "color": "#9B59B6", "gradient": ["#9B59B6", "#8E44AD"],
33
- "description": "Anxiety or apprehension", "intensity_labels": ["Uneasy", "Concerned", "Frightened", "Panicked"]
34
- },
35
- "joy": {
36
- "emoji": "😊", "color": "#F39C12", "gradient": ["#F39C12", "#E67E22"],
37
- "description": "Positive emotional state", "intensity_labels": ["Pleasant", "Cheerful", "Delighted", "Euphoric"]
38
- },
39
- "sadness": {
40
- "emoji": "😢", "color": "#3498DB", "gradient": ["#3498DB", "#2980B9"],
41
- "description": "Melancholic emotional tone", "intensity_labels": ["Down", "Unhappy", "Distressed", "Grieving"]
42
- },
43
- "surprise": {
44
- "emoji": "😲", "color": "#E91E63", "gradient": ["#E91E63", "#C2185B"],
45
- "description": "Unexpected reaction", "intensity_labels": ["Interested", "Intrigued", "Amazed", "Astonished"]
46
- }
47
- }
48
 
49
- class EmotionClassifier(nn.Module):
50
- """Neural network for emotion classification"""
51
- def __init__(self, model_name: str, num_labels: int):
52
  super().__init__()
53
- self.base_model = AutoModel.from_pretrained(model_name)
54
- self.dropout = nn.Dropout(0.35)
55
- self.output_layer = nn.Linear(768, num_labels)
56
-
 
57
  def forward(self, input_ids, attention_mask):
58
- outputs = self.base_model(input_ids=input_ids, attention_mask=attention_mask)
59
- pooled = outputs.pooler_output if hasattr(outputs, "pooler_output") else outputs.last_hidden_state[:, 0]
60
- return self.output_layer(self.dropout(pooled))
 
 
 
 
 
61
 
62
- class EmotiScanEngine:
63
- """Core analysis engine"""
64
- def __init__(self):
65
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
66
- self.model = None
67
- self.tokenizer = None
68
- self.ready = False
69
- self.session_stats = {
70
- "total_scans": 0,
71
- "emotion_detections": Counter(),
72
- "scan_history": [],
73
- "session_start": datetime.now()
74
- }
75
-
76
- def load_model(self) -> Tuple[bool, str]:
77
- """Initialize model and tokenizer"""
78
- try:
79
- self.model = EmotionClassifier(CONFIG["model"], len(CONFIG["emotions"]))
80
-
81
- if os.path.exists(CONFIG["weights_path"]):
82
- self.model.load_state_dict(
83
- torch.load(CONFIG["weights_path"], map_location=self.device)
84
- )
85
- status = "✅ Model loaded with trained weights"
86
- else:
87
- status = "⚠️ Model initialized without pre-trained weights"
88
-
89
- self.model.to(self.device).eval()
90
- self.tokenizer = AutoTokenizer.from_pretrained(CONFIG["model"])
91
- self.ready = True
92
-
93
- return True, status
94
- except Exception as e:
95
- return False, f"❌ Initialization failed: {str(e)}"
96
 
97
- def analyze_text(self, text: str) -> Dict:
98
- """Perform emotion analysis on input text"""
99
- if not self.ready:
100
- raise RuntimeError("Model not initialized")
101
-
102
- if not text.strip():
103
- raise ValueError("Empty input text")
104
 
 
 
 
 
 
 
 
 
 
 
105
  # Tokenize
106
- encoded = self.tokenizer(
107
- text.strip(),
108
  truncation=True,
109
  padding="max_length",
110
- max_length=CONFIG["max_length"],
111
  return_tensors="pt"
112
  )
113
 
114
- # Inference
 
 
 
115
  with torch.no_grad():
116
- logits = self.model(
117
- encoded["input_ids"].to(self.device),
118
- encoded["attention_mask"].to(self.device)
119
- )
120
- scores = torch.sigmoid(logits).cpu().numpy()[0]
121
- predictions = (scores > np.array(CONFIG["thresholds"])).astype(int)
122
 
123
- # Update statistics
124
- self.session_stats["total_scans"] += 1
125
- for idx, emotion in enumerate(CONFIG["emotions"]):
126
- if predictions[idx]:
127
- self.session_stats["emotion_detections"][emotion] += 1
128
 
129
- self.session_stats["scan_history"].append({
130
- "timestamp": datetime.now().isoformat(),
131
- "text_preview": text[:80],
132
- "detected_count": int(predictions.sum())
133
- })
134
 
135
- # Build result
136
- results = {
137
- "emotions": {},
138
- "metadata": {
139
- "text_length": len(text.split()),
140
- "detected_emotions": int(predictions.sum()),
141
- "dominant_emotion": None,
142
- "confidence": 0.0
143
- }
144
- }
145
 
146
- max_score_idx = np.argmax(scores)
147
- results["metadata"]["dominant_emotion"] = CONFIG["emotions"][max_score_idx]
148
- results["metadata"]["confidence"] = float(scores[max_score_idx])
 
149
 
150
- for idx, emotion in enumerate(CONFIG["emotions"]):
151
- results["emotions"][emotion] = {
152
- "score": float(scores[idx]),
153
- "detected": bool(predictions[idx]),
154
- "threshold": float(CONFIG["thresholds"][idx]),
155
- "intensity": self._get_intensity(scores[idx])
156
- }
157
 
158
- return results
159
-
160
- def _get_intensity(self, score: float) -> str:
161
- """Determine intensity level"""
162
- if score >= 0.75: return "Very High"
163
- elif score >= 0.55: return "High"
164
- elif score >= 0.35: return "Medium"
165
- elif score >= 0.20: return "Low"
166
- else: return "Very Low"
167
-
168
- # Global engine instance
169
- engine = EmotiScanEngine()
170
 
171
- def create_radar_chart(emotion_scores: Dict) -> go.Figure:
172
- """Generate radar chart for emotion visualization"""
173
- emotions = list(emotion_scores.keys())
174
- scores = [emotion_scores[e]["score"] * 100 for e in emotions]
175
-
176
- fig = go.Figure()
177
-
178
- fig.add_trace(go.Scatterpolar(
179
- r=scores,
180
- theta=[e.capitalize() for e in emotions],
181
- fill='toself',
182
- fillcolor='rgba(52, 152, 219, 0.3)',
183
- line=dict(color='#3498DB', width=3),
184
- marker=dict(size=10, color='#2980B9')
185
- ))
186
-
187
- fig.update_layout(
188
- polar=dict(
189
- radialaxis=dict(
190
- visible=True,
191
- range=[0, 100],
192
- showticklabels=True,
193
- tickfont=dict(size=11, color="#ECF0F1"),
194
- gridcolor="#34495E"
195
- ),
196
- angularaxis=dict(
197
- tickfont=dict(size=13, color="#ECF0F1", family="Arial Black")
198
- ),
199
- bgcolor="#1A1F2E"
200
- ),
201
- showlegend=False,
202
- paper_bgcolor='rgba(0,0,0,0)',
203
- plot_bgcolor='rgba(0,0,0,0)',
204
- height=400,
205
- margin=dict(l=80, r=80, t=40, b=40)
206
- )
207
-
208
- return fig
209
 
210
- def render_emotion_card(emotion: str, data: Dict, rank: int) -> str:
211
- """Generate individual emotion card HTML"""
212
- meta = EMOTION_META[emotion]
213
- score_pct = data["score"] * 100
214
- is_detected = data["detected"]
215
-
216
- border_width = "4px" if is_detected else "2px"
217
- bg_opacity = "0.15" if is_detected else "0.05"
218
-
219
- return f"""
220
- <div style='
221
- background: linear-gradient(145deg, rgba(26, 31, 46, 0.8), rgba(44, 62, 80, 0.6));
222
- border: {border_width} solid {meta["color"]};
223
- border-radius: 16px;
224
- padding: 20px;
225
- margin: 12px 0;
226
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
227
- position: relative;
228
- overflow: hidden;
229
- '>
230
- <div style='
231
- position: absolute;
232
- top: 0;
233
- left: 0;
234
- right: 0;
235
- height: 6px;
236
- background: linear-gradient(90deg, {meta["gradient"][0]}, {meta["gradient"][1]});
237
- '></div>
238
 
239
- <div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px;'>
240
- <div style='display: flex; align-items: center; gap: 12px;'>
241
- <span style='font-size: 40px;'>{meta["emoji"]}</span>
242
- <div>
243
- <h3 style='margin: 0; color: #ECF0F1; font-size: 20px; font-weight: 700;'>
244
- {emotion.upper()}
245
- </h3>
246
- <p style='margin: 4px 0 0; color: #95A5A6; font-size: 12px;'>{meta["description"]}</p>
247
- </div>
248
- </div>
249
- <div style='text-align: right;'>
250
- <div style='font-size: 32px; font-weight: 900; color: {meta["color"]};'>
251
- {score_pct:.1f}%
252
- </div>
253
- <div style='
254
- background: {meta["color"]};
255
- color: white;
256
- padding: 4px 10px;
257
- border-radius: 12px;
258
- font-size: 11px;
259
- font-weight: 700;
260
- margin-top: 4px;
261
- '>
262
- RANK #{rank}
263
- </div>
264
- </div>
265
- </div>
266
 
267
- <div style='
268
- background: rgba(0, 0, 0, 0.3);
269
- border-radius: 10px;
270
- height: 14px;
271
- overflow: hidden;
272
- margin-bottom: 10px;
273
- '>
274
- <div style='
275
- height: 100%;
276
- width: {score_pct}%;
277
- background: linear-gradient(90deg, {meta["gradient"][0]}, {meta["gradient"][1]});
278
- border-radius: 10px;
279
- transition: width 0.6s ease;
280
- '></div>
281
- </div>
282
 
283
- <div style='display: flex; justify-content: space-between; font-size: 12px; color: #BDC3C7;'>
284
- <span>Intensity: <strong style='color: {meta["color"]};'>{data["intensity"]}</strong></span>
285
- <span>Status: <strong style='color: {"#2ECC71" if is_detected else "#7F8C8D"};'>
286
- {"DETECTED" if is_detected else "Below Threshold"}
287
- </strong></span>
288
- </div>
289
- </div>
290
- """
291
-
292
- def initialize_system():
293
- """Initialize the EmotiScan engine"""
294
- success, message = engine.load_model()
295
-
296
- status_color = "#2ECC71" if success else "#E74C3C"
297
- icon = "✅" if success else "❌"
298
-
299
- status_html = f"""
300
- <div style='
301
- background: linear-gradient(135deg, {status_color}15, {status_color}25);
302
- border: 3px solid {status_color};
303
- border-radius: 18px;
304
- padding: 28px;
305
- text-align: center;
306
- '>
307
- <div style='font-size: 64px; margin-bottom: 12px;'>{icon}</div>
308
- <h2 style='color: #ECF0F1; margin: 0 0 12px 0; font-size: 26px;'>{message}</h2>
309
- <p style='color: #BDC3C7; margin: 0; font-size: 14px;'>
310
- Device: {engine.device.type.upper()} | Model: {CONFIG["model"]} | Ready: {"Yes" if success else "No"}
311
- </p>
312
- </div>
313
- """
314
-
315
- stats_html = generate_stats_panel()
316
-
317
- return status_html, stats_html
318
-
319
- def generate_stats_panel() -> str:
320
- """Create statistics panel"""
321
- stats = engine.session_stats
322
- runtime = (datetime.now() - stats["session_start"]).seconds
323
-
324
- return f"""
325
- <div style='
326
- background: linear-gradient(135deg, #16A085, #1ABC9C);
327
- border-radius: 16px;
328
- padding: 24px;
329
- margin-top: 20px;
330
- '>
331
- <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; text-align: center;'>
332
- <div>
333
- <div style='font-size: 36px; font-weight: 900; color: white;'>
334
- {stats["total_scans"]}
335
- </div>
336
- <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
337
- Total Analyses
338
- </div>
339
- </div>
340
- <div>
341
- <div style='font-size: 36px; font-weight: 900; color: white;'>
342
- {len(stats["emotion_detections"])}
343
- </div>
344
- <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
345
- Unique Emotions
346
- </div>
347
- </div>
348
- <div>
349
- <div style='font-size: 36px; font-weight: 900; color: white;'>
350
- {runtime}s
351
- </div>
352
- <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
353
- Session Time
354
- </div>
355
- </div>
356
- </div>
357
- </div>
358
- """
359
-
360
- def scan_emotion(text: str, show_chart: bool):
361
- """Main analysis function"""
362
- if not engine.ready:
363
- error_html = """
364
- <div style='padding: 40px; text-align: center; background: linear-gradient(135deg, #E74C3C, #C0392B); border-radius: 18px;'>
365
- <div style='font-size: 64px;'>⚠️</div>
366
- <h2 style='color: white; margin: 12px 0;'>System Not Ready</h2>
367
- <p style='color: rgba(255,255,255,0.9);'>Please initialize the system first</p>
368
- </div>
369
  """
370
- return error_html, "", None, "{}", generate_stats_panel()
371
-
372
- if not text.strip():
373
- empty_html = """
374
- <div style='padding: 40px; text-align: center; background: linear-gradient(135deg, #F39C12, #E67E22); border-radius: 18px;'>
375
- <div style='font-size: 64px;'>📝</div>
376
- <h2 style='color: white; margin: 12px 0;'>No Input Provided</h2>
377
- <p style='color: rgba(255,255,255,0.9);'>Please enter text to analyze</p>
378
- </div>
379
- """
380
- return empty_html, "", None, "{}", generate_stats_panel()
381
 
382
- try:
383
- results = engine.analyze_text(text)
384
-
385
- # Summary card
386
- dominant = results["metadata"]["dominant_emotion"]
387
- confidence = results["metadata"]["confidence"]
388
- detected_count = results["metadata"]["detected_emotions"]
 
 
 
 
 
389
 
390
- summary_html = f"""
391
- <div style='
392
- background: linear-gradient(135deg, #2C3E50, #34495E);
393
- border: 3px solid #16A085;
394
- border-radius: 18px;
395
- padding: 32px;
396
- text-align: center;
397
- margin-bottom: 24px;
398
- '>
399
- <div style='font-size: 72px; margin-bottom: 16px;'>
400
- {EMOTION_META[dominant]["emoji"]}
401
- </div>
402
- <h2 style='color: #ECF0F1; margin: 0 0 12px 0; font-size: 32px; font-weight: 800;'>
403
- Dominant Emotion: {dominant.upper()}
404
- </h2>
405
- <p style='color: #BDC3C7; font-size: 18px; margin: 0 0 20px 0;'>
406
- Confidence: {confidence:.1%} | Detected: {detected_count}/{len(CONFIG["emotions"])} emotions
407
- </p>
408
- <div style='color: #95A5A6; font-size: 14px;'>
409
- 📊 {results["metadata"]["text_length"]} words analyzed
410
- </div>
411
- </div>
412
  """
 
 
413
 
414
- # Emotion cards
415
- sorted_emotions = sorted(
416
- results["emotions"].items(),
417
- key=lambda x: x[1]["score"],
418
- reverse=True
419
- )
420
-
421
- cards_html = "<div style='max-width: 900px; margin: 0 auto;'>"
422
- for rank, (emotion, data) in enumerate(sorted_emotions, 1):
423
- cards_html += render_emotion_card(emotion, data, rank)
424
- cards_html += "</div>"
425
 
426
- # Chart
427
- chart = create_radar_chart(results["emotions"]) if show_chart else None
428
 
429
- # JSON
430
- json_output = json.dumps(results, indent=2)
 
 
 
 
 
431
 
432
- return summary_html, cards_html, chart, json_output, generate_stats_panel()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
- except Exception as e:
435
- error_html = f"""
436
- <div style='background: linear-gradient(135deg, #E74C3C, #C0392B); padding: 24px; border-radius: 16px;'>
437
- <h3 style='color: white; margin: 0;'>❌ Analysis Error</h3>
438
- <p style='color: rgba(255,255,255,0.9); margin: 10px 0 0;'>{str(e)}</p>
439
- </div>
440
- """
441
- return error_html, "", None, "{}", generate_stats_panel()
442
-
443
- def batch_scan(texts: str):
444
- """Batch processing function"""
445
- if not engine.ready:
446
- return "<div style='padding: 24px; background: #E74C3C; border-radius: 14px; color: white;'>❌ System not initialized</div>"
447
-
448
- lines = [l.strip() for l in texts.split('\n') if l.strip()]
449
- if not lines:
450
- return "<div style='padding: 24px; background: #F39C12; border-radius: 14px; color: white;'>⚠️ No input provided</div>"
451
-
452
- output = f"""
453
- <div style='
454
- background: linear-gradient(135deg, #16A085, #1ABC9C);
455
- padding: 28px;
456
- border-radius: 16px;
457
- margin-bottom: 24px;
458
- color: white;
459
- '>
460
- <h2 style='margin: 0; font-size: 28px; font-weight: 800;'>📦 Batch Analysis Report</h2>
461
- <p style='margin: 12px 0 0; font-size: 16px; opacity: 0.95;'>{len(lines)} samples processed</p>
462
- </div>
463
- """
464
-
465
- for idx, text in enumerate(lines, 1):
466
- try:
467
- result = engine.analyze_text(text)
468
- dom = result["metadata"]["dominant_emotion"]
469
- conf = result["metadata"]["confidence"]
470
- meta = EMOTION_META[dom]
471
-
472
- preview = text[:70] + ("..." if len(text) > 70 else "")
473
-
474
- output += f"""
475
- <div style='
476
- background: linear-gradient(145deg, #2C3E50, #34495E);
477
- border-left: 6px solid {meta["color"]};
478
- border-radius: 12px;
479
- padding: 20px;
480
- margin: 14px 0;
481
- '>
482
- <div style='display: flex; gap: 16px; align-items: start;'>
483
- <div style='font-size: 36px;'>{meta["emoji"]}</div>
484
- <div style='flex: 1;'>
485
- <div style='color: {meta["color"]}; font-weight: 700; font-size: 15px; margin-bottom: 8px;'>
486
- Sample #{idx}
487
- </div>
488
- <div style='color: #ECF0F1; font-style: italic; margin-bottom: 12px; line-height: 1.6;'>
489
- "{preview}"
490
- </div>
491
- <div style='display: flex; gap: 10px;'>
492
- <span style='background: #1A1F2E; padding: 6px 12px; border-radius: 8px; font-size: 12px; color: #95A5A6;'>
493
- {dom.upper()} ({conf:.0%})
494
- </span>
495
- <span style='background: #1A1F2E; padding: 6px 12px; border-radius: 8px; font-size: 12px; color: #95A5A6;'>
496
- {result["metadata"]["detected_emotions"]}/5 Active
497
- </span>
498
- </div>
499
- </div>
500
- </div>
501
- </div>
502
- """
503
- except Exception as e:
504
- output += f"<div style='padding: 16px; background: #E74C3C; border-radius: 10px; color: white; margin: 10px 0;'>Error in sample #{idx}: {str(e)}</div>"
505
-
506
- return output
507
-
508
- def show_history():
509
- """Display scan history"""
510
- history = engine.session_stats["scan_history"]
511
-
512
- if not history:
513
- return "<div style='padding: 24px; color: #95A5A6; text-align: center; background: #2C3E50; border-radius: 14px;'>No scans performed yet</div>"
514
-
515
- output = f"""
516
- <div style='background: #2C3E50; padding: 24px; border-radius: 14px;'>
517
- <h3 style='color: #ECF0F1; margin: 0 0 16px 0; font-size: 20px;'>
518
- 📜 Scan History ({len(history)} total)
519
- </h3>
520
- """
521
-
522
- for record in reversed(history[-15:]):
523
- timestamp = datetime.fromisoformat(record['timestamp']).strftime('%H:%M:%S')
524
 
525
- output += f"""
526
- <div style='
527
- background: #1A1F2E;
528
- padding: 16px;
529
- margin: 10px 0;
530
- border-radius: 10px;
531
- border-left: 4px solid #16A085;
532
- '>
533
- <div style='color: #95A5A6; font-size: 12px; margin-bottom: 6px;'>{timestamp}</div>
534
- <div style='color: #ECF0F1; font-size: 14px; margin-bottom: 8px;'>"{record["text_preview"]}"</div>
535
- <span style='
536
- background: #16A085;
537
- color: white;
538
- padding: 4px 10px;
539
- border-radius: 8px;
540
- font-size: 12px;
541
- font-weight: 700;
542
- '>
543
- {record["detected_count"]}/5 Detected
544
- </span>
545
- </div>
546
- """
547
-
548
- output += "</div>"
549
- return output
550
-
551
- # Build interface
552
- with gr.Blocks(title="🎭 EmotiScan") as app:
553
- gr.HTML("""
554
- <div style='
555
- text-align: center;
556
- padding: 48px 32px;
557
- background: linear-gradient(135deg, #16A085 0%, #1ABC9C 50%, #2ECC71 100%);
558
- border-radius: 24px;
559
- margin-bottom: 32px;
560
- box-shadow: 0 12px 48px rgba(22, 160, 133, 0.4);
561
- '>
562
- <div style='font-size: 96px; margin-bottom: 16px; filter: drop-shadow(0 0 20px rgba(255,255,255,0.4));'>
563
- 🎭
564
- </div>
565
- <h1 style='
566
- font-size: 56px;
567
- margin: 0;
568
- font-weight: 900;
569
- color: white;
570
- text-shadow: 0 4px 12px rgba(0,0,0,0.3);
571
- letter-spacing: -1px;
572
- '>
573
- EmotiScan
574
- </h1>
575
- <p style='font-size: 24px; margin: 16px 0; color: white; opacity: 0.95; font-weight: 600;'>
576
- Neural Emotion Detection System
577
- </p>
578
- <p style='font-size: 15px; opacity: 0.9; color: white; font-weight: 500;'>
579
- Powered by RoBERTa Transformer • Multi-Label Classification • Real-Time Processing
580
- </p>
581
- <div style='margin-top: 28px; display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;'>
582
- <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😠 Anger</span>
583
- <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😨 Fear</span>
584
- <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😊 Joy</span>
585
- <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😢 Sadness</span>
586
- <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😲 Surprise</span>
587
- </div>
588
- </div>
589
- """)
590
-
591
- with gr.Row():
592
- with gr.Column(scale=2):
593
- system_status = gr.HTML("<div style='padding: 28px; text-align: center; background: #34495E; border-radius: 18px; color: white;'><h3>🔄 Awaiting Initialization</h3></div>")
594
- with gr.Column(scale=1):
595
- init_btn = gr.Button("🚀 Initialize System", variant="primary", size="lg", scale=1)
596
-
597
- stats_display = gr.HTML("")
598
-
599
- with gr.Tabs():
600
- with gr.Tab("🔬 Single Scan"):
601
- with gr.Row():
602
- with gr.Column(scale=1):
603
- input_text = gr.Textbox(
604
- label="📄 Text Input",
605
- placeholder="Enter text for emotion analysis...",
606
- lines=8
607
- )
608
- with gr.Row():
609
- scan_btn = gr.Button("⚡ Scan Emotions", variant="primary", size="lg")
610
- clear_btn = gr.ClearButton([input_text], value="🧹 Clear", size="lg")
611
- show_radar = gr.Checkbox(label="Show Radar Visualization", value=True)
612
-
613
- gr.Examples([
614
- ["This is absolutely amazing! I'm so thrilled and excited!"],
615
- ["I'm furious about this completely unacceptable situation!"],
616
- ["I'm terrified and extremely worried about the future."],
617
- ["Wow! I never expected this to happen at all!"],
618
- ["I'm heartbroken and feel completely devastated."]
619
- ], inputs=[input_text], label="💡 Example Inputs")
620
-
621
- with gr.Column(scale=1):
622
- gr.Markdown("### 📊 Analysis Results")
623
- summary_output = gr.HTML()
624
- cards_output = gr.HTML()
625
 
626
- with gr.Tab("📈 Visualization"):
627
- radar_chart = gr.Plot(label="Emotion Radar Chart")
628
 
629
- with gr.Tab("🔄 Batch Processing"):
630
- gr.Markdown("### Process Multiple Texts\nEnter one text per line")
631
- batch_input = gr.Textbox(
632
- label="Batch Input",
633
- placeholder="Text 1\nText 2\nText 3...",
634
- lines=10
635
- )
636
- batch_btn = gr.Button("⚡ Process Batch", variant="primary", size="lg")
637
- batch_output = gr.HTML()
638
 
639
- with gr.Tab("💾 JSON Export"):
640
- gr.Markdown("### Structured Data Output")
641
- json_output = gr.Code(label="JSON Results", language="json", lines=20)
642
 
643
- with gr.Tab("📜 History"):
644
- gr.Markdown("### Scan History Log")
645
- refresh_btn = gr.Button("🔄 Refresh", variant="secondary")
646
- history_output = gr.HTML()
647
-
648
- gr.HTML("""
649
- <div style='
650
- margin-top: 32px;
651
- padding: 28px;
652
- background: linear-gradient(145deg, #2C3E50, #34495E);
653
- border-radius: 16px;
654
- border: 2px solid #16A085;
655
- '>
656
- <h2 style='color: #ECF0F1; margin-top: 0; font-size: 26px; font-weight: 700;'>
657
- 🔬 Technical Specifications
658
- </h2>
659
- <div style='color: #BDC3C7; line-height: 1.9;'>
660
- <p style='margin: 12px 0;'>
661
- <strong style='color: #16A085;'>Architecture:</strong> RoBERTa-base transformer with 125M parameters,
662
- fine-tuned for multi-label emotion classification
663
- </p>
664
- <p style='margin: 12px 0;'>
665
- <strong style='color: #16A085;'>Emotion Categories:</strong> Anger, Fear, Joy, Sadness, Surprise
666
- </p>
667
- <p style='margin: 12px 0;'>
668
- <strong style='color: #16A085;'>Performance Metrics:</strong> 87.2% F1 Score on validation dataset
669
- </p>
670
- <p style='margin: 12px 0;'>
671
- <strong style='color: #16A085;'>Processing Speed:</strong> Real-time inference with optimized threshold detection
672
- </p>
673
- <p style='margin: 12px 0;'>
674
- <strong style='color: #16A085;'>Max Sequence Length:</strong> 200 tokens with truncation support
675
- </p>
676
- </div>
677
- </div>
678
- """)
679
-
680
- # Event Handlers
681
- init_btn.click(
682
- initialize_system,
683
- outputs=[system_status, stats_display]
684
- )
685
-
686
- scan_btn.click(
687
- scan_emotion,
688
- inputs=[input_text, show_radar],
689
- outputs=[summary_output, cards_output, radar_chart, json_output, stats_display]
690
- )
691
-
692
- input_text.submit(
693
- scan_emotion,
694
- inputs=[input_text, show_radar],
695
- outputs=[summary_output, cards_output, radar_chart, json_output, stats_display]
696
  )
697
 
698
- batch_btn.click(
699
- batch_scan,
700
- inputs=[batch_input],
701
- outputs=[batch_output]
 
702
  )
703
 
704
- refresh_btn.click(
705
- show_history,
706
- outputs=[history_output]
 
707
  )
708
 
 
709
  if __name__ == "__main__":
710
- print("🎭 Launching EmotiScan interface...")
711
- app.launch(
712
- server_name="0.0.0.0",
713
- server_port=7860,
714
  share=False,
715
  show_error=True
716
  )
 
3
  import torch.nn as nn
4
  from transformers import AutoTokenizer, AutoModel
5
  import numpy as np
 
 
 
 
 
 
 
 
 
6
 
7
  # Configuration
8
+ MODEL_NAME = "roberta-base"
9
+ MAX_LEN = 200
10
+ EMOTIONS = ["anger", "fear", "joy", "sadness", "surprise"]
11
+ EMOTION_EMOJIS = ["😠", "😨", "😊", "😢", "😲"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # Model Architecture (MUST MATCH TRAINING)
14
+ class RobertaEmotion(nn.Module):
15
+ def __init__(self, model_name=MODEL_NAME, dropout=0.35, num_labels=5):
16
  super().__init__()
17
+ self.backbone = AutoModel.from_pretrained(model_name)
18
+ hidden_size = self.backbone.config.hidden_size
19
+ self.dropout = nn.Dropout(dropout)
20
+ self.head = nn.Linear(hidden_size, num_labels)
21
+
22
  def forward(self, input_ids, attention_mask):
23
+ out = self.backbone(input_ids=input_ids, attention_mask=attention_mask)
24
+ if hasattr(out, "pooler_output") and out.pooler_output is not None:
25
+ pooled = out.pooler_output
26
+ else:
27
+ pooled = out.last_hidden_state[:, 0]
28
+ x = self.dropout(pooled)
29
+ logits = self.head(x)
30
+ return logits
31
 
32
+ # Load model and tokenizer
33
+ print("🔄 Loading model and tokenizer...")
34
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
35
+ print(f"📱 Device: {device}")
36
+
37
+ try:
38
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
39
+ model = RobertaEmotion(num_labels=len(EMOTIONS))
40
+
41
+ # Load trained weights
42
+ state_dict = torch.load('roberta.pth', map_location=device)
43
+ model.load_state_dict(state_dict)
44
+ model = model.to(device)
45
+ model.eval()
46
+
47
+ print(" Model loaded successfully!")
48
+ except Exception as e:
49
+ print(f"⚠️ Error loading model: {e}")
50
+ raise e
51
+
52
+ # Optimized thresholds from training (you can update these after validation)
53
+ BEST_THRESHOLDS = np.array([0.5, 0.5, 0.5, 0.5, 0.5]) # Default thresholds
54
+
55
+ def predict_emotions(text):
56
+ """
57
+ Predict emotions from text
 
 
 
 
 
 
 
 
58
 
59
+ Args:
60
+ text: Input text string
 
 
 
 
 
61
 
62
+ Returns:
63
+ Dictionary with emotion predictions and probabilities
64
+ """
65
+ if not text or not text.strip():
66
+ return {
67
+ "⚠️ Error": "Please enter some text to analyze",
68
+ "Detected Emotions": "None"
69
+ }
70
+
71
+ try:
72
  # Tokenize
73
+ encoding = tokenizer(
74
+ text,
75
  truncation=True,
76
  padding="max_length",
77
+ max_length=MAX_LEN,
78
  return_tensors="pt"
79
  )
80
 
81
+ input_ids = encoding["input_ids"].to(device)
82
+ attention_mask = encoding["attention_mask"].to(device)
83
+
84
+ # Predict
85
  with torch.no_grad():
86
+ logits = model(input_ids, attention_mask)
87
+ probs = torch.sigmoid(logits).cpu().numpy()[0]
 
 
 
 
88
 
89
+ # Apply thresholds
90
+ predictions = (probs > BEST_THRESHOLDS).astype(int)
 
 
 
91
 
92
+ # Format results
93
+ detected = []
94
+ all_probs = {}
 
 
95
 
96
+ for i, (emotion, emoji, prob, pred) in enumerate(zip(EMOTIONS, EMOTION_EMOJIS, probs, predictions)):
97
+ all_probs[f"{emoji} {emotion.capitalize()}"] = float(prob)
98
+ if pred == 1:
99
+ detected.append(f"{emoji} {emotion.capitalize()} ({prob:.1%})")
 
 
 
 
 
 
100
 
101
+ if not detected:
102
+ detected_str = "No strong emotions detected (all probabilities below threshold)"
103
+ else:
104
+ detected_str = " • ".join(detected)
105
 
106
+ result = {
107
+ "🎯 Detected Emotions": detected_str,
108
+ "📊 All Probabilities": all_probs,
109
+ "📝 Text Length": f"{len(text)} characters",
110
+ "🔍 Analysis": f"Analyzed with RoBERTa-base model"
111
+ }
 
112
 
113
+ return result
114
+
115
+ except Exception as e:
116
+ return {
117
+ "⚠️ Error": f"Prediction failed: {str(e)}",
118
+ "Detected Emotions": "Error"
119
+ }
 
 
 
 
 
120
 
121
+ # Example texts
122
+ examples = [
123
+ ["I just got promoted at work! I can't believe it!"],
124
+ ["I'm so worried about the exam tomorrow. What if I fail?"],
125
+ ["This is absolutely unacceptable! I demand to speak to the manager!"],
126
+ ["I miss my family so much. It's been months since I've seen them."],
127
+ ["Wow! I never expected to see you here!"],
128
+ ["I'm excited but also nervous about starting my new job next week."],
129
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ # Create Gradio Interface
132
+ with gr.Blocks(theme=gr.themes.Soft(), title="Multi-Label Emotion Detection") as demo:
133
+ gr.Markdown(
134
+ """
135
+ # 😊 Multi-Label Emotion Classification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ This model detects **multiple emotions** in text using a fine-tuned RoBERTa transformer.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ ### Emotions Detected:
140
+ 😠 Anger | 😨 Fear | 😊 Joy | 😢 Sadness | 😲 Surprise
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  """
144
+ )
 
 
 
 
 
 
 
 
 
 
145
 
146
+ with gr.Row():
147
+ with gr.Column():
148
+ text_input = gr.Textbox(
149
+ label="Enter your text",
150
+ placeholder="Type or paste text here to analyze emotions...",
151
+ lines=5,
152
+ max_lines=10
153
+ )
154
+
155
+ with gr.Row():
156
+ clear_btn = gr.Button("🗑️ Clear", variant="secondary")
157
+ analyze_btn = gr.Button("🔮 Analyze Emotions", variant="primary", scale=2)
158
 
159
+ with gr.Column():
160
+ output_json = gr.JSON(
161
+ label="📊 Analysis Results",
162
+ show_label=True
163
+ )
164
+
165
+ gr.Markdown("### 💡 Try these examples:")
166
+ gr.Examples(
167
+ examples=examples,
168
+ inputs=text_input,
169
+ outputs=output_json,
170
+ fn=predict_emotions,
171
+ cache_examples=False
172
+ )
173
+
174
+ gr.Markdown(
 
 
 
 
 
 
175
  """
176
+ ---
177
+ ### 📈 How It Works
178
 
179
+ This is a **multi-label classification** model, meaning:
180
+ - Each text can have **multiple emotions** simultaneously
181
+ - For example, "I'm excited but nervous" → Both Joy ✅ and Fear ✅
182
+ - Each emotion is predicted independently with a probability score
183
+ - Emotions above the threshold are marked as "detected"
 
 
 
 
 
 
184
 
185
+ ### 🎯 Model Details
 
186
 
187
+ | Component | Details |
188
+ |-----------|---------|
189
+ | **Architecture** | RoBERTa-base (125M parameters) |
190
+ | **Training Data** | Multi-label emotion dataset |
191
+ | **Max Sequence Length** | 200 tokens |
192
+ | **Evaluation Metric** | Macro F1-Score |
193
+ | **Framework** | PyTorch + Transformers |
194
 
195
+ ### 🏗️ Architecture
196
+ ```
197
+ Input Text
198
+
199
+ RoBERTa Tokenizer (BPE)
200
+
201
+ RoBERTa Encoder (12 layers)
202
+
203
+ [CLS] Token Pooling
204
+
205
+ Dropout (0.35)
206
+
207
+ Linear Layer (768 → 5)
208
+
209
+ Sigmoid Activation
210
+
211
+ 5 Emotion Probabilities
212
+ ```
213
 
214
+ ### ⚙️ Training Configuration
215
+ - **Optimizer**: AdamW (lr=2e-5, weight_decay=0.02)
216
+ - **Scheduler**: Linear warmup (10% of steps)
217
+ - **Loss Function**: BCE with Logits + Label Smoothing (0.05)
218
+ - **Batch Size**: 8 (with 4x gradient accumulation = effective 32)
219
+ - **Epochs**: 8 (with early stopping, patience=3)
220
+ - **Validation**: Threshold tuning per emotion class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ ### 📊 Performance Optimization
223
+ - **Stratified split** by label distribution
224
+ - **Per-class threshold tuning** for optimal F1-score
225
+ - **Label smoothing** to prevent overconfidence
226
+ - **Early stopping** to prevent overfitting
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ ---
 
229
 
230
+ ### 🔗 Resources
231
+ - **Model**: RoBERTa-base ([Hugging Face](https://huggingface.co/roberta-base))
232
+ - **Framework**: PyTorch + Transformers
233
+ - **Project**: 2025 Sep DLGenAI Course
 
 
 
 
 
234
 
235
+ ---
 
 
236
 
237
+ *Built with ❤️ using PyTorch, Transformers, and Gradio*
238
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  )
240
 
241
+ # Button actions
242
+ analyze_btn.click(
243
+ fn=predict_emotions,
244
+ inputs=text_input,
245
+ outputs=output_json
246
  )
247
 
248
+ clear_btn.click(
249
+ fn=lambda: ("", None),
250
+ inputs=None,
251
+ outputs=[text_input, output_json]
252
  )
253
 
254
+ # Launch the app
255
  if __name__ == "__main__":
256
+ demo.launch(
 
 
 
257
  share=False,
258
  show_error=True
259
  )