dodo-2100 commited on
Commit
43587aa
·
verified ·
1 Parent(s): 18dbc5c

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +220 -0
  2. logic.py +368 -0
  3. requirements.txt +7 -3
  4. styles.css +51 -0
  5. translations.py +74 -0
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ ====================================================================================================
4
+ 📂 FILE: app.py | ملف واجهة المستخدم
5
+ ====================================================================================================
6
+ 🇬🇧 English Description:
7
+ This is the frontend entry point using Streamlit.
8
+ It handles:
9
+ 1. UI Layout & Design (Sidebar, Inputs, Results).
10
+ 2. Resource Monitoring (RAM, CPU, GPU Dashboard).
11
+ 3. Bilingual Support (Switching between Ar/En).
12
+ 4. Calling the backend `logic.py` to process answers.
13
+
14
+ 🇪🇬 Arabic Description:
15
+ هذا هو مدخل الواجهة الأمامية باستخدام مكتبة Streamlit.
16
+ يتولى:
17
+ 1. تصميم وتخطيط الواجهة (القائمة الجانبية، المدخلات، النتائج).
18
+ 2. مراقبة الموارد (لوحة الرامات، المعالج، وكارت الشاشة).
19
+ 3. دعم تعدد اللغات (التحويل بين العربية والإنجليزية).
20
+ 4. استدعاء الخلفية `logic.py` لمعالجة الإجابات.
21
+ ====================================================================================================
22
+ """
23
+
24
+ import streamlit as st
25
+ import time
26
+ import psutil
27
+ import subprocess
28
+ from logic import load_model, predict, device
29
+ from translations import TRANSLATIONS
30
+
31
+ # ==========================================
32
+ # 📊 RESOURCE MONITOR HELPER | مساعد مراقبة الموارد
33
+ # ==========================================
34
+ def get_system_metrics():
35
+ """
36
+ 🇬🇧 Collects real-time system stats.
37
+ Why? To debug crashes on Colab by monitoring if RAM/VRAM is full.
38
+
39
+ 🇪🇬 يجمع إحصائيات النظام لحظياً.
40
+ لماذا؟ لتتبع أسباب الانهيار على Colab عبر مراقبة امتلاء الرامات أو ذاكرة الفيديو.
41
+ """
42
+ # 🇬🇧 System RAM (Capacity: 12GB on Colab)
43
+ # 🇪🇬 رامات النظام (السعة: 12 جيجا على Colab)
44
+ ram = psutil.virtual_memory()
45
+ ram_used = ram.used / (1024 ** 3) # Convert bytes to GB
46
+ ram_total = ram.total / (1024 ** 3)
47
+ ram_percent = ram.percent
48
+
49
+ # 🇬🇧 CPU Usage (Capacity: 2 Cores on Colab)
50
+ # 🇪🇬 استهلاك المعالج (السعة: نواتين على Colab)
51
+ cpu_percent = psutil.cpu_percent(interval=None)
52
+
53
+ # 🇬🇧 GPU VRAM (Capacity: 16GB on T4) - Via nvidia-smi command
54
+ # 🇪🇬 ذاكرة الفيديو (السعة: 16 جيجا على T4) - عبر أمر nvidia-smi
55
+ gpu_info = "N/A"
56
+ gpu_memory = "N/A"
57
+ try:
58
+ # Run nvidia-smi query to get utilization and memory usage
59
+ result = subprocess.run(
60
+ ['nvidia-smi', '--query-gpu=utilization.gpu,memory.used,memory.total', '--format=csv,nounits,noheader'],
61
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
62
+ )
63
+ if result.returncode == 0:
64
+ util, used, total = result.stdout.strip().split(',')
65
+ gpu_info = f"{util.strip()}%"
66
+ gpu_memory = f"{int(used)}MB / {int(total)}MB"
67
+ except:
68
+ pass # Fail silently if no NVIDIA GPU found (e.g. local machine without CUDA)
69
+
70
+ return ram_used, ram_total, ram_percent, cpu_percent, gpu_info, gpu_memory
71
+
72
+ # ==========================================
73
+ # 🎨 PAGE SETUP (Must be first) | إعداد الصفحة (يجب أن يكون أولاً)
74
+ # ==========================================
75
+ st.set_page_config(
76
+ page_title="ASAG Grader AI",
77
+ page_icon="🎓",
78
+ layout="wide",
79
+ initial_sidebar_state="expanded"
80
+ )
81
+
82
+ # 🇬🇧 Load Custom CSS for advanced styling
83
+ # 🇪🇬 تحميل CSS المخصص للتنسيق المتقدم
84
+ with open("styles.css", "r") as f:
85
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
86
+
87
+ # ==========================================
88
+ # 🌐 LANGUAGE SETUP | إعداد اللغات
89
+ # ==========================================
90
+ # 🇬🇧 Initialize Session State for Language (Default: Arabic)
91
+ # 🇪🇬 تهيئة حالة الجلسة للغة (الافتراضي: العربية)
92
+ if 'lang' not in st.session_state:
93
+ st.session_state.lang = 'ar'
94
+
95
+ def toggle_lang():
96
+ """Switches between 'ar' and 'en'"""
97
+ if st.session_state.lang == 'ar':
98
+ st.session_state.lang = 'en'
99
+ else:
100
+ st.session_state.lang = 'ar'
101
+
102
+ # 🇬🇧 Sidebar Button to Toggle Language
103
+ # 🇪🇬 زر القائمة الجانبية لتغيير اللغة
104
+ lang_btn_label = "Switch to English 🇺🇸" if st.session_state.lang == 'ar' else "التحويل للعربية 🇪🇬"
105
+ st.sidebar.button(lang_btn_label, on_click=toggle_lang)
106
+
107
+ # 🇬🇧 Select Dictionary based on current language
108
+ # 🇪🇬 اختيار القاموس بناءً على اللغة الحالية
109
+ txt = TRANSLATIONS[st.session_state.lang]
110
+
111
+ # 🇬🇧 Dynamic Directionality (RTL for Arabic, LTR for English)
112
+ # 🇪🇬 اتجاه ديناميكي (يمين لليسار للعربية، يسار لليمين للإنجليزية)
113
+ st.markdown(f"""
114
+ <style>
115
+ .stApp {{ direction: {'rtl' if st.session_state.lang == 'ar' else 'ltr'}; }}
116
+ .stTextInput, .stTextArea {{ direction: {'rtl' if st.session_state.lang == 'ar' else 'ltr'}; }}
117
+ </style>
118
+ """, unsafe_allow_html=True)
119
+
120
+ # ==========================================
121
+ # 📟 SIDEBAR MONITOR | لوحة المراقبة الجانبية
122
+ # ==========================================
123
+ with st.sidebar.expander("📊 System Monitor (Live)", expanded=True):
124
+ r_used, r_total, r_perc, c_perc, g_util, g_mem = get_system_metrics()
125
+
126
+ # RAM Display
127
+ st.caption("💻 **System RAM:**")
128
+ st.progress(r_perc / 100)
129
+ st.write(f"{r_used:.1f}GB / {r_total:.1f}GB ({r_perc}%)")
130
+
131
+ # CPU Display
132
+ st.caption("⚙️ **CPU Usage:**")
133
+ st.progress(c_perc / 100)
134
+ st.write(f"{c_perc}%")
135
+
136
+ # GPU Display
137
+ st.caption("🎮 **GPU VRAM:**")
138
+ st.code(f"Util: {g_util}\nMem: {g_mem}")
139
+
140
+ # Refresh Button
141
+ if st.button("🔄 Refresh Stats"):
142
+ st.rerun()
143
+
144
+ # ==========================================
145
+ # 🧠 MODEL LOADING | تحميل الموديل
146
+ # ==========================================
147
+ with st.spinner(txt["loading_model"]):
148
+ model, tokenizer = load_model(device)
149
+
150
+ if model is None:
151
+ st.error(txt["error_model_not_found"])
152
+ st.stop()
153
+
154
+ # ==========================================
155
+ # 🌌 UI LAYOUT | تخطيط الواجهة
156
+ # ==========================================
157
+ st.sidebar.image("https://cdn-icons-png.flaticon.com/512/4712/4712038.png", width=100)
158
+ st.sidebar.title(txt["sidebar_title"])
159
+ st.sidebar.info(txt["sidebar_info"])
160
+
161
+ st.title(txt["main_title"])
162
+ st.markdown("---")
163
+
164
+ col1, col2 = st.columns(2)
165
+
166
+ with col1:
167
+ question = st.text_area(txt["question_label"], height=100, placeholder=txt["question_placeholder"])
168
+ model_answer = st.text_area(txt["model_answer_label"], height=150, placeholder=txt["model_answer_placeholder"])
169
+
170
+ with col2:
171
+ student_answer = st.text_area(txt["student_answer_label"], height=290, placeholder=txt["student_answer_placeholder"])
172
+
173
+ # 🇬🇧 Analyze Button Logic
174
+ # 🇪🇬 منطق زر التحليل
175
+ if st.button(txt["analyze_btn"]):
176
+ if not (question and model_answer and student_answer):
177
+ st.warning(txt["warning_fill_fields"])
178
+ else:
179
+ # Progress Bar Animation
180
+ my_bar = st.progress(0, text=txt["progress_analyzing"])
181
+ time.sleep(0.5)
182
+ my_bar.progress(50, text=txt["progress_measuring"])
183
+
184
+ # 🇬🇧 Call Backend for Prediction
185
+ # 🇪🇬 استدعاء الخلفية للتوقع
186
+ s1, s2, s3, total, feedback = predict(question, model_answer, student_answer, model, tokenizer, lang=st.session_state.lang)
187
+
188
+ my_bar.progress(100, text=txt["progress_done"])
189
+ time.sleep(0.2)
190
+ my_bar.empty()
191
+
192
+ # ====================
193
+ # 📊 RESULTS DASHBOARD | لوحة النتائج
194
+ # ====================
195
+ st.success(txt["final_score"].format(total=total))
196
+
197
+ # Metrics Row
198
+ m1, m2, m3 = st.columns(3)
199
+ m1.metric(txt["metric_c1"], f"{s1:.2f}/5", delta_color="normal")
200
+ m2.metric(txt["metric_c2"], f"{s2:.2f}/5", delta_color="normal")
201
+ m3.metric(txt["metric_c3"], f"{s3:.2f}/5", delta_color="normal")
202
+
203
+ # Visual Bars
204
+ st.markdown(txt["details_title"])
205
+ st.caption(txt["caption_c1"])
206
+ st.progress(int((s1/5)*100))
207
+ st.caption(txt["caption_c2"])
208
+ st.progress(int((s2/5)*100))
209
+
210
+ # Feedback Box
211
+ st.markdown("---")
212
+ st.markdown(txt["feedback_title"])
213
+ # Prepare feedback HTML (Replace newlines with <br>)
214
+ feedback_html = feedback.replace('\n', '<br>')
215
+
216
+ st.markdown(f"""
217
+ <div class="feedback-box">
218
+ {feedback_html}
219
+ </div>
220
+ """, unsafe_allow_html=True)
logic.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ ====================================================================================================
4
+ 📂 FILE: logic.py | ملف المنطق الأساسي
5
+ ====================================================================================================
6
+ 🇬🇧 English Description:
7
+ This file handles the backend logic for the ASAG (Automated Short Answer Grading) system.
8
+ It is responsible for:
9
+ 1. Loading the fine-tuned DeBERTa model.
10
+ 2. Managing system resources (selecting GPU vs CPU).
11
+ 3. Preprocessing text (Tokenization).
12
+ 4. Running the model to predict scores.
13
+ 5. Generating detailed feedback based on the scores.
14
+
15
+ 🇪🇬 Arabic Description:
16
+ يتولى هذا الملف المنطق الخلفي لنظام التصحيح الآلي (ASAG).
17
+ وهو مسؤول عن:
18
+ 1. تحميل نموذج DeBERTa المدرب مسبقاً.
19
+ 2. إدارة موارد النظام (اختيار المعالج الرسومي GPU أو المعالج المركزي CPU).
20
+ 3. معالجة النصوص (Tokenization).
21
+ 4. تشغيل الموديل لتوقع الدرجات.
22
+ 5. توليد تغذية راجعة مفصلة بناءً على الدرجات.
23
+ ====================================================================================================
24
+ """
25
+
26
+ import torch
27
+ import torch.nn as nn
28
+ from transformers import AutoTokenizer, AutoModel, AutoConfig
29
+ import gc
30
+ import streamlit as st
31
+ import os
32
+ from datetime import datetime
33
+
34
+ # ==========================================
35
+ # 📝 DIAGNOSTIC LOG | سجل التشخيص
36
+ # ==========================================
37
+ LOG_FILE = os.path.join(os.path.dirname(__file__), "diagnostic_log.txt")
38
+
39
+ def diag_log(msg):
40
+ """يحفظ رسالة تشخيصية في ملف diagnostic_log.txt مع الوقت."""
41
+ timestamp = datetime.now().strftime("%H:%M:%S")
42
+ line = f"[{timestamp}] {msg}"
43
+ print(line) # طباعة أيضاً في الـ terminal
44
+ with open(LOG_FILE, "a", encoding="utf-8") as f:
45
+ f.write(line + "\n")
46
+
47
+ # ==========================================
48
+ # ⚙️ CONFIGURATION & SELECTION | الإعدادات والاختيارات
49
+ # ==========================================
50
+
51
+ # 🇬🇧 Define the base model name used for training.
52
+ # 🇪🇬 تحديد اسم الموديل الأساسي المستخدم في التدريب.
53
+ MODEL_NAME = "microsoft/deberta-v2-xxlarge"
54
+
55
+ # 🇬🇧 Path to the saved model weights file.
56
+ # 🇪🇬 مسار ملف أوزان الموديل المحفوظ.
57
+ MODEL_PATH = "best_model_xxl.pth"
58
+
59
+ # 🇬🇧 Maximum length for input text tokens.
60
+ # ℹ️ IMPACT: Texts longer than this will be truncated, shorter ones padded.
61
+ # 🇪🇬 الحد الأقصى لطول النص (بالكلمات/الرموز).
62
+ # ℹ️ التأثير: النصوص الأطول من ذلك سيتم قصها، والأقصر سيتم تكميلها بأصفار.
63
+ MAX_LEN = 512
64
+
65
+ # ---------------------------------------------------------
66
+ # 🖥️ DEVICE SELECTION STRATEGY | استراتيجية اختيار الجهاز
67
+ # ---------------------------------------------------------
68
+ # 🇬🇧 We check if a GPU (CUDA) is available. If yes, we use it for speed and memory efficiency.
69
+ # ℹ️ IMPACT: Using GPU allows loading the 1.5B param model into VRAM (16GB on T4),
70
+ # saving System RAM and preventing Colab crashes.
71
+ # 🇪🇬 نتحقق مما إذا كان كارت الشاشة (GPU/CUDA) متاحاً. إذا نعم، نستخدمه للسرعة وكفاءة الذاكرة.
72
+ # ℹ️ التأثير: استخدام GPU يسمح بتحميل الموديل الضخم (1.5 مليار باراميتر) في ذاكرة الفيديو VRAM،
73
+ # مما يوفر رامات النظام ويمنع انهيار الجلسة في Colab.
74
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
75
+ # device = torch.device('cpu') # ⚠️ UNCOMMENT ONLY FOR LOCAL DEBUGGING ON WINDOWS
76
+
77
+ # ==========================================
78
+ # 🏗️ MODEL ARCHITECTURE CLASS | كلاس هيكل الموديل
79
+ # ==========================================
80
+ class ASAGModelXXL(nn.Module):
81
+ """
82
+ 🇬🇧 Custom PyTorch Module wrapping DeBERTa.
83
+ We add 3 separate Fully Connected (FC) layers (Heads) to predict 3 distinct scores:
84
+ 1. Content (C1)
85
+ 2. Logic (C2)
86
+ 3. Language (C3)
87
+
88
+ 🇪🇬 كلاس مخصص يغلف موديل DeBERTa.
89
+ نضيف 3 طبقات كاملة الاتصال (Heads) لتوقع 3 درجات منفصلة:
90
+ 1. المحتوى (C1)
91
+ 2. المنطق (C2)
92
+ 3. اللغة (C3)
93
+ """
94
+ def __init__(self, model_name, from_config=False):
95
+ super().__init__()
96
+ if from_config:
97
+ # 🇬🇧 Build architecture ONLY (no pretrained weights download = saves ~6GB RAM).
98
+ # 🇪🇬 بناء الهيكل فقط (بدون تحميل أوزان = توفير ~6 جيجا RAM).
99
+ config = AutoConfig.from_pretrained(model_name)
100
+ self.backbone = AutoModel.from_config(config)
101
+ else:
102
+ # 🇬🇧 Load with pretrained weights (only used for training).
103
+ # 🇪🇬 تحميل مع الأوزان (يُستخدم للتدريب فقط).
104
+ self.backbone = AutoModel.from_pretrained(model_name)
105
+
106
+ # 🇬🇧 Define output heads. Each maps the hidden size (1536 for XXL) to 1 score.
107
+ # 🇪🇬 تعريف طبقات الإخراج. كل طبقة تحول البعد الخفي (1536) إلى درجة واحدة.
108
+ self.fc_c1 = nn.Linear(self.backbone.config.hidden_size, 1)
109
+ self.fc_c2 = nn.Linear(self.backbone.config.hidden_size, 1)
110
+ self.fc_c3 = nn.Linear(self.backbone.config.hidden_size, 1)
111
+
112
+ # 🇬🇧 Sigmoid activation to squash outputs between 0 and 1 (for scoring).
113
+ # 🇪🇬 دالة التنشيط Sigmoid لحصر المخرجات بين 0 و 1 (للتقييم).
114
+ self.sigmoid = nn.Sigmoid()
115
+
116
+ # 🇬🇧 Dropout for regularization during training (less relevant in inference).
117
+ # 🇪🇬 Dropout لتقليل الاعتماد الزائد أثناء التدريب.
118
+ self.dropout = nn.Dropout(0.3)
119
+
120
+ def forward(self, input_ids, mask, token_type_ids=None):
121
+ # 🇬🇧 Pass input through DeBERTa.
122
+ # 🇪🇬 تمرير المدخلات عبر DeBERTa.
123
+ out = self.backbone(input_ids=input_ids, attention_mask=mask, token_type_ids=token_type_ids).last_hidden_state[:, 0, :]
124
+
125
+ # 🔍 طباعة تشخيصية: إحصائيات مخرجات الـ backbone
126
+ diag_log(f"🧠 [FWD] dtype بعد backbone: {out.dtype}")
127
+ diag_log(f"🧠 [FWD] الإحصائيات: mean={out.mean().item():.6f}, std={out.std().item():.6f}, min={out.min().item():.6f}, max={out.max().item():.6f}")
128
+
129
+ out = self.dropout(out)
130
+
131
+ # 🇬🇧 Pass the [CLS] embedding to each specific head to get scores.
132
+ # 🇪🇬 تمرير تمثيل الـ [CLS] لكل طبقة متخصصة للحصول على الدرجات.
133
+ return self.sigmoid(self.fc_c1(out)), self.sigmoid(self.fc_c2(out)), self.sigmoid(self.fc_c3(out))
134
+
135
+ # ==========================================
136
+ # 🧠 HYBRID FEEDBACK ENGINE | محرك التغذية الراجعة الهجين
137
+ # ==========================================
138
+ def generate_detailed_feedback(c1, c2, c3, lang="ar"):
139
+ """
140
+ 🇬🇧 Generates text feedback based on numerical scores.
141
+ This logic bridges the gap between "AI numbers" and "Human understanding".
142
+
143
+ 🇪🇬 يولد تغذية راجعة نصية بناءً على الدرجات الرقمية.
144
+ هذا المنطق يسد الفجوة بين "أرقام الذكاء الاصطناعي" و"الفهم البشري".
145
+ """
146
+ feedback = []
147
+
148
+ if lang == "ar":
149
+ # --- C1: Content (50%) | المحتوى العلمي ---
150
+ if c1 >= 4.8:
151
+ feedback.append("✨ **المحتوى العلمي:** ممتاز! إجابة شاملة ودقيقة جداً.")
152
+ elif c1 >= 4.0:
153
+ feedback.append("✅ **المحتوى العلمي:** جيد جداً، لكن يمكن إضافة المزيد من التفاصيل التقنية.")
154
+ elif c1 >= 2.5:
155
+ feedback.append("⚠️ **المحتوى العلمي:** مقبول، لكن ينقصه بعض النقاط الجوهرية. راجع تعريف المفاهيم.")
156
+ else:
157
+ feedback.append("❌ **المحتوى العلمي:** ضعيف. الإجابة لا تغطي المطلوب. يرجى مراجعة الدرس.")
158
+
159
+ # --- C2: Logic (35%) | التسلسل المنطقي ---
160
+ if c2 >= 4.5:
161
+ feedback.append("🧠 **التسلسل المنطقي:** رائع! الأفكار مرتبة بشكل منطقي وسلس.")
162
+ elif c2 < 3.0:
163
+ feedback.append("🔄 **التسلسل المنطقي:** الإجابة تحتاج لترتيب أفضل. حاول ربط الأسباب بالنتائج.")
164
+
165
+ # --- C3: Language (15%) | اللغة والأسلوب ---
166
+ if c3 < 3.5:
167
+ feedback.append("📝 **اللغة والأسلوب:** انتبه للأخطاء الإملائية والنحوية. الصياغة تحتاج لتحسين.")
168
+ else:
169
+ feedback.append("✍️ **اللغة والأسلوب:** صياغة سليمة وواضحة.")
170
+
171
+ else: # English Feedback 🇬🇧
172
+ # --- C1: Content (50%) ---
173
+ if c1 >= 4.8:
174
+ feedback.append("✨ **Content Accuracy:** Excellent! Comprehensive and very accurate answer.")
175
+ elif c1 >= 4.0:
176
+ feedback.append("✅ **Content Accuracy:** Very good, but could add more technical details.")
177
+ elif c1 >= 2.5:
178
+ feedback.append("⚠️ **Content Accuracy:** Acceptable, but misses some key points. Review the concepts.")
179
+ else:
180
+ feedback.append("❌ **Content Accuracy:** Weak. The answer does not cover the requirements. Please review the lesson.")
181
+
182
+ # --- C2: Logic (35%) ---
183
+ if c2 >= 4.5:
184
+ feedback.append("🧠 **Logical Flow:** Great! Ideas are organized logically and smoothly.")
185
+ elif c2 < 3.0:
186
+ feedback.append("🔄 **Logical Flow:** The answer needs better organization. Try to link causes to effects.")
187
+
188
+ # --- C3: Language (15%) ---
189
+ if c3 < 3.5:
190
+ feedback.append("📝 **Language & Style:** Watch out for spelling and grammar mistakes. Phrasing needs improvement.")
191
+ else:
192
+ feedback.append("✍️ **Language & Style:** Clear and correct phrasing.")
193
+
194
+ return "\n\n".join(feedback)
195
+
196
+ # ==========================================
197
+ # 🚀 LOAD & PREDICT | التحميل والتوقع
198
+ # ==========================================
199
+ @st.cache_resource
200
+ def load_model(device_obj):
201
+ """
202
+ 🇬🇧 Loads the model into memory. Uses st.cache_resource to prevent reloading on every interaction.
203
+ ℹ️ IMPACT: Huge performance boost. Without caching, every click would take 2+ minutes to reload 5GB model.
204
+
205
+ 🇪🇬 يحمل الموديل في الذاكرة. يستخدم st.cache_resource لمنع إعادة التحميل مع كل تفاعل.
206
+ ℹ️ التأثير: تحسين هائل في الأداء. بدون التخزين المؤقت، كل ضغطة ستستغرق دقيقتين لإعادة تحميل 5 جيجا.
207
+ """
208
+ # 🇬🇧 Check for model file recursively (current dir or parent dir).
209
+ # 🇪🇬 البحث عن ملف الموديل بشكل تكراري (المجلد الحالي أو المجلد الأب).
210
+ path_to_check = os.path.join(os.path.dirname(__file__), "..", MODEL_PATH)
211
+
212
+ if not os.path.exists(path_to_check):
213
+ path_to_check = os.path.join(os.path.dirname(__file__), MODEL_PATH)
214
+
215
+ if not os.path.exists(path_to_check):
216
+ return None, None
217
+
218
+ # 🇬🇧 Load Tokenizer
219
+ # 🇪🇬 تحميل الـ Tokenizer
220
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
221
+
222
+ # ===== مسح الـ log القديم =====
223
+ with open(LOG_FILE, "w", encoding="utf-8") as f:
224
+ f.write(f"=== بدء التشخيص {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===\n")
225
+
226
+ # =====================================================================
227
+ # ⚡ طريقة التحميل المحسّنة (Memory-Mapped):
228
+ # 1. بناء النموذج من Config (3.6GB أوزان عشوائية على CPU)
229
+ # 2. تحميل checkpoint بـ mmap=True (يقرأ من الملف مباشرة، بدون تحميل كامل في RAM)
230
+ # 3. load_state_dict ينسخ الأوزان واحدة واحدة (peak RAM ≈ 3.6GB + بضع MB)
231
+ # 4. الأوزان تبقى FP32 بالكامل (بدون فقدان دقة)
232
+ # =====================================================================
233
+
234
+ # ===== الخطوة 1: بناء هيكل النموذج =====
235
+ diag_log("🔍 [LOAD] الخطوة 1: بناء هيكل النموذج (from_config)...")
236
+ model = ASAGModelXXL(MODEL_NAME, from_config=True)
237
+ gc.collect()
238
+ diag_log("✅ [LOAD] الهيكل جاهز")
239
+
240
+ # ===== الخطوة 2: تحميل الأوزان بـ mmap (بدون استهلاك RAM إضافي) =====
241
+ diag_log("🔍 [LOAD] الخطوة 2: تحميل الأوزان (mmap - قراءة من الملف مباشرة)...")
242
+ try:
243
+ checkpoint = torch.load(path_to_check, map_location='cpu', mmap=True)
244
+ except Exception as e:
245
+ diag_log(f"❌ [LOAD] فشل تحميل الأوزان: {e}")
246
+ return None, None
247
+
248
+ sample_key = list(checkpoint.keys())[0]
249
+ diag_log(f"✅ [LOAD] تم تحميل {len(checkpoint)} مفتاح | نوع: {checkpoint[sample_key].dtype}")
250
+
251
+ # ===== الخطوة 3: تحميل الأوزان في النموذج =====
252
+ diag_log("🔍 [LOAD] الخطوة 3: تحميل الأوزان في النموذج...")
253
+ result = model.load_state_dict(checkpoint, strict=False)
254
+ if result.missing_keys:
255
+ diag_log(f"⚠️ [LOAD] مفاتيح ناقصة ({len(result.missing_keys)}): {result.missing_keys[:5]}")
256
+ if result.unexpected_keys:
257
+ diag_log(f"⚠️ [LOAD] مفاتيح زائدة ({len(result.unexpected_keys)}): {result.unexpected_keys[:5]}")
258
+ if not result.missing_keys and not result.unexpected_keys:
259
+ diag_log("✅ [LOAD] جميع المفاتيح متطابقة")
260
+
261
+ diag_log(f"🔍 [LOAD] نوع الأوزان: {next(model.backbone.parameters()).dtype}")
262
+
263
+ # ===== تحرير ذاكرة الـ checkpoint =====
264
+ del checkpoint
265
+ gc.collect()
266
+ diag_log("🗑️ [LOAD] تم تحرير الـ checkpoint")
267
+
268
+ # ===== الخطوة 4: نقل للـ GPU =====
269
+ diag_log("🔍 [LOAD] الخطوة 4: نقل النموذج للـ GPU...")
270
+ model.to(device_obj)
271
+ diag_log(f"✅ [LOAD] الجهاز: {device_obj} | dtype: {next(model.backbone.parameters()).dtype}")
272
+
273
+ if device_obj.type == 'cuda':
274
+ gpu_mem = torch.cuda.memory_allocated() / (1024**3)
275
+ diag_log(f"💾 [LOAD] ذاكرة GPU المستخدمة: {gpu_mem:.2f} GB")
276
+
277
+ diag_log("🎉 [LOAD] تم تحميل النموذج بنجاح!")
278
+
279
+ model.eval()
280
+ return model, tokenizer
281
+
282
+ def predict(question, model_answer, student_answer, model, tokenizer, lang="ar"):
283
+ """
284
+ 🇬🇧 The Core Inference Function.
285
+ 1. Prepares input string (Question + Model Answer [SEP] Student Answer).
286
+ 2. Tokenizes.
287
+ 3. Runs Model.
288
+ 4. Calculates Weighted Score.
289
+
290
+ 🇪🇬 دالة الاستنتاج الأساسية.
291
+ 1. تجهيز النص المدخل (السؤال + الإجابة النموذجية [SEP] إجابة الطالب).
292
+ 2. الترميز (Tokenization).
293
+ 3. تشغيل الموديل.
294
+ 4. حساب الدرجة الموزونة.
295
+ """
296
+ # 🇬🇧 Manual tokenization to guarantee [CLS] + [SEP] + correct token_type_ids
297
+ # 🇪🇬 ترميز يدوي لضمان وجود [CLS] و [SEP] و token_type_ids صحيحة
298
+ # بعض نسخ transformers لا تضيف special tokens تلقائياً لـ DeBERTa
299
+
300
+ cls_id = tokenizer.cls_token_id # عادة 1
301
+ sep_id = tokenizer.sep_token_id # عادة 2
302
+ pad_id = tokenizer.pad_token_id # عادة 0
303
+
304
+ # ترميز كل جزء بدون special tokens
305
+ tokens_a = tokenizer.encode(question + " " + model_answer, add_special_tokens=False)
306
+ tokens_b = tokenizer.encode(student_answer, add_special_tokens=False)
307
+
308
+ # بناء: [CLS] tokens_a [SEP] tokens_b [SEP]
309
+ input_ids = [cls_id] + tokens_a + [sep_id] + tokens_b + [sep_id]
310
+ # token_type_ids: 0 للسؤال+الإجابة النموذجية، 1 لإجابة الطالب
311
+ token_type_ids = [0] * (1 + len(tokens_a) + 1) + [1] * (len(tokens_b) + 1)
312
+
313
+ # قص إذا أطول من MAX_LEN
314
+ if len(input_ids) > MAX_LEN:
315
+ input_ids = input_ids[:MAX_LEN]
316
+ token_type_ids = token_type_ids[:MAX_LEN]
317
+
318
+ # Padding
319
+ actual_len = len(input_ids)
320
+ attention_mask = [1] * actual_len + [0] * (MAX_LEN - actual_len)
321
+ token_type_ids = token_type_ids + [0] * (MAX_LEN - actual_len)
322
+ input_ids = input_ids + [pad_id] * (MAX_LEN - actual_len)
323
+
324
+ inputs = {
325
+ 'input_ids': torch.tensor([input_ids]),
326
+ 'attention_mask': torch.tensor([attention_mask]),
327
+ 'token_type_ids': torch.tensor([token_type_ids])
328
+ }
329
+
330
+ # 🔍 تشخيص
331
+ diag_log(f"📝 [INPUT] tokens: {actual_len}/{MAX_LEN} | CLS={cls_id} SEP={sep_id}")
332
+ diag_log(f"📝 [INPUT] أول 15 token: {inputs['input_ids'][0][:15].tolist()}")
333
+ diag_log(f"📝 [INPUT] token_type_ids (أول 15): {inputs['token_type_ids'][0][:15].tolist()}")
334
+ # موقع SEP (بداية إجابة الطالب)
335
+ sep_pos = 1 + len(tokens_a)
336
+ diag_log(f"📝 [INPUT] SEP في الموقع {sep_pos} | tokens_a={len(tokens_a)} tokens_b={len(tokens_b)}")
337
+
338
+ # 🇬🇧 Disable Gradient Calculation (Save Memory & Speed Up Inference)
339
+ # 🇪🇬 تعطيل حساب التفاضل (توفير الذاكرة وتسريع الاستنتاج)
340
+ with torch.no_grad():
341
+ # 🇬🇧 Disable autocast to prevent automatic FP16 conversion during inference.
342
+ # 🇪🇬 تعطيل الـ autocast لمنع التحويل التلقائي لـ FP16 أثناء الاستنتاج.
343
+ with torch.cuda.amp.autocast(enabled=False):
344
+ c1, c2, c3 = model(
345
+ inputs['input_ids'].to(device),
346
+ inputs['attention_mask'].to(device),
347
+ inputs['token_type_ids'].to(device)
348
+ )
349
+
350
+ # 🔍 طباعة تشخيصية للمخرجات الخام (قبل الضرب في 5)
351
+ diag_log(f"📊 [RAW] c1={c1.item():.6f}, c2={c2.item():.6f}, c3={c3.item():.6f}")
352
+
353
+ # 🇬🇧 Convert 0-1 sigmoid output to 0-5 scale
354
+ # 🇪🇬 تحويل مخرجات Sigmoid (0-1) إلى مقياس (0-5)
355
+ s1 = c1.item() * 5.0
356
+ s2 = c2.item() * 5.0
357
+ s3 = c3.item() * 5.0
358
+
359
+ # 🇬🇧 Calculate Final Weighted Score
360
+ # ℹ️ FORMULA: 50% Content + 35% Logic + 15% Language
361
+ # 🇪🇬 حساب الدرجة النهائية الموزونة
362
+ # ℹ️ المعادلة: 50% محتوى + 35% منطق + 15% لغة
363
+ total = (s1 * 0.50) + (s2 * 0.35) + (s3 * 0.15)
364
+
365
+ # 🔍 طباعة الدرجات النهائية
366
+ diag_log(f"📊 [SCORE] s1={s1:.4f}, s2={s2:.4f}, s3={s3:.4f}, total={total:.4f}")
367
+
368
+ return s1, s2, s3, total, generate_detailed_feedback(s1, s2, s3, lang)
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
1
+ torch
2
+ torchvision
3
+ torchaudio
4
+ transformers
5
+ sentencepiece
6
+ protobuf
7
+ psutil
styles.css ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Google Fonts */
2
+ @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;700&display=swap');
3
+
4
+ html, body, [class*="css"] {
5
+ font-family: 'Cairo', sans-serif;
6
+ direction: rtl; /* Right To Left Support */
7
+ }
8
+
9
+ /* Titles */
10
+ h1, h2, h3 {
11
+ color: #4CAF50; /* Green Accent */
12
+ text-align: right;
13
+ }
14
+
15
+ /* Text Areas */
16
+ .stTextArea textarea {
17
+ background-color: #1E1E1E;
18
+ color: #ffffff;
19
+ border-radius: 10px;
20
+ border: 1px solid #333;
21
+ }
22
+
23
+ /* Buttons */
24
+ .stButton button {
25
+ background-color: #4CAF50;
26
+ color: white;
27
+ width: 100%;
28
+ border-radius: 12px;
29
+ font-weight: bold;
30
+ font-size: 18px;
31
+ transition: 0.3s;
32
+ }
33
+ .stButton button:hover {
34
+ background-color: #45a049;
35
+ scale: 1.02;
36
+ }
37
+
38
+ /* Metric Cards */
39
+ div[data-testid="stMetricValue"] {
40
+ font-size: 2rem;
41
+ color: #FFD700; /* Gold */
42
+ }
43
+
44
+ /* Feedback Box */
45
+ .feedback-box {
46
+ background-color: #262730;
47
+ padding: 20px;
48
+ border-radius: 10px;
49
+ border-right: 5px solid #4CAF50;
50
+ margin-top: 20px;
51
+ }
translations.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-language Dictionary
2
+
3
+ TRANSLATIONS = {
4
+ "ar": {
5
+ "page_title": "نظام تصحيح الإجابات الذكي",
6
+ "sidebar_title": "🎓 نظام التصحيح الآلي",
7
+ "sidebar_info": """
8
+ **النظام الآلي لتصحيح الإجابات المقالية**
9
+
10
+ مدعوم بواسطة:
11
+ - **DeBERTa-v3-Large** (Fine-tuned).
12
+ - **محرك تغذية راجعة هجين**.
13
+
14
+ تطوير: **فريق المشروع**
15
+ """,
16
+ "main_title": "🧙‍♂️ المصحح الذكي (AI Grader)",
17
+ "question_label": "✍️ نص السؤال:",
18
+ "question_placeholder": "اكتب السؤال هنا...",
19
+ "model_answer_label": "🔑 الإجابة النموذجية:",
20
+ "model_answer_placeholder": "الإجابة الصحيحة...",
21
+ "student_answer_label": "👨‍🎓 إجابة الطالب:",
22
+ "student_answer_placeholder": "الإجابة المراد تصحيحها...",
23
+ "analyze_btn": "🚀 تحليل وتصحيح الإجابة",
24
+ "warning_fill_fields": "⚠️ يرجى ملء جميع الحقول أولاً!",
25
+ "progress_analyzing": "جاري تحليل المعنى...",
26
+ "progress_measuring": "قياس التشابه الدلالي...",
27
+ "progress_done": "تم الانتهاء!",
28
+ "final_score": "🏆 الدرجة النهائية: **{total:.2f} / 5.0**",
29
+ "metric_c1": "دقة المحتوى (C1)",
30
+ "metric_c2": "التسلسل المنطقي (C2)",
31
+ "metric_c3": "سلامة اللغة (C3)",
32
+ "details_title": "### 📊 تفاصيل التقييم:",
33
+ "caption_c1": "دقة المحتوى (علمياً)",
34
+ "caption_c2": "المنطق وتسلسل الأفكار",
35
+ "feedback_title": "### 💬 التغذية الراجعة (AI Feedback):",
36
+ "error_model_not_found": "🚨 خطأ: ملف الموديل `best_model_xxl.pth` غير موجود! يرجى تحميله أولاً.",
37
+ "loading_model": "⏳ جارٍ تحميل نموذج الذكاء الاصطناعي (XXL)... يرجى الانتظار..."
38
+ },
39
+ "en": {
40
+ "page_title": "AI ASAG System",
41
+ "sidebar_title": "🎓 ASAG System",
42
+ "sidebar_info": """
43
+ **Automated Short Answer Grading System**
44
+
45
+ Powered by:
46
+ - **DeBERTa-v3-Large** (Fine-tuned).
47
+ - **Hybrid Feedback Engine**.
48
+
49
+ Developed by: **Project Team**
50
+ """,
51
+ "main_title": "🧙‍♂️ AI Grader",
52
+ "question_label": "✍️ Question Text:",
53
+ "question_placeholder": "Enter the question here...",
54
+ "model_answer_label": "🔑 Model Answer:",
55
+ "model_answer_placeholder": "The correct answer...",
56
+ "student_answer_label": "👨‍🎓 Student Answer:",
57
+ "student_answer_placeholder": "Answer to be graded...",
58
+ "analyze_btn": "🚀 Analyze & Grade",
59
+ "warning_fill_fields": "⚠️ Please fill in all fields first!",
60
+ "progress_analyzing": "Analyzing semantics...",
61
+ "progress_measuring": "Measuring similarity...",
62
+ "progress_done": "Done!",
63
+ "final_score": "🏆 Final Score: **{total:.2f} / 5.0**",
64
+ "metric_c1": "Content Accuracy (C1)",
65
+ "metric_c2": "Logical Flow (C2)",
66
+ "metric_c3": "Language Quality (C3)",
67
+ "details_title": "### 📊 Grading Details:",
68
+ "caption_c1": "Content Accuracy (Scientific)",
69
+ "caption_c2": "Logic & Flow",
70
+ "feedback_title": "### 💬 AI Feedback:",
71
+ "error_model_not_found": "🚨 Error: Model file `best_model_xxl.pth` not found! Please download it first.",
72
+ "loading_model": "⏳ Loading AI Model (XXL)... Please wait..."
73
+ }
74
+ }