Kodax

Batch Normalization — التعلم العميق

Batch Normalization

المشكلة

أثناء التدريب تتغير أوزان كل طبقة، فيتغير توزيع مدخلات الطبقة التالية باستمرار: كأنك تتعلم إصابة هدف يتحرك. والقيم قد تكبر أو تصغر جداً عبر الطبقات العميقة فيصعب التدريب.

الحل

لكل دفعة، Batch Normalization يقيّس مخرجات الطبقة لكل ميزة:

x̂ = (x − متوسط الدفعة) / √(تباين الدفعة + ε)
y  = γ · x̂ + β          ← γ و β معاملان يتعلمهما النموذج

γ و β يسمحان للشبكة باستعادة أي مقياس تحتاجه — حتى إلغاء التقييس إن كان مفيداً.

import numpy as np

rng = np.random.default_rng(0)
batch = rng.normal(loc=50, scale=12, size=(32, 3))     # مخرجات طبقة بمقياس كبير
mean, var = batch.mean(axis=0), batch.var(axis=0)
normalized = (batch - mean) / np.sqrt(var + 1e-5)
print("قبل: المتوسط", mean.round(1), "الانحراف", np.sqrt(var).round(1))
print("بعد: المتوسط", normalized.mean(axis=0).round(3), "الانحراف", normalized.std(axis=0).round(3))

التدريب مقابل التنبؤ

- أثناء التدريب: إحصائيات الدفعة الحالية، مع تحديث متوسط متحرك للإحصائيات.

- أثناء التنبؤ: المتوسط المتحرك المحفوظ (قد تكون "الدفعة" صورة واحدة!).

Keras يبدّل تلقائياً. لكن لهذا السبب: نموذج يحتوي BatchNorm ويتصرف بشكل مختلف بين fit و predict — أمر طبيعي.

الفوائد

- تدريب أسرع وأثبت، ويتحمل معدلات تعلم أعلى.

- أقل حساسية لتهيئة الأوزان.

- أثر تنظيمي خفيف (ضوضاء إحصائيات الدفعة).

الموضع

الشائع: Dense/Conv ← BatchNormalization ← Activation. ولأن BN يطرح المتوسط، يمكن إلغاء انحياز الطبقة السابقة (use_bias=False). عملياً الترتيب بعد التنشيط يعمل أيضاً؛ المهم الاتساق.

متى لا يناسب؟

- دفعات صغيرة جداً (2–4): الإحصائيات غير موثوقة.

- RNN و Transformers: نستخدم Layer Normalization بدلاً منه — يقيّس عبر ميزات العينة الواحدة لا عبر الدفعة، فلا يعتمد على حجمها.

- عند نقل التعلم ب BatchNorm مجمّد: اترك طبقاته في وضع التنبؤ (training=False) أثناء الضبط الدقيق.

تمرين

في المثال الكامل درّب النموذجين (مع BN وبدونه) بمعدل تعلم 0.01 بدل الافتراضي وقارن الدقة بعد 3 epochs. أيهما تحمّل المعدل الأعلى؟

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# ===== بدون Batch Normalization =====
model_without_bn = keras.Sequential([
    layers.Dense(256, activation='relu', input_shape=(784,)),
    layers.Dense(128, activation='relu'),
    layers.Dense(64, activation='relu'),
    layers.Dense(10, activation='softmax')
])

# ===== مع Batch Normalization =====
model_with_bn = keras.Sequential([
    layers.Dense(256, input_shape=(784,)),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    
    layers.Dense(128),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    
    layers.Dense(64),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    
    layers.Dense(10, activation='softmax')
])

# ===== في CNN =====
cnn_model = keras.Sequential([
    layers.Conv2D(32, (3, 3), input_shape=(28, 28, 1)),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    layers.MaxPooling2D((2, 2)),
    
    layers.Conv2D(64, (3, 3)),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    layers.MaxPooling2D((2, 2)),
    
    layers.Flatten(),
    layers.Dense(64),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    layers.Dense(10, activation='softmax')
])

# ===== مقارنة =====
# تحميل MNIST
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1, 784) / 255.0
X_test = X_test.reshape(-1, 784) / 255.0

for name, model in [('Without BN', model_without_bn), ('With BN', model_with_bn)]:
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    history = model.fit(X_train, y_train, epochs=5, validation_split=0.1, verbose=0)
    test_acc = model.evaluate(X_test, y_test, verbose=0)[1]
    print(f"{name}: Test Accuracy = {test_acc:.2%}")
اختبر فهمك