gtech13 commited on
Commit
b79d0d8
·
verified ·
1 Parent(s): 2723f8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -53
app.py CHANGED
@@ -224,9 +224,10 @@ if st.button("🚀 Run Topic Modeling Pipeline", type="primary", use_container_w
224
  st.info(f"**Theme {t_id + 1}**\n\n" + "\n".join([f"🔹 {w}" for w in theme_w]))
225
  col_idx += 1
226
 
227
- # --- METRICS CALCULATIONS ---
228
  div_val, npmi_val, umass_val, sil_val = 0.0, 0.0, 0.0, 0.0
229
  div_status, npmi_status, umass_status, sil_status = "Skipped", "Skipped", "Skipped", "Skipped"
 
230
 
231
  if len(eval_metrics) > 0:
232
  with st.spinner("Calculating mathematical metrics... (NPMI requires building a dictionary and takes a moment)"):
@@ -235,10 +236,11 @@ if st.button("🚀 Run Topic Modeling Pipeline", type="primary", use_container_w
235
  if "Topic Diversity" in eval_metrics and len(all_words) > 0:
236
  u_words = set([w for t in all_words for w in t])
237
  t_words = sum([len(t) for t in all_words])
 
238
  div_val = float(len(u_words) / t_words) if t_words > 0 else 0.0
239
  div_status = f"{div_val:.2f}"
240
 
241
- # Coherence Models (NPMI and UMass)
242
  if "NPMI Coherence" in eval_metrics or "UMass Coherence" in eval_metrics:
243
  try:
244
  tokenized = [vectorizer_model.build_analyzer()(s) for s in sentences]
@@ -247,21 +249,28 @@ if st.button("🚀 Run Topic Modeling Pipeline", type="primary", use_container_w
247
  if "NPMI Coherence" in eval_metrics:
248
  cm_npmi = CoherenceModel(topics=all_words, texts=tokenized, dictionary=dictionary, coherence='c_npmi')
249
  temp_npmi = cm_npmi.get_coherence()
250
- npmi_status = "N/A (Too few words)" if np.isnan(temp_npmi) else f"{float(temp_npmi):.2f}"
 
 
 
 
251
 
252
  if "UMass Coherence" in eval_metrics:
253
  cm_umass = CoherenceModel(topics=all_words, texts=tokenized, dictionary=dictionary, coherence='u_mass')
254
  temp_umass = cm_umass.get_coherence()
255
- umass_status = "N/A (Too few words)" if np.isnan(temp_umass) else f"{float(temp_umass):.2f}"
 
 
 
 
256
  except Exception:
257
- npmi_status = "Skipped (Data too small)"
258
- umass_status = "Skipped (Data too small)"
259
 
260
  # Silhouette
261
  if "Silhouette Score" in eval_metrics:
262
  valid_idx = [i for i, t in enumerate(topics) if t != -1]
263
  unique_topics = set([topics[i] for i in valid_idx])
264
-
265
  if 1 < len(unique_topics) < len(valid_idx):
266
  sil_val = float(silhouette_score(
267
  np.array([embeddings[i] for i in valid_idx]),
@@ -270,59 +279,98 @@ if st.button("🚀 Run Topic Modeling Pipeline", type="primary", use_container_w
270
  ))
271
  sil_status = f"{sil_val:.2f}"
272
  else:
273
- sil_status = "Skipped (Themes need ≥2 sentences)"
274
 
275
- # --- RENDER KPI DASHBOARD (UI draws instantly here!) ---
276
  st.markdown("### 📊 Key Performance Indicators (KPI)")
277
  kpi_cols = st.columns(len(eval_metrics))
278
 
279
  for idx, metric in enumerate(eval_metrics):
280
  with kpi_cols[idx]:
281
  if metric == "Topic Diversity":
282
- st.metric("Topic Diversity (Target: 1.0)", div_status)
283
  elif metric == "NPMI Coherence":
284
- st.metric("NPMI Coherence (Target: >0.1)", npmi_status)
285
  elif metric == "UMass Coherence":
286
- st.metric("UMass Coherence (Target: closer to 0)", umass_status)
287
  elif metric == "Silhouette Score":
288
- st.metric("Silhouette Score (Target: >0.0)", sil_status)
289
 
290
- # ==========================================
291
- # 5. XAI VISUALIZATION GRAPH
 
292
  # ==========================================
293
  st.markdown("### 📈 Explainable AI (XAI) Architecture Map")
294
 
295
- with st.spinner("Rendering Explainable AI Dashboard..."):
296
  sns.set_theme(style="whitegrid")
297
- fig = plt.figure(figsize=(16, 14))
298
- fig.suptitle(f"Topic Modeling Pipeline Analytics\n(Pooling: {ui_pooling.split()[0]} | Rep: {ui_extraction.split()[0]})", fontsize=20, fontweight='bold', y=0.98)
299
- box_style = dict(boxstyle="round,pad=0.4", facecolor='lightyellow', edgecolor='orange', alpha=0.9)
 
 
300
 
301
- # 1. Embeddings
 
 
302
  ax1 = plt.subplot(3, 2, 1)
303
  sns.heatmap(embeddings[:, :50], cmap="viridis", cbar=False, ax=ax1)
304
  ax1.set_title("STEP 1: Embeddings & Pooling", fontsize=13, fontweight='bold')
305
- ax1.set_ylabel("Sentences")
306
  ax1.set_xlabel("Vector Dimensions (First 50 shown)")
307
- ax1.text(0.5, -0.25, f"Math: {ui_embedding.split('/')[-1]} encodes text into 384D.\nPooling '{ui_pooling.split()[0]}' squashes word vectors into 1 sentence vector.",
308
- fontsize=10, ha='center', va='top', transform=ax1.transAxes, bbox=box_style)
 
 
 
 
 
 
 
 
 
309
 
310
- # 2. Geometry
 
 
311
  ax2 = plt.subplot(3, 2, 2)
312
  reduced_embeddings = topic_model.umap_model.transform(embeddings)
313
  ax2.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1], c='grey', s=100, alpha=0.6, edgecolor='k')
314
  ax2.set_title(f"STEP 2: Geometry ({algo_used})", fontsize=13, fontweight='bold')
315
- ax2.text(0.5, -0.25, f"Math: {algo_used} reduces 384D vectors into a 2D map.\nPlaces similar sentences close together.",
316
- fontsize=10, ha='center', va='top', transform=ax2.transAxes, bbox=box_style)
 
 
 
 
 
 
 
 
 
 
317
 
 
318
  # 3. Clustering
 
319
  ax3 = plt.subplot(3, 2, 3)
320
  ax3.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1], c=topics, cmap='tab10', s=150, edgecolor='k')
321
  ax3.set_title(f"STEP 3: Clustering ({cluster_algo})", fontsize=13, fontweight='bold')
322
- ax3.text(0.5, -0.25, f"Math: {cluster_algo} scans the 2D space to draw boundaries.\nColors represent assigned semantic clusters.",
323
- fontsize=10, ha='center', va='top', transform=ax3.transAxes, bbox=box_style)
 
 
 
 
 
 
 
 
 
324
 
 
325
  # 4. Representation
 
326
  ax4 = plt.subplot(3, 2, 4)
327
  theme_1_data = topic_model.get_topic(0)
328
  if theme_1_data:
@@ -330,43 +378,62 @@ if st.button("🚀 Run Topic Modeling Pipeline", type="primary", use_container_w
330
  scores = [x[1] for x in theme_1_data][::-1]
331
  ax4.barh(words, scores, color='coral', edgecolor='black')
332
  ax4.set_title(f"STEP 4: Topic Representation ({ui_extraction.split()[0]})", fontsize=13, fontweight='bold')
333
- ax4.text(0.5, -0.25, f"Math: Applies {ui_extraction.split()[0]} to rank vocabulary.\nLonger bars = higher semantic relevance.",
334
- fontsize=10, ha='center', va='top', transform=ax4.transAxes, bbox=box_style)
 
 
 
 
 
 
 
 
 
335
  else:
336
  ax4.text(0.5, 0.5, "Theme not found", ha='center', transform=ax4.transAxes)
337
 
338
- # 5. KPI Dashboard (Updated with UMass)
 
 
339
  ax5 = plt.subplot(3, 2, 5)
340
  ax5.axis('off')
341
- ax5.set_title("STEP 5: Key Performance Indicators (KPI)", fontsize=13, fontweight='bold', y=0.9)
342
 
343
- kpi_text = (
344
- f"📊 Topic Diversity: {div_status} (Target: 1.0)\n\n"
345
- f"🧠 NPMI Coherence: {npmi_status} (Target: >0.1)\n\n"
346
- f"📚 UMass Coherence: {umass_status} (Target: Closer to 0)\n\n"
347
- f"📐 Silhouette Score: {sil_status} (Target: >0.0)"
 
 
 
 
 
 
 
348
  )
349
- ax5.text(0.5, 0.4, kpi_text, fontsize=11, va='center', ha='center',
 
350
  bbox=dict(boxstyle="square,pad=1.5", facecolor='#e6f2ff', edgecolor='#377eb8', lw=2))
351
- ax5.text(0.5, -0.15, "Math: Since pipeline algorithms don't use 'Training Loss',\nthese KPIs provide the absolute mathematical grade of the topics.",
352
- fontsize=10, ha='center', va='top', transform=ax5.transAxes, bbox=box_style)
353
-
354
- # 6. Summary Panel
355
  ax6 = plt.subplot(3, 2, 6)
356
  ax6.axis('off')
357
  summary_text = (
358
- "=== PIPELINE ARCHITECTURE ===\n\n"
359
- f"1. Embeddings: {ui_embedding.split()[0]}\n"
360
- f"2. Pooling: {ui_pooling.split()[0]}\n"
361
- f"3. N-Grams: {ngram_range}\n"
362
- f"4. Geometry: {algo_used}\n"
363
- f"5. Clustering: {cluster_algo}\n"
364
- f"6. Representation: {ui_extraction.split()[0]}\n\n"
365
- "This modular pipeline successfully transforms unstructured text\n"
366
- "into mathematically validated semantic domains."
367
  )
368
  ax6.text(0.1, 0.5, summary_text, fontsize=12, va='center', ha='left',
369
  bbox=dict(boxstyle="square,pad=1", facecolor='#f0f0f0', edgecolor='grey', lw=2))
370
 
371
- plt.subplots_adjust(hspace=0.6, wspace=0.3)
372
- st.pyplot(fig, use_container_width=True)
 
 
 
224
  st.info(f"**Theme {t_id + 1}**\n\n" + "\n".join([f"🔹 {w}" for w in theme_w]))
225
  col_idx += 1
226
 
227
+ # --- METRICS CALCULATIONS ---
228
  div_val, npmi_val, umass_val, sil_val = 0.0, 0.0, 0.0, 0.0
229
  div_status, npmi_status, umass_status, sil_status = "Skipped", "Skipped", "Skipped", "Skipped"
230
+ u_words_len, t_words_len = 0, 0
231
 
232
  if len(eval_metrics) > 0:
233
  with st.spinner("Calculating mathematical metrics... (NPMI requires building a dictionary and takes a moment)"):
 
236
  if "Topic Diversity" in eval_metrics and len(all_words) > 0:
237
  u_words = set([w for t in all_words for w in t])
238
  t_words = sum([len(t) for t in all_words])
239
+ u_words_len, t_words_len = len(u_words), t_words
240
  div_val = float(len(u_words) / t_words) if t_words > 0 else 0.0
241
  div_status = f"{div_val:.2f}"
242
 
243
+ # Coherence Models
244
  if "NPMI Coherence" in eval_metrics or "UMass Coherence" in eval_metrics:
245
  try:
246
  tokenized = [vectorizer_model.build_analyzer()(s) for s in sentences]
 
249
  if "NPMI Coherence" in eval_metrics:
250
  cm_npmi = CoherenceModel(topics=all_words, texts=tokenized, dictionary=dictionary, coherence='c_npmi')
251
  temp_npmi = cm_npmi.get_coherence()
252
+ if np.isnan(temp_npmi):
253
+ npmi_status = "N/A"
254
+ else:
255
+ npmi_val = float(temp_npmi)
256
+ npmi_status = f"{npmi_val:.2f}"
257
 
258
  if "UMass Coherence" in eval_metrics:
259
  cm_umass = CoherenceModel(topics=all_words, texts=tokenized, dictionary=dictionary, coherence='u_mass')
260
  temp_umass = cm_umass.get_coherence()
261
+ if np.isnan(temp_umass):
262
+ umass_status = "N/A"
263
+ else:
264
+ umass_val = float(temp_umass)
265
+ umass_status = f"{umass_val:.2f}"
266
  except Exception:
267
+ npmi_status = "Skipped"
268
+ umass_status = "Skipped"
269
 
270
  # Silhouette
271
  if "Silhouette Score" in eval_metrics:
272
  valid_idx = [i for i, t in enumerate(topics) if t != -1]
273
  unique_topics = set([topics[i] for i in valid_idx])
 
274
  if 1 < len(unique_topics) < len(valid_idx):
275
  sil_val = float(silhouette_score(
276
  np.array([embeddings[i] for i in valid_idx]),
 
279
  ))
280
  sil_status = f"{sil_val:.2f}"
281
  else:
282
+ sil_status = "Skipped"
283
 
284
+ # --- RENDER KPI DASHBOARD WITH TOOLTIPS ---
285
  st.markdown("### 📊 Key Performance Indicators (KPI)")
286
  kpi_cols = st.columns(len(eval_metrics))
287
 
288
  for idx, metric in enumerate(eval_metrics):
289
  with kpi_cols[idx]:
290
  if metric == "Topic Diversity":
291
+ st.metric("Topic Diversity", div_status, help="Math: Unique Words / Total Words.\nTarget: 1.0 (No redundant words across themes).")
292
  elif metric == "NPMI Coherence":
293
+ st.metric("NPMI Coherence", npmi_status, help="Math: Normalized Pointwise Mutual Information.\nCalculates joint probability of words existing together.\nTarget: >0.1")
294
  elif metric == "UMass Coherence":
295
+ st.metric("UMass Coherence", umass_status, help="Math: Internal log-conditional probability.\nEvaluates if words co-occur strictly inside your uploaded dataset.\nTarget: Closer to 0.")
296
  elif metric == "Silhouette Score":
297
+ st.metric("Silhouette Score", sil_status, help="Math: (b - a) / max(a,b).\nMeasures intra-cluster density (a) vs nearest-cluster distance (b).\nTarget: >0.0")
298
 
299
+
300
+ # ==========================================
301
+ # 5. XAI VISUALIZATION GRAPH (With Live Math & Matrices)
302
  # ==========================================
303
  st.markdown("### 📈 Explainable AI (XAI) Architecture Map")
304
 
305
+ with st.spinner("Rendering Mathematical Dashboard..."):
306
  sns.set_theme(style="whitegrid")
307
+ fig = plt.figure(figsize=(18, 16))
308
+ fig.suptitle(f"Topic Modeling Mathematical Pipeline\n(Pooling: {ui_pooling.split()[0]} | Rep: {ui_extraction.split()[0]})", fontsize=20, fontweight='bold', y=0.98)
309
+
310
+ # Style for the Math/Data boxes
311
+ box_style = dict(boxstyle="round,pad=0.5", facecolor='#f8f9fa', edgecolor='#4b72b8', alpha=0.95, lw=2)
312
 
313
+ # --------------------------------------------------
314
+ # 1. Embeddings & Pooling
315
+ # --------------------------------------------------
316
  ax1 = plt.subplot(3, 2, 1)
317
  sns.heatmap(embeddings[:, :50], cmap="viridis", cbar=False, ax=ax1)
318
  ax1.set_title("STEP 1: Embeddings & Pooling", fontsize=13, fontweight='bold')
319
+ ax1.set_ylabel("Sentences (Docs)")
320
  ax1.set_xlabel("Vector Dimensions (First 50 shown)")
321
+
322
+ # Live Data Extraction
323
+ emb_shape = embeddings.shape
324
+ emb_sample = np.round(embeddings[0, :5], 3).tolist() # First 5 numbers of Doc 1
325
+
326
+ math_text_1 = (
327
+ r"$\mathbf{Math (Mean Pool):} \quad v = \frac{1}{N} \sum_{i=1}^{N} \text{BERT}(w_i)$" + "\n"
328
+ f"Matrix Shape: {emb_shape} (Docs x Dims)\n"
329
+ f"Doc 1 [Dims 1-5]: {emb_sample}..."
330
+ )
331
+ ax1.text(0.5, -0.25, math_text_1, fontsize=11, ha='center', va='top', transform=ax1.transAxes, bbox=box_style)
332
 
333
+ # --------------------------------------------------
334
+ # 2. Geometry (Dimensionality Reduction)
335
+ # --------------------------------------------------
336
  ax2 = plt.subplot(3, 2, 2)
337
  reduced_embeddings = topic_model.umap_model.transform(embeddings)
338
  ax2.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1], c='grey', s=100, alpha=0.6, edgecolor='k')
339
  ax2.set_title(f"STEP 2: Geometry ({algo_used})", fontsize=13, fontweight='bold')
340
+
341
+ # Live Data Extraction
342
+ red_shape = reduced_embeddings.shape
343
+ red_sample = np.round(reduced_embeddings[0, :2], 3).tolist() # X, Y coord of Doc 1
344
+
345
+ eq_2 = r"$\mathbf{Math (PCA):} \quad Z = X \cdot W_{2D}$" if algo_used == "PCA" else r"$\mathbf{Math (UMAP):} \quad \text{Topological Manifold Approx.}$"
346
+ math_text_2 = (
347
+ f"{eq_2}\n"
348
+ f"Matrix Shape: {red_shape} (Docs x 2D Coordinates)\n"
349
+ f"Doc 1 Coordinate: [X: {red_sample[0]}, Y: {red_sample[1]}]"
350
+ )
351
+ ax2.text(0.5, -0.25, math_text_2, fontsize=11, ha='center', va='top', transform=ax2.transAxes, bbox=box_style)
352
 
353
+ # --------------------------------------------------
354
  # 3. Clustering
355
+ # --------------------------------------------------
356
  ax3 = plt.subplot(3, 2, 3)
357
  ax3.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1], c=topics, cmap='tab10', s=150, edgecolor='k')
358
  ax3.set_title(f"STEP 3: Clustering ({cluster_algo})", fontsize=13, fontweight='bold')
359
+
360
+ # Live Data Extraction
361
+ topic_sample = topics[:5] # First 5 cluster assignments
362
+
363
+ eq_3 = r"$\mathbf{Math (K-Means):} \quad \arg\min_S \sum ||x_i - \mu_c||^2$" if cluster_algo == "K-Means" else r"$\mathbf{Math (HDBSCAN):} \quad \text{Density} = \frac{1}{\text{core\_dist}(x)}$"
364
+ math_text_3 = (
365
+ f"{eq_3}\n"
366
+ f"Output Array Shape: ({len(topics)},) (1 Label per Doc)\n"
367
+ f"Doc 1-5 Assignments: {topic_sample}..."
368
+ )
369
+ ax3.text(0.5, -0.25, math_text_3, fontsize=11, ha='center', va='top', transform=ax3.transAxes, bbox=box_style)
370
 
371
+ # --------------------------------------------------
372
  # 4. Representation
373
+ # --------------------------------------------------
374
  ax4 = plt.subplot(3, 2, 4)
375
  theme_1_data = topic_model.get_topic(0)
376
  if theme_1_data:
 
378
  scores = [x[1] for x in theme_1_data][::-1]
379
  ax4.barh(words, scores, color='coral', edgecolor='black')
380
  ax4.set_title(f"STEP 4: Topic Representation ({ui_extraction.split()[0]})", fontsize=13, fontweight='bold')
381
+
382
+ # Live Data Extraction
383
+ top_word_score = round(scores[-1], 4)
384
+ vocab_len = len(vectorizer_model.vocabulary_) if hasattr(vectorizer_model, 'vocabulary_') else 'N/A'
385
+
386
+ math_text_4 = (
387
+ r"$\mathbf{Math (c-TF-IDF):} \quad W_{t,c} = tf_{t,c} \times \log\left(1 + \frac{A}{df_t}\right)$" + "\n"
388
+ f"Global Vocab Extracted: {vocab_len} terms\n"
389
+ f"Top Word ('{words[-1]}') Score: {top_word_score}"
390
+ )
391
+ ax4.text(0.5, -0.25, math_text_4, fontsize=11, ha='center', va='top', transform=ax4.transAxes, bbox=box_style)
392
  else:
393
  ax4.text(0.5, 0.5, "Theme not found", ha='center', transform=ax4.transAxes)
394
 
395
+ # --------------------------------------------------
396
+ # 5. KPI Dashboard (Formulas & Live Calculations)
397
+ # --------------------------------------------------
398
  ax5 = plt.subplot(3, 2, 5)
399
  ax5.axis('off')
400
+ ax5.set_title("STEP 5: Post-Hoc Evaluation (Math & Live Data)", fontsize=13, fontweight='bold', y=0.9)
401
 
402
+ # Get the actual distance metric used for the label
403
+ dist_used = ui_metric if algo_used == "UMAP" else "euclidean"
404
+
405
+ kpi_math = (
406
+ r"$\mathbf{Topic \ Diversity:} \quad D = \frac{| \text{Unique Words} |}{| \text{Total Words} |}$" + "\n"
407
+ f"Live Calc: {u_words_len} Unique / {t_words_len} Total = {div_status}\n\n"
408
+
409
+ r"$\mathbf{Silhouette \ Score:} \quad S = \frac{b - a}{\max(a, b)}$" + "\n"
410
+ f"Live Calc: Score = {sil_status} (Using {dist_used} distance)\n\n"
411
+
412
+ r"$\mathbf{NPMI \ Coherence:} \quad \text{NPMI}(x, y) = \frac{\log \frac{P(x,y)}{P(x)P(y)}}{-\log P(x,y)}$" + "\n"
413
+ f"Live Calc: Score = {npmi_status} across {len(all_words)} valid themes"
414
  )
415
+
416
+ ax5.text(0.5, 0.4, kpi_math, fontsize=11, va='center', ha='center',
417
  bbox=dict(boxstyle="square,pad=1.5", facecolor='#e6f2ff', edgecolor='#377eb8', lw=2))
418
+
419
+ # --------------------------------------------------
420
+ # 6. Summary Matrix Transformations
421
+ # --------------------------------------------------
422
  ax6 = plt.subplot(3, 2, 6)
423
  ax6.axis('off')
424
  summary_text = (
425
+ "=== THE MATRIX TRANSFORMATION LIFECYCLE ===\n\n"
426
+ f"1. Raw Text $\\rightarrow$ {emb_shape} Matrix (Dense Meaning)\n"
427
+ f"2. {emb_shape} $\\rightarrow$ {red_shape} Matrix (Geometric Compression)\n"
428
+ f"3. {red_shape} $\\rightarrow$ ({len(topics)},) Array (Discrete Bucketing)\n"
429
+ f"4. ({len(topics)},) $\\rightarrow$ c-TF-IDF Matrix (Word Extraction)\n"
430
+ f"5. c-TF-IDF $\\rightarrow$ {CONFIG['words_per_theme']} Output Words (Per Theme)\n\n"
431
+ "This proves Topic Modeling is a sequence of \ndimensionality reductions and matrix multiplications."
 
 
432
  )
433
  ax6.text(0.1, 0.5, summary_text, fontsize=12, va='center', ha='left',
434
  bbox=dict(boxstyle="square,pad=1", facecolor='#f0f0f0', edgecolor='grey', lw=2))
435
 
436
+ plt.subplots_adjust(hspace=0.7, wspace=0.3)
437
+ st.pyplot(fig, use_container_width=True)
438
+
439
+