انتقل إلى المحتوى الرئيسي

الوحدة 5 — VGG وتراكم المرشّحات الصغيرة

استعملت AlexNet مرشّحًا 11×11 في طبقتها الأولى ومرشّحًا 5×5 في الثانية. اقترحت مجموعة Visual Geometry Group في أكسفورد عام 2014 تصميمًا مختلفًا جذريًا: مرشّح 3×3 حصرًا، مع كتل تلافيفية متكرّرة تُضاعف عدد المرشّحات بعد كلّ تجميع. جاءت VGG في المرتبة الثانية في ImageNet 2014 (بعد GoogLeNet)، لكنّها هيمنت على البحث الأكاديمي لسنوات بسبب بساطتها. تشرح هذه الوحدة لماذا المرشّحات الصغيرة أفضل، وما ثمنها في الذاكرة.

نتيجة رياضية بسيطة: طبقتان 3×3 تعادلان طبقة 5×5

خذ مرشّحًا 5×5. مجاله الاستقبالي 5×5 على الطبقة السابقة، ويحمل 5×5=255 \times 5 = 25 معاملًا لكلّ قناة دخل وخرج.

الآن كدّس طبقتَي التفاف 3×3 بلا حشو. الأولى تنظر إلى 3×3 من الدخل، والثانية تنظر إلى 3×3 من خرج الأولى، ومن هنا فإنّ العصبون في الثانية يعتمد على رقعة 5×5 من الدخل الأصلي. المجالان الاستقباليان متطابقان.

لكنّ الطبقتَين 3×3 تحملان 2×3×3=182 \times 3 \times 3 = 18 معاملًا فقط لكلّ قناة، مقابل 25 لطبقة 5×5. توفير 28 %. وثلاث طبقات 3×3 تعادل مجالًا 7×7 بـ3×9=273 \times 9 = 27 معاملًا، مقابل 49 لطبقة واحدة 7×7. التوفير يتحسّن مع كلّ طبقة إضافية.

الأفضل من ذلك: بين كلّ طبقتَي 3×3 هناك تنشيط ReLU إضافي. أي أنّ التقليل في المعاملات مصحوب بلاخطّية أعمق، وقدرة تمثيلية أكبر. هذه هي البصيرة الأساسية لـVGG.

Conv(5×5)  =  Conv(3×3)ReLUConv(3×3)\text{Conv}(5 \times 5) \;=\; \text{Conv}(3 \times 3) \to \text{ReLU} \to \text{Conv}(3 \times 3)

المعادلة في المجال الاستقبالي، والفرق في السعة التمثيلية والحساب.

معمارية VGG-16

VGG-16 (الأشهر بين متغيّرات VGG) تتكوّن من خمس كتل تلافيفية، كلّ كتلة عبارة عن سلسلة من طبقات Conv 3×3 يتبعها تجميع أقصى:

الكتلةالعملياتالشكل الخارجمعاملات
12×Conv(64) + MaxPool112×112×6438912
22×Conv(128) + MaxPool56×56×128221440
33×Conv(256) + MaxPool28×28×2561475328
43×Conv(512) + MaxPool14×14×5125899776
53×Conv(512) + MaxPool7×7×5127079424
Flatten + 3×Dense1000123642856

المجموع: نحو 138 مليون معامل، أي أكثر من ضعف AlexNet. لكن 90 % منها في الطبقات الكثيفة الأخيرة. الجزء التلافيفي وحده يحمل نحو 15 مليون معامل فقط.

from tensorflow.keras import layers, models

def vgg16_style():
modele = models.Sequential([
layers.Input(shape=(224, 224, 3)),

# كتلة 1
layers.Conv2D(64, 3, padding="same", activation="relu"),
layers.Conv2D(64, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),

# كتلة 2
layers.Conv2D(128, 3, padding="same", activation="relu"),
layers.Conv2D(128, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),

# كتلة 3
layers.Conv2D(256, 3, padding="same", activation="relu"),
layers.Conv2D(256, 3, padding="same", activation="relu"),
layers.Conv2D(256, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),

# كتلة 4
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),

# كتلة 5
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.Conv2D(512, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),

# رأس التصنيف
layers.Flatten(),
layers.Dense(4096, activation="relu"),
layers.Dropout(0.5),
layers.Dense(4096, activation="relu"),
layers.Dropout(0.5),
layers.Dense(1000, activation="softmax"),
])
return modele

تُلاحظ النمط: عمق ثابت أوّلًا (64، 128، 256، 512)، ثمّ ثابت (512، 512)، والأبعاد المكانية تنكمش كلّ مرّة بمقدار النصف عبر MaxPool. هذا الترتيب البسيط جدًّا هو ما جعل VGG سهل الاستيعاب ومركز مرجعي لعقد كامل.

ثمن VGG: الذاكرة والزمن

معاملات VGG-16: 138 مليون. لكن ذلك ليس الجانب الأثقل. تنسورات خرائط الميزات هي المشكلة الحقيقية:

  • خرج الكتلة 1: 112×112×64 = 802816 قيمة لكلّ صورة.
  • خرج الكتلة 2: 56×56×128 = 401408 قيمة.
  • خرج الكتلة 3: 28×28×256 = 200704 قيمة.

لكلّ صورة، لا بدّ من تخزين كلّ هذه الخرائط في الذاكرة أثناء التمرير الأمامي، للاستفادة منها في الانتشار الخلفي. مع دفعة من 32 صورة، تصل الذاكرة إلى عدّة غيغابايتات، ما يستدعي بطاقة GPU كبيرة. لهذا لا يُدرَّب VGG-16 من الصفر على معالج قياسي، ولهذا استحقّ الجيل التالي (ResNet وInception) تصميمًا أكثر كفاءة.

الذاكرة تفوق المعاملات

شبكة عميقة قد تحمل 10 ملايين معامل لكن تحتاج 5 غيغابايت أثناء التدريب. الذاكرة المطلوبة تُقاس بمجموع أحجام خرائط الميزات مضروبًا في حجم الدفعة، وليس بعدد المعاملات. تحقّق دائمًا من ذلك قبل بدء التدريب، لتفادي OutOfMemoryError بعد نصف ساعة.

VGG كخطّ أساس مرجعي

لماذا لا تزال VGG تُذكَر مع أنّ ResNet وEfficientNet أفضل منها في كلّ مقياس؟ لثلاثة أسباب متكاملة:

  • البساطة المعمارية: لا شيء غامض في VGG. مرشّح 3×3، تنشيط ReLU، تجميع أقصى، تكرار. كلّ مهندس يفهم البنية في دقائق.
  • مقياس مرجعي لخرائط الميزات: كثير من مهمّات النقل (فقدان الأسلوب الفنّي، مقاييس المسافة الإدراكية) تستعمل خرائط ميزات VGG كأساس، لأنّها تُنتج تمثيلات مرتّبة هرميًا يمكن مقارنتها.
  • تعليمي: VGG أفضل معمارية لفهم لماذا تعمل الشبكات التلافيفية. مقارنتها مع AlexNet تُظهر أثر المرشّحات الصغيرة، ومقارنتها مع ResNet تُظهر لِمَ نحتاج إلى وصلات متبقّية.

بناء VGG-مصغّر لـCIFAR-10

نبني نسخة مصغّرة تناسب CIFAR-10 (صور 32×32):

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

def vgg_cifar():
def bloc(x, canaux, n_conv):
for _ in range(n_conv):
x = layers.Conv2D(canaux, 3, padding="same", activation="relu")(x)
return layers.MaxPooling2D(2)(x)

entree = tf.keras.Input(shape=(32, 32, 3))
x = bloc(entree, 64, 2) # 32→16
x = bloc(x, 128, 2) # 16→8
x = bloc(x, 256, 3) # 8→4
x = bloc(x, 512, 3) # 4→2
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.5)(x)
sortie = layers.Dense(10, activation="softmax")(x)
return tf.keras.Model(entree, sortie)

modele = vgg_cifar()
modele.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])

استخدمنا GlobalAveragePooling2D بدل Flatten والطبقات الكثيفة، وهو ما يجعل المعاملات تنكمش من 138 مليون إلى نحو 3 ملايين. تصل هذه الشبكة إلى 89 % على CIFAR-10 خلال 50 حقبة، مع تكثير بيانات بسيط (الوحدة 8). تحسينات إضافية ممكنة، لكنّها ستدفعنا إلى الوصلات المتبقّية.

قاعدة عملية: كتلة VGG جيّدة
  • ابدأ بـ64 قناة، وضاعف بعد كلّ تجميع.
  • في الكتل الأولى (32×32 و16×16): طبقتان 3×3.
  • في الكتل الأعمق: ثلاث طبقات 3×3، لأنّ المجال الاستقبالي يحتاج إلى النموّ.
  • ضع padding="same" دائمًا، لتفادي تقلّص الأبعاد قبل التجميع.

حدود VGG

مع كلّ فضائل VGG، هناك حدّ صعب في العمق. حاول الباحثون تكديس 19 و30 و50 طبقة، فبدأت الدقّة تتراجع. هذا ليس فرط تخصيص: الدقّة على التدريب أيضًا تتراجع. المشكلة تلاشي التدرّج في الطبقات العميقة، وقد رأينا الميكانيك في دورة الأساسيات. تحلّ الوحدة القادمة هذه المشكلة نهائيًا.

في الخلاصة

  • طبقتان 3×3 تعادلان مجال طبقة 5×5 مع معاملات أقلّ بـ28 % ولاخطّية إضافية؛ وثلاث 3×3 تعادلان 7×7 مع فارق أكبر.
  • VGG-16 تُطبّق هذا المبدأ في كتل متكرّرة: يبدأ العمق من 64 ويُضاعَف بعد كلّ تجميع، والمرشّح 3×3 حصرًا.
  • الذاكرة تُقيّد VGG أكثر من المعاملات: خرائط الميزات الكبيرة في الطبقات الأولى تلتهم الغيغابايتات؛ حجم الدفعة لا الوزن الإجمالي هو ما يُفشل التدريب.
  • تبقى VGG خطّ الأساس المرجعي للتعليم وللنقل، لكنّها تصل إلى حدّها بحوالي 19 طبقة بسبب تلاشي التدرّج، وهي المشكلة التي حلّتها الوصلات المتبقّية.

الوحدة التالية: ResNet والوصلات المتبقّية، حيث ينهار الحاجز أمام العمق ونصل إلى شبكات بـ152 طبقة تعمل فعلًا.