الدرس 5 من 8

التدريب الاحترافي

الفرق بين كود بيشتغل في درس، وكود بيشتغل على داتا حقيقية.

الـ Mini-batches

في الدرس اللي فات دخّلنا الداتا كلها مرة واحدة. مع مليون صورة ده مستحيل، الذاكرة مش هتكفي. الحل: نقسمها batches صغيرة (مثلاً ٦٤ مثال)، وناخد خطوة update بعد كل batch. فاكر الـ generator اللي عمل batches في المرحلة ٢؟ الـ DataLoader بيعمل كده بالظبط، وكمان بيلخبط الترتيب كل epoch.

dataloader.py
import torch
from torch.utils.data import TensorDataset, DataLoader

X = torch.rand(1000, 5)
y = torch.randint(0, 2, (1000, 1)).float()

loader = DataLoader(TensorDataset(X, y), batch_size=64, shuffle=True)

print("batches per epoch:", len(loader))
xb, yb = next(iter(loader))
print("one batch:", xb.shape, yb.shape)

Validation و Dropout و Early Stopping

هنقسم الداتا ٣ أجزاء: train يتعلم منه، وvalidation نراقب بيه الأداء وقت التدريب، وtest للنهاية بس. وهنضيف Dropout: طبقة بتقفل نسبة عشوائية من الـ neurons وقت التدريب، فالشبكة متعتمدش على neuron بعينه، وده بيقلل الحفظ. زي فريق كورة بيتمرن وكل مرة لاعيبة مختلفة غايبة، فالكل يتعلم يلعب.

train_pro.py
import copy
import pandas as pd
import torch
from torch import nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

torch.manual_seed(0)
df = pd.read_csv("churn.csv")
df["yearly"] = (df["contract"] == "yearly").astype(int)
X = df[["tenure", "bill", "complaints", "data_gb", "yearly"]].values
y = df["churned"].values

X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
scaler = StandardScaler().fit(X_tr)
to_t = lambda a: torch.tensor(a, dtype=torch.float32)
train_loader = DataLoader(TensorDataset(to_t(scaler.transform(X_tr)), to_t(y_tr).unsqueeze(1)),
                          batch_size=64, shuffle=True)
X_val_t, y_val_t = to_t(scaler.transform(X_val)), to_t(y_val).unsqueeze(1)

model = nn.Sequential(nn.Linear(5, 64), nn.ReLU(), nn.Dropout(0.3),
                      nn.Linear(64, 1))
loss_fn = nn.BCEWithLogitsLoss()
opt = torch.optim.Adam(model.parameters(), lr=0.003)

best_loss, best_state, patience, wait = float("inf"), None, 8, 0
for epoch in range(200):
    model.train()                                   # dropout ON
    for xb, yb in train_loader:
        opt.zero_grad()
        loss_fn(model(xb), yb).backward()
        opt.step()

    model.eval()                                    # dropout OFF
    with torch.no_grad():
        val_loss = loss_fn(model(X_val_t), y_val_t).item()

    if val_loss < best_loss:                        # improved -> keep this version
        best_loss, best_state, wait = val_loss, copy.deepcopy(model.state_dict()), 0
    else:
        wait += 1
        if wait >= patience:                        # early stopping
            print(f"stopped at epoch {epoch}: no improvement for {patience} epochs")
            break
    if epoch % 5 == 0:
        print(f"epoch {epoch:3}: val loss = {val_loss:.4f}")

model.load_state_dict(best_state)
torch.save(model.state_dict(), "churn_net.pt")      # save the weights
print(f"best val loss: {best_loss:.4f} (saved)")
⚠ model.train() و model.eval()لازم تقول للموديل هو في وضع تدريب ولا تقييم. في الـ eval الـ Dropout بيقف. لو نسيت model.eval() وانت بتتوقع، النتايج هتبقى عشوائية شوية كل مرة ومش هتفهم ليه.

لما التدريب ميمشيش كويس

جرب: learning rate أكبر أو أصغر (جرب 0.1 و 0.001)، اتأكد إنك عامل scaling، واتأكد إن الـ labels صح. وجرب الأول تخلي الموديل يحفظ batch واحدة صغيرة: لو معرفش، يبقى فيه bug في الكود.

الدرس اللي فات