ArabicNewsAnalyzer's picture
Upload 2 files
90bec81 verified
Raw
History Blame Contribute Delete
6.84 kB
import re
import string
import torch
import torch.nn as nn
import torch.nn.functional as F
import gradio as gr
from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification
from peft import LoraConfig, get_peft_model
from huggingface_hub import hf_hub_download
# ==========================================
# 0. Setup & Class Mapping
# ==========================================
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
LABELS = ["Negative", "Neutral", "Positive"]
# ==========================================
# 1. Model 1 Setup (CAMeLBERT + LoRA)
# ==========================================
M1_NAME = "CAMeL-Lab/bert-base-arabic-camelbert-mix-sentiment"
M1_REPO = "mahmoudmohammad/MARBERTv2-Sentiment_Classification"
M1_FILE = "best_stl_model.pth"
class SentimentSTL(nn.Module):
def __init__(self, num_classes=3):
super().__init__()
self.encoder = AutoModel.from_pretrained(M1_NAME)
peft_config = LoraConfig(
task_type="FEATURE_EXTRACTION",
r=32,
lora_alpha=64,
lora_dropout=0.1,
target_modules=["query", "value"]
)
self.peft_model = get_peft_model(self.encoder, peft_config)
hidden_size = self.peft_model.config.hidden_size
self.classifier = nn.Sequential(
nn.Dropout(0.3),
nn.Linear(hidden_size, hidden_size // 2),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(hidden_size // 2, num_classes),
)
def forward(self, input_ids, attention_mask):
outputs = self.peft_model(input_ids=input_ids, attention_mask=attention_mask)
hidden = outputs.last_hidden_state
mask = attention_mask.unsqueeze(-1).float()
mean_rep = (hidden * mask).sum(1) / mask.sum(1)
cls_rep = hidden[:, 0, :]
cls_rep = (cls_rep + mean_rep) / 2.0
return self.classifier(cls_rep)
def preprocess_m1(text):
if not isinstance(text, str):
return str(text)
text = re.sub(r'http\S+|www\.\S+', '', text)
text = re.sub(r'[@]\S+', '', text)
text = re.sub(r'\S+@\S+', ' ', text)
text = re.sub(r'\d+|[٠١٢٣٤٥٦٧٨٩]+', '', text)
text = text.replace('#', ' ').replace('_', ' ')
text = re.sub("[إأآا]", "ا", text)
text = re.sub("ى", "ي", text)
text = re.sub("ؤ", "و", text)
text = re.sub("ئ", "ي", text)
text = re.sub("ة", "ه", text)
arabic_punc = '`÷×؛«»<>()*&^%][ـ،/:".،,\'{}~¦+|"…""–ـ'
eng_punc = string.punctuation.replace('!', '').replace('?', '')
text = text.translate(str.maketrans('', '', arabic_punc + eng_punc))
return re.sub(r'\s+', ' ', text).strip()
print("Loading Model 1 (CAMeLBERT + LoRA)...")
tokenizer_m1 = AutoTokenizer.from_pretrained(M1_NAME)
model_m1 = SentimentSTL(num_classes=3).to(device)
m1_weights_path = hf_hub_download(repo_id=M1_REPO, filename=M1_FILE)
model_m1.load_state_dict(torch.load(m1_weights_path, map_location=device))
model_m1.eval()
# ==========================================
# 2. Model 2 Setup (MARBERTv2 Sequence Classifier)
# ==========================================
M2_PATH = "ArabicNewsAnalyzer/MARBERTv2-Sentiment-ml128-bs32-error-fix-v6-aug"
def preprocess_m2(text: str) -> str:
if not text or not isinstance(text, str):
return ""
text = str(text)
text = re.sub(r"[\u064B-\u0652]", "", text) # Tashkeel
text = re.sub(r"\u0640", "", text) # Tatweel
text = re.sub(r"[\u0622\u0623\u0625]", "\u0627", text)
text = re.sub(r"\u0649", "\u064A", text)
text = re.sub(r"\u0629", "\u0647", text)
return re.sub(r"\s+", " ", text).strip()
print("Loading Model 2 (MARBERTv2)...")
tokenizer_m2 = AutoTokenizer.from_pretrained(M2_PATH)
model_m2 = AutoModelForSequenceClassification.from_pretrained(M2_PATH).to(device)
model_m2.eval()
# ==========================================
# 3. Prediction & Ensemble Logic
# ==========================================
def predict_m1(text):
cleaned = preprocess_m1(text)
enc = tokenizer_m1(
cleaned,
add_special_tokens=True,
max_length=256,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt'
)
ids = enc['input_ids'].to(device)
mask = enc['attention_mask'].to(device)
with torch.no_grad():
logits = model_m1(ids, mask)
probs = F.softmax(logits, dim=1).squeeze().cpu().numpy()
return probs
def predict_m2(text):
cleaned = preprocess_m2(text)
enc = tokenizer_m2(
cleaned,
return_tensors="pt",
truncation=True,
max_length=128,
padding=True
)
enc = {k: v.to(device) for k, v in enc.items()}
with torch.no_grad():
logits = model_m2(**enc).logits
probs = F.softmax(logits, dim=-1).squeeze().cpu().numpy()
return probs
def ensemble_predict(text, weight_m1=0.5):
if not text.strip():
return {label: 0.0 for label in LABELS}
weight_m2 = 1.0 - weight_m1
# Get probability vectors [prob_neg, prob_neu, prob_pos]
p1 = predict_m1(text)
p2 = predict_m2(text)
# Weighted average ensemble
ensemble_probs = (weight_m1 * p1) + (weight_m2 * p2)
return {
LABELS[0]: float(ensemble_probs[0]),
LABELS[1]: float(ensemble_probs[1]),
LABELS[2]: float(ensemble_probs[2])
}
# ==========================================
# 4. Minimal Gradio UI
# ==========================================
demo = gr.Interface(
fn=ensemble_predict,
inputs=[
gr.Textbox(
lines=4,
label="Arabic Text / النص العربي",
placeholder="اكتب النص هنا..."
),
gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.5,
step=0.05,
label="Model 1 Weight (CAMeLBERT+LoRA vs MARBERTv2)"
)
],
outputs=gr.Label(label="Ensemble Sentiment Probabilities", num_top_classes=3),
title="Arabic Sentiment Ensemble",
description="Weighted average ensemble between Model 1 (CAMeLBERT+LoRA) and Model 2 (MARBERTv2).",
examples=[
["هذا المطعم يقدم طعام سيء جدًا ومحروق، لا أنصح أحد بزيارته.", 0.5],
["أعلنت وزارة الصحة اليوم افتتاح ثلاث مستشفيات جديدة في العاصمة.", 0.5],
["بصراحة، الخدمة كانت ممتازة والموظفين غاية في الإحترام، أنصح بشدة!", 0.5]
]
)
if __name__ == "__main__":
demo.launch()