Sentimen-Analysis / services /sentiment.py
noranisa's picture
Update services/sentiment.py
d4bf77c verified
"""
services/sentiment.py
Model sentimen berbasis IndoBERT / RoBERTa-ID.
Torch di-import secara lazy agar tidak crash saat package belum siap.
"""
import os
LOCAL_MODEL_PATH = "model/final_model"
FALLBACK_MODEL = "w11wo/indonesian-roberta-base-sentiment-classifier"
# ── RULE-BASED FALLBACK ──
_POS_KW = [
"bagus","baik","senang","suka","mantap","keren","hebat","oke","setuju",
"benar","sukses","berhasil","love","good","great","nice","best","amazing",
"excellent","wonderful","happy","glad","positif","mendukung","bangga",
"luar biasa","terima kasih","apresiasi","semangat","maju","berkembang",
]
_NEG_KW = [
"buruk","jelek","benci","kecewa","gagal","salah","rugi","marah","bohong",
"hoax","fitnah","jahat","tidak setuju","parah","malu","takut","bad",
"worst","terrible","hate","fail","wrong","poor","awful","negatif","tolak",
"menolak","turun","jatuh","hancur","krisis","masalah","bahaya","ancam",
]
def _rule_based(text: str) -> str:
lower = text.lower()
pos = sum(1 for k in _POS_KW if k in lower)
neg = sum(1 for k in _NEG_KW if k in lower)
if pos > neg: return "Positive"
if neg > pos: return "Negative"
return "Neutral"
# ── MODEL LOADING ──
def _load_model():
try:
import torch
from transformers import pipeline
path = LOCAL_MODEL_PATH if os.path.exists(LOCAL_MODEL_PATH) else FALLBACK_MODEL
label = "fine-tuned" if os.path.exists(LOCAL_MODEL_PATH) else "fallback RoBERTa-ID"
clf = pipeline(
"sentiment-analysis",
model=path,
device=-1,
truncation=True,
max_length=512,
)
print(f"βœ… Sentiment model loaded: {label}")
return clf
except ImportError:
print("⚠️ PyTorch tidak tersedia β€” rule-based fallback aktif")
return None
except Exception as e:
print(f"❌ Gagal load sentiment model: {e}")
return None
classifier = _load_model()
# ── LABEL NORMALIZATION ──
def _normalize(label: str) -> str:
label = label.lower()
if "positive" in label or label == "label_2": return "Positive"
if "negative" in label or label == "label_0": return "Negative"
if "neutral" in label or label == "label_1": return "Neutral"
return "Neutral"
# ── PUBLIC API ──
def predict(texts: list) -> list:
"""Return list of label strings."""
if not texts: return []
if classifier is None:
return [_rule_based(t) for t in texts]
try:
outputs = classifier(texts, batch_size=8, truncation=True)
return [_normalize(o["label"]) for o in outputs]
except Exception as e:
print(f"❌ predict() batch error: {e} β€” per-item fallback")
results = []
for t in texts:
try:
out = classifier(t[:512], truncation=True)
results.append(_normalize(out[0]["label"]))
except Exception:
results.append(_rule_based(t))
return results
def predict_single(text: str) -> str:
return predict([text])[0]
def predict_with_score(texts: list) -> list:
"""
Return list of dicts: {label, score}
score = confidence dari model (0–1).
"""
if not texts: return []
if classifier is None:
return [{"label": _rule_based(t), "score": 0.5} for t in texts]
try:
outputs = classifier(texts, batch_size=8, truncation=True)
return [
{"label": _normalize(o["label"]), "score": round(float(o["score"]), 4)}
for o in outputs
]
except Exception as e:
print(f"❌ predict_with_score() error: {e} β€” per-item fallback")
results = []
for t in texts:
try:
out = classifier(t[:512], truncation=True)
results.append({
"label": _normalize(out[0]["label"]),
"score": round(float(out[0]["score"]), 4)
})
except Exception:
results.append({"label": _rule_based(t), "score": 0.5})
return results