الدرس الثاني: تحسين أداء الشبكات العصبية
بعد أن تعلّمت كيف تُبنى وتُدرَّب شبكة عصبية، يأتي السؤال التالي: كيف أجعلها أفضل؟ هذا الدليل يشرح 9 محاور عملية — من اختيار قيم البداية للأوزان، إلى التنظيم (Regularization)، والمحسّنات، وجدولة معدل التعلّم — بأسلوب بسيط جدًا مع تشبيهات من الحياة وأمثلة رقمية وكود PyTorch.
Initialization — تهيئة الأوزان
عندما تُنشئ شبكة عصبية جديدة، كل وزن (weight) وكل انحياز (bias) يحتاج قيمة بداية. السؤال: من أين نأتي بهذه القيم؟
تخيّل أنك تبدأ سباقًا من نقطة البداية. إذا بدأت قريبًا جدًا من خط النهاية (أوزان كبيرة) ستتجاوز الهدف وتضيع. وإذا بدأت بعيدًا جدًا (أوزان صفر أو متطابقة) لن تتحرك أبدًا أو ستتحرك الجميع بنفس الاتجاه. التهيئة الصحيحة = نقطة بداية ذكية.
لماذا لا نبدأ بأصفار؟
إذا جعلت كل الأوزان = 0، كل عصبون في الطبقة نفسها سيحسب نفس القيمة، ويأخذ نفس gradient، ويتحدّث بنفس الطريقة — يبقون متطابقين للأبد (symmetry problem). لذلك نحتاج عشوائية.
Random Initialization — التهيئة العشوائية
الفكرة البسيطة: اختر أرقامًا عشوائية صغيرة لكل وزن.
شبكة بسيطة: طبقة دخل (3 ميزات) → طبقة مخفية (2 عصبونات).
- الوزن W بحجم 3×2 — نملأه بأرقام عشوائية مثل: [[0.005, -0.008], [0.003, 0.012], [-0.007, 0.001]]
- كل عصبون يبدأ مختلفًا → يتعلّم أشياء مختلفة ✓
المشكلة: إذا كانت الشبكة عميقة (10+ طبقات)، الأرقام العشوائية الصغيرة جدًا تسبب Vanishing Gradient، والكبيرة جدًا تسبب Exploding Gradient. هنا يأتي Xavier و He.
Xavier Initialization (Glorot)
اقترح Xavier Glorot (2010): اختر التباين بحيث يبقى تباين الإشارة ثابتًا بين الطبقات — لا تكبر ولا تصغر.
حيث n_in = عدد مدخلات الطبقة، n_out = عدد مخرجاتها.
طبقة: 100 مدخل → 50 مخرج. الحد = √(6/(100+50)) = √(6/150) ≈ 0.2
كل وزن يُسحب عشوائيًا من المجال [-0.2, +0.2]. مناسب مع Sigmoid و Tanh.
He Initialization
اقترح Kaiming He (2015) لدوال ReLU: ReLU تقطع النصف السالب (يصبح صفر)، فيفقد التباين نصفه تقريبًا. لذلك نحتاج تباينًا أكبر:
طبقة: 256 مدخل. الانحراف المعياري = √(2/256) = √(0.0078) ≈ 0.088
أوزان عشوائية حول 0 بانحراف ~0.088 — أكبر من Xavier لأن ReLU «يقتل» نصف الإشارات.
| الطريقة | متى تستخدمها؟ | دالة التفعيل |
|---|---|---|
| Random صغير | شبكات ضحلة (1–2 طبقة) فقط | أي |
| Xavier / Glorot | شبكات متوسطة العمق | Sigmoid, Tanh |
| He / Kaiming | الخيار الافتراضي اليوم | ReLU, LeakyReLU, GELU |
Regularization — التنظيم
النموذج القوي جدًا يحفظ بيانات التدريب حرفيًا (مثل طالب يحفظ الإجابات بدل فهم المادة) — هذا يسمى Overfitting. التنظيم = تقنيات تجعل النموذج «أبسط» ويتعمّم أفضل على بيانات جديدة.
طالب يحفظ 1000 سؤال بالحرف → يجيبها ممتاز في الامتحان التدريبي، لكن يفشل في امتحان جديد بصياغة مختلفة. التنظيم = أن يفهم القواعد العامة بدل الحفظ.
L1 Regularization
نضيف لدالة الخسارة مجموع القيم المطلقة للأوزان:
λ (لامدا) = قوة التنظيم. كلما كبرت λ، الأوزان تصغر أكثر.
النتيجة: كثير من الأوزان تصبح بالضبط صفر → النموذج يختار ميزات مهمة فقط (feature selection تلقائي).
أوزان قبل L1: [0.8, -0.3, 0.05, 0.02, -0.7]. بعد تدريب مع L1 قوي: [0.6, -0.2, 0, 0, -0.5] — الأوزان الصغيرة اختفت.
L2 Regularization
نضيف مجموع مربعات الأوزان:
لا يجعل الأوزان صفرًا بالضبط، لكن يجعلها صغيرة — يمنع وزنًا واحدًا من السيطرة على القرار.
وزن واحد = 10. مساهمته في L2 = 10² = 100 — عقوبة كبيرة! النموذج يُجبر على تقليله إلى مثلاً 0.5 (مساهمة = 0.25 فقط).
Weight Decay
في المحسّنات الحديثة، L2 يُطبَّق غالبًا كـ Weight Decay مباشرة في خطوة التحديث:
كل خطوة، الأوزان «تذبل» قليلًا نحو الصفر. في PyTorch:
ملاحظة مهمة: مع Adam العادي، weight_decay ≠ L2 بالضبط. لذلك ظهر AdamW (نشرحه في المحور الثالث) الذي يفصل بينهما بشكل صحيح.
Dropout
أثناء التدريب، نُطفئ عشوائيًا نسبة من العصبونات (مثلاً 50%) في كل خطوة. كل عصبون له احتمال p أن يُعطَّل.
فريق كرة قدم: في كل مباراة تدريب، نصف اللاعبين «إصابات وهمية» — الباقون يتعلمون اللعب بدونهم. النتيجة: لا يعتمد الفريق على نجم واحد.
Early Stopping
نراقب validation loss أثناء التدريب. إذا بدأ يرتفع (بينما train loss ما زال ينخفض) = overfitting. نتوقف مبكرًا ونحفظ أفضل نموذج.
- Epoch 10: val_loss = 0.35 ← أفضل حتى الآن ✓
- Epoch 15: val_loss = 0.38 ← بدأ يرتفع
- Epoch 20: val_loss = 0.42 ← يرتفع أكثر
- القرار: توقف عند Epoch 10 واحفظ تلك الأوزان (patience = 5 epochs)
L1
أوزان تختفي (sparse). مفيد لاختيار الميزات.
L2 / Weight Decay
أوزان صغيرة. الأكثر شيوعًا في التطبيق.
Dropout
يُطفئ عصبونات عشوائيًا. قوي للطبقات الكبيرة.
Early Stopping
توقف عندما val_loss يتدهور. مجاني وفعّال!
Optimization Algorithms — خوارزميات التحسين
بعد حساب الـ gradient، كيف نحدّث الأوزان؟ SGD البسيط هو الأساس — لكن المحسّنات المتقدمة تجعل التدريب أسرع وأكثر استقرارًا.
أنت في قمة جبل (loss عالي) وتريد الوصول للوادي (loss منخفض). Gradient = اتجاه الانحدار الأشد. المحسّن = كيف تمشي: بخطوات صغيرة ثابتة؟ بزخم؟ بخطوات تتكيف مع التضاريس؟
SGD — Stochastic Gradient Descent
في كل خطوة، نحسب gradient على batch صغير (مثلاً 32 مثال) بدل كل البيانات:
وزن w = 2.0، gradient = 0.5، learning rate = 0.1
w_new = 2.0 - 0.1 × 0.5 = 1.95
المشكلة: المسار متذبذب (zigzag) خاصة في الوديان الضيقة. الحل: Momentum.
Momentum
نضيف «زخمًا» — كالكرة المتدحرجة: تتسارع في الاتجاه المتسق وتتباطأ عند التذبذب:
w_new = w_old - lr · v
بدون Momentum: gradient يتذبذب [+0.5, -0.4, +0.6, -0.5] → تقدم بطيء.
مع Momentum (β=0.9): المتجهات المتناقضة تُلغى، المتسقة تتراكم → مسار أسرع وأنعم نحو الحل.
RMSProp
يتكيف معدل التعلّم لكل وزن حسب تاريخ gradients. الأوزان التي gradientsها كبيرة → خطوات أصغر. الصغيرة → خطوات أكبر:
w_new = w_old - lr / √(s + ε) · ∇Loss
ممتاز للشبكات العميقة و RNN — يحل مشكلة gradients المتفاوتة الأحجام.
Adam — Adaptive Moment Estimation
يجمع Momentum (متوسط gradient) + RMSProp (متوسط gradient²). الخيار الافتراضي لمعظم المشاريع:
v = β₂·v + (1-β₂)·(∇Loss)² (التكيف)
w_new = w_old - lr · m̂ / (√v̂ + ε)
β₁=0.9, β₂=0.999, ε=1e-8 — قيم افتراضية تعمل جيدًا في أغلب الحالات.
AdamW
يصلح مشكلة Adam مع Weight Decay: يفصل تحديث الأوزان عن تقليل الأوزان (decoupled weight decay). أفضل للتنظيم مع Adam — المعيار في Transformers و LLMs.
Nadam — Nesterov + Adam
يجمع Adam مع Nesterov Momentum — «انظر للأمام» قبل القفز: يحسب gradient عند النقطة المتوقعة بعد خطوة الزخم. أحيانًا أسرع تقاربًا من Adam في بداية التدريب.
| المحسّن | متى؟ | ملاحظة |
|---|---|---|
| SGD + Momentum | CNN كبيرة، عندما تريد أقصى دقة | يحتاج ضبط lr بعناية |
| Adam | البداية الافتراضية، معظم المشاريع | سريع ويعمل «من الصندوق» |
| AdamW | Transformers, NLP, مع weight decay | الأفضل مع التنظيم |
| RMSProp | RNN, بيانات تسلسلية | أقل شيوعًا من Adam اليوم |
| Nadam | تجارب — بديل لـ Adam | ليس افتراضيًا في معظم الأطر |
Hyperparameter Tuning — ضبط المعاملات الفائقة
Parameters = الأوزان (يتعلّمها النموذج). Hyperparameters = إعدادات أنت تختارها قبل التدريب. اختيارها الخاطئ = نموذج بطيء أو فاشل.
Learning Rate — معدل التعلّم
أهم hyperparameter على الإطلاق. يحدد حجم الخطوة في كل تحديث.
على MNIST مع Adam:
- lr = 0.1 → Loss يقفز: 2.3, 0.8, 5.1, 1.2... (غير مستقر)
- lr = 0.001 → Loss: 2.3 → 0.5 → 0.15 → 0.08 (ممتاز)
- lr = 0.00001 → Loss: 2.3 → 2.1 → 2.0 → 1.95 (بطيء جدًا)
Batch Size — حجم الدفعة
كم مثالًا نعالج قبل تحديث الأوزان؟
صغير (16–32)
تحديثات أكثر = تنوع أكبر. أبطأ لكن أحيانًا يتعمّم أفضل. يحتاج GPU أصغر.
متوسط (64–128)
توازن جيد — الأكثر شيوعًا.
كبير (256–1024)
أسرع على GPU. gradients أقل ضوضاء. قد يحتاج lr أكبر.
قاعدة: عند مضاعفة batch size، جرّب مضاعفة learning rate (linear scaling rule).
Epochs — عدد الدورات
كم مرة نمر على كل بيانات التدريب؟
- قليل (5–10): Underfitting محتمل
- كثير (100+): Overfitting محتمل — استخدم Early Stopping
- الأفضل: تدرب كثيرًا مع Early Stopping (مثلاً max 100 epoch، patience 10)
Number of Layers — عدد الطبقات
كل طبقة = مستوى تجريد أعلى. MNIST بسيط: 2–3 طبقات تكفي. ImageNet: عشرات الطبقات (ResNet-50 = 50 طبقة).
- 1 طبقة مخفية (128): دقة ~95%
- 2 طبقة (256 → 128): دقة ~97%
- 5 طبقات بدون تنظيم: دقة train 99% لكن val 94% (overfitting!)
Hidden Units — عدد العصبونات
عرض كل طبقة مخفية. أكثر = قدرة تمثيل أعلى + خطر overfitting + تكلفة حسابية.
ابدأ صغيرًا (64, 128) وزِد تدريجيًا. راقب: إذا train accuracy عالية و val منخفضة → قلّل العصبونات أو أضف Dropout.
- ابدأ بإعدادات «معروفة أنها تعمل» (Adam, lr=1e-3, batch=64)
- اضبط learning rate أولًا (الأهم)
- ثم batch size و architecture
- أخيرًا regularization (dropout, weight decay)
- غيّر معاملًا واحدًا في كل تجربة — وإلا لن تعرف ما الذي ساعد!
Batch Normalization — تطبيع الدفعات
تقنية ثورية (2015) تُطبَّق بعد الطبقة الخطية وقبل دالة التفعيل. تُعيد ضبط (normalize) مخرجات كل طبقة.
Internal Covariate Shift — ماذا تعني؟
أثناء التدريب، أوزان الطبقة الأولى تتغير → مخرجاتها تتغير → الطبقة الثانية ترى «توزيعًا مختلفًا» في كل خطوة. كأنك تحاول الإصابة بهدف يتحرك باستمرار.
طباخ يتعلّم وصفة: كل يوم تتغير جودة المكونات (طبقة سابقة) — يصعب التعلّم. Batch Norm = توحيد جودة المكونات قبل كل طباخ.
كيف يعمل؟
لكل mini-batch، لكل عصبون:
x̂ = (x - μ) / √(σ² + ε)
y = γ · x̂ + β (γ, β قابلان للتعلّم)
γ و β يسمحان للشبكة بـ «إلغاء» التطبيع إذا احتاجت — المرونة محفوظة.
Benefits — الفوائد
تدريب أسرع
يمكن استخدام learning rates أكبر — يتقارب أسرع.
استقرار
يقلل حساسية التهيئة — He أقل أهمية مع BN.
تنظيم خفيف
يقلل overfitting قليلًا (ليس بديلاً لـ Dropout).
شبكات أعمق
يسمح بتدريب طبقات أكثر بدون انهيار gradient.
Training vs Inference — الفرق الحاسم
| الوضع | μ و σ | السلوك |
|---|---|---|
| Training | من الـ batch الحالي | يُحسب fresh كل خطوة |
| Inference (اختبار/إنتاج) | متوسطات متراكمة (running mean/var) | ثابتة — لا تعتمد على batch |
في PyTorch: model.train() vs model.eval() يتحكمان بهذا تلقائيًا.
Learning Rate Scheduling — جدولة معدل التعلّم
بدل lr ثابت طوال التدريب، نغيّره مع الوقت. الفكرة: خطوات كبيرة في البداية (بعيد عن الحل) → خطوات صغيرة في النهاية (دقة أعلى).
البحث عن منزل في مدينة: في البداية تمشي بسرعة بين الأحياء. عندما تقترب، تتمشى ببطء بين الشوارع. في النهاية، خطوات صغيرة جدًا أمام الباب.
Step Decay — الانخفاض المرحلي
كل N epoch، اقسم lr على مثلاً 10:
initial_lr = 0.01، step_size = 30
- Epoch 0–29: lr = 0.01
- Epoch 30–59: lr = 0.001
- Epoch 60+: lr = 0.0001
Cosine Annealing — التلاشي الجيبي
lr ينخفض بمنحنى جيب التمام (cosine) بسلاسة من القيمة العظمى إلى صفر:
Warmup — الإحماء
في أول K خطوة، lr يبدأ من صفر تقريبًا ويرتفع تدريجيًا للقيمة المستهدفة. مهم جدًا في تدريب Transformers و LLMs الكبيرة — يمنع انفجار gradients في البداية.
target_lr = 0.001، warmup_steps = 1000
- Step 0: lr = 0
- Step 500: lr = 0.0005
- Step 1000: lr = 0.001 (ثم يبدأ Cosine decay مثلاً)
Cyclic LR — معدل دوري
lr يتذبذب بين حد أدنى وأعلى بشكل دوري. الفكرة: أحيانًا lr الكبير يساعد «القفز» من minimas محلية. أقل شيوعًا من Cosine لكن مفيد في بعض التجارب.
Debugging Deep Networks — تشخيص مشاكل الشبكات
كل ممارس تعلم عميق يواجه هذه المشاكل. المفتاح: راقب المنحنيات (train vs validation loss/accuracy) واعرف ماذا تعني.
Underfitting — نقص الملاءمة
النموذج بسيط جدًا — لا يتعلّم حتى من بيانات التدريب.
- Train loss عالي ولا ينخفض كثيرًا
- Train accuracy منخفضة (مثلاً 60% على MNIST)
- Val loss قريب من train loss (كلاهما سيء)
الحلول: زِد الطبقات/العصبونات، قلّل regularization، زِد epochs، ارفع learning rate قليلًا، جرّب architecture أقوى.
Overfitting — الإفراط في الملاءمة
النموذج حفظ بيانات التدريب — يفشل على بيانات جديدة.
- Train loss منخفض جدًا (قريب من 0)
- Val loss يرتفع بينما train ينخفض
- Train accuracy 99% لكن val accuracy 85%
الحلول: Dropout، Weight Decay، Early Stopping، بيانات أكثر، Data Augmentation، نموذج أصغر.
Vanishing Gradient — اختفاء التدرج
في الشبكات العميقة، gradient يُضرب عبر طبقات كثيرة → يصبح قريبًا من صفر → الطبقات الأولى لا تتعلّم.
الأسباب: Sigmoid/Tanh (مشتقاتها صغيرة)، تهيئة سيئة، شبكة عميقة جدًا.
الحلول: ReLU، He Initialization، Batch Norm، Residual Connections (Skip connections).
Exploding Gradient — انفجار التدرج
gradient يكبر exponentially → الأوزان تصبح NaN أو Inf.
Loss = 2.3 → 1.5 → 0.8 → NaN. أو weights = [1e15, -3e20, ...]
الحلول: Gradient Clipping (قص gradient إذا تجاوز حدًا)، تقليل lr، تهيئة أفضل.
Dead ReLU — ReLU الميت
إذا دخل ReLU قيمة سالبة كبيرة، gradient = 0 → العصبون لا يتعلّم أبدًا (ميت).
الحلول: Leaky ReLU (gradient صغير للقيم السالبة: 0.01x)، He Initialization، Batch Norm، lr أصغر.
- هل البيانات normalized (0–1 أو standardized)؟
- هل loss.backward() و optimizer.step() يُنفَّذان؟
- هل model.train() / model.eval() صحيح؟
- هل shapes المدخلات صحيحة؟
- ارسم train vs val loss — ماذا يقولان؟
Experiment Tracking — تتبع التجارب
عندما تجرب 20 إعدادًا مختلفًا، كيف تتذكر أيها كان الأفضل؟ أدوات التتبع تسجّل كل شيء تلقائيًا: hyperparameters، loss، accuracy، الأوزان، والرسوم.
TensorBoard
مدمج مع TensorFlow — يعمل مع PyTorch أيضًا. يعرض منحنيات loss و accuracy، histograms للأوزان، صور، وgraphs للشبكة.
مميزات: مجاني، محلي، سهل البداية. عيوب: لا يشارك بسهولة بين الفريق.
Weights & Biases (W&B)
منصة سحابية شائعة في البحث والصناعة. تسجّل تلقائيًا، مقارنة تجارب، جداول، ولوحات جميلة.
مميزات: مشاركة سهلة، مقارنة تجارب، artifacts. عيوب: يحتاج حساب (مجاني للاستخدام الشخصي).
MLflow
مفتوح المصدر — لتتبع تجارب ML بشكل عام (ليس deep learning فقط). يسجّل parameters، metrics، models، وبيئات التشغيل.
مميزات: مفتوح المصدر، self-hosted، يدعم نشر النماذج. عيوب: واجهة أقل جاذبية من W&B.
| الأداة | ابدأ بها إذا... |
|---|---|
| TensorBoard | تتعلّم لوحدك، تريد شيئًا مجانيًا ومحليًا |
| W&B | تعمل في فريق، تريد مقارنة تجارب بسهولة |
| MLflow | شركة تريد self-hosted و MLOps كامل |
Practical Labs — مختبرات عملية
النظرية بدون تطبيق تتبخر. هنا ثلاث تجارب عملية يمكنك تنفيذها على MNIST أو CIFAR-10 خلال ساعات.
تجربة 1: تحسين نموذج حقيقي — Baseline → Improved
ابدأ بنموذج baseline ضعيف، ثم طبّق التحسينات واحدة تلو الأخرى:
| الخطوة | التغيير | النتيجة المتوقعة (MNIST) |
|---|---|---|
| Baseline | طبقة واحدة 64، SGD lr=0.01، بدون تنظيم | ~92% accuracy |
| + He Init | Kaiming initialization | ~93% |
| + Adam | optimizer = Adam(lr=1e-3) | ~96% |
| + طبقة ثانية | 128 → 64 | ~97% |
| + Dropout 0.3 | بعد كل ReLU | ~97.5% (val أفضل) |
| + Batch Norm | بعد كل Linear | ~98% |
| + LR Schedule | Cosine Annealing | ~98.2% |
تجربة 2: مقارنة Optimizers
نفس النموذج، نفس البيانات — غيّر المحسّن فقط:
- ثبّت: architecture, batch_size=64, epochs=20, seed=42
- جرّب: SGD (lr=0.01), SGD+Momentum (lr=0.01), Adam (lr=1e-3), AdamW (lr=1e-3, wd=1e-4), RMSprop (lr=1e-3)
- سجّل: وقت التدريب، أقل val loss، أعلى val accuracy
- ارسم منحنيات loss لكل محسّن على نفس الرسم
النتيجة المتوقعة: Adam/AdamW يتقاربان أسرع. SGD+Momentum قد يصل لنفس الدقة لكن يحتاج ضبط lr. SGD العادي الأبطأ.
تجربة 3: مقارنة Regularization
استخدم نموذجًا كبيرًا عمدًا (3 طبقات × 512 عصبون) لإجبار overfitting، ثم جرّب:
بدون تنظيم
Train 99.5%, Val 94% — overfitting واضح
Dropout 0.5
Train 98%, Val 97% — تحسن كبير في التعميم
Weight Decay 1e-3
Train 98.5%, Val 96.5% — تحسن معتدل
Dropout + WD + Early Stop
أفضل توازن — Val ~97.5%
لكل تجربة، سجّل في جدول أو W&B:
- الاسم: exp_03_adam_dropout05
- التغيير: أضفت Dropout=0.5 بعد الطبقة الثانية
- النتيجة: val_acc تحسنت من 94% → 97%
- الاستنتاج: Dropout فعّال لهذا الحجم من النموذج
- اليوم 1: Baseline على MNIST + TensorBoard. سجّل المنحنيات.
- اليوم 2: تجربة Optimizers (5 محسّنات). قارن في جدول.
- اليوم 3: تجربة Regularization. اكتب تقريرًا قصيرًا بما تعلّمته.