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

الوحدة 8 — التعلّم بالنقل مع نماذج مدرَّبة مسبقًا

تدريب شبكة رؤيةٍ من الصفر يستلزم ملايين الصور وأيّامًا من الحساب. أمّا التعلّم بالنقل فيبلغ نتائج أفضل ببضعة آلاف من الصور ودقائق قليلة. تُوضّح هذه الوحدة سبب نجاح ذلك، ثمّ كيفيّة تحقيقه دون الوقوع في المصيدة التي تُفسد نصف المحاولات.

لماذا تُعاد الطبقات الأولى؟

لا تبني شبكةٌ عميقة تمثيلًا واحدًا: بل تُكوّم عدّة تمثيلات، من الأعمّ إلى الأخصّ.

هرم تمثيلات شبكة رؤية في خمس مراحل. تُغذّي وحدات البكسل الخام الطبقاتِ الأولى التي تكشف الحواف والتدرّجات، ثمّ الطبقاتِ الوسطى التي تتعرّف على النُسُج وأجزاء الأشياء، ثمّ الطبقاتِ الأخيرة التي تُحدّد أشياء كاملة، وأخيرًا رأس تصنيف خاصّ بالمجموعة الأصليّة.وحدات البكسل الخامقيم الألوانبلا أيّ بنيةالطبقات الأولىحواف وزواياتدرّجات ألوانشاملة الاستعمالالطبقات الوسطىنُسُج وأنماط مُكرَّرةأجزاء أشياءالطبقات الأخيرةأشياء كاملةخاصّة بالأصناف المرئيّةرأس التصنيفاحتمالات الأصناف الألفيجب استبداله
كلّما نزلنا صار التمثيل أخصّ بالمجموعة الأصليّة. تكشف المرحلتان الأوليان حوافّ نافعة لأيّ مهمّة بصريّة: وهذه العمومية هي ما يُتيح النقل. أمّا الرأس فيجب استبداله دائمًا.

يبقى كاشف الحواف كاشفَ حواف سواء أكانت الصورة قطًّا أم صورةً إشعاعيّة للرئة. وهذا ما يُبرّر الاحتفاظ بأوزان شبكةٍ مدرَّبة على ImageNet واستبدال طرفها فقط.

حمِّل النموذج بلا رأسه

from tensorflow import keras

base = keras.applications.EfficientNetB0(
weights="imagenet",
include_top=False, # يُزيل رأس الأصناف الألف
input_shape=(224, 224, 3),
)
base.trainable = False # يُجمّد القاعدة كاملة

يُزيل include_top=False طبقةَ التصنيف الأصلية، وهي عديمة الفائدة ما دامت أصنافك ليست أصناف ImageNet. ويُجمّد trainable = False الأوزانَ: فلن تستقبل بعد الآن أيّ تدرّج.

ثمّ نُضيف رأسنا الخاصّ، بالواجهة الوظيفية من الوحدة 3:

from tensorflow.keras import layers

entree = keras.Input(shape=(224, 224, 3))
x = keras.applications.efficientnet.preprocess_input(entree)
x = base(x, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.3)(x)
sortie = layers.Dense(nb_classes, activation="softmax")(x)

modele = keras.Model(entree, sortie)

الـpreprocess_input الخاصّ بكلّ عائلة نماذج ليس اختياريًّا. فكلّ معمارية تنتظر مدخلاتها في مجالٍ محدَّد — بعضها في [0,1][0, 1]، وبعضها في [1,1][-1, 1]، وبعضها مُمركَز على متوسّطات ImageNet. وتقديم وحدات بكسلٍ خام من 0 إلى 255 لشبكةٍ تنتظر [1,1][-1, 1] يُعطي نتائج ضعيفة بلا أيّ رسالة خطأ.

مصيدة BatchNormalization

هذا هو الخطأ الذي يُفسّر أغلبيّة عمليات النقل الفاشلة، وهو خطأ شبه غير مرئيّ.

تحوي طبقة BatchNormalization نوعين من الكمّيات: أوزان مُتعلَّمة، وإحصائيّات جارية — متوسّط وتباين مُتراكمان أثناء التدريب الأصليّ. فـbase.trainable = False يُجمّد الأوزان، لكنّه لا يكفي لتجميد الإحصائيّات: فإن استُدعيت الطبقة في وضع التدريب استمرّت في تحديثها ببياناتك.

على مجموعةٍ صغيرة تنحرف هذه الإحصائيّات، وتبتعد عن التي جرى تحسين الأوزان المجمَّدة من أجلها، فتُدمّر التمثيل الذي كنّا نسعى إلى حفظه بالذات. والعرَض دقّة تستوي أو تتراجع مع أنّ كلّ شيء يبدو صحيحًا.

المخرج قائم في training=False في استدعاء base(x, training=False) أعلاه. فهو يفرض وضع الاستدلال على القاعدة كاملة، بما فيها الإحصائيّات.

آليّتان متمايزتان، تُخلَطان كثيرًا

يقرّر base.trainable = False أيّ الأوزان تتلقّى تدرّجًا. أمّا الوسيط training=False فيقرّر كيف تتصرّف الطبقات. وكلتاهما ضرورية ولا تُغني إحداهما عن الأخرى. فعلى قاعدةٍ تحوي تطبيعًا بالحزم — وهذه حال جميع المعماريات الحديثة تقريبًا — يُلغي إغفال الثانية جُلّ فائدة الأولى.

نظامان، بهذا الترتيب

المقاربة الفعّالة تجري على مرحلتين، والترتيب غير قابلٍ للتفاوض.

المرحلة 1، استخراج الخصائص. القاعدة مُجمَّدة، ولا يتدرّب سوى الرأس الجديد، بمعدّل تعلّم عاديّ.

modele.compile(
optimizer=keras.optimizers.Adam(1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
modele.fit(jeu, validation_data=jeu_val, epochs=10)

المرحلة 2، الضبط الدقيق. نفكّ تجميد جزءٍ من القاعدة ونستأنف التدريب بمعدّل أضعف بكثير.

base.trainable = True
for couche in base.layers[:-30]: # لا يفكّ إلّا آخر 30 طبقة
couche.trainable = False

modele.compile(
optimizer=keras.optimizers.Adam(1e-5), # مئة مرّة أضعف
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
modele.fit(jeu, validation_data=jeu_val, epochs=10)

إعادة التصريف بعد تعديل trainable إلزاميّة: فبغيرها تبقى قائمة المتغيّرات القابلة للتدريب هي المُثبَّتة عند التصريف السابق، ولا يكون لفكّ التجميد أيّ أثر.

المعدّل المنخفض جدًّا يُفسَّر ببساطة. فالرأس الجديد المُهيَّأ عشوائيًّا يُنتج بدايةً تدرّجات هائلة. وتُطبَّق هذه التدرّجات على قاعدةٍ مدرَّبة مسبقًا بمعدّل عاديّ فتمحو في بضع حزم المعلومةَ المُتراكمة على ملايين الصور. وهذا أيضًا سبب وجوب أن تسبق المرحلة 1 المرحلة 2: فهي تجلب الرأس إلى حالةٍ معقولة قبل أن تتعرّض القاعدة.

كم من الطبقات نفكّ؟

الحالةالاستراتيجية
بيانات قليلة، مجال قريبتجميد الكلّ، تدريب الرأس وحده
بيانات كثيرة، مجال قريبفكّ الثلث الأخير
بيانات قليلة، مجال بعيدفكّ الطبقات الوسطى، إبقاء الأولى مجمَّدة
بيانات كثيرة، مجال بعيد جدًّاالتفكير في تدريبٍ كامل

الحالة «بيانات قليلة، مجال بعيد» هي الأدقّ، والتصوير الطبّي مثالها النمطيّ. فالطبقات الأولى تبقى نافعة، والأخيرة أخصّ من أن تُلائم أشياء ImageNet، ولا توجد بيانات كافية لإعادة تعلّم كلّ شيء. وفكّ الوسط هو الحلّ الوسط، ويُصادَق عليه تجريبيًّا.

في الخلاصة

  • يعمل النقل لأنّ الطبقات الأولى تتعلّم أنماطًا شاملة — حوافّ ونُسُج — مستقلّة عن المهمّة الأصلية.
  • يُزيل include_top=False الرأس الأصلي، و**preprocess_input** الخاصّ بعائلة النماذج لا غنى عنه، إذ تنتظر كلّ معمارية مدخلاتها في مجالٍ محدَّد.
  • يُجمّد trainable = False الأوزان لكن لا يُجمّد إحصائيّات التطبيع بالحزم: فبلا training=False عند الاستدعاء تنحرف وتُدمّر التمثيل المحفوظ.
  • استخراج الخصائص أوّلًا دائمًا، ثمّ الضبط الدقيق بمعدّل أضعف بمئة مرّة، مع إعادة التصريف بعد كلّ تغيير في trainable.

الوحدة التالية: التدريب الموزّع، حين لا يعود المسرّع الواحد كافيًا.