ML Learning Hub
التعلم العميقمتوسط

التحسين في التعلم العميق

من SGD إلى Adam — الحيل التي تجعل الشبكات العميقة تتدرب فعلاً

SGD مقابل Adam مقابل AdamW وإحماء معدل التعلم والجدولة التقوسية وتطبيع الدُّفعة والـDropout وقطع التدرج والتدريب بالدقة المختلطة.

40 min
10 مخططات
8 المفاهيم المغطاة

المتطلبات الأساسية

Neural Networks

المفاهيم المغطاة

AdamAdamWMomentumBatch NormalizationDropoutLR WarmupCosine ScheduleGradient Clipping

الصيغ الرئيسية

SGD + الزخم

تراكم السرعة في اتجاه التدرج — يخرج من الحدود الدنيا الضحلة ويُخمِّد التذبذبات

آدم

معدل تعلم تكيفي لكل معامل: m̂_t = متوسط التدرج المصحَّح، v̂_t = تباين التدرج

تطبيع الدفعة

يُطبِّع التنشيطات لكل ميني-دفعة؛ γ,β قابلة للتعلم تستعيد القدرة التمثيلية

جدولة lr بالجيب التمام

تناقص سلس لمعدل التعلم من ηmax إلى ηmin على T خطوة — أفضل من الانخفاض بالخطوات

محاكاة تفاعلية

Loading visualization…
🎯

نموذج جيد مع تحسين سيئ لا قيمة له

motivation

نفس الشبكة مع SGD البسيط والتهيئة الجيدة والجدولة المناسبة يمكنها التفوق على شبكة أكبر مدربة باهمال. خدع التحسين هي الفرق بين 'يعمل في الورقة' و'يعمل على GPU في الإنتاج'. التاريخ: فشلت شبكات الأعماق المبكرة بسبب التدرجات المتلاشية والتهيئة السيئة. اختراق ImageNet 2012 (AlexNet) استخدم ReLU + dropout + weight decay. أضافت ResNets (2015) اتصالات قافزة لحل تدفق التدرج عند عمق 100+. تتدرب المحولات الحديثة بثبات عند عمق 1000+ مع تطبيع دقيق وإحماء معدل التعلم وقطع التدرج. كل خدعة حلّت نمط فشل محدد.

معدل التعلم هو المعامل الأهم. معدل تعلم خاطئ بمقدار 10× غالباً يصنع الفرق بين نموذج يتدرب وآخر يتباعد — قبل تجربة أي شيء آخر.

💡

لماذا توجد كل حيلة

intuition

**الزخم (μ=0.9):** ينزلق الانحدار التدرجي على وعاء خسارة مستطيل بتعرج عبر البعد الضيق. يخمّد الزخم هذه التذبذبات بمتوسطة التدرجات عبر الزمن — محولاً تعرجاً بطيئاً إلى منحنى سلس نحو الحد الأدنى. **آدم:** لدى معاملات مختلفة قيم تدرج مختلفة جداً. يُطبِّع آدم كل معامل بحجم تدرجه التاريخي — الميزات النادرة تحصل على معدلات تعلم فعلية أكبر. **Batch Normalization:** يُرغم التحول في التوزيع الداخلي الطبقات اللاحقة على التعديل المستمر. يُعيد BatchNorm توسيط التنشيطات في كل طبقة مستقرةً التدريب ومتيحةً معدلات تعلم أعلى 10×. **Dropout (p=0.5):** يُصفِّر نصف التنشيطات عشوائياً أثناء التدريب — يُجبر الشبكة على تعلم تمثيلات زائدة مانعاً التكيف المشترك للخلايا.

حجم الدفعة ومعدل التعلم مرتبطان: مضاعفة حجم الدفعة له أثر مشابه لتقسيم معدل التعلم على اثنين. قاعدة التوسع الخطي (Goyal et al. 2017): ضبط lr بالتناسب مع حجم الدفعة، إضافة 5 إبوكات إحماء.

⚙️

وصفة التدريب الحديثة للتعلم العميق

algorithm
1

تهيئة الأوزان: He init لطبقات ReLU (σ=√(2/fan_in))، Xavier لـtanh/sigmoid (σ=√(2/(fan_in+fan_out))).

2

اختيار المحسِّن: Adam (β₁=0.9، β₂=0.999، ε=1e-8، lr=3e-4) لمعظم المهام. SGD+زخم للضبط الدقيق على ImageNet.

3

إضافة إحماء معدل التعلم: رفع خطي من 0 إلى lr المستهدف على 5% من الخطوات الكلية — يمنع خطوات التدرج الكبيرة قبل استقرار النموذج.

4

التبريد بالجيب التمام (أو ReduceLROnPlateau): تقليل lr بسلاسة إلى 1e-6. OneCycleLR بديل قوي.

5

قطع التدرج (max_norm=1.0): تحديد معيار التدرج قبل التحديث — ضروري لـRNN والمحولات. torch.nn.utils.clip_grad_norm_().

6

التنظيم: تضاؤل الأوزان (L2، λ=1e-4 إلى 1e-2). Dropout (p=0.1–0.5). تمهيد التسميات (ε=0.1) للتصنيف.

7

الدقة المختلطة (torch.cuda.amp): float16 للمرور الأمامي، float32 للخسارة — سرعة مضاعفة وكفاءة ذاكرة مضاعفة على GPU الحديثة.

</>

حلقة التدريب الحديثة في PyTorch

code
python103 lines
import torch
import torch.nn as nn
import torch.optim as optim
from torch.cuda.amp import GradScaler, autocast
from torch.utils.data import TensorDataset, DataLoader

class="tok-comment"># ── Minimal model + dataloader for the demo ────────────────────────────
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(class="tok-num">16, class="tok-num">64), nn.ReLU(), nn.Linear(class="tok-num">64, class="tok-num">10))
    def forward(self, x): return self.net(x)

X_data = torch.randn(class="tok-num">512, class="tok-num">16)
y_data = torch.randint(class="tok-num">0, class="tok-num">10, (class="tok-num">512,))
train_loader = DataLoader(TensorDataset(X_data, y_data), batch_size=class="tok-num">32, shuffle=True)

def train_one_epoch(model, loader, optimizer, scaler, scheduler, device, clip_norm=class="tok-num">1.0):
    model.train()
    total_loss = class="tok-num">0.0
    for batch_idx, (X, y) in enumerate(loader):
        X, y = X.to(device), y.to(device)
        optimizer.zero_grad(set_to_none=True)   class="tok-comment"># faster than zero_grad()

        class="tok-comment"># ── Mixed precision forward pass ──────────────────────────────────────
        with autocast():
            logits = model(X)
            loss   = nn.functional.cross_entropy(logits, y, label_smoothing=class="tok-num">0.1)

        class="tok-comment"># ── Scaled backward + gradient clipping ──────────────────────────────
        scaler.scale(loss).backward()
        scaler.unscale_(optimizer)
        torch.nn.utils.clip_grad_norm_(model.parameters(), clip_norm)
        scaler.step(optimizer)
        scaler.update()
        scheduler.step()                         class="tok-comment"># step per batch for OneCycleLR

        total_loss += loss.item()

    return total_loss / len(loader)

class="tok-comment"># ── Setup ─────────────────────────────────────────────────────────────────────
device = torch.device(class="tok-str">"cuda" if torch.cuda.is_available() else class="tok-str">"cpu")
model  = MyModel().to(device)

class="tok-comment"># AdamW (Adam with decoupled weight decay — better than Adam+L2)
optimizer = optim.AdamW(model.parameters(), lr=class="tok-num">3e-4, weight_decay=class="tok-num">1e-2)

class="tok-comment"># OneCycle LR: warmup + cosine anneal in one schedule
scheduler = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=class="tok-num">3e-4,
    total_steps=class="tok-num">100 * len(train_loader),    class="tok-comment"># epochs × steps_per_epoch
    pct_start=class="tok-num">0.05,                          class="tok-comment"># class="tok-num">5% warmup
    anneal_strategy=class="tok-str">"cos",
)

scaler = GradScaler()                        class="tok-comment"># for mixed precision

class="tok-comment"># ── Optimizer comparison ──────────────────────────────────────────────────────
import torch.optim as optim

class="tok-comment"># SGD + Momentum (strong for fine-tuning pretrained CNNs)
sgd = optim.SGD(model.parameters(), lr=class="tok-num">0.01, momentum=class="tok-num">0.9, weight_decay=class="tok-num">1e-4, nesterov=True)

class="tok-comment"># Adam (default for most deep learning tasks)
adam = optim.Adam(model.parameters(), lr=class="tok-num">3e-4, betas=(class="tok-num">0.9, class="tok-num">0.999))

class="tok-comment"># AdamW (Adam with properly decoupled weight decay — recommended by modern best practices)
adamw = optim.AdamW(model.parameters(), lr=class="tok-num">3e-4, weight_decay=class="tok-num">0.01)

class="tok-comment"># ── Batch Normalization example ───────────────────────────────────────────────
class ConvBlock(nn.Module):
    def __init__(self, in_ch, out_ch):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, class="tok-num">3, padding=class="tok-num">1, bias=False),
            nn.BatchNorm2d(out_ch),   class="tok-comment"># bias=False because BN has its own β
            nn.ReLU(inplace=True),
        )
    def forward(self, x): return self.block(x)

class="tok-comment"># ── Layer Normalization (Transformers prefer LayerNorm) ───────────────────────
class TransformerBlock(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.attn    = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
        self.ff      = nn.Sequential(nn.Linear(d_model, d_model*class="tok-num">4), nn.GELU(), nn.Linear(d_model*class="tok-num">4, d_model))
        self.norm1   = nn.LayerNorm(d_model)    class="tok-comment"># pre-norm (more stable than post-norm)
        self.norm2   = nn.LayerNorm(d_model)
        self.drop    = nn.Dropout(class="tok-num">0.1)
    def forward(self, x):
        class="tok-comment"># Pre-norm architecture (used in GPT-class="tok-num">2+, better gradient flow)
        x = x + self.drop(self.attn(self.norm1(x), self.norm1(x), self.norm1(x))[class="tok-num">0])
        x = x + self.drop(self.ff(self.norm2(x)))
        return x

class="tok-comment"># ── Learning rate finder ──────────────────────────────────────────────────────
class="tok-comment"># pip install torch-lr-finder
from torch_lr_finder import LRFinder
finder = LRFinder(model, optimizer, nn.CrossEntropyLoss(), device=device)
finder.range_test(train_loader, end_lr=class="tok-num">10, num_iter=class="tok-num">100)
finder.plot()   class="tok-comment"># look for the steepest descent — that's your max_lr
⚠️

أوضاع فشل BatchNorm الخفية

pitfall

BatchNorm قوي لكنه يمتلك عدة أوضاع فشل خفية: (1) **أحجام دفعات صغيرة:** يحسب BatchNorm إحصائيات على الدفعة — مع batch_size < 8 تكون التقديرات صاخبة جداً. استخدم GroupNorm (group_size=32) أو LayerNorm بدلاً. (2) **model.eval() حيوي:** في وضع التقييم يستخدم BatchNorm الإحصائيات الجارية المحسوبة أثناء التدريب. نسيان استدعاء model.eval() قبل الاستدلال يسبب تنبؤات مختلفة تماماً. (3) **الضبط الدقيق على توزيعات مختلفة:** قد لا تتطابق المتوسط/التباين الجاريان مع بياناتك. فكر في track_running_stats=False أو معدل تعلم منخفض لطبقات BN. (4) **RNNs:** لا يعمل BatchNorm مع التسلسلات ذات الأطوال المتغيرة — استخدم LayerNorm بدلاً.

الخطأ الثاني الأكثر شيوعاً في PyTorch (بعد أبعاد التنسور الخاطئة) هو نسيان model.eval() — يتصرف BatchNorm وDropout بشكل مختلف في وضعي التدريب والتقييم.

?اختبار المعرفة

يتم حفظ التقدم في متصفحك — لا حاجة لحساب.

تحتاج مهندس ذكاء اصطناعي أو عالم بيانات؟

أبني نماذج تعلم آلي مخصصة، ووكلاء ذكاء اصطناعي، ورؤية حاسوب، وأتمتة — من الفكرة إلى الإنتاج.