"""
Taana-Tracker v1.0 ➜ (Hindi + English) Sarcasm Detector
Model: XLM-RoBERTa (fine-tuned) | Framework: PyTorch | UI: Streamlit
"""
import streamlit as st
import torch
import pandas as pd
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from pathlib import Path
# ─────────────────────── PAGE CONFIG ─────────────────────────────────────────
st.set_page_config(
page_title="Taana-Tracker: AI Sarcasm Intelligence",
page_icon="🔥",
layout="wide",
initial_sidebar_state="collapsed",
)
# ─────────────────────── CSS ──────────────────────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
# ─────────────────────── CONSTANTS ───────────────────────────────────────────
FINETUNED_REPO = "PrachiSandipkumar/Taana-Tracker-Sarcasm"
BASE_MODEL = "xlm-roberta-base"
MAX_LEN = 256
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ─────────────────────── MODEL LOADING ───────────────────────────────────────
@st.cache_resource(show_spinner=False)
def load_model():
"""
Loads tokenizer from xlm-roberta-base and fine-tuned weights
from the dedicated Hugging Face model repository.
"""
load_log = []
source = "base-NOT-fine-tuned"
# 1. Tokenizer ➔ Always pull from xlm-roberta-base as per Cell 22
try:
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
load_log.append("Tokenizer: Successfully loaded from xlm-roberta-base")
except Exception as e:
st.error(f"Failed to load base tokenizer: {e}")
raise e
# 2. Model ➔ Pull configuration and weights from your fine-tuned repo
try:
# Load model with weights from your fine-tuned repository
model = AutoModelForSequenceClassification.from_pretrained(
FINETUNED_REPO,
num_labels=2
)
# Check and handle vocab size alignment safely
tokenizer_vocab_size = len(tokenizer)
model_vocab_size = model.get_input_embeddings().weight.shape[0]
if tokenizer_vocab_size != model_vocab_size:
load_log.append(f"Resizing embeddings: {model_vocab_size} → {tokenizer_vocab_size}")
model.resize_token_embeddings(tokenizer_vocab_size)
source = "fine-tuned"
load_log.append(f"Model: Fine-tuned weights successfully loaded from HF Hub ({FINETUNED_REPO})")
except Exception as e:
# Fallback safeguard back to base if repo files fail to initialize
load_log.append(f"Model: Fine-tuned load FAILED ➔ {e}")
load_log.append("Model: WARNING ➔ Falling back to base xlm-roberta-base.")
model = AutoModelForSequenceClassification.from_pretrained(BASE_MODEL, num_labels=2)
source = "base-NOT-fine-tuned"
model.to(DEVICE)
model.eval()
return model, tokenizer, source, load_log
# ─────────────────────── NOTEBOOK FUNCTIONS ──────────────────────────────────
def clean_text(text: str) -> str:
"""From notebook Cell 9."""
return text.strip()
def predict_sarcasm(text: str, model, tokenizer):
"""
Exact replica of predict_sarcasm() from notebook Cell 42.
Patched for stable single-sentence inference on Hugging Face.
Returns: (predicted_label, confidence, sarcasm_pct, not_sarcasm_pct)
"""
text = clean_text(text)
# FIX: Changed padding to 'max_length' to match your training pipeline shape
inputs = tokenizer(
text,
padding="max_length",
truncation=True,
max_length=MAX_LEN,
return_tensors="pt"
)
input_ids = inputs["input_ids"].to(DEVICE)
attention_mask = inputs["attention_mask"].to(DEVICE)
model.eval()
with torch.no_grad():
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
logits = outputs.logits
probabilities = torch.softmax(logits, dim=1)
predicted_class = torch.argmax(probabilities, dim=1).item()
confidence = probabilities[0][predicted_class].item()
label_map = {0: "Not Sarcastic", 1: "Sarcastic"}
predicted_label = label_map[predicted_class]
sarcasm_pct = probabilities[0][1].item() * 100
not_sarcasm_pct = probabilities[0][0].item() * 100
return predicted_label, confidence, sarcasm_pct, not_sarcasm_pct
# ─────────────────────── HELPERS ─────────────────────────────────────────────
def confidence_label(conf: float) -> str:
if conf >= 0.90: return "High Confidence"
if conf >= 0.70: return "Moderate"
return "Low / Uncertain"
def render_result(label, confidence, sarcasm_pct, not_sarcasm_pct):
"""Shared result block used in Tab 1 and Tab 2."""
conf_lbl = confidence_label(confidence)
css_cls = "verdict-sarcastic" if label == "Sarcastic" else "verdict-neutral"
icon = "😏" if label == "Sarcastic" else "🙂"
st.markdown(f"""
{icon} {label}
Confidence: {confidence*100:.1f}% · {conf_lbl}
""", unsafe_allow_html=True)
# Probability bars (plain HTML ➜ no extra lib needed)
st.markdown(f"""
Sarcastic {sarcasm_pct:.1f}%
Not Sarcastic {not_sarcasm_pct:.1f}%
""", unsafe_allow_html=True)
# Model awareness warning (from requirements)
if confidence < 0.90:
st.markdown("""
⚠️ Model Awareness:
This prediction may be less reliable for contextual or implicit (Hindi + English) sarcasm.
""", unsafe_allow_html=True)
# ─────────────────────── MAIN ────────────────────────────────────────────────
def main():
with st.spinner("Loading model…"):
model, tokenizer, source, load_log = load_model()
if model is None:
st.error("❌ Model failed to load.")
st.stop()
# ── Hero ──────────────────────────────────────────────────────────────
st.markdown(f"""
🔥 Taana-Tracker
An AI that understands sarcasm ➜ and knows when it fails
XLM-RoBERTa
PyTorch
(Hindi + English)
94% Accuracy
""", unsafe_allow_html=True)
# Sidebar ➜ model info
with st.sidebar:
st.markdown("## 🔥 Taana-Tracker")
st.markdown("---")
if source == "fine-tuned":
st.success("✅ Fine-tuned model loaded")
else:
st.error("❌ Base model only ➜ predictions will be wrong!")
with st.expander("🔍 Load Details", expanded=(source != "fine-tuned")):
for line in load_log:
st.caption(line)
if source != "fine-tuned":
st.warning("""
**Fix:** Your fine-tuned weights were not found.
Add this to your notebook after Cell 45:
```python
tokenizer.save_pretrained("/content/fine_tuned_roberta_model")
```
Then copy the full `fine_tuned_roberta_model/` folder next to `app.py`.
""")
st.markdown(f"""
**Model:** XLM-RoBERTa-base
**Task:** Binary Classification
**Languages:** (Hindi + English)
**Max Length:** {MAX_LEN} tokens
**Test Accuracy:** 94%
""")
st.markdown("---")
st.markdown("#### 🔗 Quick Links")
col_g, col_h = st.columns(2)
with col_g:
if st.button("GitHub", use_container_width=True):
st.markdown("[Repository link](https://github.com/10Prachi2006/Sarcasm-Intelligence-System.git)")
with col_h:
if st.button("HuggingFace", use_container_width=True):
st.markdown("[HuggingFace model](https://huggingface.co/xlm-roberta-base)")
st.markdown("")
col_d, col_p = st.columns(2)
with col_d:
if st.button("Dataset", use_container_width=True):
st.markdown("[Dataset file](https://github.com/10Prachi2006/Sarcasm-Intelligence-System.git)")
with col_p:
if st.button("Paper", use_container_width=True):
st.markdown("[XLM-R paper](https://arxiv.org/abs/1911.02116)")
st.caption("Taana-Tracker v1.0 · PyTorch + Streamlit")
# ── Tabs ──────────────────────────────────────────────────────────────
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"🔍 Analyze",
"⚡ Live Demo",
"📊 Model Insights",
"🧪 Stress Test",
"📌 About",
])
# ══════════════════════════════════════════════════════════════════════
# TAB 1 ➜ ANALYZE
# ══════════════════════════════════════════════════════════════════════
with tab1:
st.markdown("### Drop your sentence… let's measure the taana level 🌶️")
user_text = st.text_area(
label="",
placeholder='e.g. "Wah beta fail hoke bhi proud moment"',
height=120,
max_chars=256,
key="tab1_input",
)
char_count = len(user_text)
st.caption(f"{char_count}/256 characters")
if st.button("🎯 Detect Sarcasm", type="primary", use_container_width=True, key="tab1_btn"):
if not user_text.strip():
st.warning("Please enter some text first.")
else:
with st.spinner("Analyzing…"):
label, confidence, sarcasm_pct, not_sarcasm_pct = predict_sarcasm(
user_text, model, tokenizer
)
render_result(label, confidence, sarcasm_pct, not_sarcasm_pct)
# Deterministic, no-model reasoning (only for exact matches)
reasoning_examples = {
"Wah beta fail hoke bhi proud moment": "Possible cue: Contradiction between failure ('fail hoke') and celebration ('proud moment'), a common sarcasm pattern.",
"Bahut badiya, sab barbaad kar diya": "Possible cue: Positive praise followed by negative outcome ('barbaad kar diya').",
"Oh, you're leaving at 6 PM? Half-day le liya kya aaj": "Possible cue: Rhetorical question used to mock normal workplace behavior.",
"Aaj mausam bahut accha hai.": "Possible cue: Straightforward statement without strong sarcasm indicators.",
"Thank you for your support!": "Possible cue: Direct appreciation with no obvious sarcastic indicators.",
}
if user_text in reasoning_examples:
st.info(f"💡 {reasoning_examples[user_text]}")
c1, c2, c3 = st.columns(3)
with c1: st.metric("🎯 Verdict", label)
with c2: st.metric("🧠 Confidence", f"{confidence*100:.1f}%")
with c3: st.metric("📊 Conf. Level", confidence_label(confidence))
# ══════════════════════════════════════════════════════════════════════
# TAB 2 ➜ LIVE DEMO
# ══════════════════════════════════════════════════════════════════════
with tab2:
st.markdown("### ⚡ Live Demo ➜ Click any example to analyze")
demo_examples = {
"💼 Workplace Sarcasm": [
"Oh you came on time today, miracle hai kya?",
"Oh, you're leaving at 6 PM? Half-day le liya kya aaj",
"It's okay, you're only 2 hours late, no wories....",
],
"🤖 Tech Sarcasm": [
"Bhai tera ML model itna fast hai ki output agle janam mein aayega!",
"Wow, you used GenAI for a simple 'Hello World' code? Einstein ho kya",
"Nice logic! Iska patent karwa le, dimaag kharch hone se bach jayega.",
"Bhai kya speed hai... 10 min mein 1 epoch",
"Model itna fast hai, result agle janam mein",
],
"😈 Savage Hindi + English": [
"Wah beta fail hoke bhi proud moment",
"Bahut badiya, sab barbaad kar diya",
"Wah kya baat hai, fail ho gaya",
"Kya baat hai, itni achi salary pe bhi khush nahi ho?",
],
"🙂 Genuine (Not Sarcastic)": [
"Aaj mausam bahut accha hai.",
"Mujhe tumhari madad chahiye thi.",
"Thank you for your support!",
"I really appreciate your help.",
],
}
selected_cat = st.selectbox("Pick a category:", list(demo_examples.keys()))
for idx, ex in enumerate(demo_examples[selected_cat]):
if st.button(f"📝 {ex}", key=f"demo_{selected_cat}_{idx}", use_container_width=True):
with st.spinner("Analyzing…"):
label, confidence, sarcasm_pct, not_sarcasm_pct = predict_sarcasm(
ex, model, tokenizer
)
st.markdown(f"**Analyzing:** `{ex}`")
render_result(label, confidence, sarcasm_pct, not_sarcasm_pct)
# ══════════════════════════════════════════════════════════════════════
# TAB 3 ➜ MODEL INSIGHTS
# ══════════════════════════════════════════════════════════════════════
with tab3:
st.markdown("### 📊 Model Insights & Performance")
# Metrics from notebook classification_report (Cell 37)
m1, m2, m3, m4 = st.columns(4)
with m1: st.metric("✅ Accuracy", "94%")
with m2: st.metric("🎯 Precision", "94%")
with m3: st.metric("📡 Recall", "94%")
with m4: st.metric("⚖️ F1-Score", "0.94")
st.divider()
# Per-class table from notebook classification_report
st.markdown("#### Per-Class Performance (Test Set ➜ 1,439 samples)")
st.dataframe(pd.DataFrame({
"Class": ["Not Sarcastic (0)", "Sarcastic (1)"],
"Precision": [0.96, 0.93],
"Recall": [0.91, 0.97],
"F1-Score": [0.93, 0.95],
"Support": [633, 806],
}), use_container_width=True, hide_index=True)
st.divider()
# Confusion matrix from notebook (Cell 36)
st.markdown("#### Confusion Matrix (Test Set)")
st.markdown("""
| | Predicted: Not Sarcastic | Predicted: Sarcastic |
|---------------------|:------------------------:|:--------------------:|
| **Actual: Not Sarcastic** | 576 ✅ (TN) | 57 ❌ (FP) |
| **Actual: Sarcastic** | 24 ❌ (FN) | 782 ✅(TP) |
""")
st.divider()
# Dataset analysis from notebook
st.markdown("#### Dataset Analysis")
col_a, col_b = st.columns(2)
with col_a:
st.markdown("**Class Distribution**")
st.dataframe(pd.DataFrame({
"Class": ["Sarcastic (1)", "Not Sarcastic (0)", "Total"],
"Samples": [5544, 4049, 9593],
"Share": ["57.8%", "42.2%", "100%"],
}), use_container_width=True, hide_index=True)
with col_b:
st.markdown("**Data Splits (from notebook)**")
st.dataframe(pd.DataFrame({
"Split": ["Train (70%)", "Validation (15%)", "Test (15%)"],
"Samples": [6714, 1439, 1440],
}), use_container_width=True, hide_index=True)
st.divider()
st.markdown("#### 🔑 Key Observations")
st.markdown("""
- Strong on explicit sarcasm: Emotional tone markers ("wah", "bilkul") → high recall for the Sarcastic class (97%).
- Weak on implicit/contextual sarcasm: Sentences requiring conversational history or cultural context are harder for the model.
- Hindi + English advantage: XLM-RoBERTa's multilingual pre-training gives it an edge over monolingual models on code-switched text.
- Slight class imbalance: Sarcastic class has ~37% more samples ➜ reflected in higher recall for class 1.
""", unsafe_allow_html=True)
# ══════════════════════════════════════════════════════════════════════
# TAB 4 ➜ STRESS TEST
# ══════════════════════════════════════════════════════════════════════
with tab4:
st.markdown("### 🧪 Stress Test ➜ Where the Model Fails")
st.markdown("""
🔬 Transparency: A model that hides its failures is a dangerous model.
These are cases from the notebook (Cell 43) marked as #--> FAILED.
""", unsafe_allow_html=True)
# Failure cases from notebook Cell 43 ➜ marked #--> FAILED
failure_cases = [
{
"text": "Bhai tera ML model itna fast hai ki output agle janam mein aayega!",
"expected": "Sarcastic",
"reason": "Indirect metaphor ('next birth = never'). Model misses the cultural exaggeration.",
},
{
"text": "Please, thoda aur slow chalao car. Cycle wale bhi humein overtake karke jaa raha hai",
"expected": "Sarcastic",
"reason": "Context-dependent irony ➜ meaning flips only when you know slow car + cycles overtaking = embarrassing.",
},
{
"text": "Oh, you're leaving at 6 PM? Half-day le liya kya aaj",
"expected": "Sarcastic",
"reason": "Workplace irony needing domain context. Question form lowers model confidence.",
},
{
"text": "Meri MLOps knowledge aur mera bank balance—dono hi zero hain",
"expected": "Sarcastic",
"reason": "Self-deprecating humor stated plainly ➜ model may read it as factual.",
},
]
if st.button("🧪 Run All Failure Cases", type="primary", use_container_width=True):
correct = 0
for i, case in enumerate(failure_cases, 1):
with st.spinner(f"Running case {i}/{len(failure_cases)}…"):
label, confidence, _, _ = predict_sarcasm(case["text"], model, tokenizer)
got_it_right = label == case["expected"]
if got_it_right:
correct += 1
icon = "✅" if got_it_right else "❌"
conf_lbl = confidence_label(confidence)
st.markdown(f"""
{icon} Case {i}: "{case['text']}"
Expected: {case['expected']} |
Got: {label} |
Confidence: {confidence*100:.1f}% ({conf_lbl})
🔬 Why it's hard: {case['reason']}
""", unsafe_allow_html=True)
st.divider()
st.metric("Score on Hard Cases", f"{correct}/{len(failure_cases)}")
# ══════════════════════════════════════════════════════════════════════
# TAB 5 ➜ ABOUT
# ══════════════════════════════════════════════════════════════════════
with tab5:
st.markdown("### 📌 About Taana-Tracker")
col1, col2 = st.columns(2)
with col1:
st.markdown("#### System Description")
st.markdown("""
**Taana-Tracker** is a fine-tuned XLM-RoBERTa model for sarcasm detection
in (Hindi + English) text.
Trained on ~9,593 real-world Hindi + English samples from social media and news comments.
""")
with col2:
st.markdown("#### Tech Stack")
st.dataframe(pd.DataFrame({
"Component": ["Model", "Framework", "UI", "Language"],
"Technology": ["XLM-RoBERTa-base (fine-tuned)",
"PyTorch + HuggingFace Transformers",
"Streamlit", "Python 3.10+"],
}), use_container_width=True, hide_index=True)
st.divider()
st.markdown("#### ⚠️ Limitations")
st.markdown("""
- Requires context: Complex sarcasm needing conversational history will often fail.
- Cultural nuance: Idioms and region-specific humor outside the training set get misclassified.
- Hindi + English ambiguity: Variable transliteration and code-switching create genuine model uncertainty.
- Dataset ceiling: ~9,593 samples. More diverse data would improve generalisation.
""", unsafe_allow_html=True)
st.divider()
st.markdown("#### 🔗 References")
st.markdown("""
- [XLM-RoBERTa Paper ➜ Conneau et al. 2019](https://arxiv.org/abs/1911.02116)
- [HuggingFace `xlm-roberta-base`](https://huggingface.co/xlm-roberta-base)
- [HuggingFace Transformers Docs](https://huggingface.co/docs/transformers)
""")
if __name__ == "__main__":
main()