هذه الصفحة جزء من سلسلة Troubleshooting. تشرح لماذا نموذج ML يعمل overfitting في الواقع، و كيف تكتشفه، و ما الإصلاحات الفعّالة.
ملاحظة: هذه الصفحة تركّز على التشخيص والإصلاح العملي. للشرح النظري الكامل، راجع Overfitting و Bias-Variance.
العلامات الأولى
1. فجوة كبيرة بين train و test
Train Accuracy: 0.99 ← مريب
Test Accuracy: 0.71 ← ضعيف
الفرق: 0.28 ← مؤشر قوي على overfitting
القاعدة: الفرق > 0.10 (10%) يعني غالبًا overfitting. كلما زاد الفرق، زادت المشكلة.
2. النموذج حسّاس لبيانات التدريب
# اختبر: غيّر random_state
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
scores_default = cross_val_score(
DecisionTreeClassifier(random_state=42), X, y, cv=5
)
scores_different = cross_val_score(
DecisionTreeClassifier(random_state=99), X, y, cv=5
)
print(f"Default state: {scores_default.mean():.3f} ± {scores_default.std():.3f}")
print(f"Different state: {scores_different.mean():.3f} ± {scores_different.std():.3f}")
# إذا كانت std عالية (> 0.10) أو الفرق بين الـ runs كبير → instability
3. التعقيد يرفع train لكن يخفض test
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier
train_scores, test_scores = [], []
for depth in [1, 3, 5, 10, 20, None]:
model = DecisionTreeClassifier(max_depth=depth, random_state=42)
model.fit(X_train, y_train)
train_scores.append(model.score(X_train, y_train))
test_scores.append(model.score(X_test, y_test))
plt.plot([1, 3, 5, 10, 20, 50], train_scores, label="Train")
plt.plot([1, 3, 5, 10, 20, 50], test_scores, label="Test")
plt.xlabel("max_depth")
plt.ylabel("Score")
plt.legend()
plt.show()
Train accuracy: يتصاعد مع العمق، يصل ~100%
Test accuracy: يتصاعد ثم ينخفض — عندها overfitting يبدأ
الأسباب الخمسة الأكثر شيوعًا
1. بيانات تدريب قليلة
عدد العينات: 100
عدد المعاملات في النموذج: 5000
→ النموذج "يحفظ" بدلاً من "يتعلّم"
الحل: اجمع بيانات أكثر. إذا غير ممكن → قلل تعقيد النموذج.
2. نموذج معقّد جدًا
Decision Tree بعمق 50، Neural Network بملايين المعاملات — كل هذا قد يكون overfitting على بيانات قليلة.
الحل: استخدم max_depth, min_samples_leaf, n_estimators المناسب. لا تتركها بالقيمة الافتراضية الضخمة.
3. تطبيع (Normalization) خاطئ
# ❌ خطأ كلاسيكي
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # ← يحسب mean/std على كل البيانات
X_train, X_test = train_test_split(X_scaled, y)
# ✅ صح
X_train, X_test = train_test_split(X, y)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
هذا الـ data leakage يجعل train accuracy عالية جدًا لكن test accuracy أقلّ. ليس overfitting بالمعنى الدقيق، لكن يبدو مثله.
4. ضبط hyperparameters على test set
# ❌ خطأ: تختار أفضل نموذج بناءً على test score
for depth in [3, 5, 10, 20]:
model = DecisionTreeClassifier(max_depth=depth)
model.fit(X_train, y_train)
score = model.score(X_test, y_test) # ← الآن test "تسرّب"
if score > best_score:
best_depth = depth
# النتيجة: test accuracy متفائلة لكنها غير صادقة
5. تدريب طويل جدًا (للشبكات العصبية)
النموذج يصل إلى optimal generalization في مرحلة معيّنة، ثم يبدأ بالـ overfitting مع استمرار التدريب.
الحل: Early stopping — احفظ النموذج عند أفضل validation score.
تشخيص عملي — خطوة بخطوة
الخطوة 1: قِس الفجوة
from sklearn.metrics import accuracy_score
train_pred = model.predict(X_train)
test_pred = model.predict(X_test)
train_acc = accuracy_score(y_train, train_pred)
test_acc = accuracy_score(y_test, test_pred)
gap = train_acc - test_acc
print(f"Train: {train_acc:.3f}, Test: {test_acc:.3f}, Gap: {gap:.3f}")
if gap > 0.15:
print("⚠️ علامات واضحة لـ overfitting")
elif gap > 0.05:
print("⚠️ علامات خفيفة لـ overfitting")
else:
print("✅ الفجوة صغيرة — النموذج جيّد")
الخطوة 2: اعرض learning curves
from sklearn.model_selection import learning_curve
import numpy as np
train_sizes, train_scores, val_scores = learning_curve(
model, X, y, cv=5, train_sizes=np.linspace(0.1, 1.0, 5)
)
train_mean = train_scores.mean(axis=1)
val_mean = val_scores.mean(axis=1)
plt.plot(train_sizes, train_mean, label="Train")
plt.plot(train_sizes, val_mean, label="Validation")
plt.xlabel("Training set size")
plt.ylabel("Score")
plt.legend()
plt.show()
القراءة:
- Train عالي، val منخفض، والفجوة لا تضيق بزيادة البيانات → overfitting.
- Train و val قريبان، كلاهما منخفض → underfitting.
- Train و val عاليان، والفجوة صغيرة → جيّد.
الخطوة 3: اعرض residuals (للانحدار)
import matplotlib.pyplot as plt
y_pred = model.predict(X_test)
residuals = y_test - y_pred
plt.scatter(y_pred, residuals)
plt.axhline(y=0, color='r')
plt.xlabel("Predicted")
plt.ylabel("Residual (actual - predicted)")
plt.title("Residual Plot")
plt.show()
إذا رأيت نمطًا واضحًا (U-shape مثلًا)، النموذج يفوّت بنية.
الإصلاحات الفعّالة
1. جمع بيانات أكثر
الأقوى والأكثر فعالية. إذا كانت لديك 100 عيّنة الآن وجمع 1000، الفرق سيكون كبيرًا.
قبل: train 95%, test 70%, gap 25%
بعد: train 92%, test 85%, gap 7% ← الفجوة تضيق
2. تبسيط النموذج
# بدلاً من
model = RandomForestClassifier(n_estimators=500, max_depth=None)
# جرّب
model = RandomForestClassifier(n_estimators=100, max_depth=10, min_samples_leaf=5)
3. Regularization
يضيف عقوبة على المعاملات الكبيرة، يجبر النموذج على البساطة.
# L1 (Lasso): يدفع بعض المعاملات إلى 0
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(penalty='l1', C=0.1, solver='liblinear')
# L2 (Ridge): يصغّر المعاملات
from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)
# ElasticNet: مزيج
from sklearn.linear_model import ElasticNet
model = ElasticNet(alpha=0.1, l1_ratio=0.5)
4. Cross-Validation الصحيح
from sklearn.model_selection import cross_val_score
# ✅ آمن: cross_val_score مع Pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipe = Pipeline([('scaler', StandardScaler()), ('model', model)])
scores = cross_val_score(pipe, X, y, cv=5)
print(f"CV: {scores.mean():.3f} ± {scores.std():.3f}")
5. Dropout (للشبكات العصبية)
يعطّل عشوائيًا جزءًا من الوحدات أثناء التدريب، يجبر الشبكة على تعلّم أنماط أقوى.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(784, 256),
nn.Dropout(0.5), # ← 50% dropout
nn.ReLU(),
nn.Linear(256, 10),
nn.Dropout(0.5)
)
6. Data Augmentation
إضافة تنويعات للبيانات الموجودة:
# للصور: دوران، انعكاس، تكبير
# للنصوص: ترجمة، paraphrasing
# للصوت: ضوضاء خفيفة، تغيير السرعة
7. Early Stopping
للشبكات العصبية: احفظ النموذج عند أفضل validation score.
from pytorch_lightning.callbacks import EarlyStopping
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
trainer = Trainer(callbacks=[early_stopping])
ما لا يصلح
- "أضف ميزات أكثر": قد يزيد overfitting، لا يخفّفه. (لكن قد يخفف underfitting.)
- "استخدم نموذج أعقد": عكس المطلوب تمامًا.
- "درّب أكثر": يزيد overfitting أكثر.
- "اختر النموذج بأعلى test accuracy": data leakage.
قائمة فحص سريعة
قبل أن تنشر نموذجك، اسأل:
- train accuracy ≤ 95% (إذا 100%، مريب)
- gap بين train و test ≤ 10%
- CV score قريب من test score
- النموذج مستقرّ عبر random_state مختلفة
- Regularization مُطبَّق
- Pipeline يحمي من data leakage
- لا توجد ميزة "تشبه" target (data leakage)
مثال كامل — تشخيص وإصلاح
from sklearn.datasets import make_classification
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# 1. بيانات
X, y = make_classification(n_samples=200, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# 2. نموذج معقّد بدون ضبط → overfitting محتمل
model_bad = DecisionTreeClassifier(max_depth=None, random_state=42)
model_bad.fit(X_train, y_train)
print("=== النموذج غير المضبوط ===")
print(f"Train: {model_bad.score(X_train, y_train):.3f}")
print(f"Test: {model_bad.score(X_test, y_test):.3f}")
# 3. نموذج مضبوط
pipe_good = Pipeline([
('scaler', StandardScaler()),
('model', DecisionTreeClassifier(max_depth=5, min_samples_leaf=5, random_state=42))
])
pipe_good.fit(X_train, y_train)
print("\n=== النموذج المضبوط ===")
print(f"Train: {pipe_good.score(X_train, y_train):.3f}")
print(f"Test: {pipe_good.score(X_test, y_test):.3f}")
# 4. Cross-Validation
scores = cross_val_score(pipe_good, X, y, cv=5)
print(f"\nCV: {scores.mean():.3f} ± {scores.std():.3f}")
الخلاصة
- العلامة الأولى: فجوة train-test > 10%.
- أكثر الأسباب شيوعًا: بيانات قليلة + نموذج معقّد.
- أقوى الإصلاحات: جمع بيانات أكثر + تبسيط النموذج + regularization.
- احذر data leakage الذي يبدو مثل overfitting.
🔗 للشرح النظري الكامل، راجع Overfitting و Bias-Variance.