Machine Learning: أشجار القرار والغابات العشوائية
آخر تحديث: 2026-08-26
شجرة القرار مثل شجرة من الأسئلة — كل عقدة تطرح سؤالًا واحدًا، وتتبع الإجابة لأسفل إلى ورقة، وهي تنبؤك.
1. ما ستتعلمه
- أساسيات شجرة القرار: كسب المعلومات / نسبة الكسب / معامل جيني، وعائلة خوارزميات ID3 / C4.5 / CART
- تقليم الشجرة: التقليم المسبق (max_depth / min_samples_leaf) والتقليم اللاحق
- الغابة العشوائية: فكرة Bagging، واختيار الميزات العشوائي، وتقدير OOB
- تحليل أهمية الميزات: متوسط تناقص الشوائب مقابل أهمية التبديل
- تصنيف فئة المنتج لـ Bob: استخدام الغابة العشوائية للتنبؤ بما إذا كان المنتج سيصبح رائجًا
2. قصة حقيقية من مدير منتج التجارة الإلكترونية
(1) نقطة الألم: التنبؤ بالمنتجات الرائجة اعتمد كليًا على الحدس، بمعدل نجاح أقل من 30%
كان Bob بحاجة لاختيار المنتجات الرائجة المحتملة من بين 1000 منتج جديد. في الماضي كان هذا حكمًا بحتًا، وكان معدل نجاحه 30% فقط. كل منتج رائج يحقق في المتوسط 500 ألف دولار من الإيرادات الشهرية، لذلك تفويت واحد يمثل خسارة ضخمة. الحدس لا يمكن تحديده كميًا أو إعادة استخدامه أو التكرار عليه.
(2) حل الغابة العشوائية
يمكن للغابة العشوائية اكتشاف أنماط المنتجات الرائجة تلقائيًا من بيانات المنتجات التاريخية — نطاق السعر، خصائص الفئة، اتجاهات مبيعات الأسبوع الأول — وتوفر بشكل طبيعي ترتيبًا لأهمية الميزات.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, oob_score=True, random_state=42)
rf.fit(X_train, y_train)
print(f"OOB Accuracy: {rf.oob_score_:.3f}")
print(f"Top feature: {feature_names[rf.feature_importances_.argmax()]}")
(3) النتيجة: ارتفعت دقة التنبؤ بالمنتجات الرائجة من 30% إلى 65%
استبدل Bob الحدس بالغابة العشوائية، مما رفع دقة التنبؤ بالمنتجات الرائجة من 30% إلى 65%. يحدد الآن 5 منتجات رائجة إضافية كل شهر، مما يضيف ما يقرب من 3 ملايين دولار في الإيرادات السنوية.
3. كيف تعمل أشجار القرار
(1) معايير التقسيم
في كل عقدة، تختار شجرة القرار أفضل ميزة وعتبة لتقسيم البيانات عليها. توجد ثلاثة معايير شائعة:
graph TB
ROOT[العقدة الجذرية<br/>جميع البيانات] --> SPLIT1{الميزة: السعر<br/>العتبة: 50 دولار}
SPLIT1 -->|≤ 50| LEFT[الطفل الأيسر<br/>60% منتجات رائجة]
SPLIT1 -->|> 50| RIGHT[الطفل الأيمن<br/>10% منتجات رائجة]
LEFT --> SPLIT2{الميزة: مبيعات الأسبوع الأول}
SPLIT2 -->|> 500| LEAF1[الورقة: رائج<br/>ثقة 90%]
SPLIT2 -->|≤ 500| LEAF2[الورقة: ليس رائجًا<br/>ثقة 40%]
RIGHT --> LEAF3[الورقة: ليس رائجًا<br/>ثقة 5%]
| المعيار | الصيغة الأساسية | التفضيل | عائلة الخوارزمية |
|---|---|---|---|
| كسب المعلومات | $H(D) - H(D|A)$ | يفضل الميزات متعددة القيم | ID3 |
| نسبة الكسب | كسب المعلومات / القيمة الجوهرية | تصحيح تحيز متعدد القيم | C4.5 |
| شائبة جيني | $1 - \sum p_i^2$ | يفضل نقاء الفئة الكبيرة | CART |
▶ مثال: تصنيف شجرة القرار على Iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42, stratify=iris.target
)
# التدريب مع التقليم المسبق
tree = DecisionTreeClassifier(max_depth=3, min_samples_leaf=5, random_state=42)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"Tree depth: {tree.get_depth()}")
print(f"Number of leaves: {tree.get_n_leaves()}")
# تصور الشجرة
fig, ax = plt.subplots(figsize=(14, 8))
plot_tree(tree, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True, rounded=True, ax=ax)
plt.title("Decision Tree (max_depth=3)")
plt.tight_layout()
plt.savefig("decision_tree.png", dpi=150)
Output:
# Runs successfully
(2) استراتيجيات التقليم
▶ مثال: مقارنة معاملات التقليم المسبق
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
configs = {
"No pruning": DecisionTreeClassifier(random_state=42),
"max_depth=2": DecisionTreeClassifier(max_depth=2, random_state=42),
"max_depth=3": DecisionTreeClassifier(max_depth=3, random_state=42),
"min_samples_leaf=5": DecisionTreeClassifier(min_samples_leaf=5, random_state=42),
"max_depth=3 + min_samples_leaf=5": DecisionTreeClassifier(
max_depth=3, min_samples_leaf=5, random_state=42),
}
for name, model in configs.items():
scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
model.fit(X, y)
print(f"{name:35s}: Accuracy={scores.mean():.3f}, Leaves={model.get_n_leaves()}")
Output:
# Runs successfully
| نوع التقليم | المعامل | التأثير | القيمة الموصى بها |
|---|---|---|---|
| التقليم المسبق | max_depth | يحد من عمق الشجرة | 3-10 |
| التقليم المسبق | min_samples_leaf | الحد الأدنى للعينات لكل ورقة | 5-20 |
| التقليم المسبق | min_samples_split | الحد الأدنى للعينات للتقسيم | 10-40 |
| التقليم المسبق | max_features | الميزات التي يتم النظر فيها لكل تقسيم | sqrt(n_features) |
| التقليم اللاحق | ccp_alpha | تقليم تعقيد التكلفة | اختر عبر GridSearch |
4. الغابة العشوائية
(1) فكرة Bagging
الغابة العشوائية = Bagging (تجميع البوتستراب) + اختيار الميزات العشوائي.
graph TB
DATA[البيانات الأصلية] --> B1[عينة بوتستراب 1]
DATA --> B2[عينة بوتستراب 2]
DATA --> B3[عينة بوتستراب 3]
DATA --> BN[عينة بوتستراب N]
B1 --> T1[شجرة 1<br/>مجموعة ميزات عشوائية]
B2 --> T2[شجرة 2<br/>مجموعة ميزات عشوائية]
B3 --> T3[شجرة 3<br/>مجموعة ميزات عشوائية]
BN --> TN[شجرة N<br/>مجموعة ميزات عشوائية]
T1 --> VOTE[تصويت الأغلبية<br/>/ المتوسط]
T2 --> VOTE
T3 --> VOTE
TN --> VOTE
▶ مثال: تصنيف الغابة العشوائية + تقييم OOB
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
rf = RandomForestClassifier(
n_estimators=100,
max_depth=None,
oob_score=True,
random_state=42,
)
rf.fit(X_train, y_train)
print(f"OOB Score: {rf.oob_score_:.4f}")
print(f"Test Accuracy: {accuracy_score(y_test, rf.predict(X_test)):.4f}")
print(f"Number of trees: {rf.n_estimators}")
Output:
# Runs successfully
(2) ضبط المعاملات
▶ مثال: انحدار الغابة العشوائية — التنبؤ بمبيعات SalesPredict
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np
rng = np.random.default_rng(42)
n = 500
X = rng.uniform(0, 100, (n, 6))
y = 50 + 0.8 * X[:, 0] + 1.2 * X[:, 1] - 0.5 * X[:, 2] + rng.normal(0, 5, n)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# مقارنة n_estimators المختلفة
for n_est in [10, 50, 100, 200]:
rf = RandomForestRegressor(n_estimators=n_est, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
score = rf.score(X_test, y_test)
mae = mean_absolute_error(y_test, rf.predict(X_test))
print(f"n_estimators={n_est:3d}: R²={score:.4f}, MAE={mae:.2f}")
Output:
# Runs successfully
5. تحليل أهمية الميزات
(1) متوسط تناقص الشوائب مقابل أهمية التبديل
▶ مثال: مقارنة طريقتين لأهمية الميزات
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np
X, y = load_iris(return_X_y=True)
feature_names = load_iris().feature_names
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
# الطريقة 1: MDI (مدمجة، سريعة لكن متحيزة)
mdi_importance = rf.feature_importances_
# الطريقة 2: أهمية التبديل (أبطأ لكن أكثر موثوقية)
perm_result = permutation_importance(rf, X, y, n_repeats=30, random_state=42, n_jobs=-1)
perm_importance = perm_result.importances_mean
# المقارنة
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
y_pos = np.arange(len(feature_names))
axes[0].barh(y_pos, mdi_importance, color="#2196F3")
axes[0].set_yticks(y_pos)
axes[0].set_yticklabels(feature_names)
axes[0].set_title("MDI Feature Importance")
axes[1].barh(y_pos, perm_importance, color="#4CAF50")
axes[1].set_yticks(y_pos)
axes[1].set_yticklabels(feature_names)
axes[1].set_title("Permutation Feature Importance")
plt.tight_layout()
plt.savefig("feature_importance.png", dpi=150)
Output:
# Runs successfully
| البُعد | أهمية MDI | أهمية التبديل |
|---|---|---|
| سرعة الحساب | سريعة (محسوبة أثناء التدريب) | بطيئة (تتطلب تنبؤات متكررة) |
| التحيز | متحيزة نحو ميزات عالية الكاردينالية | غير متحيزة |
| القابلية للتطبيق | مدمجة في النماذج الشجرية | تعمل مع أي نموذج |
| الموثوقية | معتدلة | أكثر موثوقية |
▶ مثال: ترتيب ميزات المنتجات الرائجة لـ Bob
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 1000
df = pd.DataFrame({
"price_usd": rng.uniform(10, 200, n),
"first_week_sales": rng.integers(10, 2000, n),
"category_trend_score": rng.uniform(0, 1, n),
"ad_budget_k": rng.uniform(1, 50, n),
"review_score": rng.uniform(1, 5, n),
"return_rate": rng.uniform(0, 0.3, n),
"stock_depth": rng.integers(10, 500, n),
})
df["is_hit"] = (
(df["first_week_sales"] > 500)
& (df["category_trend_score"] > 0.6)
& (df["price_usd"] < 100)
).astype(int) | (rng.random(n) < 0.05) # أضف 5% ضوضاء
X = df.drop(columns=["is_hit"])
y = df["is_hit"]
rf = RandomForestClassifier(n_estimators=200, random_state=42)
rf.fit(X, y)
importance = pd.DataFrame({
"feature": X.columns,
"importance": rf.feature_importances_,
}).sort_values("importance", ascending=False)
print("Feature Importance for Hit Product Prediction:")
print(importance.to_string(index=False))
Output:
Feature Importance for Hit Product Prediction:
❓ أسئلة شائعة
📖 ملخص
- تبني شجرة القرار بشكل متكرر باستخدام معيار التقسيم (كسب المعلومات / معامل جيني) حتى يتحقق شرط التوقف
- يمنع التقليم فرط التجهيز: التقليم المسبق (حدد العمق / عدد عينات الورقة) + التقليم اللاحق (ccp_alpha)
- الغابة العشوائية = Bagging + اختيار الميزات العشوائي، مما يقلل التباين ويقاوم فرط التجهيز
- درجة OOB هي التحقق المتقاطع "المجاني" للغابة العشوائية، لتحل محل تقييم تقسيم التدريب/الاختبار
- طريقتان لأهمية الميزات: MDI (سريعة لكن متحيزة) مقابل التبديل (بطيئة لكن موثوقة)
- الغابة العشوائية هي "سكين الجيش السويسري" في صندوق أدوات ML — دائمًا تقريبًا خيار خط أساس قوي
📝 تمارين
- أساسي (الصعوبة ⭐): استخدم DecisionTreeClassifier لتصنيف Iris، مع تنويع max_depth من 1 إلى 5، وارسم منحنى الدقة مقابل العمق. تلميح: حلقة على التدريب باستخدام
cross_val_score. - متوسط (الصعوبة ⭐⭐): استخدم RandomForestRegressor على California Housing، وقارن R² ودرجة OOB لـ n_estimators=[10,50,100,200]. تلميح: اضبط
oob_score=True. - تحدي (الصعوبة ⭐⭐⭐): نفّذ خط أنابيب التنبؤ بالمنتجات الرائجة الكامل لـ Bob — ولّد البيانات المحاكاة، ودرب RandomForestClassifier، وحلل الميزات باستخدام permutation_importance، وتعامل مع عدم التوازن باستخدام class_weight (المنتجات الرائجة < 10%)، وأخرج تقرير التصنيف. تلميح: راجع مثال المنتجات الرائجة في القسم 5.
← الدرس السابق: الانحدار اللوجستي | الدرس التالي: آلات المتجهات الداعمة →