الذكاء الاصطناعي | Transfer Learning

Transfer Learning

نعيد استخدام خبرة نموذج سبق تدريبه على مهمة كبيرة، ثم نكيفه على مهمتنا الخاصة بدلًا من البدء من الصفر.

Pretrained Feature Reuse Fine Tuning CNN

لماذا يوجد Transfer Learning؟

تخيّل أن الشبكة العصبية مثل طالب درس لسنوات طويلًا على رؤية الأشكال، الحواف، الألوان، الأنماط، والقطع المختلفة من الأشياء.

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

الاستعارة الذهنية: نموذج مُدرب مسبقًا = طالب يعرف كثيرًا بالفعل. مهمتنا الجديدة = نعلّمه فقط الجزء الجديد.
🧠
♻️
🎯
Pretrained model → reuse knowledge → adapt to new task

ما الذي يتعلمه النموذج؟

هذا رسم بسيط جدًا، وليس تفسيرًا حرفيًا لكل خلية. الفكرة هي أن الشبكة تتعلم شيئًا من البسيط إلى الأكثر تعقيدًا.
🟦
بكسلات
〰️
حواف
🔷
أشكال
👁️
أجزاء كائن
🐱
مفاهيم
🎯
تنبؤ
الطبقات المبكرة

تتعلم الحواف، الخطوط، الألوان، والأنماط البسيطة.

الطبقات المتوسطة

تتعلم قوالب وتراكيب أوسع مثل الأشكال والنسيج.

الطبقات المتأخرة

تتعلم أجزاء الكائنات مثل العيون، العجلات، الأذنين، الوجوه.

الطبقة النهائية

تتخذ القرار: هذا كائن أم نفاية أم مادة معينة.

ما معنى freezing؟

تخيّل أن الشبكة كفريق كبير من العمال. بعض العمال يعرفون مهارة قوية جدًا بالفعل، فنتوقف عن تغييرهم.

نقول لهم: “أبقوا كما أنتم” هذا هو freezing.

Frozen = الأوزان لا تتغير أثناء التدريب.

لكن الطبقة لا تختفي. هي ما زالت تعطي مخرجات وتستخدم المعلومات، لكنها لا تعيد كتابة خبرتها.

🔒Layer 1
🔒Layer 2
🔒Layer 3
🔓Layer 4
🔓Output layer
❌ ما لا يعنيه Freeze
  • • لا يعني اختفاء الطبقة
  • • لا يعني توقفها عن العمل
  • • لا يعني أنه لا يتم استخدام هذه المعلومات
✅ ما يعنيه Freeze
  • • لا تزال تعالج البيانات
  • • لا تزال تولد activations
  • • لكن الأوزان لا تتغير في هذه الطبقة

ما هو activation؟

الـ neuron ككاشف صغير. عندما يرى شيئًا مهمًا، يخرج إشارة أقوى.

هذا الإخراج يسمى activation.

Weight = ما تعلمه الخلية
Activation = ما تخرجه الخلية عند رؤية شيء ما

إذا كانت الخلية تلاحظ شدة لون أو زاوية أو شكل، فإخراجها يزداد.

قوة الإشارة
ضعيف | متوسط | قوي

المقارنة: frozen vs trainable

INPUT IMAGE
     ↓
┌───────────────┐
│ 🔒 Layer 1    │
│ ● ● ○ ● ●     │
└───────────────┘
     ↓
┌───────────────┐
│ 🔒 Layer 2    │
│ ● ○ ● ● ○     │
└───────────────┘
     ↓
┌───────────────┐
│ 🔓 Layer 3    │
│ ● ● ● ○ ●     │
└───────────────┘
     ↓
┌───────────────┐
│ 🔓 Classifier │
└───────────────┘
     ↓
   PREDICTION
الأهم: الطبقات المقفلة لا تزال تُفعّل المعلومات، لكنها لا تغير وزنها. الطبقات القابلة للتدريب هي التي تتعلم وتصحح نفسها أثناء التدريب.

لماذا نقفل الطبقات؟

بدون Transfer Learning
🐣 نموذج عشوائي
يتعلم كل شيء من الصفر
يحتاج بيانات كثيرة
يحتاج تدريب طويل
حساب أكثر
مع Transfer Learning
🧠 نموذج مُدرب
إعادة استخدام المعرفة
نقفل الطبقات المفيدة
نُدرب فقط الجزء الخاص بالمهمة
أسرع وأكثر فاعلية مع بيانات أقل

Transfer Learning vs Fine Tuning: الفرق الحقيقي

Transfer Learning
Reuse
🧠نأخذ نموذجًا مُدربًا مسبقًا.
🔒نثبت معظم الطبقات لأن لها معرفة قوية جدًا.
🧩نُدرب فقط الرأس الجديد أو الطبقة الأخيرة.
سريع جدًا، يحتاج بيانات أقل، ويفيد عند وجود وقت/مصادر محدودة.
المبدأ:
“أنا أستخدم المعرفة القديمة، لا أبدأ من الصفر.”
Fine Tuning
Adjust
🧠نبدأ من نفس النموذج المبدئي.
🔓نفتح بعض الطبقات الأخيرة، أو حتى معظمها، بحسب المهمة.
🎯نُعدّلها ببطء حتى تتناسب مع بياناتنا الجديدة.
📈يستغرق وقتًا أطول لكنه يعطي جودة أفضل عندما تكون البيانات كثيرة النوعية.
المبدأ:
“أنا أستعمل المعرفة القديمة، ثم أعدلها ببطء لتناسب مهمتي.”
المقارنة السريعة في صورة واحدة
🧱
Transfer Learning
أغلب الطبقات ثابتة، نُدرب فقط بعض التفاصيل.
🛠️
Fine Tuning
نفتح جزءًا من المعرفة القديمة ونعدّلها قليلاً.

الفرق في سطر واحد

Transfer Learning

“أخذ المعرفة من نموذج كبير، ثم استخدام تلك المعرفة مباشرة على المهمة الجديدة.”

freeze most layers
train new head
use previous knowledge
Fine Tuning

“أخذ المعرفة من نموذج كبير، ثم تعديلها تدريجيًا لتناسب المهمة الجديدة بشكل أفضل.”

unfreeze last layers
lower learning rate
adjust model carefully

ما الذي يحدث في الكود؟

Transfer Learning
for layer in model.layers:
    layer.trainable = False

# train only the new classifier
model.compile(...)
model.fit(train_data)
النتيجة: النموذج يستخدم المعرفة القديمة بسرعة، لكنه لا يغير كل الطبقات.
Fine Tuning
for layer in model.layers[-10:]:
    layer.trainable = True

optimizer = Adam(learning_rate=1e-5)
model.compile(optimizer=optimizer, loss=...)
model.fit(train_data)
النتيجة: النموذج يفتح بعض الخبرة القديمة ويعدلها ببطء ليتناسب مع المهمة.

رؤية لمخرجات التدريب

بعد Transfer Learning
Loss 0.42
Accuracy 78%
Training time قصير
“قوي جدًا في البداية، لكنه لم يطور كل التفاصيل بعد.”
بعد Fine Tuning
Loss 0.19
Accuracy 91%
Training time أطول
“يعدل الخبرة القديمة ببطء، فيحصل على دقة أعلى في مهمتنا الخاصة.”

الخلاصة البسيطة جدًا

Transfer Learning = أستخدم المعرفة الموجودة وأبني عليها بسرعة.
Fine Tuning = أستخدم نفس المعرفة، ثم أعدلها بعناية حتى تتناسب مع مهمتي.

إذا أردت الاختصار:
Transfer = reuse ، Fine Tuning = repair + adapt

الرابط الكامل للكود

هذا هو الـ notebook الكامل الذي نتابعه في المشروع الحقيقي:

Open Full Notebook
المعلومة المهمة: هذا الرابط هو المصدر الكامل للـ code. نستخدمه كدليل أثناء الشرح، ثم نضيف الفهم البصري خطوة بخطوة حتى تصبح العملية واضحة.

الجزء العملي: ما هي مجموعة البيانات؟

الـ dataset في الـ notebook يحتوي على صور للنفايات، وكل صورة لها تسمية توضح مكان كل كائن داخل الصورة.

المجلدات الأساسية كالتالي:

waste_detection/
├── train/
│ ├── images/
│ └── labels/
├── valid/
│ ├── images/
│ └── labels/
├── test/
│ ├── images/
│ └── labels/
└── data.yaml
بعض أسماء الفئات
Glass
Metal
Paper
Plastic
Waste

خطوة 1: قراءة فئات البيانات

def read_data_yaml(yaml_path):
    with open(yaml_path, 'r') as stream:
        data_config = yaml.safe_load(stream)
    return data_config.get("names", [])

data_yaml_path = "waste_detection/data.yaml"
class_names = read_data_yaml(data_yaml_path)
print("Class names from data.yaml:", class_names)
هذا الكود يفتح ملف data.yaml ويقرأ أسماء الفئات فقط. بهذه الطريقة نعرف أن النموذج سيكتشف: Glass، Metal، Paper، Plastic، Waste.

خطوة 2: معرفة عدد الصور في كل قسم

def count_images_in_folder(base_path):
    splits = ['train', 'valid', 'test']
    image_counts = {}

    for split in splits:
        image_folder = os.path.join(base_path, split, 'images')
        if os.path.exists(image_folder):
            image_counts[split] = len([
                f for f in os.listdir(image_folder)
                if f.lower().endswith(('.jpg', '.png', '.jpeg'))
            ])
    return image_counts

base_path = 'waste_detection'
results = count_images_in_folder(base_path)
for split, count in results.items():
    print(f"Number of images in {split}: {count}")
هذا الكود يجيب على سؤال مهم: “كم عدد الصور في كل قسم؟” لأن تدريب النموذج يحتاج تقسيمًا جيدًا للتدريب، والتحقق، والاختبار.

خطوة 3: عرض بعض الصور

def display_random_images_from_folder(folder_path, image_extensions=None, num_images=4):
    if image_extensions is None:
        image_extensions = ['.jpg', '.jpeg', '.png']

    image_files = [f for f in os.listdir(folder_path)
                   if os.path.splitext(f)[1].lower() in image_extensions]

    selected_files = random.sample(image_files, min(num_images, len(image_files)))

    plt.figure(figsize=(10, 8))
    for i, filename in enumerate(selected_files):
        image_path = os.path.join(folder_path, filename)
        image = cv2.imread(image_path)
        image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        plt.subplot(2, 2, i + 1)
        plt.imshow(image_rgb)
        plt.title(filename, fontsize=10)
        plt.axis('off')

    plt.tight_layout()
    plt.show()

display_random_images_from_folder("/content/waste_detection/train/images")
هذا الكود يفتح الصور بشكل عشوائي ليشبه “اللمحة الأولى” على البيانات. هذه الخطوة مهمة جدًا لأنها تشرح لنا إذا كانت الصور واضحة، وإذا كانت الفئات مألوفة، وإذا كان هناك تشويش أو مشاكل في البيانات.

خطوة 4: فهم ماذا يوجد داخل ملف التسميات

def polygon_to_bbox(polygon):
    xs = polygon[0::2]
    ys = polygon[1::2]
    min_x = min(xs)
    max_x = max(xs)
    min_y = min(ys)
    max_y = max(ys)

    x_center = (min_x + max_x) / 2.0
    y_center = (min_y + max_y) / 2.0
    width = max_x - min_x
    height = max_y - min_y
    return x_center, y_center, width, height

def convert_polygon_to_yolo_lines(input_file):
    yolo_lines = []
    with open(input_file, 'r') as f_in:
        for line in f_in:
            line = line.strip()
            if not line:
                continue
            tokens = line.split()
            cls = tokens[0]
            coords = list(map(float, tokens[1:]))
            x_center, y_center, width, height = polygon_to_bbox(coords)
            yolo_line = f"{cls} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}"
            yolo_lines.append(yolo_line)
    return yolo_lines
هذه هي نقطة مهمة جدًا في المشروع: ملف التسميات ليس في شكل YOLO مباشرة. لذلك نستخدم polygon-to-bounding-box لتحويل الشكل إلى مربع محيط. هذا المربع يتيح للنموذج أن يعرف أين يوجد الكائن داخل الصورة.

خطوة 5: ارسم المربع المحيط على الصورة

def visualize_polygon_annotations(image_path, label_path, class_list):
    image = cv2.imread(image_path)
    height, width = image.shape[:2]
    yolo_boxes = convert_polygon_to_yolo_lines(label_path)

    for yolo_line in yolo_boxes:
        tokens = yolo_line.strip().split()
        cls_idx = int(tokens[0])
        cls_name = class_list[cls_idx]
        x_center, y_center, w, h = map(float, tokens[1:])

        x_center *= width
        y_center *= height
        w *= width
        h *= height

        x1 = int(x_center - w / 2)
        y1 = int(y_center - h / 2)
        x2 = int(x_center + w / 2)
        y2 = int(y_center + h / 2)

        cv2.rectangle(image, (x1, y1), (x2, y2), color=(0, 255, 0), thickness=2)
        cv2.putText(image, cls_name, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 2)

    cv2_imshow(image)

visualize_polygon_annotations(image_path, label_path, class_names)
هذا الكود يترجم البيانات المجردة إلى شيء مرئي. نرى مربعًا أخضر حول الكائن، ونرى اسم الفئة فوقه. هذا ما يجعل النموذج “يرى” ما الذي نريد اكتشافه.

خطوة 6: تدريب YOLO

from ultralytics import YOLO

model = YOLO('yolov8n.pt')
run = model.train(
    data=str(DATA_YAML),
    epochs=20,
    imgsz=640,
    batch=16,
    project='runs',
    name='exp_epochs_20'
)
هذا هو اللحظة الحقيقية: نستخدم نموذج YOLO مُدربًا مسبقًا، ثم نُدربه على بيانات النفايات الخاصة بنا. هذا هو transfer learning عمليًا. النموذج لا يبدأ من الصفر؛ بل يبدأ بمعرفة تعلمها على صور كثيرة، ثم يكيف نفسه على هذا المشروع.

خطوة 7: تقييم النموذج

val_results = best_model.val(data=str(DATA_YAML), split='val')
print(f"VAL mAP@0.5    = {val_results.box.map50:.3f}")
print(f"VAL mAP@0.5:0.95 = {val_results.box.map:.3f}")

test_results = best_model.val(data=str(DATA_YAML), split='test')
print(f"TEST mAP@0.5    = {test_results.box.map50:.3f}")
بعد التدريب، نستخدم validation و test لمعرفة مدى جودة النموذج. mAP ببساطة يوضح مدى دقة النموذج في اكتشاف الأشياء. كلما ارتفع الرقم، كان النموذج أفضل في تمييز الأجسام داخل الصور.

خطوة 8: رؤية النموذج في العمل

results = best_model.predict(img_path, conf=0.25, verbose=False)
boxes = results[0].boxes.xyxy.cpu().numpy()
classes = results[0].boxes.cls.cpu().numpy()

for box, cls in zip(boxes, classes):
    x1, y1, x2, y2 = map(int, box)
    cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 0), 2)
    cv2.putText(img, class_names[int(cls)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
هذه هي اللحظة التي نرى فيها النموذج يطلق “عينيه” على الصور الجديدة. يحدد مربعات حول الأشياء، ثم يكتب اسم الفئة فوقها. هذا هو الدرس العملي في المشاهدة والفهم.

التلخيص الأبسط

1. نقرأ أسماء الفئات من data.yaml.
2. نراجع عدد الصور في كل قسم.
3. نعرض صورًا عشوائية لنتأكد من البيانات.
4. نفهم شكل التسميات ونعيد تحويلها إلى مربعات.
5. نستخدم نموذج YOLO مُدربًا مسبقًا ونُدرّبه على بياناتنا.
6. نقيس الأداء بمقاييس مثل mAP.
7. نرى النموذج يعمل على الصور الجديدة.
8. نستخدم هذه المعرفة في مشروع النفايات الفعلي.