Optimizers والتدريب — التعلم العميق
المحسِّنات وعملية التدريب
Backpropagation يعطينا اتجاه التحسين (التدرج). المحسِّن (Optimizer) يقرر كيف نتحرك: بأي خطوة، وهل نتذكر الاتجاهات السابقة.
معدل التعلم: أهم معامل
| معدل التعلم | ما يحدث |
|---|---|
| صغير جداً | تقدم بطيء جداً، وقد يعلق |
| مناسب | تنخفض الخسارة بثبات |
| كبير جداً | تتذبذب الخسارة أو تنفجر إلى NaN |
إذا ظهرت nan في الخسارة، أول ما تجربه: معدل تعلم أصغر.
الدفعات و Epochs
- Epoch: مرور كامل على كل بيانات التدريب.
- Batch: عدد العينات التي نحسب منها التدرج قبل كل تحديث.
| | التدرج من | المزايا والعيوب |
|---|---|---|
| Batch كامل | كل البيانات | دقيق لكن بطيء ويحتاج ذاكرة كبيرة |
| Mini-batch (32–256) | دفعة صغيرة | الشائع: سريع، والضوضاء الصغيرة تساعد على تجاوز النقاط السيئة |
| SGD نقي | عينة واحدة | ضوضاء كبيرة |
مع 50,000 عينة و batch_size=32: كل epoch = نحو 1,563 تحديثاً.
المحسِّنات
| المحسِّن | الفكرة |
|---|---|
| SGD | خطوة ثابتة عكس التدرج |
| SGD + Momentum | يتذكر الاتجاه السابق ككرة متدحرجة: يتسارع في الاتجاهات الثابتة ويخفف التذبذب |
| RMSprop | معدل تعلم مستقل لكل وزن حسب حجم تدرجاته الأخيرة |
| Adam | Momentum + RMSprop معاً — الخيار الافتراضي الجيد (lr=0.001) |
| AdamW | Adam مع Weight Decay صحيح — شائع في Transformers |
المثال الكامل يطبق الثلاثة الأولى بـ NumPy على دالة "وادٍ ضيق" ويقارن عدد الخطوات.
في Keras
from tensorflow import keras
model = keras.Sequential([keras.Input(shape=(20,)), keras.layers.Dense(1, activation="sigmoid")])
model.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-3), loss="binary_crossentropy", metrics=["accuracy"])
# history = model.fit(X_train, y_train, epochs=20, batch_size=32, validation_split=0.2)قراءة منحنيات التدريب
ارسم خسارة التدريب والتحقق لكل epoch (history.history):
| الشكل | التشخيص | العلاج |
|---|---|---|
| الاثنان ينخفضان | جيد — استمر | |
| التدريب ينخفض والتحقق يرتفع | Overfitting | Dropout، بيانات أكثر، إيقاف مبكر |
| الاثنان مرتفعان وثابتان | Underfitting | نموذج أكبر، تدريب أطول، lr مناسب |
| قفزات عشوائية | lr كبير أو batch صغير جداً | خفّض lr |
جدولة معدل التعلم
البدء بمعدل كبير ثم تخفيضه تدريجياً يعطي غالباً نتائج أفضل: ReduceLROnPlateau (درس Callbacks) أو جداول مثل Cosine Decay.
تمرين
في المثال الكامل جرّب معدلات تعلم مختلفة لـ SGD (0.001، 0.01، 0.05). عند أي قيمة ينفجر؟ ولماذا يتحمل Adam معدلات أوسع؟
import numpy as np
# دالة "وادٍ ضيق": منحدرة جداً في اتجاه y ومسطحة في اتجاه x — أصعب حالة لـ SGD
def loss(p):
x, y = p
return 0.5 * x ** 2 + 10 * y ** 2
def grad(p):
x, y = p
return np.array([x, 20 * y])
def sgd(lr=0.05, steps=500):
p = np.array([-8.0, 2.0])
for step in range(steps):
p = p - lr * grad(p)
if loss(p) < 1e-4:
return step + 1, loss(p)
return steps, loss(p)
def momentum(lr=0.05, beta=0.9, steps=500):
p, v = np.array([-8.0, 2.0]), np.zeros(2)
for step in range(steps):
v = beta * v + grad(p) # يتذكر الاتجاهات السابقة
p = p - lr * v
if loss(p) < 1e-4:
return step + 1, loss(p)
return steps, loss(p)
def adam(lr=0.3, b1=0.9, b2=0.999, eps=1e-8, steps=500):
p, m, s = np.array([-8.0, 2.0]), np.zeros(2), np.zeros(2)
for t in range(1, steps + 1):
g = grad(p)
m = b1 * m + (1 - b1) * g # متوسط التدرجات (momentum)
s = b2 * s + (1 - b2) * g ** 2 # متوسط مربعاتها (حجم التدرج لكل بعد)
m_hat, s_hat = m / (1 - b1 ** t), s / (1 - b2 ** t) # تصحيح الانحياز في البداية
p = p - lr * m_hat / (np.sqrt(s_hat) + eps)
if loss(p) < 1e-4:
return t, loss(p)
return steps, loss(p)
for name, fn in [("SGD", sgd), ("Momentum", momentum), ("Adam", adam)]:
steps, final = fn()
print(f"{name:9s} وصل إلى خسارة < 0.0001 خلال {steps} خطوة (الخسارة {final:.2e})")
print("\nSGD بمعدل تعلم كبير (0.11):", f"{sgd(lr=0.11)[1]:.2e}", "← ينفجر لأن الخطوة تتجاوز الوادي")اختبر فهمك