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

الوحدة 4 — التدريب الواعي بالتكميم

الوحدة 3 أعطتنا نموذجًا بحجم 3.5 مِيغا يخسر 0.9 نقطة مئويّة. في تسعين بالمئة من الحالات، هذا مقبول ونتوقّف هنا. أمّا في العشرة بالمئة الأخرى — نماذج صغيرة جدًّا، نماذج لكشف الأمراض النادرة، نماذج تعمل عند حدود المدى الديناميكيّ — فالخسارة تكون خمس نقاط أو أكثر، وتصبح غير مقبولة. التدريب الواعي بالتكميم (Quantization-Aware Training أو QAT) هو الحلّ الثاني: لا نُكمِّم بعد التدريب، بل نتدرَّب على نموذج يعرف مسبقًا أنّه سيُكمَّم، فيُعوِّض الفقدان أثناء التعلّم.

الفكرة: محاكاة التكميم داخل التدريب

في PTQ، نبني نموذجًا بـfloat32، ثمّ نضغطه إلى int8. الأوزان التي كانت مضبوطة بدقّة نقطة عائمة تُدوَّر إلى الأقرب من 256 قيمة صحيحة ممكنة. بعض الأوزان الحرجة قد تكون قريبة من عتبة تدوير، فتقفز إلى قيمة أبعد ممّا يجب، وينخفض المخرَج.

في QAT، نُدرِج داخل الرسم الحسابيّ عمليّات تُدعى fake quantization: تُدوِّر التنسور إلى int8 ثمّ ترجعه إلى float32، بحيث يرى النموذج فقدان الدقّة أثناء التدريب. الأوزان تتعلّم أن تعيش في مساحة الأعداد الصحيحة الصغيرة. المشتقّات تعمل من خلال دالّة تدوير مستقيمة (Straight-Through Estimator)، وهي حيلة بسيطة تسمح للتدرّج بالمرور رغم أنّ التدوير في حدّ ذاته غير قابل للاشتقاق.

النتيجة: نموذج يعمل بدقّة قريبة من float32 قبل التكميم، ثمّ يفقد أقلّ بكثير عند التحويل إلى int8 الحقيقيّ.

متى نلجأ إلى QAT

القرار ليس بديهيًّا. QAT يكلّف: إعادة تدريب (ساعات إلى أيّام)، وحساسيّة لمعدَّل التعلّم، وتعقيد إضافيّ. لا نلجأ إليه إلاّ حين:

  • PTQ يفقد أكثر من نقطتَين مئويَّتَين ويصبح غير مقبول للاستعمال.
  • النموذج المستهدَف صغير جدًّا (MobileNetV1 أو V2 بمُضاعف عرض 0.35 أو 0.5). النماذج الأكبر (MobileNetV2 بـ1.0، أو EfficientNet) تحتمل PTQ أفضل.
  • العتاد يستوجب int8 صافيًا (EdgeTPU، بعض NPU على الهواتف)، ولا يقبل التفعيلات بـfloat32.
  • الدقّة عنصر حرج (تشخيص طبّيّ، كشف عيوب صناعيّة)، وفقدان نقطة يقلب تصميم النظام.

في مسألتنا (كشف أمراض 38 صنفًا من أوراق النباتات، MobileNetV2 بـ1.0)، PTQ كافٍ ونقاطنا الضائعة أقلّ من واحدة. QAT ليس ضروريًّا من أجل هذه الوحدة، لكنّنا نطبِّقه كتمرين لنُبيّن الطريقة والفارق.

التطبيق: من نموذج مدرَّب إلى نموذج QAT

نستعمل حزمة tensorflow_model_optimization التي تُضيف عوامل fake quant تلقائيًّا:

import tensorflow as tf
import tensorflow_model_optimization as tfmot

# 1) نموذج مدرَّب سابقًا من الوحدة 08 (نقطة انطلاق QAT)
base_model = tf.keras.models.load_model("plantvillage_mobilenetv2.keras")

# 2) تغليف النموذج بعوامل fake quant. لا يُغيِّر البنية، لكنّه يضيف حسّاسات
qat_model = tfmot.quantization.keras.quantize_model(base_model)

# 3) إعادة تجميع مع معدَّل تعلّم أصغر (المهمّ في QAT)
qat_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)

qat_model.summary() # ستظهر عقد QuantizeWrapperV2 حول كلّ طبقة

نصيحة: يجب أن يكون النموذج مدرَّبًا بالكامل قبل تطبيق QAT. QAT مرحلة ضبط دقيق، لا تدريب من الصفر. إن بدأت QAT من أوزان عشوائيّة، ستدور في مكانك.

حلقة إعادة التدريب

QAT يحتاج ثلاث إلى عشر عصور فقط، بمعدَّل تعلّم منخفض:

train_ds = tf.keras.utils.image_dataset_from_directory(
"plantvillage/train",
image_size=(224, 224),
batch_size=32,
).map(lambda x, y: ((tf.cast(x, tf.float32) / 127.5) - 1.0, y))

val_ds = tf.keras.utils.image_dataset_from_directory(
"plantvillage/val",
image_size=(224, 224),
batch_size=32,
).map(lambda x, y: ((tf.cast(x, tf.float32) / 127.5) - 1.0, y))

qat_model.fit(
train_ds,
epochs=5,
validation_data=val_ds,
callbacks=[
tf.keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True),
],
)

qat_model.save("plantvillage_qat.keras")

نلاحظ في العصور الأولى ارتفاعًا سريعًا لدقّة التحقّق ثمّ استقرارًا. الفكرة أنّ الأوزان تتحرَّك قليلًا لتُلائم مساحة int8 بدل مساحة float32.

معدَّل تعلّم صغير

معدَّل تعلّم اعتياديّ (1e-3) يُخرِّب النموذج المدرَّب. القاعدة العمليّة: خفِّض المعدَّل بعامل 100 مقارنة بالتدريب الأصليّ. 1e-5 نقطة بداية جيّدة، ويمكن تجربة 5e-6 إن رأيت اهتزازًا في دقّة التحقّق.

التحويل إلى TFLite بعد QAT

بعد QAT، نُحوِّل إلى .tflite بالمسار نفسه، مع فارق: لسنا مضطرّين إلى جَمْع تمثيليّ، لأنّ عوامل fake quant حسبت معاملات التكميم أثناء التدريب.

qat_model = tf.keras.models.load_model(
"plantvillage_qat.keras",
custom_objects=tfmot.quantization.keras.default_8bit.default_8bit_quantize_registry.__dict__,
)

converter = tf.lite.TFLiteConverter.from_keras_model(qat_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8

tflite_qat = converter.convert()
with open("plantvillage_qat_int8.tflite", "wb") as f:
f.write(tflite_qat)

print(f"الحجم QAT int8 : {len(tflite_qat) / 1024 / 1024:.2f} Mo")

الحجم مطابق لـPTQ (نفس الأعداد الصحيحة الـint8)، لكنّ الدقّة أعلى بنصف نقطة إلى نقطة كاملة على نموذجنا.

المقارنة بين PTQ وQAT

الجدول أدناه يوسِّع جدول الوحدة 3 بسطرَين:

الاستراتيجيّةالحجمزمن CPUالدقّةكلفة الإنتاج
خام (float32)14.0 Mo62 ms0.9450
PTQ int8 كامل3.5 Mo31 ms0.936دقائق
QAT int83.5 Mo31 ms0.941ساعات إلى أيّام
PTQ int8 على MobileNetV2 α=0.351.2 Mo12 ms0.881دقائق
QAT int8 على MobileNetV2 α=0.351.2 Mo12 ms0.912ساعات إلى أيّام

نلاحظ أنّ فرق QAT صغير على MobileNetV2 بـ1.0 (0.5 نقطة)، وكبير على MobileNetV2 بـ0.35 (3.1 نقاط). هذه هي القاعدة: كلّما صغر النموذج، ازدادت الحاجة إلى QAT.

فخّان يجب معرفتهما

الفخّ الأوّل: تطبيق QAT على نموذج غير مدرَّب. الطبقات لم تستقرّ بعد، وإضافة fake quant تُبعثر التدريب. الحلّ: درِّب أوّلًا بـfloat32 حتّى الاستقرار، ثمّ طبِّق QAT.

الفخّ الثاني: نسيان الطبقات المخصَّصة. إن كانت المعماريّة تحوي طبقة كتبتها بنفسك، tfmot.quantization لا يعرف كيف يُغلِّفها. ستُعطي رسالة خطأ، والحلّ إمّا استبدال الطبقة بواحدة قياسيّة، أو كتابة QuantizeConfig مخصَّص لها. للنموذج المرجعيّ (MobileNetV2 قياسيّ) لا مشكلة.

الخلاصة

  • QAT تدريب واعٍ بالفقدان: عوامل fake quant تُدرِج التدوير داخل الرسم، فتتعلّم الأوزان أن تعيش في مساحة int8. النتيجة تكميم أدقّ من PTQ.
  • لا تلجأ إليه إلاّ عند الحاجة: PTQ يفقد أكثر من نقطتَين، أو النموذج صغير جدًّا، أو العتاد يستوجب int8 صافيًا، أو الدقّة عنصر حرج. للنموذج المتوسّط في مسألتنا، PTQ يكفي.
  • معدَّل تعلّم صغير جدًّا: خفِّض بعامل مئة عن التدريب الأصليّ. QAT ضبط دقيق قصير (ثلاث إلى عشر عصور)، لا تدريب من الصفر.
  • الحجم لا يتغيّر، الدقّة ترتفع: QAT يعطي ملفًّا بحجم PTQ نفسه، لكن بدقّة أعلى. الكلفة كلّها في وقت التدريب، لا في وقت الاستدلال.

الوحدة التالية تنتقل إلى استراتيجيّة أخرى لتقليص النموذج: التقليم، الذي يعمل بمنطق مختلف تمامًا، ولا يتنافى مع التكميم.