الدرس 4 من 8
أول موديل بـ PyTorch
نرجع لعملاء شركة الاتصالات، بس بشبكة عصبية.
تعريف الموديل
كل موديل في PyTorch عبارة عن class بيورث من nn.Module (فاكر الوراثة في المرحلة ٢؟). في __init__ بتعرّف الطبقات، وفي forward بتقول الداتا تمشي فيهم إزاي.
import torch from torch import nn class ChurnNet(nn.Module): def __init__(self, n_features): super().__init__() self.layers = nn.Sequential( nn.Linear(n_features, 32), # 5 inputs -> 32 neurons nn.ReLU(), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 1), # one output: churn score (logit) ) def forward(self, x): return self.layers(x) model = ChurnNet(n_features=5) print(model) print("parameters:", sum(p.numel() for p in model.parameters()))
الـ Training Loop
ده الكود اللي هتكتبه في كل مشروع تقريباً، بنفس الخطوات الأربعة اللي عملناها بـ NumPy:
import pandas as pd import torch from torch import nn import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import roc_auc_score torch.manual_seed(42) # ---- data (same churn.csv from phase 5) ---- df = pd.read_csv("churn.csv") df["yearly"] = (df["contract"] == "yearly").astype(int) features = ["tenure", "bill", "complaints", "data_gb", "yearly"] X_train, X_test, y_train, y_test = train_test_split( df[features], df["churned"], test_size=0.2, random_state=42, stratify=df["churned"]) scaler = StandardScaler().fit(X_train) # neural nets NEED scaling X_train = torch.tensor(scaler.transform(X_train), dtype=torch.float32) X_test = torch.tensor(scaler.transform(X_test), dtype=torch.float32) y_train = torch.tensor(y_train.values, dtype=torch.float32).unsqueeze(1) # ---- model, loss, optimizer ---- model = nn.Sequential(nn.Linear(5, 32), nn.ReLU(), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 1)) loss_fn = nn.BCEWithLogitsLoss() # binary classification loss optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # ---- training loop ---- losses = [] for epoch in range(150): logits = model(X_train) # 1. forward loss = loss_fn(logits, y_train) # 2. loss optimizer.zero_grad() # clear old gradients loss.backward() # 3. backward optimizer.step() # 4. update weights losses.append(loss.item()) # ---- evaluate ---- with torch.no_grad(): # no gradients needed here probs = torch.sigmoid(model(X_test)).squeeze().numpy() print(f"final train loss: {losses[-1]:.3f}") print(f"test AUC: {roc_auc_score(y_test, probs):.3f}") plt.figure(figsize=(7, 3.2)) plt.plot(losses, color="#3776AB", linewidth=2) plt.xlabel("epoch"); plt.ylabel("loss"); plt.title("Training loss") plt.grid(alpha=0.3) plt.show()
BCEWithLogitsLoss loss للتصنيف الثنائي، وبتعمل الـ sigmoid جواها.Adam optimizer ذكي بيظبط حجم الخطوة لكل وزن لوحده. الاختيار الافتراضي الكويس.zero_grad() بتمسح الـ gradients القديمة، لأن PyTorch بيجمعها فوق بعض.no_grad() بتقول لـ PyTorch ميسجلش عمليات، فالتوقع يبقى أسرع وأخف.الشبكة اشتغلت، والـ loss نزل بشكل سليم. بس لو قارنت بالمرحلة ٥، الـ Logistic Regression جاب AUC أعلى على نفس الداتا! ده مش فشل، ده بالظبط اللي قلناه: داتا جدولية صغيرة وعلاقاتها بسيطة مش محتاجة شبكة عصبية. فايدة الدرس ده إنك تتقن الـ training loop، لأنه هو هو اللي هتستخدمه مع الصور والنصوص حيث الشبكات مالهاش منافس.
⚠ أشهر ٣ غلطاتنسيان
optimizer.zero_grad() (الـ gradients بتتراكم والتدريب يبوظ). ونسيان الـ scaling (الشبكات حساسة جداً للمقاييس). وإنك تحسب الـ scaler على الداتا كلها بدل الـ train بس (leakage زي ما اتعلمنا).