Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import torch | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline | |
| # ===================== | |
| # DEVICE | |
| # ===================== | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| # ===================== | |
| # Helpers | |
| # ===================== | |
| def clamp(x: float, lo: float = -5.0, hi: float = 5.0) -> float: | |
| return max(lo, min(hi, x)) | |
| def score01_to_minus5_plus5(p: float) -> float: | |
| """ | |
| 0.0 -> -5 | |
| 0.5 -> 0 | |
| 1.0 -> +5 | |
| """ | |
| return clamp((float(p) - 0.5) * 10) | |
| # ===================== | |
| # 1) Agreement (MNLI) -> [-5..+5] | |
| # ===================== | |
| MNLI_MODEL = "facebook/bart-large-mnli" | |
| mnli_tokenizer = None | |
| mnli_model = None | |
| def load_mnli(): | |
| global mnli_tokenizer, mnli_model | |
| if mnli_model is None: | |
| mnli_tokenizer = AutoTokenizer.from_pretrained(MNLI_MODEL) | |
| mnli_model = AutoModelForSequenceClassification.from_pretrained(MNLI_MODEL) | |
| mnli_model.to(DEVICE) | |
| mnli_model.eval() | |
| def agreement_score_minus5_plus5(msg1: str, msg2: str) -> float: | |
| """ | |
| -5 = contradiction | |
| +5 = entailment | |
| """ | |
| load_mnli() | |
| inputs = mnli_tokenizer(msg1, msg2, return_tensors="pt", truncation=True).to(DEVICE) | |
| with torch.no_grad(): | |
| logits = mnli_model(**inputs).logits | |
| probs = torch.softmax(logits, dim=-1)[0] | |
| # entailment - contradiction => [-1..+1] | |
| raw = (probs[2] - probs[0]).item() | |
| return round(clamp(raw * 5), 2) | |
| # ===================== | |
| # 2) Sentiment -> [-5..+5] | |
| # ===================== | |
| SENTIMENT_MODEL = "nlptown/bert-base-multilingual-uncased-sentiment" | |
| sent_tokenizer = None | |
| sent_model = None | |
| def load_sentiment(): | |
| global sent_tokenizer, sent_model | |
| if sent_model is None: | |
| sent_tokenizer = AutoTokenizer.from_pretrained(SENTIMENT_MODEL) | |
| sent_model = AutoModelForSequenceClassification.from_pretrained(SENTIMENT_MODEL) | |
| sent_model.to(DEVICE) | |
| sent_model.eval() | |
| def analyze_sentiment(text: str) -> float: | |
| """ | |
| 1..5 stars -> [-5..+5] | |
| """ | |
| load_sentiment() | |
| inputs = sent_tokenizer(text, return_tensors="pt", truncation=True).to(DEVICE) | |
| with torch.no_grad(): | |
| logits = sent_model(**inputs).logits | |
| probs = torch.softmax(logits, dim=-1) | |
| stars = torch.argmax(probs, dim=-1).item() + 1 | |
| score = (stars - 3) * 2.5 | |
| return round(clamp(score), 2) | |
| # ===================== | |
| # 3) Sarcasm / Irony -> [-5..+5] | |
| # ===================== | |
| SARCASM_MODEL = "cardiffnlp/twitter-roberta-base-irony" | |
| sarcasm_pipe = None | |
| def load_sarcasm(): | |
| global sarcasm_pipe | |
| if sarcasm_pipe is None: | |
| sarcasm_pipe = pipeline( | |
| "text-classification", | |
| model=SARCASM_MODEL, | |
| device=0 if torch.cuda.is_available() else -1, | |
| truncation=True, | |
| ) | |
| def sarcasm_score(text: str) -> float: | |
| """ | |
| +5 = irony | |
| -5 = non-irony | |
| """ | |
| load_sarcasm() | |
| res = sarcasm_pipe(text)[0] | |
| label = res["label"].lower() | |
| conf = float(res["score"]) | |
| if "irony" in label: | |
| return round(clamp(conf * 5), 2) | |
| return round(clamp(-conf * 5), 2) | |
| # ===================== | |
| # 4) Neutrality -> [-5..+5] | |
| # ===================== | |
| def neutrality_score(text: str) -> float: | |
| """ | |
| +5 = максимально нейтрально | |
| -5 = максимально эмоционально/заряжено | |
| """ | |
| sent = abs(analyze_sentiment(text)) # 0..5 | |
| sarc = max(0.0, sarcasm_score(text)) # 0..5 (только если irony) | |
| neutrality = 5.0 - (sent + sarc) / 2.0 | |
| return round(clamp(neutrality), 2) | |
| # ===================== | |
| # 5) Agreement with irony adjustment | |
| # ===================== | |
| def agreement_with_irony(msg1: str, msg2: str) -> float: | |
| base = agreement_score_minus5_plus5(msg1, msg2) | |
| s2 = max(0.0, sarcasm_score(msg2)) # 0..5 | |
| sarcasm_strength = s2 / 5.0 # 0..1 | |
| # чем больше сарказм, тем меньше доверяем agreement | |
| multiplier = 1.0 - 0.65 * sarcasm_strength | |
| final_score = base * multiplier | |
| return round(clamp(final_score), 2) | |
| # ===================== | |
| # 6) Multilabel Zero-Shot -> [-5..+5] | |
| # ===================== | |
| ZS_MODEL = "facebook/bart-large-mnli" | |
| zs_classifier = None | |
| CATEGORIES = [ | |
| # базовые | |
| "politique", | |
| "woke", | |
| "racism", | |
| "crime", | |
| "police_abuse", | |
| "corruption", | |
| "hate_speech", | |
| "activism", | |
| # типичные твиттер-дискуссии | |
| "outrage / moral outrage", | |
| "cancel culture", | |
| "culture war", | |
| "polarization / us vs them", | |
| "misinformation / fake news", | |
| "conspiracy / deep state", | |
| "propaganda / spin", | |
| "whataboutism", | |
| "virtue signaling", | |
| "dogwhistle / coded language", | |
| "trolling / bait", | |
| "ragebait", | |
| "harassment / bullying", | |
| "callout / public shaming", | |
| "ratio / pile-on", | |
| "stan / fandom war", | |
| "hot take", | |
| "doomposting", | |
| "memes / shitposting", | |
| "political satire", | |
| "debunking / fact-checking", | |
| "support / solidarity", | |
| ] | |
| def load_zero_shot(): | |
| global zs_classifier | |
| if zs_classifier is None: | |
| zs_classifier = pipeline( | |
| "zero-shot-classification", | |
| model=ZS_MODEL, | |
| device=0 if torch.cuda.is_available() else -1, | |
| ) | |
| def classify_message(text: str) -> dict: | |
| load_zero_shot() | |
| result = zs_classifier(text, candidate_labels=CATEGORIES, multi_label=True) | |
| labels = result["labels"] | |
| scores = result["scores"] | |
| out = {} | |
| for label, score in zip(labels, scores): | |
| out[label] = round(score01_to_minus5_plus5(score), 2) | |
| return out | |
| # ===================== | |
| # Gradio UI | |
| # ===================== | |
| with gr.Blocks(title="Unified NLP API (-5..+5)") as demo: | |
| gr.Markdown("## 📈 Unified NLP API (all scores: -5 .. +5)") | |
| gr.Markdown( | |
| """ | |
| **Шкалы:** | |
| - **Agreement**: -5 = сильное противоречие, +5 = сильное согласие | |
| - **Sentiment**: -5 = негатив, +5 = позитив | |
| - **Sarcasm**: -5 = уверенно НЕ сарказм, +5 = уверенно сарказм/ирония | |
| - **Neutrality**: +5 = максимально нейтрально, -5 = максимально “заряжено” | |
| - **Multilabel**: уверенность метки в шкале -5..+5 (0.5 → 0) | |
| """ | |
| ) | |
| with gr.Tab("Agreement"): | |
| msg1 = gr.Textbox(label="Message 1") | |
| msg2 = gr.Textbox(label="Message 2") | |
| btn_agree = gr.Button("Check Agreement") | |
| out_agree = gr.Number(label="Agreement Score (-5..+5)") | |
| btn_agree.click(fn=agreement_score_minus5_plus5, inputs=[msg1, msg2], outputs=out_agree) | |
| gr.Markdown("### Agreement (irony-aware)") | |
| btn_agree_irony = gr.Button("Check Agreement (with irony)") | |
| out_agree_irony = gr.Number(label="Agreement Score (irony-aware) (-5..+5)") | |
| btn_agree_irony.click(fn=agreement_with_irony, inputs=[msg1, msg2], outputs=out_agree_irony) | |
| with gr.Tab("Sentiment"): | |
| text_sent = gr.Textbox(label="Text") | |
| btn_sent = gr.Button("Analyze Sentiment") | |
| out_sent = gr.Number(label="Sentiment Score (-5..+5)") | |
| btn_sent.click(fn=analyze_sentiment, inputs=text_sent, outputs=out_sent) | |
| with gr.Tab("Sarcasm / Irony"): | |
| text_sarc = gr.Textbox(label="Text") | |
| btn_sarc = gr.Button("Analyze Sarcasm") | |
| out_sarc = gr.Number(label="Sarcasm Score (-5..+5)") | |
| btn_sarc.click(fn=sarcasm_score, inputs=text_sarc, outputs=out_sarc) | |
| with gr.Tab("Neutrality"): | |
| text_neu = gr.Textbox(label="Text") | |
| btn_neu = gr.Button("Analyze Neutrality") | |
| out_neu = gr.Number(label="Neutrality Score (-5..+5)") | |
| btn_neu.click(fn=neutrality_score, inputs=text_neu, outputs=out_neu) | |
| with gr.Tab("Multilabel Classification"): | |
| text_clf = gr.Textbox(label="Text") | |
| btn_clf = gr.Button("Classify") | |
| out_clf = gr.Label(label="Categories & Scores (-5..+5)") | |
| btn_clf.click(fn=classify_message, inputs=text_clf, outputs=out_clf) | |
| demo.launch() | |