الدرس 7 من 9

الـ Overfitting و Cross-Validation

أشهر مشكلة في الـ ML، وإزاي تكتشفها وتعالجها.

حافظ ضد فاهم

طالب حفظ إجابات امتحانات السنين اللي فاتت حرف حرف. في المراجعة بيجيب الدرجة النهائية، وفي الامتحان الحقيقي بيسقط لأن الأسئلة اتغيرت. ده Overfitting: الموديل ممتاز على الـ train وضعيف على الـ test. والعكس اسمه Underfitting: الموديل بسيط زيادة ومش فاهم حتى الـ train.

شوفها بعينك

هنكبر عمق الشجرة تدريجياً ونقيس الأداء على الاتنين:

overfit.py
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

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.3,
    random_state=42, stratify=df["churned"])

depths = range(1, 21)
train_acc, test_acc = [], []
for d in depths:
    tree = DecisionTreeClassifier(max_depth=d, random_state=42).fit(X_train, y_train)
    train_acc.append(tree.score(X_train, y_train))
    test_acc.append(tree.score(X_test, y_test))

plt.figure(figsize=(7, 3.6))
plt.plot(depths, train_acc, marker="o", label="train", color="#3776AB")
plt.plot(depths, test_acc, marker="o", label="test", color="#E0A800")
plt.xlabel("tree depth")
plt.ylabel("accuracy")
plt.title("Deeper tree: memorizing, not learning")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

الخط الأزرق (train) بيطلع لحد ما يوصل لـ ١٠٠٪ تقريباً: الشجرة حفظت الداتا. والأصفر (test) بيطلع في الأول وبعدين بينزل. أحسن عمق هو اللي عنده الأصفر أعلى حاجة، مش الأزرق.

العلاج

  • بسّط الموديل: عمق أقل، أو عدد أوراق أقل في كل فرع.
  • داتا أكتر: أصعب إنك تحفظ مليون مثال.
  • Regularization: عقاب للموديل لما يبقى معقد زيادة (هتقابله كتير في الـ Deep Learning).
  • Ensembles: زي الـ Random Forest، الأصوات الكتير بتلغي الحفظ.

الـ Cross-Validation

تقسيمة train/test واحدة ممكن تبقى محظوظة أو منحوسة. الـ K-Fold Cross-Validation بتقسم الداتا ٥ أجزاء، وكل مرة تمتحن في جزء وتذاكر من الـ ٤ الباقيين، وتاخد المتوسط. تقييم أثبت بكتير.

cv.py
import pandas as pd
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

df = pd.read_csv("churn.csv")
df["yearly"] = (df["contract"] == "yearly").astype(int)
features = ["tenure", "bill", "complaints", "data_gb", "yearly"]

scores = cross_val_score(
    RandomForestClassifier(200, min_samples_leaf=5, random_state=42),
    df[features], df["churned"], cv=5, scoring="roc_auc")

print("Each fold:", scores.round(3))
print(f"Mean AUC: {scores.mean():.3f} (+/- {scores.std():.3f})")

تظبيط الإعدادات أوتوماتيك

الإعدادات اللي انت بتختارها (زي max_depth) اسمها hyperparameters، على عكس الأوزان اللي الموديل بيتعلمها. GridSearchCV بيجرب كل التركيبات بالـ cross-validation ويقولك الأحسن:

grid.py
import pandas as pd
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier

df = pd.read_csv("churn.csv")
df["yearly"] = (df["contract"] == "yearly").astype(int)
features = ["tenure", "bill", "complaints", "data_gb", "yearly"]

grid = GridSearchCV(
    DecisionTreeClassifier(random_state=42),
    param_grid={"max_depth": [2, 3, 4, 5, 6, 8, 12],
                "min_samples_leaf": [1, 5, 20]},
    cv=5, scoring="roc_auc")
grid.fit(df[features], df["churned"])

print("Best settings:", grid.best_params_)
print(f"Best CV AUC: {grid.best_score_:.3f}")
الدرس اللي فات