Loading
Loading
Convolutional Neural Networks (CNN) ثورت مجال رؤية الحاسوب. من تصنيف الصور إلى السيارات ذاتية القيادة — كلها تعتمد على CNN.
صورة 224×224 RGB = 150,528 pixel. إذا استخدمنا MLP:
طبقة واحدة: 150,528 × 512 = 77 مليون وزن!
المشاكل:
بدلاً من ربط كل pixel بكل عصبون، نستخدم Filter صغير يتحرك على الصورة:
صورة 6×6 × Filter 3×3 = Feature Map 4×4
الـ Filter يتعلم:
Conv Layer:
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1)
# 3 قنوات (RGB) → 64 Feature Map
Pooling Layer (MaxPool):
يُقلّص حجم Feature Map بنصف
يحتفظ بالمعلومات الأهم
يُضفي ثباتاً (الشيء يُعرَّف بغض النظر عن موضعه)
Batch Normalization:
تُسرّع التدريب + تُقلّل Overfitting + تُحسّن الاستقرار
Input (224×224×3)
↓
Conv → BN → ReLU → MaxPool
↓
Conv → BN → ReLU → MaxPool
↓
Conv → BN → ReLU
↓
Global Average Pooling
↓
FC → Softmax (Output)
| المعمارية | السنة | المميزة | |----------|-------|---------| | AlexNet | 2012 | أول انتصار كبير على ImageNet | | VGG16 | 2014 | بسيطة وعميقة (16 طبقة) | | ResNet | 2015 | Residual Connections — حلّت Vanishing Gradient | | EfficientNet | 2019 | دقة عالية بمعاملات أقل | | ViT | 2020 | Transformer للصور (يُزاحم CNN) |
بدلاً من التدريب من الصفر، نبدأ من نموذج مُدرَّب مسبقاً:
from torchvision.models import resnet50, ResNet50_Weights
model = resnet50(weights=ResNet50_Weights.IMAGENET1K_V1)
# تجميد الطبقات المُدرَّبة
for param in model.parameters():
param.requires_grad = False
# استبدال الطبقة الأخيرة فقط
model.fc = nn.Linear(2048, num_classes)
# الآن ندرّب fc فقط على بياناتنا
لماذا يعمل؟ الطبقات الأولى تعلّمت حوافاً وألواناً — هذه مفيدة لأي صورة.
transforms.Compose([
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomRotation(15),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.RandomCrop(224, padding=4),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
يُضاعف بيانات التدريب افتراضياً ويُقلّل Overfitting.
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
print("=" * 55)
print("CNN: تصنيف الصور — PyTorch")
print("=" * 55)
device = "cuda" if torch.cuda.is_available() else "cpu"
# ─────────────────────────────────────────
# 1. بيانات مصطنعة (تحاكي CIFAR-10)
# ─────────────────────────────────────────
torch.manual_seed(42)
np.random.seed(42)
n_train, n_val = 500, 100
num_classes = 5 # 5 فئات مبسطة
# صور 3×32×32
X_train = torch.randn(n_train, 3, 32, 32)
y_train = torch.randint(0, num_classes, (n_train,))
X_val = torch.randn(n_val, 3, 32, 32)
y_val = torch.randint(0, num_classes, (n_val,))
# محاكاة أنماط (فئة 0 تميل للأحمر، إلخ)
for cls in range(num_classes):
mask_tr = (y_train == cls)
mask_vl = (y_val == cls)
X_train[mask_tr, cls % 3] += 0.5
X_val[mask_vl, cls % 3] += 0.5
train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=32, shuffle=True)
val_loader = DataLoader(TensorDataset(X_val, y_val), batch_size=32)
print(f"\n1. بيانات: {n_train} train / {n_val} val")
print(f" شكل الصور: {X_train.shape[1:]} (channels × height × width)")
# ─────────────────────────────────────────
# 2. معمارية CNN
# ─────────────────────────────────────────
class SmallCNN(nn.Module):
def __init__(self, num_classes: int = 5):
super().__init__()
# Block 1: 3 → 32 channels, 32×32 → 16×16
self.block1 = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # 32×32 → 16×16
)
# Block 2: 32 → 64 channels, 16×16 → 8×8
self.block2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # 16×16 → 8×8
)
# Block 3: 64 → 128 channels, 8×8 → 4×4
self.block3 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # 8×8 → 4×4
)
# Classifier
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool2d(1), # 128×4×4 → 128×1×1
nn.Flatten(), # → 128
nn.Dropout(0.4),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, num_classes),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
return self.classifier(x)
model = SmallCNN(num_classes=num_classes).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)
params = sum(p.numel() for p in model.parameters())
print(f"\n2. CNN Architecture: {params:,} معامل")
# ─────────────────────────────────────────
# 3. التدريب
# ─────────────────────────────────────────
def train(model, loader, opt, crit):
model.train()
total_loss = correct = total = 0
for X, y in loader:
X, y = X.to(device), y.to(device)
opt.zero_grad()
out = model(X)
loss = crit(out, y)
loss.backward()
opt.step()
total_loss += loss.item() * len(y)
correct += (out.argmax(1) == y).sum().item()
total += len(y)
return total_loss / total, correct / total
def evaluate(model, loader, crit):
model.eval()
total_loss = correct = total = 0
with torch.no_grad():
for X, y in loader:
X, y = X.to(device), y.to(device)
out = model(X)
total_loss += crit(out, y).item() * len(y)
correct += (out.argmax(1) == y).sum().item()
total += len(y)
return total_loss / total, correct / total
print("\n3. التدريب (15 epoch):")
print(f" {'Epoch':6s} | {'Train Loss':10s} | {'Train Acc':9s} | {'Val Acc':8s}")
print(" " + "-" * 45)
for epoch in range(1, 16):
tr_loss, tr_acc = train(model, train_loader, optimizer, criterion)
vl_loss, vl_acc = evaluate(model, val_loader, criterion)
if epoch % 5 == 0 or epoch == 1:
print(f" {epoch:6d} | {tr_loss:10.4f} | {tr_acc:9.1%} | {vl_acc:8.1%}")
# ─────────────────────────────────────────
# 4. مقارنة: CNN vs MLP على نفس البيانات
# ─────────────────────────────────────────
print("\n4. مقارنة: CNN vs MLP")
mlp = nn.Sequential(
nn.Flatten(),
nn.Linear(3*32*32, 256), nn.ReLU(), nn.Dropout(0.3),
nn.Linear(256, 128), nn.ReLU(),
nn.Linear(128, num_classes)
).to(device)
opt_mlp = optim.Adam(mlp.parameters(), lr=0.001)
for _ in range(15):
train(mlp, train_loader, opt_mlp, criterion)
_, mlp_acc = evaluate(mlp, val_loader, criterion)
_, cnn_acc = evaluate(model, val_loader, criterion)
mlp_params = sum(p.numel() for p in mlp.parameters())
cnn_params = sum(p.numel() for p in model.parameters())
print(f" {'النموذج':8s} | {'Params':>10s} | {'Val Acc':8s}")
print(f" {'MLP':8s} | {mlp_params:>10,} | {mlp_acc:.1%}")
print(f" {'CNN':8s} | {cnn_params:>10,} | {cnn_acc:.1%} ← أقل معاملات، دقة أعلى")