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

الوحدة 10 — التفسير: Grad-CAM وخرائط البروز

وصلنا في الوحدة السابقة إلى دقّة 93 % على مجموعة النفايات. رقم مُريح، لكنّه لا يخبرنا شيئًا: هل النموذج ينظر إلى الزجاجة، أم إلى الحزام الناقل الذي كانت عليه؟ الفرق حاسم في الإنتاج: نموذج يعتمد على الخلفية سيفشل في اللحظة التي يتغيّر فيها لون الحزام. تعرض هذه الوحدة أداة التفسير الأكثر انتشارًا في الرؤية: Grad-CAM، وتنبّه إلى حدود القراءة البصرية.

الفكرة الرئيسية: التدرّج بالنسبة إلى خرائط الميزات

Grad-CAM (Selvaraju وآخرون 2017) يُجيب على سؤال: أيّ مناطق من الصورة أثّرت في قرار النموذج لهذه الفئة؟ الفكرة الرياضية أنيقة:

خذ خريطة الميزات الأخيرة (قبل التجميع الشامل)، وسمِّ قنواتها AkA^k حيث kk من 1 إلى عدد القنوات (2048 مثلًا في ResNet50). كلّ خريطة AkA^k ذات بعد H×WH \times W صغير (7×7 عادةً).

احسب متوسّط التدرّج لتنبّؤ الصنف cc بالنسبة إلى كلّ قناة:

αkc=1HWi,jycAijk\alpha_k^c = \frac{1}{H W} \sum_{i, j} \frac{\partial y^c}{\partial A_{ij}^k}

هذا الرقم يقيس أهمّية القناة kk في قرار الصنف cc. القنوات التي رفعها كثيرًا يعني تنشيطها ضروري.

ثمّ اجمع القنوات بأوزانها، وضع تنشيط ReLU لإبقاء المساهمات الإيجابية فقط:

LGrad-CAMc=ReLU(kαkcAk)L^c_{\text{Grad-CAM}} = \text{ReLU}\left(\sum_k \alpha_k^c A^k\right)

النتيجة: خريطة حرارية 7×7 نُوسّعها إلى حجم الصورة الأصلي (224×224)، ونضعها فوق الصورة. المناطق الحمراء أثّرت أكثر، والزرقاء أقلّ.

تنفيذ Grad-CAM في Keras

import tensorflow as tf
import numpy as np

def grad_cam(modele, image, indice_classe, nom_couche="conv5_block3_out"):
# نموذج فرعي يخرج خرائط الميزات وتنبّؤات النموذج
modele_grad = tf.keras.Model(
inputs=modele.input,
outputs=[modele.get_layer(nom_couche).output, modele.output],
)

with tf.GradientTape() as tape:
cartes, predictions = modele_grad(image)
score = predictions[:, indice_classe]

# تدرّج الصنف بالنسبة إلى خرائط الميزات
grads = tape.gradient(score, cartes)
# متوسّط التدرّجات لكلّ قناة → أوزان
poids = tf.reduce_mean(grads, axis=(0, 1, 2))
# جمع القنوات موزونًا
carte_heatmap = tf.reduce_sum(cartes[0] * poids, axis=-1)
# ReLU وتطبيع إلى [0, 1]
carte_heatmap = tf.nn.relu(carte_heatmap)
carte_heatmap = carte_heatmap / (tf.reduce_max(carte_heatmap) + 1e-8)
return carte_heatmap.numpy()

يبقى فقط تطبيق العملية على صورة اختبار:

import matplotlib.pyplot as plt
from matplotlib import cm

def superposer(image, heatmap, alpha=0.5):
# تكبير الخريطة إلى حجم الصورة
heatmap = tf.image.resize(heatmap[..., tf.newaxis],
(image.shape[0], image.shape[1]))
heatmap = heatmap.numpy().squeeze()
# تحويل إلى ألوان
jet = cm.get_cmap("jet")
couleurs = jet(np.uint8(heatmap * 255))[:, :, :3]
# التقاطع
superposition = image * (1 - alpha) + couleurs * 255 * alpha
return np.clip(superposition.astype(np.uint8), 0, 255)

image = ... # صورة زجاجة معالجة مسبقًا
classe = 0 # مؤشّر «زجاج»
heatmap = grad_cam(modele, image[np.newaxis], classe)
plt.imshow(superposer(image, heatmap))

قراءة النتيجة: النموذج ينظر أين؟

نأخذ عشر صور من مجموعة النفايات، ونطبّق Grad-CAM على كلّ منها. النتائج المُرضية:

  • زجاجة زجاجية: الخريطة الحرارية تُبرز عنق الزجاجة وحوافّها اللامعة. النموذج يعتمد على الميزات المتوقّعة.
  • علبة معدن: التركيز على القطر المعدني اللامع في الأعلى، ونمط الطباعة على الجانب. جيّد.
  • كرتون: القوام المموّج في الحافّة. مقبول.

النتائج المقلقة:

  • بلاستيك أزرق فاتح: الخريطة تُبرز بشكل شديد الخلفية البيضاء للحزام الناقل، ليس الزجاجة نفسها. علامة إنذار خطيرة.
  • ورق: التركيز على الظلّ حول الورقة، لا على الورقة نفسها.

اكتشفنا شيئًا لم يظهر في مقاييس الدقّة: نموذجنا يعتمد جزئيًا على الخلفية لبعض الأصناف. سيفشل عندما تتغيّر بيئة الفرز.

دقّة عالية لا تعني نموذجًا صحيحًا

النموذج الذي يبلغ 93 % قد يفعل ذلك لأسباب خاطئة تمامًا. أشهر مثال في الأدب: نموذج تشخيص السلّ الرئوي بلغ 98 % في المستشفى المُدرَّب، ثم انهار في مستشفى آخر. Grad-CAM كشف أنّه يتعرّف على شعار المستشفى في زاوية الصورة، لا على السلّ. المقاييس تكذب أحيانًا، Grad-CAM يكشف الكذب.

تصحيح النموذج بناءً على Grad-CAM

عندما نكتشف اعتمادًا على الخلفية، لدينا عدّة خيارات:

1. تكثير أكثر عدوانية للخلفية: يُنشئ خلفيات صناعية مختلفة تجبر النموذج على تجاهل الخلفية. أدوات مثل CopyPaste أو Cutmix تُنتج صورًا مركّبة.

2. اقتصاصات مركزّة: إذا كنّا نعرف موقع الغرض تقريبًا، اقتصاصات عشوائية على الغرض قبل التمرير.

3. تعديل خسارة: خسارة إضافية تعاقب النموذج عندما يركّز خارج قناع الغرض (إذا كان لدينا أقنعة). هذا يُسمّى «التعلّم بالإشراف الجزئي».

4. إعادة جمع بيانات: قد يكون الحلّ الأنظف. صور بأرضيات مختلفة، بإضاءات مختلفة، بزوايا مختلفة.

خرائط البروز البسيطة: بديل حسّاس

Grad-CAM ينظر إلى الطبقات العميقة، فيُعطي مناطق كبيرة. خرائط البروز (saliency maps) تنظر إلى الطبقة الأولى: التدرّج للصنف بالنسبة للـبكسل نفسه:

Sij=ycxijS_{ij} = \left| \frac{\partial y^c}{\partial x_{ij}} \right|

النتيجة تحبيبيّة جدًّا: نقاط منتشرة في الصورة. صعبة القراءة بالعين، لكنّها تُعطي دقّة أعلى في التوطين. اعتُبرت هذه الطريقة أساسًا للتفسير قبل Grad-CAM، لكنّها أقلّ استعمالًا اليوم.

def saliency_map(modele, image, indice_classe):
image_var = tf.Variable(image)
with tf.GradientTape() as tape:
predictions = modele(image_var)
score = predictions[:, indice_classe]
grads = tape.gradient(score, image_var)
saliency = tf.reduce_max(tf.abs(grads), axis=-1)
return saliency[0].numpy()

حدود التفسير البصري

Grad-CAM أداة مفيدة، لكن لها ثلاث حدود يجب أن تُذكَر:

1. التفسير غير حصري: خريطة حرارية تُظهر منطقة أثّرت، لكنّها لا تُثبت أنّها كانت السبب الوحيد. النموذج ينظر إلى كلّ الصورة، والخريطة تُظهر التركيز الأكبر فقط.

2. الحساسية للأسلوب: هناك عائلة من طرق التفسير (Guided Backprop، Integrated Gradients، SmoothGrad، Grad-CAM++)، وكلّ منها يعطي خريطة مختلفة قليلًا. لا يوجد تفسير «صحيح»، هناك أساليب متكاملة.

3. الخداع الممكن: يمكن إنتاج صور معدَّلة قليلًا (adversarial) تُنتج خرائط حرارية مضلّلة. Adebayo وآخرون (2018) أظهروا أنّ بعض الطرق تُنتج خرائط شبيهة جدًّا حتى عندما تُخلَط أوزان النموذج عشوائيًا، ما يعني أنّها لا تعكس فعلًا ما تعلّمه النموذج. Grad-CAM أفضل الطرق مقاومةً في هذا الاختبار، لكنّها ليست معصومة.

قواعد استعمال Grad-CAM بحكمة
  • لا تكتفِ بعيّنة واحدة: طبّق Grad-CAM على 30 إلى 50 صورة من كلّ صنف، وابحث عن الأنماط.
  • راقب الأخطاء الحقيقية: صور صنّفها النموذج خطأً، وGrad-CAM يُظهر لماذا.
  • قارن مع أسلوب آخر: SmoothGrad وGrad-CAM يتّفقان في الحقائق المهمّة، ويختلفان في التفاصيل.
  • لا تعتمد Grad-CAM كمقياس تحقّق نهائي: هو أداة استكشاف، ليس شهادة صحّة.

تطبيق شامل: تدقيق نموذج النفايات

جمعنا 100 صورة اختبار من كلّ صنف. طبّقنا Grad-CAM. تفحّصنا النتائج بالعين. النتائج:

الصنفالدقّةGrad-CAM يُظهر
زجاج96 %العنق والحواف اللامعة (جيّد)
كرتون91 %القوام المموّج (جيّد)
ورق88 %الظلّ والحواف (مقبول لكن مقلق)
بلاستيك87 %مركّز على المصنّف أحيانًا، على الخلفية أحيانًا (سيّئ)
معدن94 %القطر واللمعان (جيّد)
عضوي82 %القوام واللون (جيّد)

ما نكتشفه: نموذجنا يعمل جيّدًا على الأصناف ذات الميزات المميّزة (زجاج، معدن)، ويكافح على البلاستيك حيث يخلط بين الأشياء المختلفة. الحلّ: زيادة تنوّع صور البلاستيك وتوسيع تعريف الصنف.

هذا هو الفارق بين مقاييس الدقّة الرقمية والفهم الحقيقي للنموذج. Grad-CAM لا يُصلح النموذج، لكنّه يُرشد إلى ما يحتاج إلى إصلاح.

في الخلاصة

  • Grad-CAM يحسب التدرّج بالنسبة لخرائط الميزات الأخيرة، ثم يجمعها موزونةً لينتج خريطة حرارية تُظهر مناطق الصورة التي أثّرت في تنبّؤ الصنف.
  • التنفيذ يعتمد على GradientTape في Keras: نموذج فرعي يُخرج الخرائط والتنبّؤات، ثم تدرّج الصنف، ثم مجموع موزون بمتوسّط التدرّجات.
  • النموذج قد ينظر إلى الخلفية حتى مع دقّة عالية؛ ومقاييس التقييم وحدها لا تكشف هذا الاعتماد الخاطئ، لكنّ Grad-CAM يفعل.
  • التفسير البصري له حدود: غير حصري، حسّاس للأسلوب، وقابل للخداع؛ استعمله كأداة استكشاف واقرن نتائجه بخبرة الميدان.

الوحدة التالية: المراجعة والاختبار، حيث نُلخّص خيوط الدورة العشرة ونستعدّ لامتحان الأربعين سؤالًا.