import os import re import html import numpy as np import torch import torch.nn as nn from transformers import BertTokenizerFast, BertForSequenceClassification import gradio as gr # ---------------------------------------------------- # Config # ---------------------------------------------------- MODEL_NAME = "bert-base-uncased" MAX_LEN = 128 SENTIMENT_MODEL_PATH = "sentiment_bert_best.pt" SARCASM_MODEL_PATH = "sarcasm_bert_best.pt" SARCASM_THRESHOLD = 0.6 CORRECTION_CONFIDENCE_THRESHOLD = 0.55 id2sentiment = {0: "Negative", 1: "Neutral", 2: "Positive"} device = torch.device("cuda" if torch.cuda.is_available() else "cpu") softmax = nn.Softmax(dim=1) # ---------------------------------------------------- # Minimal cleaning for BERT # ---------------------------------------------------- def clean_text(text: str) -> str: if not isinstance(text, str): return "" text = html.unescape(text) text = re.sub(r"<.*?>", " ", text) text = re.sub(r"http\S+|www\.\S+", " ", text) text = re.sub(r"\s+", " ", text).strip() return text # ---------------------------------------------------- # Load tokenizer & models # ---------------------------------------------------- tokenizer = BertTokenizerFast.from_pretrained(MODEL_NAME) sentiment_model = BertForSequenceClassification.from_pretrained( MODEL_NAME, num_labels=3 ) sarcasm_model = BertForSequenceClassification.from_pretrained( MODEL_NAME, num_labels=2 ) if os.path.exists(SENTIMENT_MODEL_PATH): sentiment_model.load_state_dict( torch.load(SENTIMENT_MODEL_PATH, map_location=device) ) if os.path.exists(SARCASM_MODEL_PATH): sarcasm_model.load_state_dict( torch.load(SARCASM_MODEL_PATH, map_location=device) ) sentiment_model.to(device).eval() sarcasm_model.to(device).eval() # ---------------------------------------------------- # Prediction Helpers # ---------------------------------------------------- def get_probs(model, text): encoding = tokenizer( text, max_length=MAX_LEN, padding="max_length", truncation=True, return_tensors="pt", ) input_ids = encoding["input_ids"].to(device) attention_mask = encoding["attention_mask"].to(device) with torch.no_grad(): outputs = model(input_ids=input_ids, attention_mask=attention_mask) probs = softmax(outputs.logits).cpu().numpy()[0] return probs # ---------------------------------------------------- # Fusion Logic (Sarcasm-aware correction) # ---------------------------------------------------- def fusion_polarity_correction(sarc_probs, sent_probs): sarc_pred = int(np.argmax(sarc_probs)) sarc_conf = float(sarc_probs[sarc_pred]) sent_pred = int(np.argmax(sent_probs)) sent_conf = float(sent_probs[sent_pred]) final_pred = sent_pred corrected = False if sarc_pred == 1 and sarc_conf >= SARCASM_THRESHOLD: if sent_conf >= CORRECTION_CONFIDENCE_THRESHOLD: if sent_pred == 2: final_pred = 0 corrected = True elif sent_pred == 0: final_pred = 2 corrected = True elif sent_pred == 1: final_pred = 0 corrected = True return final_pred, corrected, sarc_conf, sent_conf # ---------------------------------------------------- # Main Prediction Function # ---------------------------------------------------- def predict_review(text: str): if not isinstance(text, str) or not text.strip(): return "⚠ Please enter valid input text." text_clean = clean_text(text) sarc_probs = get_probs(sarcasm_model, text_clean) sent_probs = get_probs(sentiment_model, text_clean) final_pred, corrected, sarc_conf, sent_conf = fusion_polarity_correction( sarc_probs, sent_probs ) return { "Input": text, "Sarcasm Probability": round(float(sarc_probs[1]), 4), "Original Sentiment": id2sentiment[int(np.argmax(sent_probs))], "Original Confidence": round(sent_conf, 4), "Final Sentiment": id2sentiment[final_pred], "Correction Applied": corrected, "Sentiment Probabilities": { "Negative": round(float(sent_probs[0]), 4), "Neutral": round(float(sent_probs[1]), 4), "Positive": round(float(sent_probs[2]), 4), }, } # ---------------------------------------------------- # Gradio UI # ---------------------------------------------------- def gradio_predict(text): result = predict_review(text) if isinstance(result, str): return result return ( f"Input: {result['Input']}\n\n" f"Sarcasm Probability: {result['Sarcasm Probability']}\n\n" f"Original Sentiment: {result['Original Sentiment']} " f"(conf={result['Original Confidence']})\n\n" f"Final Sentiment: {result['Final Sentiment']}\n\n" f"Correction Applied: {result['Correction Applied']}\n\n" f"Sentiment Probabilities:\n" f" Negative: {result['Sentiment Probabilities']['Negative']}\n" f" Neutral: {result['Sentiment Probabilities']['Neutral']}\n" f" Positive: {result['Sentiment Probabilities']['Positive']}" ) with gr.Blocks() as demo: gr.Markdown("## 🔍 Sarcasm-Aware Sentiment Analysis (BERT Fusion Model)") textbox = gr.Textbox( lines=4, placeholder="Yeah great, the battery died in 1 hour...", label="Enter review text", ) output = gr.Textbox(lines=12, label="Analysis Result") btn = gr.Button("Analyze") btn.click(gradio_predict, textbox, output) if __name__ == "__main__": demo.launch()