الدرس 6 من 8

الشبكات الالتفافية (CNN)

الموديلات اللي بتتعرف على الوشوش والأرقام والأمراض في الأشعة.

المشكلة مع الشبكة العادية

لو فردت صورة ٢٢٤×٢٢٤ ملونة لمتجه، هيبقى ١٥٠ ألف رقم، وأول طبقة لوحدها هتبقى ملايين الأوزان. والأهم: الشبكة العادية مش فاهمة إن البكسلات اللي جنب بعض مرتبطة، ولا إن القطة هي هي لو اتحركت يمين شوية.

الـ Convolution: عدسة صغيرة بتمسح الصورة

الفكرة: فلتر صغير (مثلاً ٣×٣) بيتحرك على الصورة كلها، وعند كل مكان بيعمل dot product مع البكسلات اللي تحته. فلتر معين ممكن «يولّع» عند الحواف الرأسية، وتاني عند المنحنيات. والسحر إن الـ CNN بيتعلم الفلاتر دي لوحده.

convolution.py
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_sample_image

photo = load_sample_image("china.jpg").mean(axis=2)[::3, ::3]   # grayscale, smaller

vertical_edges = np.array([[-1, 0, 1],
                           [-1, 0, 1],
                           [-1, 0, 1]])    # a 3x3 filter

def convolve(img, kernel):
    h, w = img.shape[0] - 2, img.shape[1] - 2
    out = np.zeros((h, w))
    for i in range(h):
        for j in range(w):
            out[i, j] = np.sum(img[i:i + 3, j:j + 3] * kernel)  # slide & dot
    return out

fig, axes = plt.subplots(1, 2, figsize=(9, 3.2))
axes[0].imshow(photo, cmap="gray"); axes[0].set_title("original")
axes[1].imshow(np.abs(convolve(photo, vertical_edges)), cmap="gray"); axes[1].set_title("vertical edges")
for ax in axes: ax.axis("off")
plt.tight_layout()
plt.show()

الفلتر ده لقط الحواف الرأسية بس: أعمدة المبنى وحوافه بقت منورة، والسما والأسطح الناعمة بقت سودا. الطبقات الأولى في الـ CNN بتتعلم حاجات بسيطة زي كده، واللي بعدها بتجمعهم لأشكال، واللي بعدها لأجزاء (عين، ودن)، لحد ما آخر طبقة تقول «قطة».

Conv2d طبقة فيها فلاتر كتير بتتعلمها.
channels عدد الفلاتر، أو الألوان في المدخل (3 للـ RGB).
MaxPool بتصغّر الصورة بإنها تاخد أكبر قيمة في كل مربع صغير.
Flatten بتفرد الناتج لمتجه عشان يدخل طبقة عادية في الآخر.

CNN بيقرا أرقام بخط الإيد

cnn_digits.py
import torch
from torch import nn
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

torch.manual_seed(0)
digits = load_digits()                       # 1797 images, 8x8, digits 0-9
X = torch.tensor(digits.images / 16.0, dtype=torch.float32).unsqueeze(1)  # (N, 1, 8, 8)
y = torch.tensor(digits.target)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0)

model = nn.Sequential(
    nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(),   # 16 learned filters
    nn.MaxPool2d(2),                                         # 8x8 -> 4x4
    nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(),
    nn.MaxPool2d(2),                                         # 4x4 -> 2x2
    nn.Flatten(),
    nn.Linear(32 * 2 * 2, 10),                               # 10 classes
)
loss_fn = nn.CrossEntropyLoss()              # multi-class (softmax inside)
opt = torch.optim.Adam(model.parameters(), lr=0.01)

for epoch in range(30):
    model.train()
    for i in range(0, len(X_tr), 64):
        opt.zero_grad()
        loss_fn(model(X_tr[i:i + 64]), y_tr[i:i + 64]).backward()
        opt.step()

model.eval()
with torch.no_grad():
    preds = model(X_te).argmax(dim=1)
print(f"test accuracy: {(preds == y_te).float().mean():.1%}")
print("first 10 predictions:", preds[:10].tolist())
print("true labels:         ", y_te[:10].tolist())
🤖 نفس الكود، صور أكبرالأرقام هنا ٨×٨ بس. لصور حقيقية بتزود طبقات Conv وقنوات، والكود نفسه تقريباً مابيتغيرش. وفي الغالب مش هتبني CNN من الصفر أصلاً، هتستخدم موديل متدرب جاهز، ودي فكرة آخر درس.
الدرس اللي فات