Spaces:
Sleeping
Sleeping
| import torch | |
| import gradio as gr | |
| from transformers import BertTokenizer, BertForSequenceClassification | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| from wordcloud import WordCloud | |
| from collections import Counter, defaultdict | |
| import re | |
| import json | |
| import csv | |
| import io | |
| import tempfile | |
| from datetime import datetime | |
| import logging | |
| from functools import lru_cache, wraps | |
| from dataclasses import dataclass | |
| from typing import List, Dict, Optional, Tuple, Any, Callable | |
| from contextlib import contextmanager | |
| import gc | |
| import pandas as pd | |
| from lime.lime_text import LimeTextExplainer | |
| import shap | |
| import base64 | |
| class _C7x9: | |
| _m1: int = 1000 | |
| _b2: int = 50 | |
| _t3: int = 512 | |
| _w4: int = 2 | |
| _c5: int = 128 | |
| _p6: int = 8 | |
| _fs1: Tuple[int, int] = (8, 5) | |
| _fs2: Tuple[int, int] = (12, 8) | |
| _ws: Tuple[int, int] = (10, 5) | |
| _th = { | |
| 'default': {'pos': '#4ecdc4', 'neg': '#ff6b6b'}, | |
| 'ocean': {'pos': '#0077be', 'neg': '#ff6b35'}, | |
| 'forest': {'pos': '#228b22', 'neg': '#dc143c'}, | |
| 'sunset': {'pos': '#ff8c00', 'neg': '#8b0000'} | |
| } | |
| _sw = { | |
| 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', | |
| 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', | |
| 'been', 'have', 'has', 'had', 'will', 'would', 'could', 'should' | |
| } | |
| _cfg = _C7x9() | |
| _log = logging.getLogger(__name__) | |
| def _err_wrap(_def_ret=None): | |
| def _dec(_func: Callable) -> Callable: | |
| def _wrap(*args, **kwargs): | |
| try: | |
| return _func(*args, **kwargs) | |
| except Exception as e: | |
| _log.error(f"{_func.__name__} failed: {e}") | |
| return _def_ret if _def_ret is not None else f"Error: {str(e)}" | |
| return _wrap | |
| return _dec | |
| def _fig_mgr(*args, **kwargs): | |
| _f = plt.figure(*args, **kwargs) | |
| try: | |
| yield _f | |
| finally: | |
| plt.close(_f) | |
| gc.collect() | |
| class _Th7: | |
| def __init__(self, _t: str = 'default'): | |
| self._t = _t | |
| self._c = _cfg._th.get(_t, _cfg._th['default']) | |
| class _MM9: | |
| _inst = None | |
| _mdl = None | |
| _tok = None | |
| _dev = None | |
| def __new__(cls): | |
| if cls._inst is None: | |
| cls._inst = super().__new__(cls) | |
| return cls._inst | |
| def _m(self): | |
| if self._mdl is None: | |
| self._load() | |
| return self._mdl | |
| def _t(self): | |
| if self._tok is None: | |
| self._load() | |
| return self._tok | |
| def _d(self): | |
| if self._dev is None: | |
| self._dev = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| return self._dev | |
| def _load(self): | |
| try: | |
| self._dev = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| _mp = base64.b64decode("ZW50cm9weTI1L3NlbnRpbWVudGFuYWx5c2lz").decode() | |
| self._tok = BertTokenizer.from_pretrained(_mp) | |
| self._mdl = BertForSequenceClassification.from_pretrained(_mp) | |
| self._mdl.to(self._dev) | |
| _log.info(f"Model loaded on {self._dev}") | |
| except Exception as e: | |
| _log.error(f"Model loading failed: {e}") | |
| raise | |
| class _TP8: | |
| def _clean(_txt: str) -> Tuple[str, ...]: | |
| _w = re.findall(r'\b\w{3,}\b', _txt.lower()) | |
| return tuple(w for w in _w if w not in _cfg._sw) | |
| class _HM2: | |
| def __init__(self): | |
| self._h = [] | |
| def _add(self, _e: Dict): | |
| self._h.append({**_e, 'timestamp': datetime.now().isoformat()}) | |
| if len(self._h) > _cfg._m1: | |
| self._h = self._h[-_cfg._m1:] | |
| def _get(self) -> List[Dict]: | |
| return self._h.copy() | |
| def _clr(self) -> int: | |
| _cnt = len(self._h) | |
| self._h.clear() | |
| return _cnt | |
| def _sz(self) -> int: | |
| return len(self._h) | |
| class _SE3: | |
| def __init__(self): | |
| self._mm = _MM9() | |
| self._le = LimeTextExplainer(class_names=['Negative', 'Positive']) | |
| self._se = None | |
| def _pred(self, _txts): | |
| if isinstance(_txts, str): | |
| _txts = [_txts] | |
| _inp = self._mm._t( | |
| _txts, return_tensors="pt", padding=True, | |
| truncation=True, max_length=_cfg._t3 | |
| ).to(self._mm._d) | |
| with torch.no_grad(): | |
| _out = self._mm._m(**_inp) | |
| _probs = torch.nn.functional.softmax(_out.logits, dim=-1).cpu().numpy() | |
| return _probs | |
| def _fast(self, _txt: str) -> Dict: | |
| if not _txt.strip(): | |
| raise ValueError("Empty text") | |
| _probs = self._pred([_txt])[0] | |
| _sent = "Positive" if _probs[1] > _probs[0] else "Negative" | |
| return { | |
| 'sentiment': _sent, | |
| 'confidence': float(_probs.max()), | |
| 'pos_prob': float(_probs[1]), | |
| 'neg_prob': float(_probs[0]) | |
| } | |
| def _lime_kw(self, _txt: str, _k: int = 10) -> List[Tuple[str, float]]: | |
| try: | |
| _exp = self._le.explain_instance( | |
| _txt, self._pred, num_features=_k, num_samples=200 | |
| ) | |
| _ws = [] | |
| for _w, _s in _exp.as_list(): | |
| if len(_w.strip()) >= _cfg._w4: | |
| _ws.append((_w.strip().lower(), abs(_s))) | |
| _ws.sort(key=lambda x: x[1], reverse=True) | |
| return _ws[:_k] | |
| except Exception as e: | |
| _log.error(f"LIME extraction failed: {e}") | |
| return [] | |
| def _shap_kw(self, _txt: str, _k: int = 10) -> List[Tuple[str, float]]: | |
| try: | |
| _words = _txt.split() | |
| _ws = [] | |
| _base = self._pred([_txt])[0][1] | |
| for i, _w in enumerate(_words): | |
| _mod_w = _words[:i] + _words[i+1:] | |
| _mod_t = ' '.join(_mod_w) | |
| if _mod_t.strip(): | |
| _mod_p = self._pred([_mod_t])[0][1] | |
| _imp = abs(_base - _mod_p) | |
| _clean_w = re.sub(r'[^\w]', '', _w.lower()) | |
| if len(_clean_w) >= _cfg._w4: | |
| _ws.append((_clean_w, _imp)) | |
| _uniq = {} | |
| for _w, _s in _ws: | |
| if _w in _uniq: | |
| _uniq[_w] = max(_uniq[_w], _s) | |
| else: | |
| _uniq[_w] = _s | |
| _sorted = sorted(_uniq.items(), key=lambda x: x[1], reverse=True) | |
| return _sorted[:_k] | |
| except Exception as e: | |
| _log.error(f"SHAP extraction failed: {e}") | |
| return [] | |
| def _heatmap(self, _txt: str, _ws: Dict[str, float]) -> str: | |
| _words = _txt.split() | |
| _html = ['<div style="font-family: Arial; font-size: 16px; line-height: 1.6;">'] | |
| if _ws: | |
| _max = max(abs(_s) for _s in _ws.values()) | |
| _min = min(_ws.values()) | |
| else: | |
| _max = _min = 0 | |
| for _w in _words: | |
| _clean = re.sub(r'[^\w]', '', _w.lower()) | |
| _score = _ws.get(_clean, 0) | |
| if _score > 0: | |
| _int = min(255, int(180 * (_score / _max) if _max > 0 else 0)) | |
| _color = f"rgba(0, {_int}, 0, 0.3)" | |
| elif _score < 0: | |
| _int = min(255, int(180 * (abs(_score) / abs(_min)) if _min < 0 else 0)) | |
| _color = f"rgba({_int}, 0, 0, 0.3)" | |
| else: | |
| _color = "transparent" | |
| _html.append( | |
| f'<span style="background-color: {_color}; padding: 2px; margin: 1px; ' | |
| f'border-radius: 3px;" title="Score: {_score:.3f}">{_w}</span> ' | |
| ) | |
| _html.append('</div>') | |
| return ''.join(_html) | |
| def _adv(self, _txt: str) -> Dict: | |
| if not _txt.strip(): | |
| raise ValueError("Empty text") | |
| _probs = self._pred([_txt])[0] | |
| _sent = "Positive" if _probs[1] > _probs[0] else "Negative" | |
| _lime = self._lime_kw(_txt) | |
| _shap = self._shap_kw(_txt) | |
| _ws_dict = dict(_lime) | |
| _heat = self._heatmap(_txt, _ws_dict) | |
| return { | |
| 'sentiment': _sent, | |
| 'confidence': float(_probs.max()), | |
| 'pos_prob': float(_probs[1]), | |
| 'neg_prob': float(_probs[0]), | |
| 'lime_words': _lime, | |
| 'shap_words': _shap, | |
| 'heatmap_html': _heat | |
| } | |
| def _batch(self, _txts: List[str], _prog=None) -> List[Dict]: | |
| if len(_txts) > _cfg._b2: | |
| _txts = _txts[:_cfg._b2] | |
| _res = [] | |
| _bs = _cfg._p6 | |
| for i in range(0, len(_txts), _bs): | |
| _b = _txts[i:i+_bs] | |
| if _prog: | |
| _prog((i + len(_b)) / len(_txts)) | |
| _inp = self._mm._t( | |
| _b, return_tensors="pt", padding=True, | |
| truncation=True, max_length=_cfg._t3 | |
| ).to(self._mm._d) | |
| with torch.no_grad(): | |
| _out = self._mm._m(**_inp) | |
| _probs = torch.nn.functional.softmax(_out.logits, dim=-1).cpu().numpy() | |
| for _txt, _prob in zip(_b, _probs): | |
| _sent = "Positive" if _prob[1] > _prob[0] else "Negative" | |
| _res.append({ | |
| 'text': _txt[:50] + '...' if len(_txt) > 50 else _txt, | |
| 'full_text': _txt, | |
| 'sentiment': _sent, | |
| 'confidence': float(_prob.max()), | |
| 'pos_prob': float(_prob[1]), | |
| 'neg_prob': float(_prob[0]) | |
| }) | |
| return _res | |
| class _PF4: | |
| def _bars(_probs: np.ndarray, _th: _Th7) -> plt.Figure: | |
| with _fig_mgr(figsize=_cfg._fs1) as _f: | |
| _ax = _f.add_subplot(111) | |
| _lbl = ["Negative", "Positive"] | |
| _clr = [_th._c['neg'], _th._c['pos']] | |
| _b = _ax.bar(_lbl, _probs, color=_clr, alpha=0.8) | |
| _ax.set_title("Sentiment Probabilities", fontweight='bold') | |
| _ax.set_ylabel("Probability") | |
| _ax.set_ylim(0, 1) | |
| for _bar, _prob in zip(_b, _probs): | |
| _ax.text(_bar.get_x() + _bar.get_width()/2., _bar.get_height() + 0.02, | |
| f'{_prob:.3f}', ha='center', va='bottom', fontweight='bold') | |
| _f.tight_layout() | |
| return _f | |
| def _gauge(_conf: float, _sent: str, _th: _Th7) -> plt.Figure: | |
| with _fig_mgr(figsize=_cfg._fs1) as _f: | |
| _ax = _f.add_subplot(111) | |
| _theta = np.linspace(0, np.pi, 100) | |
| _clr = [_th._c['neg'] if i < 50 else _th._c['pos'] for i in range(100)] | |
| for i in range(len(_theta)-1): | |
| _ax.fill_between([_theta[i], _theta[i+1]], [0, 0], [0.8, 0.8], | |
| color=_clr[i], alpha=0.7) | |
| _pos = np.pi * (0.5 + (0.4 if _sent == 'Positive' else -0.4) * _conf) | |
| _ax.plot([_pos, _pos], [0, 0.6], 'k-', linewidth=6) | |
| _ax.plot(_pos, 0.6, 'ko', markersize=10) | |
| _ax.set_xlim(0, np.pi) | |
| _ax.set_ylim(0, 1) | |
| _ax.set_title(f'{_sent} - Confidence: {_conf:.3f}', fontweight='bold') | |
| _ax.set_xticks([0, np.pi/2, np.pi]) | |
| _ax.set_xticklabels(['Negative', 'Neutral', 'Positive']) | |
| _ax.axis('off') | |
| _f.tight_layout() | |
| return _f | |
| def _lime_chart(_lw: List[Tuple[str, float]], _sent: str, _th: _Th7) -> Optional[plt.Figure]: | |
| if not _lw: | |
| return None | |
| with _fig_mgr(figsize=_cfg._fs1) as _f: | |
| _ax = _f.add_subplot(111) | |
| _w = [_word for _word, _score in _lw] | |
| _s = [_score for _word, _score in _lw] | |
| _clr = _th._c['pos'] if _sent == 'Positive' else _th._c['neg'] | |
| _b = _ax.barh(range(len(_w)), _s, color=_clr, alpha=0.7) | |
| _ax.set_yticks(range(len(_w))) | |
| _ax.set_yticklabels(_w) | |
| _ax.set_xlabel('LIME Attention Weight') | |
| _ax.set_title(f'LIME: Top Contributing Words ({_sent})', fontweight='bold') | |
| for i, (_bar, _score) in enumerate(zip(_b, _s)): | |
| _ax.text(_bar.get_width() + 0.001, _bar.get_y() + _bar.get_height()/2., | |
| f'{_score:.3f}', ha='left', va='center', fontsize=9) | |
| _ax.invert_yaxis() | |
| _ax.grid(axis='x', alpha=0.3) | |
| _f.tight_layout() | |
| return _f | |
| def _shap_chart(_sw: List[Tuple[str, float]], _sent: str, _th: _Th7) -> Optional[plt.Figure]: | |
| if not _sw: | |
| return None | |
| with _fig_mgr(figsize=_cfg._fs1) as _f: | |
| _ax = _f.add_subplot(111) | |
| _w = [_word for _word, _score in _sw] | |
| _s = [_score for _word, _score in _sw] | |
| _clr = _th._c['pos'] if _sent == 'Positive' else _th._c['neg'] | |
| _b = _ax.barh(range(len(_w)), _s, color=_clr, alpha=0.7) | |
| _ax.set_yticks(range(len(_w))) | |
| _ax.set_yticklabels(_w) | |
| _ax.set_xlabel('SHAP Value') | |
| _ax.set_title(f'SHAP: Top Contributing Words ({_sent})', fontweight='bold') | |
| for i, (_bar, _score) in enumerate(zip(_b, _s)): | |
| _ax.text(_bar.get_width() + 0.001, _bar.get_y() + _bar.get_height()/2., | |
| f'{_score:.3f}', ha='left', va='center', fontsize=9) | |
| _ax.invert_yaxis() | |
| _ax.grid(axis='x', alpha=0.3) | |
| _f.tight_layout() | |
| return _f | |
| def _cloud(_txt: str, _sent: str, _th: _Th7) -> Optional[plt.Figure]: | |
| if len(_txt.split()) < 3: | |
| return None | |
| _cm = 'Greens' if _sent == 'Positive' else 'Reds' | |
| _wc = WordCloud(width=800, height=400, background_color='white', | |
| colormap=_cm, max_words=30).generate(_txt) | |
| with _fig_mgr(figsize=_cfg._ws) as _f: | |
| _ax = _f.add_subplot(111) | |
| _ax.imshow(_wc, interpolation='bilinear') | |
| _ax.axis('off') | |
| _ax.set_title(f'{_sent} Word Cloud', fontweight='bold') | |
| _f.tight_layout() | |
| return _f | |
| def _batch_viz(_res: List[Dict], _th: _Th7) -> plt.Figure: | |
| with _fig_mgr(figsize=_cfg._fs2) as _f: | |
| _gs = _f.add_gridspec(2, 2, hspace=0.3, wspace=0.3) | |
| _ax1 = _f.add_subplot(_gs[0, 0]) | |
| _sc = Counter([_r['sentiment'] for _r in _res]) | |
| _clr = [_th._c['pos'], _th._c['neg']] | |
| _ax1.pie(_sc.values(), labels=_sc.keys(), | |
| autopct='%1.1f%%', colors=_clr[:len(_sc)]) | |
| _ax1.set_title('Sentiment Distribution') | |
| _ax2 = _f.add_subplot(_gs[0, 1]) | |
| _confs = [_r['confidence'] for _r in _res] | |
| _ax2.hist(_confs, bins=8, alpha=0.7, color='skyblue', edgecolor='black') | |
| _ax2.set_title('Confidence Distribution') | |
| _ax2.set_xlabel('Confidence') | |
| _ax3 = _f.add_subplot(_gs[1, :]) | |
| _pp = [_r['pos_prob'] for _r in _res] | |
| _idx = range(len(_res)) | |
| _cs = [_th._c['pos'] if _r['sentiment'] == 'Positive' | |
| else _th._c['neg'] for _r in _res] | |
| _ax3.scatter(_idx, _pp, c=_cs, alpha=0.7, s=60) | |
| _ax3.axhline(y=0.5, color='gray', linestyle='--', alpha=0.5) | |
| _ax3.set_title('Sentiment Progression') | |
| _ax3.set_xlabel('Review Index') | |
| _ax3.set_ylabel('Positive Probability') | |
| return _f | |
| class _DH5: | |
| def _exp(_data: List[Dict], _fmt: str) -> Tuple[Optional[str], str]: | |
| if not _data: | |
| return None, "No data to export" | |
| _tf = tempfile.NamedTemporaryFile(mode='w', delete=False, | |
| suffix=f'.{_fmt}', encoding='utf-8') | |
| if _fmt == 'csv': | |
| _w = csv.writer(_tf) | |
| _w.writerow(['Timestamp', 'Text', 'Sentiment', 'Confidence', 'Pos_Prob', 'Neg_Prob']) | |
| for _e in _data: | |
| _w.writerow([ | |
| _e.get('timestamp', ''), | |
| _e.get('text', ''), | |
| _e.get('sentiment', ''), | |
| f"{_e.get('confidence', 0):.4f}", | |
| f"{_e.get('pos_prob', 0):.4f}", | |
| f"{_e.get('neg_prob', 0):.4f}" | |
| ]) | |
| elif _fmt == 'json': | |
| json.dump(_data, _tf, indent=2, ensure_ascii=False) | |
| _tf.close() | |
| return _tf.name, f"Exported {len(_data)} entries" | |
| def _proc(_file) -> str: | |
| if not _file: | |
| return "" | |
| try: | |
| _fp = _file.name | |
| if _fp.endswith('.csv'): | |
| for _enc in ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']: | |
| try: | |
| _df = pd.read_csv(_fp, encoding=_enc) | |
| _tc = [] | |
| for _col in _df.columns: | |
| _sv = _df[_col].dropna().head(10) | |
| if len(_sv) > 0: | |
| _cnt = sum(1 for _val in _sv | |
| if isinstance(_val, str) and len(str(_val).strip()) > 10) | |
| if _cnt > len(_sv) * 0.7: | |
| _tc.append(_col) | |
| if _tc: | |
| _sc = _tc[0] | |
| else: | |
| _sc = _df.columns[0] | |
| _rev = _df[_sc].dropna().astype(str).tolist() | |
| _cr = [] | |
| for _r in _rev: | |
| _r = _r.strip() | |
| if len(_r) > 10 and _r.lower() != 'nan': | |
| _cr.append(_r) | |
| if _cr: | |
| _log.info(f"Successfully read {len(_cr)} reviews from CSV") | |
| return '\n'.join(_cr) | |
| except Exception as e: | |
| continue | |
| return "Error: Could not read CSV file. Please check the file format and encoding." | |
| else: | |
| for _enc in ['utf-8', 'latin-1', 'cp1252']: | |
| try: | |
| with open(_fp, 'r', encoding=_enc) as _f: | |
| _cont = _f.read().strip() | |
| if _cont: | |
| return _cont | |
| except Exception as e: | |
| continue | |
| return "Error: Could not read text file. Please check the file encoding." | |
| except Exception as e: | |
| _log.error(f"File processing error: {e}") | |
| return f"Error processing file: {str(e)}" | |
| class _SA6: | |
| def __init__(self): | |
| self._eng = _SE3() | |
| self._hist = _HM2() | |
| self._dh = _DH5() | |
| self._ex = [ | |
| ["While the film's visual effects were undeniably impressive, the story lacked emotional weight, and the pacing felt inconsistent throughout."], | |
| ["An extraordinary achievement in filmmaking — the direction was masterful, the script was sharp, and every performance added depth and realism."], | |
| ["Despite a promising start, the film quickly devolved into a series of clichés, with weak character development and an ending that felt rushed and unearned."], | |
| ["A beautifully crafted story with heartfelt moments and a soundtrack that perfectly captured the emotional tone of each scene."], | |
| ["The movie was far too long, with unnecessary subplots and dull dialogue that made it difficult to stay engaged until the end."] | |
| ] | |
| def _fast_ana(self, _txt: str, _th: str = 'default'): | |
| if not _txt.strip(): | |
| return "Please enter text", None, None, None | |
| _res = self._eng._fast(_txt) | |
| self._hist._add({ | |
| 'text': _txt[:100], | |
| 'full_text': _txt, | |
| **_res | |
| }) | |
| _thc = _Th7(_th) | |
| _probs = np.array([_res['neg_prob'], _res['pos_prob']]) | |
| _pp = _PF4._bars(_probs, _thc) | |
| _gp = _PF4._gauge(_res['confidence'], _res['sentiment'], _thc) | |
| _cp = _PF4._cloud(_txt, _res['sentiment'], _thc) | |
| _rt = f"Sentiment: {_res['sentiment']} (Confidence: {_res['confidence']:.3f})" | |
| return _rt, _pp, _gp, _cp | |
| def _adv_ana(self, _txt: str, _th: str = 'default'): | |
| if not _txt.strip(): | |
| return "Please enter text", None, None, None | |
| _res = self._eng._adv(_txt) | |
| self._hist._add({ | |
| 'text': _txt[:100], | |
| 'full_text': _txt, | |
| **_res | |
| }) | |
| _thc = _Th7(_th) | |
| _lp = _PF4._lime_chart(_res['lime_words'], _res['sentiment'], _thc) | |
| _sp = _PF4._shap_chart(_res['shap_words'], _res['sentiment'], _thc) | |
| _lws = ", ".join([f"{_w}({_s:.3f})" for _w, _s in _res['lime_words'][:5]]) | |
| _sws = ", ".join([f"{_w}({_s:.3f})" for _w, _s in _res['shap_words'][:5]]) | |
| _rt = (f"Sentiment: {_res['sentiment']} (Confidence: {_res['confidence']:.3f})\n" | |
| f"LIME Key Words: {_lws}\n" | |
| f"SHAP Key Words: {_sws}") | |
| return _rt, _lp, _sp, _res['heatmap_html'] | |
| def _batch_ana(self, _revs: str, _prog=None): | |
| if not _revs.strip(): | |
| return None | |
| _txts = [_r.strip() for _r in _revs.split('\n') if _r.strip()] | |
| if len(_txts) < 2: | |
| return None | |
| _res = self._eng._batch(_txts, _prog) | |
| for _r in _res: | |
| self._hist._add(_r) | |
| _thc = _Th7('default') | |
| return _PF4._batch_viz(_res, _thc) | |
| def _hist_plot(self, _th: str = 'default'): | |
| _hist = self._hist._get() | |
| if len(_hist) < 2: | |
| return None, f"Need at least 2 analyses for trends. Current: {len(_hist)}" | |
| _thc = _Th7(_th) | |
| with _fig_mgr(figsize=(12, 8)) as _f: | |
| _gs = _f.add_gridspec(2, 1, hspace=0.3) | |
| _idx = list(range(len(_hist))) | |
| _pp = [_item['pos_prob'] for _item in _hist] | |
| _confs = [_item['confidence'] for _item in _hist] | |
| _ax1 = _f.add_subplot(_gs[0, 0]) | |
| _clr = [_thc._c['pos'] if _p > 0.5 else _thc._c['neg'] | |
| for _p in _pp] | |
| _ax1.scatter(_idx, _pp, c=_clr, alpha=0.7, s=60) | |
| _ax1.plot(_idx, _pp, alpha=0.5, linewidth=2) | |
| _ax1.axhline(y=0.5, color='gray', linestyle='--', alpha=0.5) | |
| _ax1.set_title('Sentiment History') | |
| _ax1.set_ylabel('Positive Probability') | |
| _ax1.grid(True, alpha=0.3) | |
| _ax2 = _f.add_subplot(_gs[1, 0]) | |
| _ax2.bar(_idx, _confs, alpha=0.7, color='lightblue', edgecolor='navy') | |
| _ax2.set_title('Confidence Over Time') | |
| _ax2.set_xlabel('Analysis Number') | |
| _ax2.set_ylabel('Confidence') | |
| _ax2.grid(True, alpha=0.3) | |
| _f.tight_layout() | |
| return _f, f"History: {len(_hist)} analyses" | |
| def _create_ui(): | |
| _app = _SA6() | |
| with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as _demo: | |
| gr.Markdown("# 🎬 AI Movie Sentiment Analyzer") | |
| gr.Markdown("Fast sentiment analysis with advanced deep learning explanations") | |
| with gr.Tab("Quick Analysis"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| _ti = gr.Textbox( | |
| label="Movie Review", | |
| placeholder="Enter your movie review...", | |
| lines=5 | |
| ) | |
| with gr.Row(): | |
| _ab = gr.Button("Analyze", variant="primary") | |
| _ts = gr.Dropdown( | |
| choices=list(_cfg._th.keys()), | |
| value="default", | |
| label="Theme" | |
| ) | |
| gr.Examples( | |
| examples=_app._ex, | |
| inputs=_ti | |
| ) | |
| with gr.Column(): | |
| _ro = gr.Textbox(label="Result", lines=3) | |
| with gr.Row(): | |
| _pp = gr.Plot(label="Probabilities") | |
| _gp = gr.Plot(label="Confidence") | |
| with gr.Row(): | |
| _wp = gr.Plot(label="Word Cloud") | |
| with gr.Tab("Advanced Analysis"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| _ati = gr.Textbox( | |
| label="Movie Review", | |
| placeholder="Enter your movie review for deep analysis...", | |
| lines=5 | |
| ) | |
| with gr.Row(): | |
| _aab = gr.Button("Deep Analyze", variant="primary") | |
| _ats = gr.Dropdown( | |
| choices=list(_cfg._th.keys()), | |
| value="default", | |
| label="Theme" | |
| ) | |
| gr.Examples( | |
| examples=_app._ex, | |
| inputs=_ati | |
| ) | |
| with gr.Column(): | |
| _aro = gr.Textbox(label="Analysis Result", lines=4) | |
| with gr.Row(): | |
| _lp = gr.Plot(label="LIME: Key Contributing Words") | |
| _sp = gr.Plot(label="SHAP: Key Contributing Words") | |
| with gr.Row(): | |
| _ho = gr.HTML(label="Word Importance Heatmap (LIME-based)") | |
| with gr.Tab("Batch Analysis"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| _fu = gr.File(label="Upload File", file_types=[".csv", ".txt"]) | |
| _bi = gr.Textbox( | |
| label="Reviews (one per line)", | |
| lines=8 | |
| ) | |
| with gr.Column(): | |
| _lb = gr.Button("Load File") | |
| _bb = gr.Button("Analyze Batch", variant="primary") | |
| _bp = gr.Plot(label="Batch Results") | |
| with gr.Tab("History & Export"): | |
| with gr.Row(): | |
| _rb = gr.Button("Refresh") | |
| _cb = gr.Button("Clear", variant="stop") | |
| with gr.Row(): | |
| _csvb = gr.Button("Export CSV") | |
| _jb = gr.Button("Export JSON") | |
| _hs = gr.Textbox(label="Status") | |
| _hp = gr.Plot(label="History Trends") | |
| _csvf = gr.File(label="CSV Download", visible=True) | |
| _jf = gr.File(label="JSON Download", visible=True) | |
| # Event handlers | |
| _ab.click( | |
| _app._fast_ana, | |
| inputs=[_ti, _ts], | |
| outputs=[_ro, _pp, _gp, _wp] | |
| ) | |
| _aab.click( | |
| _app._adv_ana, | |
| inputs=[_ati, _ats], | |
| outputs=[_aro, _lp, _sp, _ho] | |
| ) | |
| _lb.click(_app._dh._proc, inputs=_fu, outputs=_bi) | |
| _bb.click(_app._batch_ana, inputs=_bi, outputs=_bp) | |
| _rb.click( | |
| lambda _th: _app._hist_plot(_th), | |
| inputs=_ts, | |
| outputs=[_hp, _hs] | |
| ) | |
| _cb.click( | |
| lambda: f"Cleared {_app._hist._clr()} entries", | |
| outputs=_hs | |
| ) | |
| _csvb.click( | |
| lambda: _app._dh._exp(_app._hist._get(), 'csv'), | |
| outputs=[_csvf, _hs] | |
| ) | |
| _jb.click( | |
| lambda: _app._dh._exp(_app._hist._get(), 'json'), | |
| outputs=[_jf, _hs] | |
| ) | |
| return _demo | |
| if __name__ == "__main__": | |
| logging.basicConfig(level=logging.INFO) | |
| _demo = _create_ui() | |
| _demo.launch(share=True) |