| import os |
| import re |
| import torch |
| import gradio as gr |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification |
|
|
| |
| MODEL_PATH = "ArabicNewsAnalyzer/MARBERTv2-Sentiment-ml128-bs32-error-fix-v6-aug" |
|
|
| print(f"Loading model and tokenizer from: {MODEL_PATH}...") |
|
|
| try: |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) |
| model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH) |
| model.eval() |
| print("Model and tokenizer successfully loaded!") |
| except Exception as e: |
| print(f"Error loading model from {MODEL_PATH}: {e}") |
| tokenizer = None |
| model = None |
|
|
| |
| ID2LABEL = { |
| 0: "negative", |
| 1: "neutral", |
| 2: "positive" |
| } |
|
|
| LABEL_DISPLAY = { |
| "positive": "إيجابي (Positive) 🟢", |
| "negative": "سلبي (Negative) 🔴", |
| "neutral": "محايد (Neutral) 🟡" |
| } |
|
|
| LABEL_DESCRIPTIONS = { |
| "positive": "يعبر النص عن مشاعر إيجابية أو رضا أو استحسان.", |
| "negative": "يعبر النص عن مشاعر سلبية أو استياء أو انتقاد.", |
| "neutral": "نص محايد لا يحمل شحنة عاطفية صريحة." |
| } |
|
|
| |
| def preprocess_arabic_text(text: str) -> str: |
| if not text or not isinstance(text, str): |
| return "" |
| text = str(text) |
| |
| text = re.sub(r"[\u064B-\u0652]", "", text) |
| |
| text = re.sub(r"\u0640", "", text) |
| |
| text = re.sub(r"[\u0622\u0623\u0625]", "\u0627", text) |
| |
| text = re.sub(r"\u0649", "\u064A", text) |
| |
| text = re.sub(r"\u0629", "\u0647", text) |
| |
| text = re.sub(r"\s+", " ", text).strip() |
| return text |
|
|
| |
| def analyze_sentiment(text: str): |
| if not text or len(text.strip()) == 0: |
| return ( |
| "⚠️ الرجاء إدخال نص لتحليله / Please enter text to analyze.", |
| {}, |
| "" |
| ) |
| |
| cleaned_text = preprocess_arabic_text(text) |
| |
| if model is None or tokenizer is None: |
| |
| return ( |
| "⏳ جاري تحميل نموذج MARBERTv2 على Hugging Face Hub...", |
| {"positive": 0.33, "negative": 0.33, "neutral": 0.33}, |
| "النموذج قيد التحميل من المسار: ArabicNewsAnalyzer/MARBERTv2-Sentiment-ml512-bs32" |
| ) |
|
|
| |
| inputs = tokenizer( |
| cleaned_text, |
| return_tensors="pt", |
| truncation=True, |
| max_length=128, |
| padding=True |
| ) |
| |
| with torch.no_grad(): |
| outputs = model(**inputs) |
| logits = outputs.logits |
| probabilities = torch.softmax(logits, dim=-1)[0].tolist() |
|
|
| |
| confidences = {} |
| for i, prob in enumerate(probabilities): |
| label_key = model.config.id2label.get(i, ID2LABEL.get(i, f"class_{i}")) |
| display_label = LABEL_DISPLAY.get(label_key, label_key) |
| confidences[display_label] = float(prob) |
|
|
| |
| top_pred_id = int(torch.argmax(logits, dim=-1).item()) |
| top_label_key = model.config.id2label.get(top_pred_id, ID2LABEL.get(top_pred_id, "neutral")) |
| top_display = LABEL_DISPLAY.get(top_label_key, top_label_key) |
| top_score = probabilities[top_pred_id] * 100 |
| top_description = LABEL_DESCRIPTIONS.get(top_label_key, "") |
|
|
| result_md = f""" |
| <div style="padding: 18px; border-radius: 12px; background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border: 1px solid #334155; color: #f8fafc; font-family: 'Segoe UI', Tahoma, sans-serif;"> |
| <h3 style="margin-top: 0; color: #38bdf8; font-size: 1.4rem;">نتيجة التحليل | Prediction Result</h3> |
| <p style="font-size: 1.2rem; margin: 8px 0;"><strong>التصنيف الرئيس:</strong> <span style="color: #4ade80;">{top_display}</span></p> |
| <p style="font-size: 1.1rem; margin: 8px 0;"><strong>نسبة الثقة:</strong> <span style="color: #facc15;">{top_score:.2f}%</span></p> |
| <p style="font-size: 0.95rem; color: #94a3b8; margin-top: 10px;">{top_description}</p> |
| </div> |
| """ |
|
|
| return result_md, confidences, cleaned_text |
|
|
| |
| custom_css = """ |
| .container { |
| max-width: 1000px; |
| margin: 0 auto; |
| } |
| .arabic-title { |
| text-align: center; |
| font-family: 'Cairo', 'Tajawal', 'Segoe UI', sans-serif; |
| color: #1e293b; |
| } |
| .arabic-input textarea { |
| direction: rtl !important; |
| text-align: right !important; |
| font-size: 1.1rem !important; |
| font-family: 'Cairo', 'Tajawal', 'Segoe UI', sans-serif !important; |
| } |
| .result-box { |
| direction: rtl; |
| text-align: right; |
| } |
| """ |
|
|
| |
| EXAMPLES = [ |
| ["أعلنت وزارة الصحة اليوم افتتاح ثلاث مستشفيات جديدة في العاصمة للارتقاء بمستوى الخدمات الطبية."], |
| ["الخدمة ممتازة والمنتج وصل في وقت قياسي وبجودة عالية جداً، شكراً لكم على الاحترافية."], |
| ["للأسف الشديد التجربة كانت سيئة والمنتج لا يعمل والخدمة منعدمة، لا أنصح بالتعامل معهم."], |
| ["عقد المجلس جلسته الاعتيادية اليوم لمناقشة جدول الأعمال واختتام الدورة الحالية."], |
| ["يعتبر هذا الكتاب من أمتع ما قرأت من روايات، أسلوب الكاتب مشوق والحبكة متماسكة للغاية."] |
| ] |
|
|
| |
| with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="blue")) as demo: |
| |
| gr.Markdown( |
| """ |
| # 🇸🇦 محرر ومحلل المشاعر العربي | Arabic Sentiment Analyzer (MARBERTv2) |
| ### Model: `ArabicNewsAnalyzer/MARBERTv2-Sentiment-ml512-bs32` |
| |
| تطبيق ذكاء اصطناعي مخصص لتحليل المشاعر والاتجاهات في النصوص والمنشورات والأخبار العربية باستخدام نموذج **MARBERTv2** الدقيق. |
| |
| *Fine-tuned transformer model for 4-class Arabic Sentiment Analysis: Positive, Negative, and Neutral* |
| """ |
| ) |
| |
| with gr.Row(): |
| with gr.Column(scale=3): |
| text_input = gr.Textbox( |
| lines=5, |
| placeholder="أدخل النص العربي هنا للتحليل (أخبار، تغريدات، مراجعات كتب، تعليقات)...", |
| label="النص المدخل | Input Arabic Text", |
| elem_classes=["arabic-input"] |
| ) |
| |
| with gr.Row(): |
| submit_btn = gr.Button("🔍 تحليل المشاعر | Analyze Sentiment", variant="primary", size="lg") |
| clear_btn = gr.Button("🗑️ مسح | Clear", variant="secondary", size="lg") |
|
|
| gr.Examples( |
| examples=EXAMPLES, |
| inputs=[text_input], |
| label="💡 أمثلة تجريبية جاهزة | Quick Test Examples" |
| ) |
|
|
| with gr.Column(scale=3): |
| output_html = gr.HTML(label="النتيجة | Prediction", elem_classes=["result-box"]) |
| output_labels = gr.Label(num_top_classes=4, label="توزيع الاحتمالات | Probability Distribution") |
| cleaned_output = gr.Textbox(label="النص المعالج | Preprocessed Text", interactive=False) |
|
|
| submit_btn.click( |
| fn=analyze_sentiment, |
| inputs=[text_input], |
| outputs=[output_html, output_labels, cleaned_output] |
| ) |
| |
| clear_btn.click( |
| fn=lambda: ("", "", {}, ""), |
| inputs=[], |
| outputs=[text_input, output_html, output_labels, cleaned_output] |
| ) |
|
|
|
|
| |
| if __name__ == "__main__": |
| demo.launch() |
|
|