تخطَّ إلى المحتوى

١٨ سبتمبر ٢٠٢٦

لماذا نموذج ML عندك يعمل Overfitting — وكيف تكتشفه وتصلحه

شارك المقال:

هذه الصفحة جزء من سلسلة 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.

📚 مصادر رسمية للتعمّق: freeCodeCamp — مصدر تعلّم البرمجة

الأسئلة الشائعة

كيف أعرف أن نموذجي يعمل overfitting؟

العلامة الأوضح: train accuracy عالية (مثل 99%) لكن test accuracy أقل بكثير (مثل 70%). قِس الفرق. إذا كان الفرق > 10-15%، فأنت في overfitting. العلامات الأخرى: النموذج حسّاس لتغييرات صغيرة في البيانات (نفس البيانات مع shuffle مختلف → نتائج مختلفة).

ما الفرق بين overfitting و underfitting؟

Overfitting: train high + test low. النموذج حفظ بيانات التدريب. Underfitting: train low + test low. النموذج بسيط جدًا ولم يتعلّم شيئًا. الحل مختلف: overfitting = تبسيط/regularization، underfitting = تعقيد/ميزات أكثر.

هل جمع بيانات أكثر يحلّ overfitting؟

نعم، غالبًا. البيانات الأكثر تعطي النموذج أنماطًا عامة بدل حفظ نقاط فردية. إذا لم تستطع جمع بيانات أكثر، regularization و تبسيط النموذج و dropout (للشبكات العصبية) هي البدائل.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.