Spaces:
Sleeping
Sleeping
OliverPerrin commited on
Commit ยท
fc64ea0
1
Parent(s): 38eb401
Redesign Gradio demo with book/news browsing, update .gitignore
Browse files- .gitignore +4 -0
- scripts/demo_gradio.py +457 -189
- src/training/safe_compile.py +5 -2
.gitignore
CHANGED
|
@@ -64,3 +64,7 @@ ehthumbs_vista.db
|
|
| 64 |
# Config overrides
|
| 65 |
configs/local/*.png
|
| 66 |
*.pt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
# Config overrides
|
| 65 |
configs/local/*.png
|
| 66 |
*.pt
|
| 67 |
+
|
| 68 |
+
# Backup/private files
|
| 69 |
+
scripts/demo_gradio_old.py
|
| 70 |
+
docs/paper.tex
|
scripts/demo_gradio.py
CHANGED
|
@@ -1,20 +1,23 @@
|
|
| 1 |
"""
|
| 2 |
Gradio demo for LexiMind multi-task NLP model.
|
| 3 |
|
| 4 |
-
|
| 5 |
-
-
|
| 6 |
-
-
|
| 7 |
-
-
|
|
|
|
| 8 |
|
| 9 |
Author: Oliver Perrin
|
| 10 |
-
Date: 2025-12-05
|
| 11 |
"""
|
| 12 |
|
| 13 |
from __future__ import annotations
|
| 14 |
|
| 15 |
import json
|
|
|
|
| 16 |
import sys
|
| 17 |
from pathlib import Path
|
|
|
|
| 18 |
|
| 19 |
import gradio as gr
|
| 20 |
|
|
@@ -37,14 +40,115 @@ logger = get_logger(__name__)
|
|
| 37 |
# --------------- Constants ---------------
|
| 38 |
|
| 39 |
OUTPUTS_DIR = PROJECT_ROOT / "outputs"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
EVAL_REPORT_PATH = OUTPUTS_DIR / "evaluation_report.json"
|
| 41 |
TRAINING_HISTORY_PATH = OUTPUTS_DIR / "training_history.json"
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
"
|
| 46 |
-
"
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
# --------------- Pipeline Management ---------------
|
| 50 |
|
|
@@ -76,68 +180,38 @@ def get_pipeline():
|
|
| 76 |
return _pipeline
|
| 77 |
|
| 78 |
|
| 79 |
-
# --------------- Core Functions ---------------
|
| 80 |
|
| 81 |
|
| 82 |
-
def
|
| 83 |
"""Run all three tasks and return formatted results."""
|
| 84 |
if not text or not text.strip():
|
| 85 |
-
return "Please enter text
|
| 86 |
|
| 87 |
try:
|
| 88 |
pipe = get_pipeline()
|
| 89 |
|
| 90 |
# Run tasks
|
| 91 |
-
summary = pipe.summarize([text], max_length=
|
| 92 |
if not summary:
|
| 93 |
summary = "(Unable to generate summary)"
|
| 94 |
|
| 95 |
-
emotions = pipe.predict_emotions([text], threshold=0.3)[0]
|
| 96 |
topic = pipe.predict_topics([text])[0]
|
| 97 |
|
| 98 |
-
# Format emotions
|
| 99 |
-
emotion_emoji = {
|
| 100 |
-
"joy": "๐",
|
| 101 |
-
"love": "โค๏ธ",
|
| 102 |
-
"anger": "๐ ",
|
| 103 |
-
"fear": "๐จ",
|
| 104 |
-
"sadness": "๐ข",
|
| 105 |
-
"surprise": "๐ฒ",
|
| 106 |
-
"neutral": "๐",
|
| 107 |
-
"admiration": "๐คฉ",
|
| 108 |
-
"amusement": "๐",
|
| 109 |
-
"annoyance": "๐ค",
|
| 110 |
-
"approval": "๐",
|
| 111 |
-
"caring": "๐ค",
|
| 112 |
-
"confusion": "๐",
|
| 113 |
-
"curiosity": "๐ค",
|
| 114 |
-
"desire": "๐",
|
| 115 |
-
"disappointment": "๐",
|
| 116 |
-
"disapproval": "๐",
|
| 117 |
-
"disgust": "๐คข",
|
| 118 |
-
"embarrassment": "๐ณ",
|
| 119 |
-
"excitement": "๐",
|
| 120 |
-
"gratitude": "๐",
|
| 121 |
-
"grief": "๐ญ",
|
| 122 |
-
"nervousness": "๏ฟฝ๏ฟฝ",
|
| 123 |
-
"optimism": "๐",
|
| 124 |
-
"pride": "๐ฆ",
|
| 125 |
-
"realization": "๐ก",
|
| 126 |
-
"relief": "๐",
|
| 127 |
-
"remorse": "๐",
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
if emotions.labels:
|
| 131 |
emotion_parts = []
|
| 132 |
for lbl, score in zip(emotions.labels[:5], emotions.scores[:5], strict=False):
|
| 133 |
-
emoji =
|
| 134 |
emotion_parts.append(f"{emoji} **{lbl.title()}** ({score:.0%})")
|
| 135 |
emotion_str = "\n".join(emotion_parts)
|
| 136 |
else:
|
| 137 |
emotion_str = "๐ No strong emotions detected"
|
| 138 |
|
| 139 |
# Format topic
|
| 140 |
-
|
|
|
|
| 141 |
|
| 142 |
return summary, emotion_str, topic_str
|
| 143 |
|
|
@@ -146,9 +220,85 @@ def analyze(text: str) -> tuple[str, str, str]:
|
|
| 146 |
return f"Error: {e}", "", ""
|
| 147 |
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
def load_metrics() -> str:
|
| 150 |
"""Load evaluation metrics and format as markdown."""
|
| 151 |
-
# Load evaluation report
|
| 152 |
eval_metrics = {}
|
| 153 |
if EVAL_REPORT_PATH.exists():
|
| 154 |
try:
|
|
@@ -157,7 +307,6 @@ def load_metrics() -> str:
|
|
| 157 |
except Exception:
|
| 158 |
pass
|
| 159 |
|
| 160 |
-
# Load training history
|
| 161 |
train_metrics = {}
|
| 162 |
if TRAINING_HISTORY_PATH.exists():
|
| 163 |
try:
|
|
@@ -166,18 +315,17 @@ def load_metrics() -> str:
|
|
| 166 |
except Exception:
|
| 167 |
pass
|
| 168 |
|
| 169 |
-
# Get final validation metrics
|
| 170 |
val_final = train_metrics.get("val_epoch_3", {})
|
| 171 |
|
| 172 |
md = """
|
| 173 |
## ๐ Model Performance
|
| 174 |
|
| 175 |
-
### Training Results
|
| 176 |
|
| 177 |
-
| Task | Metric |
|
| 178 |
-
|
| 179 |
| **Topic Classification** | Accuracy | **{topic_acc:.1%}** |
|
| 180 |
-
| **Emotion Detection** | F1
|
| 181 |
| **Summarization** | ROUGE-like | {rouge:.1%} |
|
| 182 |
|
| 183 |
### Evaluation Results
|
|
@@ -188,13 +336,6 @@ def load_metrics() -> str:
|
|
| 188 |
| Emotion F1 (macro) | {eval_emo:.1%} |
|
| 189 |
| ROUGE-like | {eval_rouge:.1%} |
|
| 190 |
| BLEU | {eval_bleu:.3f} |
|
| 191 |
-
|
| 192 |
-
---
|
| 193 |
-
|
| 194 |
-
### Topic Classification Details
|
| 195 |
-
|
| 196 |
-
| Category | Precision | Recall | F1 |
|
| 197 |
-
|----------|-----------|--------|-----|
|
| 198 |
""".format(
|
| 199 |
topic_acc=val_final.get("topic_accuracy", 0),
|
| 200 |
emo_f1=val_final.get("emotion_f1", 0),
|
|
@@ -205,183 +346,303 @@ def load_metrics() -> str:
|
|
| 205 |
eval_bleu=eval_metrics.get("summarization", {}).get("bleu", 0),
|
| 206 |
)
|
| 207 |
|
| 208 |
-
# Add per-class metrics
|
| 209 |
-
topic_report = eval_metrics.get("topic", {}).get("classification_report", {})
|
| 210 |
-
for cat, metrics in topic_report.items():
|
| 211 |
-
if cat in ["macro avg", "weighted avg", "micro avg"]:
|
| 212 |
-
continue
|
| 213 |
-
if isinstance(metrics, dict):
|
| 214 |
-
md += f"| {cat} | {metrics.get('precision', 0):.1%} | {metrics.get('recall', 0):.1%} | {metrics.get('f1-score', 0):.1%} |\n"
|
| 215 |
-
|
| 216 |
return md
|
| 217 |
|
| 218 |
|
| 219 |
-
def get_viz_path(filename: str) -> str | None:
|
| 220 |
-
"""Get visualization path if file exists."""
|
| 221 |
-
path = OUTPUTS_DIR / filename
|
| 222 |
-
return str(path) if path.exists() else None
|
| 223 |
-
|
| 224 |
-
|
| 225 |
# --------------- Gradio Interface ---------------
|
| 226 |
|
| 227 |
with gr.Blocks(
|
| 228 |
title="LexiMind - Multi-Task NLP",
|
| 229 |
theme=gr.themes.Soft(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
) as demo:
|
| 231 |
gr.Markdown(
|
| 232 |
"""
|
| 233 |
# ๐ง LexiMind
|
| 234 |
### Multi-Task Transformer for Document Analysis
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
|
|
|
|
|
|
| 238 |
|
| 239 |
-
>
|
| 240 |
"""
|
| 241 |
)
|
| 242 |
|
| 243 |
-
#
|
| 244 |
-
with gr.Tab("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
with gr.Row():
|
| 246 |
-
with gr.Column(scale=
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
value=SAMPLE_TEXTS[0],
|
| 252 |
)
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
)
|
| 258 |
-
|
| 259 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
with gr.Row():
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
with gr.Column(scale=2):
|
| 270 |
-
gr.
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
interactive=False,
|
| 275 |
)
|
|
|
|
| 276 |
with gr.Row():
|
| 277 |
with gr.Column():
|
| 278 |
-
gr.
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
with gr.Column():
|
| 281 |
-
gr.
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
)
|
| 289 |
|
| 290 |
-
#
|
| 291 |
with gr.Tab("๐ Metrics"):
|
| 292 |
with gr.Row():
|
| 293 |
with gr.Column(scale=2):
|
| 294 |
gr.Markdown(load_metrics())
|
| 295 |
with gr.Column(scale=1):
|
| 296 |
-
confusion_path =
|
| 297 |
-
if confusion_path:
|
| 298 |
-
gr.Image(confusion_path, label="Confusion Matrix"
|
| 299 |
-
|
| 300 |
-
# --------------- Visualizations Tab ---------------
|
| 301 |
-
with gr.Tab("๐จ Visualizations"):
|
| 302 |
-
gr.Markdown("### Model Internals")
|
| 303 |
-
|
| 304 |
-
with gr.Row():
|
| 305 |
-
attn_path = get_viz_path("attention_visualization.png")
|
| 306 |
-
if attn_path:
|
| 307 |
-
gr.Image(attn_path, label="Self-Attention Pattern")
|
| 308 |
-
|
| 309 |
-
pos_path = get_viz_path("positional_encoding_heatmap.png")
|
| 310 |
-
if pos_path:
|
| 311 |
-
gr.Image(pos_path, label="Positional Encodings")
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
if multi_path:
|
| 316 |
-
gr.Image(multi_path, label="Multi-Head Attention")
|
| 317 |
-
|
| 318 |
-
single_path = get_viz_path("single_vs_multihead.png")
|
| 319 |
-
if single_path:
|
| 320 |
-
gr.Image(single_path, label="Single vs Multi-Head Comparison")
|
| 321 |
-
|
| 322 |
-
# --------------- Architecture Tab ---------------
|
| 323 |
-
with gr.Tab("๐ง Architecture"):
|
| 324 |
gr.Markdown(
|
| 325 |
"""
|
| 326 |
-
###
|
| 327 |
-
|
| 328 |
-
| Component | Configuration |
|
| 329 |
-
|-----------|---------------|
|
| 330 |
-
| **Base** | Custom Transformer (encoder-decoder) |
|
| 331 |
-
| **Initialization** | FLAN-T5-base weights |
|
| 332 |
-
| **Encoder** | 6 layers, 768 hidden dim, 12 heads |
|
| 333 |
-
| **Decoder** | 6 layers with cross-attention |
|
| 334 |
-
| **Activation** | Gated-GELU |
|
| 335 |
-
| **Position** | Relative position bias |
|
| 336 |
-
|
| 337 |
-
### Training Configuration
|
| 338 |
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
| **Optimizer** | AdamW (lr=2e-5, wd=0.01) |
|
| 342 |
-
| **Scheduler** | Cosine with 1000 warmup steps |
|
| 343 |
-
| **Batch Size** | 14 ร 3 accumulation = 42 effective |
|
| 344 |
-
| **Precision** | TF32 (Ampere GPU) |
|
| 345 |
-
| **Compilation** | torch.compile (inductor) |
|
| 346 |
|
| 347 |
-
|
| 348 |
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
"""
|
| 355 |
-
)
|
| 356 |
-
|
| 357 |
-
# --------------- About Tab ---------------
|
| 358 |
-
with gr.Tab("โน๏ธ About"):
|
| 359 |
-
gr.Markdown(
|
| 360 |
-
"""
|
| 361 |
-
### About LexiMind
|
| 362 |
|
| 363 |
-
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
| 370 |
|
| 371 |
-
|
| 372 |
|
| 373 |
-
- **
|
| 374 |
-
- **
|
| 375 |
-
-
|
| 376 |
|
| 377 |
-
|
| 378 |
|
| 379 |
-
-
|
| 380 |
-
-
|
|
|
|
| 381 |
|
| 382 |
---
|
| 383 |
|
| 384 |
-
**Built by Oliver Perrin** |
|
| 385 |
"""
|
| 386 |
)
|
| 387 |
|
|
@@ -389,5 +650,12 @@ with gr.Blocks(
|
|
| 389 |
# --------------- Entry Point ---------------
|
| 390 |
|
| 391 |
if __name__ == "__main__":
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 1 |
"""
|
| 2 |
Gradio demo for LexiMind multi-task NLP model.
|
| 3 |
|
| 4 |
+
Redesigned to showcase the model's capabilities on training data:
|
| 5 |
+
- Browse classic literature and news articles
|
| 6 |
+
- Filter by topic and emotion
|
| 7 |
+
- View real-time summaries and classifications
|
| 8 |
+
- Compare model outputs across different texts
|
| 9 |
|
| 10 |
Author: Oliver Perrin
|
| 11 |
+
Date: 2025-12-05, Updated: 2026-01-12
|
| 12 |
"""
|
| 13 |
|
| 14 |
from __future__ import annotations
|
| 15 |
|
| 16 |
import json
|
| 17 |
+
import random
|
| 18 |
import sys
|
| 19 |
from pathlib import Path
|
| 20 |
+
from typing import Any
|
| 21 |
|
| 22 |
import gradio as gr
|
| 23 |
|
|
|
|
| 40 |
# --------------- Constants ---------------
|
| 41 |
|
| 42 |
OUTPUTS_DIR = PROJECT_ROOT / "outputs"
|
| 43 |
+
DATA_DIR = PROJECT_ROOT / "data" / "processed"
|
| 44 |
+
BOOKS_DIR = DATA_DIR / "books"
|
| 45 |
+
SUMMARIZATION_DIR = DATA_DIR / "summarization"
|
| 46 |
+
|
| 47 |
EVAL_REPORT_PATH = OUTPUTS_DIR / "evaluation_report.json"
|
| 48 |
TRAINING_HISTORY_PATH = OUTPUTS_DIR / "training_history.json"
|
| 49 |
|
| 50 |
+
# Emotion display with emojis
|
| 51 |
+
EMOTION_EMOJI = {
|
| 52 |
+
"joy": "๐", "love": "โค๏ธ", "anger": "๐ ", "fear": "๐จ",
|
| 53 |
+
"sadness": "๐ข", "surprise": "๐ฒ", "neutral": "๐",
|
| 54 |
+
"admiration": "๐คฉ", "amusement": "๐", "annoyance": "๐ค",
|
| 55 |
+
"approval": "๐", "caring": "๐ค", "confusion": "๐",
|
| 56 |
+
"curiosity": "๐ค", "desire": "๐", "disappointment": "๐",
|
| 57 |
+
"disapproval": "๐", "disgust": "๐คข", "embarrassment": "๐ณ",
|
| 58 |
+
"excitement": "๐", "gratitude": "๐", "grief": "๐ญ",
|
| 59 |
+
"nervousness": "๐ฐ", "optimism": "๐", "pride": "๐ฆ",
|
| 60 |
+
"realization": "๐ก", "relief": "๐", "remorse": "๐",
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
# Topic display with emojis
|
| 64 |
+
TOPIC_EMOJI = {
|
| 65 |
+
"World": "๐", "Sports": "๐", "Business": "๐ผ",
|
| 66 |
+
"Sci/Tech": "๐ฌ", "Science & Mathematics": "๐ฌ",
|
| 67 |
+
"Education & Reference": "๐", "Entertainment & Music": "๐ฌ",
|
| 68 |
+
"Health": "๐ฅ", "Family & Relationships": "๐จโ๐ฉโ๐ง",
|
| 69 |
+
"Society & Culture": "๐๏ธ", "Politics & Government": "๐ณ๏ธ",
|
| 70 |
+
"Computers & Internet": "๐ป",
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
# --------------- Data Loading ---------------
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def load_books_data() -> list[dict[str, Any]]:
|
| 77 |
+
"""Load book paragraphs from JSONL files."""
|
| 78 |
+
books = []
|
| 79 |
+
library_path = BOOKS_DIR / "library.json"
|
| 80 |
+
|
| 81 |
+
if library_path.exists():
|
| 82 |
+
with open(library_path) as f:
|
| 83 |
+
library = json.load(f)
|
| 84 |
+
|
| 85 |
+
for book_info in library.get("books", []):
|
| 86 |
+
title = book_info["title"]
|
| 87 |
+
jsonl_name = book_info["filename"].replace(".txt", ".jsonl")
|
| 88 |
+
jsonl_path = BOOKS_DIR / jsonl_name
|
| 89 |
+
|
| 90 |
+
if jsonl_path.exists():
|
| 91 |
+
paragraphs = []
|
| 92 |
+
with open(jsonl_path) as f:
|
| 93 |
+
for line in f:
|
| 94 |
+
if line.strip():
|
| 95 |
+
para = json.loads(line)
|
| 96 |
+
# Only include paragraphs with substantial content
|
| 97 |
+
if para.get("token_count", 0) > 50:
|
| 98 |
+
paragraphs.append(para)
|
| 99 |
+
|
| 100 |
+
if paragraphs:
|
| 101 |
+
books.append({
|
| 102 |
+
"title": title,
|
| 103 |
+
"paragraphs": paragraphs[:20], # Limit to first 20 substantial paragraphs
|
| 104 |
+
"word_count": book_info.get("word_count", 0),
|
| 105 |
+
})
|
| 106 |
+
|
| 107 |
+
return books
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def load_news_data(split: str = "validation", max_items: int = 100) -> list[dict[str, Any]]:
|
| 111 |
+
"""Load news articles from summarization dataset."""
|
| 112 |
+
articles = []
|
| 113 |
+
data_path = SUMMARIZATION_DIR / f"{split}.jsonl"
|
| 114 |
+
|
| 115 |
+
if data_path.exists():
|
| 116 |
+
with open(data_path) as f:
|
| 117 |
+
for i, line in enumerate(f):
|
| 118 |
+
if i >= max_items:
|
| 119 |
+
break
|
| 120 |
+
if line.strip():
|
| 121 |
+
article = json.loads(line)
|
| 122 |
+
# Only include articles with reasonable length
|
| 123 |
+
source = article.get("source", "")
|
| 124 |
+
if len(source) > 200:
|
| 125 |
+
articles.append({
|
| 126 |
+
"text": source,
|
| 127 |
+
"reference_summary": article.get("summary", ""),
|
| 128 |
+
"id": i,
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
return articles
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# Cache the loaded data
|
| 135 |
+
_books_cache: list[dict] | None = None
|
| 136 |
+
_news_cache: list[dict] | None = None
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def get_books() -> list[dict]:
|
| 140 |
+
global _books_cache
|
| 141 |
+
if _books_cache is None:
|
| 142 |
+
_books_cache = load_books_data()
|
| 143 |
+
return _books_cache
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def get_news() -> list[dict]:
|
| 147 |
+
global _news_cache
|
| 148 |
+
if _news_cache is None:
|
| 149 |
+
_news_cache = load_news_data()
|
| 150 |
+
return _news_cache
|
| 151 |
+
|
| 152 |
|
| 153 |
# --------------- Pipeline Management ---------------
|
| 154 |
|
|
|
|
| 180 |
return _pipeline
|
| 181 |
|
| 182 |
|
| 183 |
+
# --------------- Core Analysis Functions ---------------
|
| 184 |
|
| 185 |
|
| 186 |
+
def analyze_text(text: str) -> tuple[str, str, str]:
|
| 187 |
"""Run all three tasks and return formatted results."""
|
| 188 |
if not text or not text.strip():
|
| 189 |
+
return "Please enter or select text to analyze.", "", ""
|
| 190 |
|
| 191 |
try:
|
| 192 |
pipe = get_pipeline()
|
| 193 |
|
| 194 |
# Run tasks
|
| 195 |
+
summary = pipe.summarize([text], max_length=150)[0].strip()
|
| 196 |
if not summary:
|
| 197 |
summary = "(Unable to generate summary)"
|
| 198 |
|
| 199 |
+
emotions = pipe.predict_emotions([text], threshold=0.3)[0]
|
| 200 |
topic = pipe.predict_topics([text])[0]
|
| 201 |
|
| 202 |
+
# Format emotions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
if emotions.labels:
|
| 204 |
emotion_parts = []
|
| 205 |
for lbl, score in zip(emotions.labels[:5], emotions.scores[:5], strict=False):
|
| 206 |
+
emoji = EMOTION_EMOJI.get(lbl.lower(), "โข")
|
| 207 |
emotion_parts.append(f"{emoji} **{lbl.title()}** ({score:.0%})")
|
| 208 |
emotion_str = "\n".join(emotion_parts)
|
| 209 |
else:
|
| 210 |
emotion_str = "๐ No strong emotions detected"
|
| 211 |
|
| 212 |
# Format topic
|
| 213 |
+
topic_emoji = TOPIC_EMOJI.get(topic.label, "๐")
|
| 214 |
+
topic_str = f"{topic_emoji} **{topic.label}**\n\nConfidence: {topic.confidence:.0%}"
|
| 215 |
|
| 216 |
return summary, emotion_str, topic_str
|
| 217 |
|
|
|
|
| 220 |
return f"Error: {e}", "", ""
|
| 221 |
|
| 222 |
|
| 223 |
+
# --------------- Book Browser Functions ---------------
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def get_book_titles() -> list[str]:
|
| 227 |
+
"""Get list of available book titles."""
|
| 228 |
+
books = get_books()
|
| 229 |
+
return [b["title"] for b in books]
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def get_book_excerpt(title: str, paragraph_idx: int = 0) -> str:
|
| 233 |
+
"""Get a specific paragraph from a book."""
|
| 234 |
+
books = get_books()
|
| 235 |
+
for book in books:
|
| 236 |
+
if book["title"] == title:
|
| 237 |
+
paragraphs = book["paragraphs"]
|
| 238 |
+
if 0 <= paragraph_idx < len(paragraphs):
|
| 239 |
+
text = paragraphs[paragraph_idx].get("text", "")
|
| 240 |
+
return str(text) if text else ""
|
| 241 |
+
return ""
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def get_book_info(title: str) -> str:
|
| 245 |
+
"""Get book metadata."""
|
| 246 |
+
books = get_books()
|
| 247 |
+
for book in books:
|
| 248 |
+
if book["title"] == title:
|
| 249 |
+
num_paras = len(book["paragraphs"])
|
| 250 |
+
word_count = book["word_count"]
|
| 251 |
+
return f"**{title}**\n\n๐ {word_count:,} words | {num_paras} excerpts available"
|
| 252 |
+
return ""
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def on_book_select(title: str) -> tuple[str, str, int]:
|
| 256 |
+
"""Handle book selection - return first excerpt and info."""
|
| 257 |
+
info = get_book_info(title)
|
| 258 |
+
excerpt = get_book_excerpt(title, 0)
|
| 259 |
+
return info, excerpt, 0
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def on_paragraph_change(title: str, idx: int) -> str:
|
| 263 |
+
"""Handle paragraph slider change."""
|
| 264 |
+
return get_book_excerpt(title, int(idx))
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def get_max_paragraphs(title: str) -> int:
|
| 268 |
+
"""Get the number of paragraphs for a book."""
|
| 269 |
+
books = get_books()
|
| 270 |
+
for book in books:
|
| 271 |
+
if book["title"] == title:
|
| 272 |
+
return len(book["paragraphs"]) - 1
|
| 273 |
+
return 0
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# --------------- News Browser Functions ---------------
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def get_random_news() -> tuple[str, str]:
|
| 280 |
+
"""Get a random news article and its reference summary."""
|
| 281 |
+
news = get_news()
|
| 282 |
+
if news:
|
| 283 |
+
article = random.choice(news)
|
| 284 |
+
return article["text"], article.get("reference_summary", "")
|
| 285 |
+
return "", ""
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def get_news_by_index(idx: int) -> tuple[str, str]:
|
| 289 |
+
"""Get news article by index."""
|
| 290 |
+
news = get_news()
|
| 291 |
+
if 0 <= idx < len(news):
|
| 292 |
+
article = news[idx]
|
| 293 |
+
return article["text"], article.get("reference_summary", "")
|
| 294 |
+
return "", ""
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# --------------- Metrics Loading ---------------
|
| 298 |
+
|
| 299 |
+
|
| 300 |
def load_metrics() -> str:
|
| 301 |
"""Load evaluation metrics and format as markdown."""
|
|
|
|
| 302 |
eval_metrics = {}
|
| 303 |
if EVAL_REPORT_PATH.exists():
|
| 304 |
try:
|
|
|
|
| 307 |
except Exception:
|
| 308 |
pass
|
| 309 |
|
|
|
|
| 310 |
train_metrics = {}
|
| 311 |
if TRAINING_HISTORY_PATH.exists():
|
| 312 |
try:
|
|
|
|
| 315 |
except Exception:
|
| 316 |
pass
|
| 317 |
|
|
|
|
| 318 |
val_final = train_metrics.get("val_epoch_3", {})
|
| 319 |
|
| 320 |
md = """
|
| 321 |
## ๐ Model Performance
|
| 322 |
|
| 323 |
+
### Training Results
|
| 324 |
|
| 325 |
+
| Task | Metric | Score |
|
| 326 |
+
|------|--------|-------|
|
| 327 |
| **Topic Classification** | Accuracy | **{topic_acc:.1%}** |
|
| 328 |
+
| **Emotion Detection** | F1 | {emo_f1:.1%} |
|
| 329 |
| **Summarization** | ROUGE-like | {rouge:.1%} |
|
| 330 |
|
| 331 |
### Evaluation Results
|
|
|
|
| 336 |
| Emotion F1 (macro) | {eval_emo:.1%} |
|
| 337 |
| ROUGE-like | {eval_rouge:.1%} |
|
| 338 |
| BLEU | {eval_bleu:.3f} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
""".format(
|
| 340 |
topic_acc=val_final.get("topic_accuracy", 0),
|
| 341 |
emo_f1=val_final.get("emotion_f1", 0),
|
|
|
|
| 346 |
eval_bleu=eval_metrics.get("summarization", {}).get("bleu", 0),
|
| 347 |
)
|
| 348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
return md
|
| 350 |
|
| 351 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
# --------------- Gradio Interface ---------------
|
| 353 |
|
| 354 |
with gr.Blocks(
|
| 355 |
title="LexiMind - Multi-Task NLP",
|
| 356 |
theme=gr.themes.Soft(),
|
| 357 |
+
css="""
|
| 358 |
+
.book-card { padding: 10px; border-radius: 8px; background: #f0f4f8; }
|
| 359 |
+
.results-panel { min-height: 200px; }
|
| 360 |
+
"""
|
| 361 |
) as demo:
|
| 362 |
gr.Markdown(
|
| 363 |
"""
|
| 364 |
# ๐ง LexiMind
|
| 365 |
### Multi-Task Transformer for Document Analysis
|
| 366 |
|
| 367 |
+
Explore classic literature and news articles with AI-powered analysis:
|
| 368 |
+
- ๐ **Summarization** - Generate concise summaries
|
| 369 |
+
- ๐ **Emotion Detection** - Identify emotional tones
|
| 370 |
+
- ๐ **Topic Classification** - Categorize by subject
|
| 371 |
|
| 372 |
+
> Built with a custom Transformer initialized from FLAN-T5 weights.
|
| 373 |
"""
|
| 374 |
)
|
| 375 |
|
| 376 |
+
# ===================== TAB 1: EXPLORE BOOKS =====================
|
| 377 |
+
with gr.Tab("๐ Explore Books"):
|
| 378 |
+
gr.Markdown(
|
| 379 |
+
"""
|
| 380 |
+
### Classic Literature Collection
|
| 381 |
+
Browse excerpts from classic novels and see how LexiMind analyzes them.
|
| 382 |
+
Select a book, navigate through excerpts, and click **Analyze** to run the model.
|
| 383 |
+
"""
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
with gr.Row():
|
| 387 |
+
with gr.Column(scale=1):
|
| 388 |
+
book_dropdown = gr.Dropdown(
|
| 389 |
+
choices=get_book_titles(),
|
| 390 |
+
label="๐ Select a Book",
|
| 391 |
+
value=get_book_titles()[0] if get_book_titles() else None,
|
|
|
|
| 392 |
)
|
| 393 |
+
book_info = gr.Markdown(elem_classes=["book-card"])
|
| 394 |
+
|
| 395 |
+
para_slider = gr.Slider(
|
| 396 |
+
minimum=0,
|
| 397 |
+
maximum=19,
|
| 398 |
+
step=1,
|
| 399 |
+
value=0,
|
| 400 |
+
label="๐ Excerpt Number",
|
| 401 |
+
info="Navigate through different parts of the book"
|
| 402 |
)
|
| 403 |
+
|
| 404 |
+
analyze_book_btn = gr.Button("๐ Analyze This Excerpt", variant="primary")
|
| 405 |
+
|
| 406 |
+
with gr.Column(scale=2):
|
| 407 |
+
book_excerpt = gr.Textbox(
|
| 408 |
+
label="๐ Book Excerpt",
|
| 409 |
+
lines=10,
|
| 410 |
+
max_lines=15,
|
| 411 |
+
interactive=False,
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
with gr.Row():
|
| 415 |
+
with gr.Column():
|
| 416 |
+
book_summary = gr.Textbox(
|
| 417 |
+
label="๐ Generated Summary",
|
| 418 |
+
lines=4,
|
| 419 |
+
interactive=False,
|
| 420 |
+
)
|
| 421 |
+
with gr.Column():
|
| 422 |
+
with gr.Row():
|
| 423 |
+
book_emotions = gr.Markdown(
|
| 424 |
+
label="๐ Emotions",
|
| 425 |
+
value="*Click Analyze*",
|
| 426 |
+
)
|
| 427 |
+
book_topic = gr.Markdown(
|
| 428 |
+
label="๐ Topic",
|
| 429 |
+
value="*Click Analyze*",
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
# Book event handlers
|
| 433 |
+
book_dropdown.change(
|
| 434 |
+
fn=on_book_select,
|
| 435 |
+
inputs=[book_dropdown],
|
| 436 |
+
outputs=[book_info, book_excerpt, para_slider],
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
para_slider.change(
|
| 440 |
+
fn=on_paragraph_change,
|
| 441 |
+
inputs=[book_dropdown, para_slider],
|
| 442 |
+
outputs=[book_excerpt],
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
analyze_book_btn.click(
|
| 446 |
+
fn=analyze_text,
|
| 447 |
+
inputs=[book_excerpt],
|
| 448 |
+
outputs=[book_summary, book_emotions, book_topic],
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
# Initialize with first book
|
| 452 |
+
demo.load(
|
| 453 |
+
fn=on_book_select,
|
| 454 |
+
inputs=[book_dropdown],
|
| 455 |
+
outputs=[book_info, book_excerpt, para_slider],
|
| 456 |
+
)
|
| 457 |
|
| 458 |
+
# ===================== TAB 2: EXPLORE NEWS =====================
|
| 459 |
+
with gr.Tab("๐ฐ Explore News"):
|
| 460 |
+
gr.Markdown(
|
| 461 |
+
"""
|
| 462 |
+
### CNN/DailyMail News Articles
|
| 463 |
+
Explore news articles from the training dataset. Compare the model's
|
| 464 |
+
generated summary with the original human-written summary.
|
| 465 |
+
"""
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
with gr.Row():
|
| 469 |
+
with gr.Column(scale=1):
|
| 470 |
+
news_slider = gr.Slider(
|
| 471 |
+
minimum=0,
|
| 472 |
+
maximum=99,
|
| 473 |
+
step=1,
|
| 474 |
+
value=0,
|
| 475 |
+
label="๐ฐ Article Number",
|
| 476 |
+
)
|
| 477 |
+
random_news_btn = gr.Button("๐ฒ Random Article", variant="secondary")
|
| 478 |
+
analyze_news_btn = gr.Button("๐ Analyze Article", variant="primary")
|
| 479 |
+
|
| 480 |
+
gr.Markdown("### Reference Summary")
|
| 481 |
+
gr.Markdown("*Original human-written summary from the dataset:*")
|
| 482 |
+
reference_summary = gr.Textbox(
|
| 483 |
+
label="",
|
| 484 |
+
lines=4,
|
| 485 |
+
interactive=False,
|
| 486 |
+
show_label=False,
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
with gr.Column(scale=2):
|
| 490 |
+
news_text = gr.Textbox(
|
| 491 |
+
label="๐ฐ News Article",
|
| 492 |
+
lines=12,
|
| 493 |
+
max_lines=15,
|
| 494 |
interactive=False,
|
| 495 |
)
|
| 496 |
+
|
| 497 |
with gr.Row():
|
| 498 |
with gr.Column():
|
| 499 |
+
news_summary = gr.Textbox(
|
| 500 |
+
label="๐ LexiMind Summary",
|
| 501 |
+
lines=4,
|
| 502 |
+
interactive=False,
|
| 503 |
+
)
|
| 504 |
with gr.Column():
|
| 505 |
+
with gr.Row():
|
| 506 |
+
news_emotions = gr.Markdown(
|
| 507 |
+
label="๐ Emotions",
|
| 508 |
+
value="*Click Analyze*",
|
| 509 |
+
)
|
| 510 |
+
news_topic = gr.Markdown(
|
| 511 |
+
label="๐ Topic",
|
| 512 |
+
value="*Click Analyze*",
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
# News event handlers
|
| 516 |
+
news_slider.change(
|
| 517 |
+
fn=get_news_by_index,
|
| 518 |
+
inputs=[news_slider],
|
| 519 |
+
outputs=[news_text, reference_summary],
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
random_news_btn.click(
|
| 523 |
+
fn=get_random_news,
|
| 524 |
+
outputs=[news_text, reference_summary],
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
analyze_news_btn.click(
|
| 528 |
+
fn=analyze_text,
|
| 529 |
+
inputs=[news_text],
|
| 530 |
+
outputs=[news_summary, news_emotions, news_topic],
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
# Initialize with first article
|
| 534 |
+
demo.load(
|
| 535 |
+
fn=lambda: get_news_by_index(0),
|
| 536 |
+
outputs=[news_text, reference_summary],
|
| 537 |
+
)
|
| 538 |
|
| 539 |
+
# ===================== TAB 3: FREE TEXT =====================
|
| 540 |
+
with gr.Tab("โ๏ธ Free Text"):
|
| 541 |
+
gr.Markdown(
|
| 542 |
+
"""
|
| 543 |
+
### Try Your Own Text
|
| 544 |
+
Enter any text to analyze. Note that the model performs best on
|
| 545 |
+
**news-style articles** and **literary prose** similar to the training data.
|
| 546 |
+
"""
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
with gr.Row():
|
| 550 |
+
with gr.Column(scale=3):
|
| 551 |
+
free_text_input = gr.Textbox(
|
| 552 |
+
label="๐ Enter Text",
|
| 553 |
+
lines=8,
|
| 554 |
+
placeholder="Paste or type your text here...\n\nThe model works best with news articles or literary passages.",
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
with gr.Row():
|
| 558 |
+
analyze_free_btn = gr.Button("๐ Analyze", variant="primary")
|
| 559 |
+
clear_btn = gr.Button("๐๏ธ Clear", variant="secondary")
|
| 560 |
+
|
| 561 |
+
gr.Markdown("**Sample texts:**")
|
| 562 |
+
with gr.Row():
|
| 563 |
+
sample1 = gr.Button("๐ Business News", size="sm")
|
| 564 |
+
sample2 = gr.Button("๐ฌ Science News", size="sm")
|
| 565 |
+
sample3 = gr.Button("๐ Sports News", size="sm")
|
| 566 |
+
|
| 567 |
+
with gr.Column(scale=2):
|
| 568 |
+
free_summary = gr.Textbox(
|
| 569 |
+
label="๐ Summary",
|
| 570 |
+
lines=4,
|
| 571 |
+
interactive=False,
|
| 572 |
+
)
|
| 573 |
+
with gr.Row():
|
| 574 |
+
free_emotions = gr.Markdown(value="*Enter text and click Analyze*")
|
| 575 |
+
free_topic = gr.Markdown(value="")
|
| 576 |
+
|
| 577 |
+
# Sample texts
|
| 578 |
+
SAMPLES = {
|
| 579 |
+
"business": "Global markets tumbled today as investors reacted to rising inflation concerns. The Federal Reserve hinted at potential interest rate hikes, sending shockwaves through technology and banking sectors. Analysts predict continued volatility as economic uncertainty persists. Major indices fell by over 2%, with tech stocks leading the decline.",
|
| 580 |
+
"science": "Scientists at MIT have developed a breakthrough quantum computing chip that operates at room temperature. This advancement could revolutionize drug discovery, cryptography, and artificial intelligence. The research team published their findings in Nature, demonstrating stable qubit operations for over 100 microseconds.",
|
| 581 |
+
"sports": "The championship game ended in dramatic fashion as the underdog team scored in the final seconds to secure victory. Fans rushed the field in celebration, marking the team's first title in 25 years. The winning goal came from a rookie player who had only joined the team this season.",
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
sample1.click(fn=lambda: SAMPLES["business"], outputs=free_text_input)
|
| 585 |
+
sample2.click(fn=lambda: SAMPLES["science"], outputs=free_text_input)
|
| 586 |
+
sample3.click(fn=lambda: SAMPLES["sports"], outputs=free_text_input)
|
| 587 |
+
clear_btn.click(fn=lambda: ("", "", "", ""), outputs=[free_text_input, free_summary, free_emotions, free_topic])
|
| 588 |
+
|
| 589 |
+
analyze_free_btn.click(
|
| 590 |
+
fn=analyze_text,
|
| 591 |
+
inputs=[free_text_input],
|
| 592 |
+
outputs=[free_summary, free_emotions, free_topic],
|
| 593 |
)
|
| 594 |
|
| 595 |
+
# ===================== TAB 4: METRICS =====================
|
| 596 |
with gr.Tab("๐ Metrics"):
|
| 597 |
with gr.Row():
|
| 598 |
with gr.Column(scale=2):
|
| 599 |
gr.Markdown(load_metrics())
|
| 600 |
with gr.Column(scale=1):
|
| 601 |
+
confusion_path = OUTPUTS_DIR / "topic_confusion_matrix.png"
|
| 602 |
+
if confusion_path.exists():
|
| 603 |
+
gr.Image(str(confusion_path), label="Topic Confusion Matrix")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
|
| 605 |
+
# ===================== TAB 5: ABOUT =====================
|
| 606 |
+
with gr.Tab("โน๏ธ About"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
gr.Markdown(
|
| 608 |
"""
|
| 609 |
+
### About LexiMind
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
|
| 611 |
+
LexiMind is a **multi-task NLP system** built from scratch with PyTorch,
|
| 612 |
+
demonstrating end-to-end machine learning engineering.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
|
| 614 |
+
#### ๐๏ธ Architecture
|
| 615 |
|
| 616 |
+
- **Custom Transformer** encoder-decoder (12 layers each)
|
| 617 |
+
- **Pre-LN with RMSNorm** for training stability
|
| 618 |
+
- **T5 Relative Position Bias** for sequence modeling
|
| 619 |
+
- **FLAN-T5-base** weight initialization
|
| 620 |
+
- **Task-specific heads**: LM head (summarization), Classification heads (emotion, topic)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
|
| 622 |
+
#### ๐ Training Data
|
| 623 |
|
| 624 |
+
| Task | Dataset | Description |
|
| 625 |
+
|------|---------|-------------|
|
| 626 |
+
| Summarization | CNN/DailyMail | ~100K news articles with summaries |
|
| 627 |
+
| Emotion | GoEmotions | Multi-label emotion classification |
|
| 628 |
+
| Topic | AG News | 4-class news categorization |
|
| 629 |
+
| Books | Project Gutenberg | 8 classic novels for evaluation |
|
| 630 |
|
| 631 |
+
#### โ ๏ธ Known Limitations
|
| 632 |
|
| 633 |
+
- **Domain-specific**: Best results on news articles and literary text
|
| 634 |
+
- **Summarization quality**: Limited by model size and training data
|
| 635 |
+
- **Generalization**: May struggle with very different text styles
|
| 636 |
|
| 637 |
+
#### ๐ Links
|
| 638 |
|
| 639 |
+
- [GitHub Repository](https://github.com/OliverPerrin/LexiMind)
|
| 640 |
+
- [Model on HuggingFace](https://huggingface.co/OliverPerrin/LexiMind-Model)
|
| 641 |
+
- [HuggingFace Space](https://huggingface.co/spaces/OliverPerrin/LexiMind)
|
| 642 |
|
| 643 |
---
|
| 644 |
|
| 645 |
+
**Built by Oliver Perrin** | Appalachian State University | 2025-2026
|
| 646 |
"""
|
| 647 |
)
|
| 648 |
|
|
|
|
| 650 |
# --------------- Entry Point ---------------
|
| 651 |
|
| 652 |
if __name__ == "__main__":
|
| 653 |
+
# Pre-load pipeline and data
|
| 654 |
+
logger.info("Loading inference pipeline...")
|
| 655 |
+
get_pipeline()
|
| 656 |
+
logger.info("Loading book data...")
|
| 657 |
+
get_books()
|
| 658 |
+
logger.info("Loading news data...")
|
| 659 |
+
get_news()
|
| 660 |
+
logger.info("Starting Gradio server...")
|
| 661 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
src/training/safe_compile.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
-
from typing import Any
|
| 6 |
|
| 7 |
import torch
|
| 8 |
|
|
@@ -25,7 +25,10 @@ def compile_model_safe(
|
|
| 25 |
Parameters mirror `torch.compile` but default to conservative settings.
|
| 26 |
"""
|
| 27 |
|
| 28 |
-
return
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
def apply_safe_config() -> None:
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
+
from typing import Any, cast
|
| 6 |
|
| 7 |
import torch
|
| 8 |
|
|
|
|
| 25 |
Parameters mirror `torch.compile` but default to conservative settings.
|
| 26 |
"""
|
| 27 |
|
| 28 |
+
return cast(
|
| 29 |
+
torch.nn.Module,
|
| 30 |
+
torch.compile(model, backend="inductor", mode=mode, dynamic=dynamic),
|
| 31 |
+
)
|
| 32 |
|
| 33 |
|
| 34 |
def apply_safe_config() -> None:
|