Machine Learning: تقييم النموذج والضبط

آخر تحديث: 2026-08-26

إذا كانت منهجية التقييم الخاصة بك خاطئة، فإن جميع التحسينات مبنية على الرمال — قس بدقة أولًا، ثم اضبط.

1. ما ستتعلمه


2. قصة حقيقية من مهندس ML

(1) المشكلة: دقة 95% على مجموعة الاختبار، 75% بعد النشر

قيّم Bob نموذجه بـ train_test_split وحصل على دقة اختبار 95%. ولكن بعد أسبوع من النشر، كانت دقة العالم الحقيقي 75% فقط — لأنه مع بيانات السلاسل الزمنية، تداخلت توزيعات التدريب والاختبار، وكان النموذج "يتلصص" على معلومات المستقبل. أدت منهجية التقييم المعيبة إلى مقاييس متضخمة وإطلاق كارثي.

(2) الإصلاح: منهجية التقييم الصحيحة

يجب أن تستخدم بيانات السلاسل الزمنية TimeSeriesSplit — تحتوي مجموعة التدريب على البيانات الماضية فقط، ومجموعة الاختبار هي بيانات مستقبلية، مما يحاكي ظروف العالم الحقيقي.

PYTHON
from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
    # train_idx دائمًا < test_idx (لا تسرب مستقبلي)
    model.fit(X[train_idx], y[train_idx])
    score = model.score(X[test_idx], y[test_idx])

(3) النتيجة: مقاييس تعكس الأداء الحقيقي

أعاد Bob التقييم باستخدام TimeSeriesSplit، وانخفضت الدقة من 95% "متضخمة" إلى 82% "واقعية". انخفض الرقم، لكن دقة ما بعد النشر كانت 82% أيضًا — التقييم والواقع تطابقا أخيرًا، دون مزيد من المفاجآت غير السارة.


3. منهجية التقييم

(1) أربع استراتيجيات للتحقق المتقاطع

100%
graph TB
    DATA[مجموعة البيانات] --> HO[Hold-out<br/>تقسيم تدريب/اختبار واحد]
    DATA --> KF[K-Fold CV<br/>تقسيمات عشوائية]
    DATA --> SKF[Stratified K-Fold<br/>تقسيمات متوازنة الفئة]
    DATA --> TSS[TimeSeriesSplit<br/>تقسيمات زمنية]
    
    HO --> HO_USE[خط أساس سريع<br/>مجموعة بيانات كبيرة]
    KF --> KF_USE[للأغراض العامة<br/>تقدير مستقر]
    SKF --> SKF_USE[التصنيف<br/>بيانات غير متوازنة]
    TSS --> TSS_USE[سلسلة زمنية<br/>لا تسرب مستقبلي]

▶ مثال: مقارنة أربع استراتيجيات للتحقق المتقاطع

PYTHON
from sklearn.model_selection import (train_test_split, KFold, StratifiedKFold,
                                       TimeSeriesSplit, cross_val_score)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
import numpy as np

X, y = load_iris(return_X_y=True)
model = RandomForestClassifier(n_estimators=50, random_state=42)

# 1. Hold-out
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
model.fit(X_tr, y_tr)
holdout_score = model.score(X_te, y_te)

# 2. K-Fold
kf_scores = cross_val_score(model, X, y, cv=KFold(n_splits=5, shuffle=True, random_state=42))

# 3. Stratified K-Fold
skf_scores = cross_val_score(model, X, y, cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=42))

# 4. TimeSeriesSplit (للبيانات المرتبة زمنيًا)
tss_scores = cross_val_score(model, X, y, cv=TimeSeriesSplit(n_splits=5))

print(f"Hold-out:           {holdout_score:.4f}")
print(f"K-Fold:             {kf_scores.mean():.4f} +/- {kf_scores.std():.4f}")
print(f"Stratified K-Fold:  {skf_scores.mean():.4f} +/- {skf_scores.std():.4f}")
print(f"TimeSeriesSplit:    {tss_scores.mean():.4f} +/- {tss_scores.std():.4f}")

Output:

TEXT 📖 للعرض فقط
# Execution successful
الطريقة نوع البيانات المزايا العيوب
Hold-out أي سريع النتيجة تعتمد على التقسيم
K-Fold أي مستقر تقسيم عشوائي
Stratified K-Fold التصنيف يحافظ على نسب الفئة التصنيف فقط
TimeSeriesSplit سلسلة زمنية لا تسرب مستقبلي حجم تدريب متزايد

4. مقاييس التصنيف والانحدار

(1) مقاييس التصنيف الكاملة

▶ مثال: تقييم تصنيف شامل

PYTHON
from sklearn.metrics import (accuracy_score, precision_score, recall_score,
                               f1_score, roc_auc_score, classification_report,
                               confusion_matrix)
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

pipe = Pipeline([("scaler", StandardScaler()), ("model", LogisticRegression(max_iter=500))])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
y_prob = pipe.predict_proba(X_test)[:, 1]

print(f"Accuracy:  {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred):.4f}")
print(f"Recall:    {recall_score(y_test, y_pred):.4f}")
print(f"F1:        {f1_score(y_test, y_pred):.4f}")
print(f"AUC-ROC:   {roc_auc_score(y_test, y_prob):.4f}")
print(f"\nConfusion Matrix:\n{confusion_matrix(y_test, y_pred)}")
print(f"\nDetailed Report:\n{classification_report(y_test, y_pred)}")

Output:

TEXT 📖 للعرض فقط
# Execution successful

(2) مقاييس الانحدار المتوائمة مع أهداف العمل

▶ مثال: مقاييس الانحدار ومعناها التجاري

PYTHON
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np

y_true = np.array([100, 150, 200, 250, 300])  # ألف دولار
y_pred = np.array([95, 160, 190, 260, 310])

mae = mean_absolute_error(y_true, y_pred)
rmse = np.sqrt(mean_squared_error(y_true, y_pred))
r2 = r2_score(y_true, y_pred)
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100

print(f"MAE:  {mae:.2f} k USD")
print(f"RMSE: {rmse:.2f} k USD")
print(f"R²:   {r2:.4f}")
print(f"MAPE: {mape:.1f}%")

# حساب التأثير التجاري
avg_monthly_revenue = 1000  # ألف دولار
mape_pct = mape / 100
inventory_cost_pct = 0.3  # 30% من المخزون الزائد هو هدر
annual_loss = avg_monthly_revenue * mape_pct * inventory_cost_pct * 12
print(f"\nAnnual inventory loss from prediction error: {annual_loss:.0f} k USD")

Output:

TEXT 📖 للعرض فقط
# Execution successful
المقياس الصيغة المعنى التجاري
MAE متوسط|y-ŷ| متوسط الانحراف (ألف دولار)
RMSE √(متوسط(y-ŷ)²) يعاقب الأخطاء الكبيرة أكثر
1 - SS_res/SS_tot نسبة التباين المفسّر
MAPE متوسط|y-ŷ|/y × 100% الخطأ النسبي (%)

5. ضبط المعاملات

(1) ثلاث استراتيجيات بحث

▶ مثال: GridSearch مقابل RandomSearch

PYTHON
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
import time

X, y = fetch_california_housing(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

param_grid = {
    "n_estimators": [50, 100, 200],
    "max_depth": [5, 10, 15, None],
    "min_samples_leaf": [1, 5, 10],
}

# GridSearch: بحث شامل
start = time.time()
grid = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring="r2", n_jobs=-1)
grid.fit(X_train, y_train)
grid_time = time.time() - start

# RandomSearch: أخذ عينات عشوائية
start = time.time()
random = RandomizedSearchCV(RandomForestRegressor(random_state=42), param_grid,
                             n_iter=15, cv=3, scoring="r2", n_jobs=-1, random_state=42)
random.fit(X_train, y_train)
random_time = time.time() - start

print(f"GridSearch:   R²={grid.best_score_:.4f}, Time={grid_time:.1f}s, Trials={len(grid.cv_results_['mean_test_score'])}")
print(f"RandomSearch: R²={random.best_score_:.4f}, Time={random_time:.1f}s, Trials=15")

Output:

TEXT 📖 للعرض فقط
# Execution successful
الطريقة استراتيجية البحث التغطية السرعة الأفضل لـ
GridSearch شامل لجميع التركيبات 100% بطيء معاملات قليلة
RandomSearch أخذ عينات عشوائية جزئي سريع معاملات كثيرة
Optuna تحسين Bayesian بحث موجّه فعّال فضاءات بحث معقدة

(2) تحسين Optuna Bayesian

▶ مثال: ضبط المعاملات باستخدام Optuna

PYTHON
# pip install optuna
import optuna
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
import numpy as np

X, y = fetch_california_housing(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

def objective(trial):
    n_estimators = trial.suggest_int("n_estimators", 50, 500)
    max_depth = trial.suggest_int("max_depth", 3, 20)
    min_samples_leaf = trial.suggest_int("min_samples_leaf", 1, 20)
    max_features = trial.suggest_float("max_features", 0.3, 1.0)

    model = RandomForestRegressor(
        n_estimators=n_estimators, max_depth=max_depth,
        min_samples_leaf=min_samples_leaf, max_features=max_features,
        random_state=42, n_jobs=-1,
    )
    scores = cross_val_score(model, X_train, y_train, cv=3, scoring="r2")
    return scores.mean()

study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=30, show_progress_bar=False)

print(f"Best R²: {study.best_value:.4f}")
print(f"Best params: {study.best_params}")

Output:

TEXT 📖 للعرض فقط
# Function defined successfully

6. تشخيص فرط التجهيز

(1) منحنيات التعلم ومنحنيات التحقق

▶ مثال: رسم منحنيات التعلم

PYTHON
from sklearn.model_selection import learning_curve, validation_curve
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import fetch_california_housing
import matplotlib.pyplot as plt
import numpy as np

X, y = fetch_california_housing(return_X_y=True)

# منحنى التعلم: الأداء مقابل حجم مجموعة التدريب
train_sizes, train_scores, val_scores = learning_curve(
    RandomForestRegressor(n_estimators=50, random_state=42),
    X, y, train_sizes=np.linspace(0.1, 1.0, 10),
    cv=3, scoring="r2", n_jobs=-1,
)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# منحنى التعلم
axes[0].plot(train_sizes, train_scores.mean(axis=1), "o-", label="Training")
axes[0].plot(train_sizes, val_scores.mean(axis=1), "o-", label="Validation")
axes[0].fill_between(train_sizes, train_scores.mean(axis=1) - train_scores.std(axis=1),
                     train_scores.mean(axis=1) + train_scores.std(axis=1), alpha=0.1)
axes[0].fill_between(train_sizes, val_scores.mean(axis=1) - val_scores.std(axis=1),
                     val_scores.mean(axis=1) + val_scores.std(axis=1), alpha=0.1)
axes[0].set_xlabel("Training Size")
axes[0].set_ylabel("R² Score")
axes[0].set_title("Learning Curve")
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# منحنى التحقق: الأداء مقابل المعامل
param_range = [3, 5, 7, 10, 15, 20, None]
train_scores2, val_scores2 = validation_curve(
    RandomForestRegressor(n_estimators=50, random_state=42),
    X, y, param_name="max_depth", param_range=param_range,
    cv=3, scoring="r2", n_jobs=-1,
)

axes[1].plot(range(len(param_range)), train_scores2.mean(axis=1), "o-", label="Training")
axes[1].plot(range(len(param_range)), val_scores2.mean(axis=1), "o-", label="Validation")
axes[1].set_xticks(range(len(param_range)))
axes[1].set_xticklabels([str(p) for p in param_range])
axes[1].set_xlabel("max_depth")
axes[1].set_ylabel("R² Score")
axes[1].set_title("Validation Curve")
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig("learning_validation_curves.png", dpi=150)

Output:

TEXT 📖 للعرض فقط
# Execution successful
التشخيص نمط المنحنى المعنى الحل
فرط التجهيز المنخفض كل من نتائج التدريب والتحقق منخفضة النموذج بسيط جدًا زيادة التعقيد / إضافة ميزات
فرط التجهيز تدريب عالٍ، تحقق منخفض النموذج معقد جدًا تنظيم / تبسيط / إضافة بيانات
ملاءمة جيدة كلاهما عالٍ ومتقارب متوازن جيدًا لا حاجة لتغييرات
يحتاج مزيدًا من البيانات منحنى التحقق لا يزال يرتفع بيانات غير كافية جمع مزيد من البيانات

❓ أسئلة شائعة

س كيف أختار عدد الطيات للتحقق المتقاطع؟
ج 5 أو 10 هي الخيارات الأكثر شيوعًا. توفر 5-fold توازنًا جيدًا بين السرعة والاستقرار؛ 10-fold أكثر استقرارًا ولكن أبطأ بمرتين. لمجموعات البيانات الكبيرة (>50 ألف عينة)، 3 طيات كافية؛ لمجموعات البيانات الصغيرة (<1 ألف عينة)، استخدم 10 طيات.
س لماذا لا يمكنني استخدام K-Fold لبيانات السلاسل الزمنية؟
ج يقوم K-Fold بخلط البيانات عشوائيًا، مما قد يؤدي إلى التدريب على بيانات "مستقبلية" للتنبؤ بـ "الماضي"، مما يسبب تسرب البيانات. يضمن TimeSeriesSplit أن مجموعة التدريب دائمًا تسبق مجموعة الاختبار، مما يحاكي سيناريوهات التنبؤ الحقيقي.
س ماذا لو كان GridSearchCV بطيئًا جدًا؟
ج ثلاث استراتيجيات — 1) تقليص فضاء البحث (بحث خشن أولًا، ثم دقيق)؛ 2) استخدم RandomizedSearchCV (n_iter=20)؛ 3) استخدم تحسين Optuna Bayesian (بحث ذكي).
س ما عيوب MAPE؟
ج ينفجر MAPE عندما تقترب y_true من الصفر (القسمة على قيمة قريبة من الصفر). البدائل — استخدم SMAPE (MAPE المتماثل) أو MASE (متوسط الخطأ المطلق المقيس).
س ماذا لو كانت الفجوة بين نتائج التدريب والتحقق في منحنى التعلم كبيرة؟
ج فجوة كبيرة = فرط التجهيز. الحلول — 1) زيادة التنظيم؛ 2) تقليل تعقيد النموذج؛ 3) إضافة مزيد من بيانات التدريب؛ 4) استخدام Dropout / التوقف المبكر.
س كم هو أفضل Optuna من GridSearch؟
ج يستخدم Optuna تحسين Bayesian لاختيار المجموعة التالية من المعاملات بذكاء. يجد عادة في 30-50 تجربة ما يحتاج GridSearch إلى 500+ تجربة لإيجاده، مما يوفر أكثر من 80% من موارد الحوسبة.

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): قارن متوسط الدقة والانحراف المعياري لـ RandomForestClassifier على Iris باستخدام 5-fold و 10-fold تحقق متقاطع. تلميح: cross_val_score(cv=5/10).
  2. متوسط (الصعوبة ⭐⭐): استخدم GridSearchCV لإيجاد المعاملات المثلى لـ XGBoost (max_depth / learning_rate / n_estimators)، وأخرج أفضل المعاملات و R² للتحقق المتقاطع. تلميح: Pipeline + param_grid.
  3. تحدٍّ (الصعوبة ⭐⭐⭐): ارسم منحنى تعلم ومنحنى تحقق لتشخيص فرط التجهيز/فرط التجهيز المنخفض في نموذج، واقترح خطة تحسين ملموسة، وتحقق من التحسين. تلميح: learning_curve + validation_curve + مقارنة قبل/بعد.

← الدرس السابق: الشبكات العصبية التلافيفية | الدرس التالي: مقدمة في MLOps →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%