Spaces:
Running
Running
File size: 8,812 Bytes
aa9de32 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | import os
import sys
import time
import pickle
import numpy as np
import pandas as pd
from tanglish_sentiment_pipeline import (
resolve_path,
load_dataset,
normalize_text,
CongMoeClassifier,
TARGET_CLASSES,
EMOJI_MAP
)
MODEL_FILE = resolve_path("tanglish_congmoe_model.pkl")
DATASET_FILE = resolve_path("tanglish_test_20000_with_labels.csv")
STATE_EMOJIS = {
"Positive": "๐ POSITIVE (Praise / High Excitement)",
"Negative": "๐ NEGATIVE (Disappointment / Criticism)",
"Mixed_feelings": "โ๏ธ MIXED FEELINGS (Conflicted / Nuanced Review)",
"Unknown_state": "โ UNKNOWN STATE (Question / Inquiry / Neutral)"
}
def get_or_train_model():
"""Loads the pre-trained model or trains and saves it if not already present."""
if os.path.exists(MODEL_FILE):
print(f"[*] Loading pre-trained ConG-MoE v2 model from '{MODEL_FILE}'...")
t0 = time.perf_counter()
with open(MODEL_FILE, "rb") as f:
model = pickle.load(f)
print(f"[*] Model loaded in {(time.perf_counter() - t0)*1000:.1f} ms!\n")
return model
print(f"[*] Pre-trained model not found. Training once on {DATASET_FILE}...")
df, text_col, label_col = load_dataset(DATASET_FILE)
X_train_raw = df[text_col]
y_train = df[label_col]
print("[*] Normalizing text representations...")
X_train_norm = X_train_raw.apply(normalize_text)
print("[*] Training ConG-MoE v2 architecture...")
t0 = time.perf_counter()
model = CongMoeClassifier(confidence_threshold=0.65, C=0.5, random_state=42)
model.fit(X_train_norm, y_train, X_raw=X_train_raw)
train_time = time.perf_counter() - t0
print(f"[*] Training finished in {train_time:.2f} seconds.")
print(f"[*] Saving model to '{MODEL_FILE}' for instant future startup...")
with open(MODEL_FILE, "wb") as f:
pickle.dump(model, f)
print("[*] Model successfully cached!\n")
return model
def predict_comment(raw_text: str, model: CongMoeClassifier):
"""Predicts the sentiment state of a user comment using ConG-MoE v2."""
t0 = time.perf_counter()
clean_text = normalize_text(raw_text)
# 1. Base statistical pipeline prediction & probabilities
probs = model.base_pipeline.predict_proba([clean_text])[0]
classes = model.base_pipeline.classes_
prob_dict = {cls: float(p) for cls, p in zip(classes, probs)}
base_pred = classes[probs.argmax()]
confidence = float(probs.max())
# 2. Check Expert Heads & Linguistic Markers
# Extended pivot check
extended_pivots = {"but", "aana", "aanal", "aanaa", "anaa", "ana", "irundhalum", "irunthalum", "yet", "though", "however", "analum"}
has_pivot = any(p in clean_text.split() for p in extended_pivots)
# Counterfactual regret markers ('could have been better')
regret_markers = {"irunthurukalam", "irunthirukalam", "irundhurukalam", "irundhirukalam", "irukalam", "irunthrukalam", "panniirukalam"}
has_regret = any(m in clean_text for m in regret_markers) or ("inno" in clean_text and "best" in clean_text) or ("innum" in clean_text and "nalla" in clean_text)
pivot_pred = model.pivot_head.predict_sample(raw_text)
emoji_pred = model.emoji_head.predict_sample(raw_text)
# Affective terms
pos_terms = {"sema", "mass", "super", "nalla", "best", "verithanam", "thalaiva", "level", "good", "love", "arputham", "marana"}
neg_terms = {"worst", "waste", "mokka", "bore", "flaw", "bad", "disaster", "terrible", "hate", "daridram", "kevalam"}
words_set = set(clean_text.split())
has_pos_term = any(t in words_set for t in pos_terms)
has_neg_term = any(t in words_set for t in neg_terms)
has_sad_emoji = any(e in raw_text for e in ["๐ข", "๐ญ", "๐ก", "๐", "๐คฎ", "๐", "๐คฆ", "๐คฆโโ๏ธ", "๐ "])
has_happy_emoji = any(e in raw_text for e in ["๐", "๐", "๐", "๐ฅ", "โค๏ธ", "๐", "๐", "๐ฏ", "๐ฅ", "๐คฉ"])
is_emoji_dissonance = (has_pos_term and has_sad_emoji) or (has_neg_term and has_happy_emoji)
# 3. Gating Decision Logic
override_reason = None
if (has_pivot and has_pos_term and has_neg_term) or (has_pivot and has_regret) or (has_regret and "acting" in clean_text):
final_state = "Mixed_feelings"
override_reason = "Contrastive pivot ('aana'/'but') connecting positive and negative clauses"
elif is_emoji_dissonance and confidence < 0.85:
final_state = "Mixed_feelings"
override_reason = "Affective dissonance detected between text and emojis"
elif confidence < model.confidence_threshold and (pivot_pred == "Mixed_feelings" or (emoji_pred == "Mixed_feelings" and not (has_neg_term and has_sad_emoji))):
final_state = "Mixed_feelings"
override_reason = "Contrastive pivot or discordance detected by specialist head"
elif ("?" in raw_text or any(q in words_set for q in ["eppo", "eppadi", "enna", "ethu", "when", "why", "who", "what"])) and (base_pred == "Unknown_state" or prob_dict.get("Unknown_state", 0) > 0.15 or confidence < 0.60):
final_state = "Unknown_state"
override_reason = "Inquiry syntax ('?') and interrogative token detected"
elif has_neg_term and not has_pos_term and (base_pred != "Negative"):
# Strong negative keywords should not fall to Positive
final_state = "Negative"
override_reason = "Explicit negative evaluation keywords detected"
else:
final_state = base_pred
latency_ms = (time.perf_counter() - t0) * 1000
return {
"raw_text": raw_text,
"clean_text": clean_text,
"final_state": final_state,
"base_prediction": base_pred,
"confidence": confidence,
"override_reason": override_reason,
"prob_dict": prob_dict,
"latency_ms": latency_ms
}
def print_prediction_result(res: dict):
state_str = STATE_EMOJIS.get(res['final_state'], res['final_state'])
print("\n" + "โ" * 70)
print(f" Input Comment : \"{res['raw_text']}\"")
print(f" Processed As : \"{res['clean_text']}\"")
print(f" Predicted State: {state_str}")
print("โ" * 70)
print(f" Base Model Prediction : {res['base_prediction']} (Confidence: {res['confidence']*100:.1f}%)")
if res['override_reason']:
print(f" Specialist Intervention: [YES] Overridden to Mixed feelings")
print(f" Reason : {res['override_reason']}")
else:
print(f" Specialist Intervention: [NO] Base prediction accepted (Confidence >= 65% or no conflict)")
print(f" Inference Latency : {res['latency_ms']:.3f} milliseconds")
print("\n Class Probability Breakdown:")
for cls in TARGET_CLASSES:
p = res['prob_dict'].get(cls, 0.0)
bar = "โ" * int(p * 30)
print(f" - {cls:<16}: {p*100:5.1f}% |{bar:<30}|\n")
print("โ" * 70 + "\n")
def main():
model = get_or_train_model()
# If the user passed a comment directly as a command-line argument:
if len(sys.argv) > 1:
user_comment = " ".join(sys.argv[1:])
res = predict_comment(user_comment, model)
print_prediction_result(res)
return
# Otherwise, run interactive prompt loop:
print("=" * 70)
print(" TANGLISH SENTIMENT PREDICTOR (ConG-MoE v2 Interactive CLI)")
print("=" * 70)
print("Type any Tanglish comment below and hit Enter.")
print("Commands: 'exit' or 'q' to quit, 'samples' to test built-in examples.\n")
sample_comments = [
"Thalaivar entry vera level mass ๐ฅ padam blockbuster confirmed!",
"Padam romba mokka da timing waste don't go theatre ๐คฎ worst movie",
"Acting sema mass aana screenplay romba worst",
"first half nalla irukku aana climax mokka ๐คฎ",
"GOAT trailer vera level bro... eppo ott release date???"
]
while True:
try:
user_input = input("Enter Tanglish Comment > ").strip()
except (EOFError, KeyboardInterrupt):
print("\nExiting. Vanakkam!")
break
if not user_input:
continue
if user_input.lower() in ["q", "quit", "exit"]:
print("Exiting predictor. Vanakkam!")
break
if user_input.lower() == "samples":
print("\n--- Running Built-In Sample Comments ---")
for sample in sample_comments:
res = predict_comment(sample, model)
print_prediction_result(res)
continue
res = predict_comment(user_input, model)
print_prediction_result(res)
if __name__ == "__main__":
main()
|