Logo

أشجار القرار والغابة العشوائية

24 دقيقة قراءة
شرائح الدرس

تعلم الآلة، الأسبوع 6

أشجار القرار والغابة العشوائية

صنف المقترض بطرح الأسئلة، ثم دع أشجارا كثيرة تصوت: قروض Lending Club.

يقدم هذا القسم مصنفين (classifiers) يتنبآن بطرح الأسئلة. تطرح شجرة القرار (decision tree) على العينة سلسلة قصيرة من الأسئلة التي يجاب عنها بنعم أو لا، مثل "هل درجة الائتمان 677 على الأكثر؟"، ثم تقرأ الإجابة في نهاية السلسلة. أما الغابة العشوائية (random forest) فتنمي أشجارا كثيرة مختلفة وتدعها تصوت. ستنمي أولا شجرة صغيرة يدويا على عشرة مقترضين حقيقيين، ثم تدرب شجرة وغابة في scikit-learn على قروض Lending Club، وهي بيانات الأسبوع 6.

الأهداف

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

  • شرح كيف تصنف شجرة القرار عينة: سلسلة من أسئلة العتبة (threshold) تنتهي عند ورقة (leaf).
  • حساب عدم نقاء جيني (Gini impurity) لعقدة (node) وجيني الموزون (weighted Gini) لتقسيم (split) يدويا، واستخدامه لاختيار التقسيم.
  • شرح لماذا تطابق الشجرة التي تنمو حتى تصبح كل أوراقها نقية بيانات التدريب تماما، لكنها تتنبأ تنبؤا ضعيفا بالبيانات الجديدة.
  • شرح الأفكار الثلاث للغابة العشوائية: عينات bootstrap، ومجموعة جزئية عشوائية من الخصائص (features) عند كل تقسيم، والتصويت بالأغلبية (majority vote).
  • تدريب DecisionTreeClassifier وRandomForestClassifier على بيانات Lending Club، وقراءة مصفوفة الالتباس (confusion matrix) لكل منهما.
  • إدراك متى لا تعكس الدقة (accuracy) العالية إلا هدفا (target) غير متوازن، وتطبيق الخطوات نفسها على مشروع فريقك.

موقع الدرس من المقرر

للأسبوع 6 في الخطة ثلاثة أجزاء:

  • دفتر واحد. يدرب الدفتر (notebook) DecisionTrees_RandomForest_Classification شجرة قرار وغابة عشوائية على بيانات إقراض عامة.
  • سيناريو من الواقع. تربط Lending Club بين من يحتاجون إلى المال (المقترضين) ومن يملكون المال (المستثمرين). نحاول إنشاء نموذج يتنبأ بخطر إقراض المال لشخص ما بناء على مجموعة واسعة من البيانات الائتمانية: نتنبأ بما إذا كان المقترض قد سدد قرضه كاملا أم لا.
  • مرحلة المشروع (project milestone). تنفيذ نموذج تعلم الآلة (Machine Learning Model Implementation): يواصل فريقك تنفيذ النماذج على بياناته الخاصة.

في الأسبوع 5 صنفت البيانات باستخدام KNN، وهي تقيس المسافات. أما الشجرة فلا تقيس أي مسافة: إنها تقارن خاصية واحدة بعتبة واحدة في كل مرة. ويبقى نمط fit وpredict وscore كما هو تماما.

الجزءما نفعلهالزمن
1فكرة الشجرة، وشجرة ننميها يدويا30 دقيقة
2دفتر Lending Club: البيانات والاستكشاف15 دقيقة
3شجرة قرار واحدة على القروض10 دقائق
4الغابة العشوائية: يدويا، ثم على القروض25 دقيقة
5الأخطاء، والمشروع، والتمارين مع الإجابات، والخلاصة40 دقيقة

الجزء 1: فكرة شجرة القرار

ما شجرة القرار

تصنف شجرة القرار العينة بطرح سلسلة من الأسئلة عن خصائصها. يقارن كل سؤال خاصية واحدة بعتبة، مثل fico ≤ 677. وترسل الإجابة العينة إلى الفرع الأيسر (نعم) أو إلى الفرع الأيمن (لا)، حيث ينتظرها السؤال التالي. وحين لا يبقى أي سؤال، تكون العينة قد وصلت إلى ورقة، وصنف الورقة هو التنبؤ.

المصطلحات على بيانات القروض

المصطلحفي بيانات Lending Club
الخصائص Xالبيانات الائتمانية للمقترض، مثل fico (درجة الائتمان) وint.rate (سعر الفائدة)
التسمية (label) ynot.fully.paid: 1 لم يسدد كاملا، و0 سدد كاملا
عقدة الجذر (root node)السؤال الأول، ويطرح على كل مقترض
العقدة الداخلية (internal node)سؤال لاحق، لا يطرح إلا على المقترضين الذين وصلوا إليه
الورقةعقدة بلا سؤال؛ تتنبأ بصنف الأغلبية بين مقترضي التدريب فيها
العمق (depth)عدد الأسئلة على أطول مسار من الجذر إلى ورقة

كيف تختار الشجرة سؤالا: عدم نقاء جيني

السؤال الجيد يفصل بين الصنفين: بعده يجب أن يحمل كل جانب صنفا واحدا في الغالب. يبني الدفتر شجرته باستخدام criterion='gini'، الذي يقيس مدى اختلاط العقدة باستخدام عدم نقاء جيني:

G = 1 - (p0^2 + p1^2)

هنا p0 هي نسبة مقترضي العقدة في الصنف 0 (سدد)، وp1 نسبتهم في الصنف 1 (لم يسدد). وتحدد حالتان المقياس:

  • عقدة نقية (pure)، كلها من صنف واحد: p0 = 1، وp1 = 0، إذن G = 1 - 1 = 0.
  • عقدة 50 إلى 50: G = 1 - (0.25 + 0.25) = 0.5، وهي أكبر قيمة يمكن أن يبلغها صنفان.

يقسم السؤال عقدة فيها n من المقترضين إلى جانب أيسر فيه nL وجانب أيمن فيه nR. وجودة السؤال هي جيني الموزون للجانبين، حيث يحسب كل جانب بنسبة حجمه:

Gw = (nL / n) * GL + (nR / n) * GR

GL وGR هما عدم نقاء جيني للجانبين الأيسر والأيمن. تجرب الشجرة كل خاصية وكل عتبة، وتحتفظ بالسؤال صاحب أقل Gw، وهو نفسه صاحب أكبر انخفاض G - Gw. والعتبات المرشحة هي نقاط المنتصف (midpoints) بين القيم المتجاورة للخاصية في العقدة.

مثال محلول: شجرة ننميها يدويا

نأخذ عشرة مقترضين حقيقيين من بيانات Lending Club ونستخدم خاصيتين، fico وint.rate. أربعة منهم لم يسددوا قروضهم كاملة.

المقترضficoint.ratenot.fully.paid
B16470.14821 (لم يسدد)
B26620.15070 (سدد)
B36670.12991 (لم يسدد)
B46720.13471 (لم يسدد)
B56820.11030 (سدد)
B66870.13870 (سدد)
B76920.12840 (سدد)
B87070.10911 (لم يسدد)
B97270.10590 (سدد)
B107570.11890 (سدد)

هؤلاء هم الصفوف 8443 و1259 و380 و1794 و520 و1183 و2472 و142 و7764 و2217 من loan_data.csv، مرتبين حسب fico.

الخطوة 1: جيني الجذر

يضم الجذر المقترضين العشرة كلهم: 6 سددوا و4 لم يسددوا.

G = 1 - (0.6^2 + 0.4^2) = 1 - (0.36 + 0.16) = 0.48

الجذر قريب من أسوأ قيمة، 0.5: الصنفان مختلطان اختلاطا كبيرا.

الخطوة 2: جرب السؤال fico ≤ 677

العدد 677 هو نقطة المنتصف بين B4 (672) وB5 (682). يرسل السؤال المقترضين من B1 إلى B4 إلى اليسار، ومن B5 إلى B10 إلى اليمين.

  • اليسار، 4 مقترضين: B1 وB3 وB4 لم يسددوا، وB2 سدد. إذن 3 لم يسددوا و1 سدد.
  • اليمين، 6 مقترضين: B8 وحده لم يسدد. إذن 1 لم يسدد و5 سددوا.

الخطوة 3: جيني كل جانب

GL = 1 - ((3/4)^2 + (1/4)^2) = 1 - (0.5625 + 0.0625) = 0.375
GR = 1 - ((1/6)^2 + (5/6)^2) = 1 - (1/36 + 25/36) = 10/36 = 0.2778

الجانبان كلاهما أنقى من الجذر (0.48).

الخطوة 4: جيني الموزون للتقسيم

Gw = (4/10) * 0.375 + (6/10) * 0.2778 = 0.15 + 0.1667 = 0.3167

الانخفاض هو 0.48 - 0.3167 = 0.1633.

الخطوة 5: الخاصية الأخرى

أفضل سؤال على int.rate هو int.rate ≤ 0.12915، وهو نقطة المنتصف بين B3 (0.1299) وB7 (0.1284).

  • اليسار، 5 مقترضين أسعار فائدتهم أقل: B5 وB7 وB8 وB9 وB10. وB8 وحده لم يسدد: GL = 1 - (0.2^2 + 0.8^2) = 0.32.
  • اليمين، 5 مقترضين أسعار فائدتهم أعلى: B1 وB2 وB3 وB4 وB6. ثلاثة منهم لم يسددوا: GR = 1 - (0.6^2 + 0.4^2) = 0.48.
Gw = (5/10) * 0.32 + (5/10) * 0.48 = 0.40

القيمة 0.40 أعلى من 0.3167، لذا تعطي درجة الائتمان سؤالا أولا أفضل.

الخطوة 6: يجرب البحث كل عتبة

تعطي قيم fico العشر تسع نقاط منتصف. وهذه قيم جيني الموزون لها:

العتبةاليسار: لم يسدد، سدداليمين: لم يسدد، سددجيني الموزون
654.51, 03, 60.4000
664.51, 13, 50.4750
669.52, 12, 50.4190
6773, 11, 50.3167
684.53, 21, 40.4000
689.53, 31, 30.4500
699.53, 41, 20.4762
7174, 40, 20.4000
7424, 50, 10.4444

وتعطي نقاط المنتصف التسع لـint.rate قيما بين 0.40 و0.4762. إذن أقل قيمة بين الأسئلة الثمانية عشر المرشحة كلها هي قيمة fico ≤ 677، فيصبح هذا السؤال سؤال الجذر.

الخطوة 7: تنمية العقدة الابنة اليسرى

تضم العقدة الابنة (child node) اليسرى المقترضين من B1 إلى B4 (3 لم يسددوا، و1 سدد، G = 0.375). والمقترض الذي سدد، B2، له أعلى سعر فائدة بين الأربعة (0.1507)، لذا يفصله السؤال int.rate ≤ 0.14945:

  • اليسار: B1 وB3 وB4، ولم يسدد أي منهم، G = 0.
  • اليمين: B2، وقد سدد، G = 0.

جيني الموزون يساوي 0: الجانبان نقيان ويصبحان ورقتين. (أفضل سؤال على fico هنا، fico ≤ 664.5، لا يصل إلا إلى 0.25.)

الخطوة 8: تنمية العقدة الابنة اليمنى

تضم العقدة الابنة اليمنى المقترضين من B5 إلى B10 (1 لم يسدد، و5 سددوا، G = 0.2778). والمتعثر (defaulter) الوحيد، B8، درجته جيدة (707) وسعر فائدته منخفض (0.1091). وأفضل سؤال هو int.rate ≤ 0.1097:

  • اليسار: B9 وB8، 1 سدد و1 لم يسدد، G = 0.5.
  • اليمين: B5 وB6 وB7 وB10، وكلهم سددوا، G = 0.
Gw = (2/6) * 0.5 + (4/6) * 0 = 0.1667

أفضل سؤال على fico لهذه العقدة، fico ≤ 699.5، يعطي 0.2222، لذا يفوز سعر الفائدة مرة أخرى.

الشجرة التي بنيناها

fico <= 677 ?
|-- yes: int.rate <= 0.14945 ?
|        |-- yes: leaf [3 not paid, 0 paid]  -> not paid
|        |-- no:  leaf [0 not paid, 1 paid]  -> paid
|-- no:  int.rate <= 0.1097 ?
         |-- yes: leaf [1 not paid, 1 paid]  -> paid (a tie goes to class 0)
         |-- no:  leaf [0 not paid, 4 paid]  -> paid

تبني scikit-learn هذه الشجرة نفسها تماما من الصفوف العشرة نفسها باستخدام DecisionTreeClassifier(max_depth=2): الأسئلة نفسها، والعتبات نفسها، وقيم جيني نفسها (0.48 عند الجذر، و0.375 و0.2778 عند العقدتين الابنتين). وحين تحمل ورقة تعادلا، تتنبأ scikit-learn بالصنف صاحب الرقم الأصغر، وهو هنا 0.

التنبؤ بمقترضين جديدين

  • fico 670، int.rate 0.14. بما أن 670 ≤ 677، اتجه يسارا. وبما أن 0.14 ≤ 0.14945، اتجه يسارا مرة أخرى. تتنبأ الورقة بأنه لم يسدد.
  • fico 700، int.rate 0.12. بما أن 700 > 677، اتجه يمينا. وبما أن 0.12 > 0.1097، اتجه يمينا مرة أخرى. تتنبأ الورقة بأنه سدد.

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

الإنتروبيا، المعيار الآخر في الدفتر

يدرب الدفتر أيضا غابات باستخدام criterion='entropy'، وهو مقياس ثان لمدى اختلاط العقدة يسمى الإنتروبيا (entropy):

H = -(p0 * log2(p0) + p1 * log2(p1))

للعقدة النقية H = 0، وللعقدة 50 إلى 50 H = 1. ولجذرنا H = -(0.6 * log2(0.6) + 0.4 * log2(0.4)) = 0.971. ومع الإنتروبيا تختار scikit-learn هنا السؤال الأول نفسه، fico ≤ 677. وفي العادة يتفق المعياران على التقسيمات الجيدة.

شجرة لا تتوقف

ينمي الدفتر شجرته باستخدام max_depth=None: تستمر الشجرة في التقسيم حتى تصبح كل ورقة نقية. وعلى مقترضينا العشرة، يفصل سؤال إضافي واحد، fico ≤ 717، بين B8 وB9. عمق الشجرة الكاملة 3 ولها 5 أوراق، وهي تصنف مقترضي التدريب العشرة كلهم تصنيفا صحيحا. هذا السؤال الأخير موجود فقط لعزل مقترض واحد. والشجرة التي تنمو حتى تحفظ صفوف تدريبها تعاني فرط الملاءمة (overfitting): إنها تتعلم مصادفات مجموعة التدريب، ويدفع المقترضون الجدد الثمن. ويبين الجزء 3 ذلك على البيانات الحقيقية.

الجزء 2: دفتر Lending Club

افتح الدفتر

افتح DecisionTrees_RandomForest_Classification في Colab

يقرأ الدفتر loan_data.csv من مجلده الخاص. وهذا الملف غير موجود هناك في Colab: فهو في المستودع داخل المجلد Datasets. اقرأه من المستودع بدلا من ذلك:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
 
url = ("https://raw.githubusercontent.com/"
       "tirthajyoti/Machine-Learning-with-Python/"
       "master/Datasets/loan_data.csv")
df = pd.read_csv(url)
df.info()

تحميل البيانات وفحصها

يبلغ df.info() عن 9578 صفا و14 عمودا، ولا توجد قيم مفقودة. ومن الأعمدة التي يصفها الدفتر:

العمودالمعنى
credit.policy1 إذا استوفى المقترض معايير الاكتتاب الائتماني (credit underwriting criteria) لموقع LendingClub.com، و0 خلاف ذلك
purposeالغرض من القرض، وهو نص مثل credit_card أو debt_consolidation
int.rateسعر الفائدة في صورة نسبة (تخزن 11% على أنها 0.11)؛ ويحصل المقترضون الأكثر خطورة على أسعار أعلى
ficoدرجة FICO الائتمانية للمقترض
not.fully.paidالتسمية: 1 إذا لم يسدد المقترض كاملا

والأعمدة الثمانية الأخرى أرقام أيضا: القسط الشهري، ولوغاريتم الدخل السنوي، ونسبة الدين إلى الدخل، وعدد الأيام التي امتلك فيها المقترض خط ائتمان، والرصيد المتجدد ونسبة استخدامه، والاستعلامات في آخر 6 أشهر، ومرات التأخر في السداد خلال 2 من السنوات، والسجلات العامة.

العددان المهمان

print(df['credit.policy'].value_counts())
print(df['not.fully.paid'].value_counts())
  • credit.policy: يستوفي 7710 من المقترضين المعايير، ولا يستوفيها 1868.
  • not.fully.paid: سدد 8045 مقترضا قروضهم كاملة، ولم يسددها 1533. أي أن 16% فقط من المقترضين في الصنف 1.

تذكر العدد الثاني. فالنموذج الذي يجيب "سدد" عن كل المقترضين يصيب بالفعل في 84% من الحالات.

استكشف قبل أن تبني النموذج

يرسم الدفتر مدرجا تكراريا (histogram) لـfico لكل قيمة من قيم credit.policy:

مدرج تكراري لدرجات FICO للمقترضين الذين يستوفون سياسة الائتمان (بالأزرق) والذين لا يستوفونها (بالأحمر)

كل المقترضين تقريبا الذين تقل درجتهم عن 660 لا يستوفون سياسة الائتمان: 487 مقترضا مقابل 2. فعتبة واحدة على خاصية واحدة تفصل مجموعة فصلا جيدا، وهذا بالضبط نوع السؤال الذي تطرحه الشجرة.

ثم يعد القروض حسب الغرض، ملونة حسب not.fully.paid:

عدد القروض حسب الغرض، مقسمة إلى قروض سددت كاملة (بالأحمر) وقروض لم تسدد كاملة (بالأزرق)

الغرض الأكثر شيوعا هو debt_consolidation (3957 قرضا). وتختلف نسبة القروض التي لم تسدد كاملة حسب الغرض: 27.8% لـsmall_business، و11.6% لـcredit_card، و11.2% لـmajor_purchase.

تمرر خلية lmplot اللاحقة الوسيط size=6. وقد غيرت الإصدارات الحالية من seaborn اسم هذا الوسيط، فتتوقف الخلية بالخطأ TypeError: lmplot() got an unexpected keyword argument 'size'. اكتب height=6 بدلا منه.

تحويل عمود النص إلى أرقام

يحمل purpose نصا، وتحتاج scikit-learn إلى أرقام. ينشئ الدفتر متغيرات وهمية (dummy variables):

df_final = pd.get_dummies(df, ['purpose'], drop_first=True)
  • تصبح الأغراض السبعة ستة أعمدة قيمها 0 أو 1، مثل purpose_credit_card. ويحذف drop_first=True الغرض الأول، all_other: فالمقترض الذي قيمه 0 في الأعمدة الستة كلها غرضه هو هذا الغرض.
  • في df_final 19 عمودا: أعمدة الخصائص الـ12 الأخرى، والأعمدة الوهمية الـ6، والتسمية.
  • الوسيط الثاني هنا هو prefix للأعمدة الجديدة. والصيغة الأوضح هي pd.get_dummies(df, columns=['purpose'], drop_first=True)، وهي تعطي الأعمدة الـ19 نفسها.

التقسيم إلى مجموعة تدريب ومجموعة اختبار

from sklearn.model_selection import train_test_split
 
X = df_final.drop('not.fully.paid', axis=1)
y = df_final['not.fully.paid']
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.30, random_state=101)
  • في X 18 عمودا للخصائص. ويحتفظ test_size=0.30 بـ2874 مقترضا للاختبار، ويدرب على 6704.
  • تضم مجموعة الاختبار 2431 مقترضا سددوا و443 لم يسددوا.
  • يستدعي الدفتر train_test_split دون random_state، لذا يعطي كل تشغيل تقسيما مختلفا وأرقاما مختلفة قليلا. ونضيف random_state=101 لكي تطابق أرقامك هذه الصفحة.

الجزء 3: شجرة قرار واحدة على القروض

التدريب والتنبؤ

from sklearn.tree import DecisionTreeClassifier
 
dtree = DecisionTreeClassifier(criterion='gini', max_depth=None,
                               random_state=101)
dtree.fit(X_train, y_train)
predictions = dtree.predict(X_test)

criterion='gini' هو عدم النقاء الذي رأيناه في الجزء 1. وmax_depth=None يدع الشجرة تنمو حتى تصبح كل ورقة نقية. وrandom_state=101 يثبت الترتيب الذي تجرب به الخصائص، لذا تكسر حالات التعادل بين التقسيمات المتساوية في الجودة بالطريقة نفسها في كل تشغيل.

مثال محلول: تقييم الشجرة

from sklearn.metrics import classification_report, confusion_matrix
 
cm = confusion_matrix(y_test, predictions)
print(cm)
print("Accuracy of prediction:", round((cm[0,0]+cm[1,1])/cm.sum(), 3))
التنبؤ 0 (سدد)التنبؤ 1 (لم يسدد)
الفعلي 0 (سدد)1975456
الفعلي 1 (لم يسدد)336107
  • التنبؤات الصحيحة: 1975 + 107 = 2082 من 2874، إذن الدقة 2082 / 2874 = 0.724. ويطبع الدفتر Accuracy of prediction: 0.724.
  • استدعاء (recall) الصنف 1: من بين 443 مقترضا لم يسددوا، وجدت الشجرة 107 / 443 = 0.24.
  • ضبط (precision) الصنف 1: من بين 456 + 107 = 563 مقترضا صنفتهم الشجرة على أنهم لم يسددوا، لم يسدد فعلا 107 / 563 = 0.19.

ويطبع classification_report(y_test, predictions) القيم نفسها، مقربة:

              precision    recall  f1-score   support
 
           0       0.85      0.81      0.83      2431
           1       0.19      0.24      0.21       443
 
    accuracy                           0.72      2874
   macro avg       0.52      0.53      0.52      2874
weighted avg       0.75      0.72      0.74      2874

الشجرة حفظت مجموعة تدريبها

print(dtree.get_depth(), dtree.get_n_leaves())
print(dtree.score(X_train, y_train))

المخرجات هي 27 و1005 و1.0. تطرح الشجرة حتى 27 سؤالا، وتنتهي عند 1005 أوراق، وتصنف كل مقترض من مقترضي التدريب الـ6704 تصنيفا صحيحا. وعلى مقترضي الاختبار تسجل 0.724، وهي أقل من 0.846 التي تحققها الإجابة "سدد" عن الجميع. هذا هو فرط الملاءمة الذي رأيناه في الجزء 1، ولكن بحجمه الكامل.

الجزء 4: الغابة العشوائية

الفكرة: أشجار كثيرة مختلفة، وتصويت واحد

تدرب الغابة العشوائية أشجارا كثيرة وتجمع إجاباتها. وثلاث أفكار تجعل الأشجار مختلفة بعضها عن بعض:

  1. عينة bootstrap لكل شجرة. من صفوف التدريب البالغ عددها n، اسحب n صفا عشوائيا مع الإرجاع (with replacement). بعض الصفوف يسحب مرتين أو أكثر، وبعضها يترك. وهذا هو bootstrap=True في النموذج المدرب.
  2. مجموعة جزئية عشوائية من الخصائص عند كل تقسيم. عند كل عقدة لا تبحث الشجرة إلا في خصائص قليلة مختارة عشوائيا. ومع max_features='sqrt'، وهو الإعداد الافتراضي، تبحث في m خاصية من أصل p، حيث m هو الجذر التربيعي لـp مقربا إلى الأسفل.
  3. التصويت. تصنف كل شجرة العينة الجديدة، ويفوز الصنف الحاصل على أكثر الأصوات. (تحسب scikit-learn متوسط احتمالات الأصناف في الأشجار؛ وحين تكون كل ورقة نقية، كما في الأشجار التي نمت بالكامل، يساوي ذلك تماما عد الأصوات.)

تتغير الشجرة العميقة الواحدة كثيرا حين تتغير بضعة صفوف من صفوف التدريب. أما الأشجار الكثيرة التي رأت كل منها عينة مختلفة فترتكب أخطاء مختلفة، ويلغي التصويت كثيرا منها.

مثال محلول: خمس أشجار على المقترضين العشرة

ندرب RandomForestClassifier(n_estimators=5, random_state=0) على المقترضين العشرة من الجزء 1، ونقرأ عينة bootstrap لكل شجرة من rf.estimators_samples_. ومع p = 2 من الخصائص تكون m = 1: كل تقسيم يبحث في خاصية عشوائية واحدة.

سحبت الشجرة 1 المقترضين B4، B3، B1، B1، B3، B7، B10، B8، B4، B10. إذن سحب B1 وB3 وB4 وB10 مرتين، وB7 وB8 مرة واحدة، ولم يسحب B2 وB5 وB6 وB9 إطلاقا. وسؤال جذرها هو fico ≤ 682، لا 677: فالمقترض B5 (682) ليس في عينتها، لذا تقع نقطة المنتصف بين B4 (672) وB7 (692).

للمقترض الجديد fico 710 وint.rate 0.11. وتصوت كل شجرة:

الشجرةالمقترضون الذين تركوا خارج عينتهاالصوت
1B2، B5، B6، B9لم يسدد
2B3، B6، B7سدد
3B1، B3، B7، B10سدد
4B2، B3، B7، B8، B10سدد
5B1، B3، B5، B9لم يسدد

نتيجة التصويت 3 أصوات لـ"سدد" مقابل 2 لـ"لم يسدد"، إذن تتنبأ الغابة بأنه سدد. ويعيد rf.predict_proba القيمة [0.6, 0.4]: 3 من 5 أشجار للصنف 0، و2 من 5 للصنف 1. تضع الشجرتان 1 و5 المقترض الجديد في منطقة صغيرة بنتاها حول B8، المتعثر ذي الدرجة الجيدة القريب منه. وتضعه الأشجار الثلاث الأخرى مع المقترضين الذين سددوا، فتتغلب في التصويت على الشجرتين الأوليين.

افتح أداة تصويت الغابة العشوائية بملء الشاشة. اضغط تشغيل لتشاهد كل شجرة تسحب عينة bootstrap الخاصة بها وتصوت، واسحب النجمة لتحريك المقترض الجديد، أو أنشئ غابة عشوائية جديدة من تسع أشجار.

كم خاصية عند كل تقسيم، على القروض

في X_train يوجد p = 18 من الخصائص. والجذر التربيعي لـ18 هو 4.24، لذا يبحث كل تقسيم في 4 خصائص مختارة عشوائيا. وتؤكد الغابة المدربة ذلك: قيمة rfc.estimators_[0].max_features_ هي 4.

تترك عينة bootstrap صفوفا كثيرة خارجها. فعبر الأشجار الـ600 أدناه، رأت كل شجرة ما بين 4160 و4310 مقترضا مختلفا من صفوف التدريب الـ6704، أي 63.2% في المتوسط؛ أما البقية فلم تسحب أبدا لتلك الشجرة.

تدريب الغابة

from sklearn.ensemble import RandomForestClassifier
 
rfc = RandomForestClassifier(n_estimators=600, random_state=101)
rfc.fit(X_train, y_train)
rfc_pred = rfc.predict(X_test)
print(confusion_matrix(y_test, rfc_pred))
print(classification_report(y_test, rfc_pred))

n_estimators=600 ينمي 600 شجرة. ويعرض الدفتر عند طباعة النموذج المدرب الإعداد الافتراضي الأقدم max_features='auto'؛ أما الإصدارات الحالية من scikit-learn فتسمي الإعداد نفسه 'sqrt'.

مثال محلول: مصفوفة الالتباس للغابة

التنبؤ 0 (سدد)التنبؤ 1 (لم يسدد)
الفعلي 0 (سدد)242011
الفعلي 1 (لم يسدد)43112
  • الدقة: (2420 + 12) / 2874 = 0.846، وهي أعلى بكثير من 0.724 للشجرة الواحدة.
  • لكن الغابة لا تصنف على أنهم لم يسددوا إلا 11 + 12 = 23 من مقترضي الاختبار الـ2874. ومن بين 443 لم يسددوا، تجد 12: الاستدعاء 12 / 443 = 0.03.
  • الإجابة "سدد" عن كل مقترض تعطي 2431 / 2874 = 0.846 أيضا. والغابة تتفوق على ذلك بمقترض واحد فقط.
              precision    recall  f1-score   support
 
           0       0.85      1.00      0.92      2431
           1       0.52      0.03      0.05       443
 
    accuracy                           0.85      2874
   macro avg       0.69      0.51      0.48      2874
weighted avg       0.80      0.85      0.78      2874

يسأل الدفتر عند هذه النقطة: "هل تجد شيئا غريبا؟" وهذا هو الجزء الغريب. فمع 16% فقط من المتعثرين، تصوت معظم الأشجار بـ"سدد" لكل المقترضين تقريبا: لا يحصل أي مقترض اختبار على أكثر من 375 صوتا بـ"لم يسدد" من الأشجار الـ600 (62.5%). الدقة العالية على هدف غير متوازن قد تخفي نموذجا نادرا ما يجد الصنف الذي يهمك. فاقرأ دائما استدعاء ذلك الصنف، لا الدقة وحدها.

زلة في الدفتر

خلية الدفتر الخاصة بتقرير الغابة هي:

cr = classification_report(y_test, predictions)

تحمل predictions تنبؤات الشجرة الواحدة، لذا تطبع تلك الخلية تقرير الشجرة مرة ثانية. استخدم rfc_pred، كما في الشيفرة أعلاه.

كم شجرة؟

يدرب الدفتر غابات من 5 و10 و15 شجرة وحتى 100 شجرة، ويسجل دقة كل منها:

nsimu = 21
accuracy = [0] * nsimu
ntree = [0] * nsimu
for i in range(1, nsimu):
    rfc = RandomForestClassifier(n_estimators=i*5, min_samples_split=10,
                                 max_depth=None, criterion='gini',
                                 random_state=101)
    rfc.fit(X_train, y_train)
    rfc_pred = rfc.predict(X_test)
    cm = confusion_matrix(y_test, rfc_pred)
    accuracy[i] = (cm[0,0] + cm[1,1]) / cm.sum()
    ntree[i] = i * 5

يوقف min_samples_split=10 تقسيم أي عقدة تضم أقل من 10 صفوف تدريب. ونضيف random_state=101 إلى كل غابة لكي تعطي الحلقة الأرقام نفسها في كل تشغيل.

دقة الغابات من 5 إلى 100 شجرة على مجموعة الاختبار، ترتفع من 0.824 وتستقر قرب 0.846، بجانب القيمة 0.8459 التي تحققها الإجابة سدد عن الجميع

  • مع 5 أشجار تبلغ الدقة 0.8239. ثم ترتفع بسرعة، ومن نحو 30 شجرة فصاعدا تبقى بين 0.8455 و0.8473.
  • زيادة الأشجار تجعل التصويت أكثر ثباتا، لكنها لا تستطيع تجاوز حد البيانات: يستقر المنحنى عند مستوى الإجابة "سدد" عن الجميع.

يكرر الدفتر الحلقة بإعدادات أخرى. وهذا نطاق الدقة عبر الغابات الـ20 في كل تشغيل:

الإعداد الذي تغيرأقل دقةأعلى دقة
لا شيء (gini، min_samples_split=10)0.82390.8473
criterion='entropy'0.82500.8473
max_depth=50.84340.8459
min_samples_split=20.80620.8466
min_samples_split=200.83020.8466

الفروق عند القمة مقترض أو اثنان من مقترضي الاختبار الـ2874. وتغيير هذه الإعدادات لا يحل المشكلة الحقيقية، وهي أن الغابة لا تكاد تتنبأ بالصنف 1 أبدا.

الأخطاء الشائعة

  • قراءة loan_data.csv في Colab من مجلد لا يحتويه.
  • نسيان random_state، ثم مقارنة أرقام من تقسيمين عشوائيين مختلفين.
  • تنمية شجرة باستخدام max_depth=None والثقة بنتيجتها المثالية على بيانات التدريب.
  • الحكم على نموذج بالدقة وحدها حين يكون أحد الصنفين نادرا.
  • طباعة تقرير predictions وأنت تقصد rfc_pred.
  • تمرير عمود نصي مثل purpose إلى scikit-learn دون متغيرات وهمية.

مرحلة المشروع: تنفيذ النموذج

تستمر المرحلة هذا الأسبوع: نفذ شجرة قرار وغابة عشوائية على بيانات مشروع فريقك.

  1. خذ الخصائص والهدف اللذين استخدمتهما مع KNN في الأسبوع الماضي، وحول أي عمود نصي باستخدام pd.get_dummies.
  2. قسم البيانات إلى مجموعة تدريب ومجموعة اختبار مع قيمة ثابتة لـrandom_state.
  3. درب DecisionTreeClassifier وقارن نتيجته على بيانات التدريب بنتيجته على بيانات الاختبار.
  4. درب RandomForestClassifier واعرض مصفوفة الالتباس الخاصة به وclassification_report.
  5. قارن الشجرة والغابة ونموذج KNN من الأسبوع الماضي على مجموعة الاختبار نفسها، وانظر إلى استدعاء كل صنف، لا الدقة وحدها.

الخلاصة

  1. تصنف شجرة القرار بسلسلة من أسئلة العتبة؛ وتتنبأ الورقة بصنف الأغلبية بين صفوف التدريب فيها.
  2. تختار الشجرة كل سؤال بأقل قيمة لجيني الموزون عبر كل خاصية وكل عتبة.
  3. الشجرة التي تنمو حتى تصبح كل ورقة نقية تحفظ مجموعة تدريبها وتعاني فرط الملاءمة.
  4. تدرب الغابة العشوائية أشجارا كثيرة على عينات bootstrap، مع مجموعة جزئية عشوائية من الخصائص عند كل تقسيم، ثم تجري تصويتا.
  5. حين يكون أحد الصنفين نادرا، قد تخفي الدقة العالية نموذجا لا يكاد يجد ذلك الصنف أبدا: فاقرأ الاستدعاء.

التمارين

نحو 30 دقيقة. حاول حل كل مهمة قبل أن تقرأ إجابتها في نهاية الصفحة.

التمرين 1: اختيار السؤال الأول (نحو 10 دقائق)

ثمانية مقترضين حقيقيين آخرين من loan_data.csv:

المقترضficoint.ratenot.fully.paid
P16470.14821
P26670.13430
P36770.17341
P46820.11970
P56920.12870
P66970.13161
P77420.11140
P87470.07510
  1. احسب عدم نقاء جيني للجذر.
  2. احسب جيني الموزون للسؤال fico ≤ 679.5.
  3. احسب جيني الموزون للسؤال int.rate ≤ 0.13015.
  4. أي سؤال تختاره الشجرة، وما مقدار الانخفاض في جيني؟

التمرين 2: قراءة مصفوفة الالتباس للغابة (نحو 5 دقائق)

استخدم مصفوفة الغابة ذات الأشجار الـ600 من الجزء 4: [[2420, 11], [431, 12]].

  1. احسب الضبط والاستدعاء للصنف 1.
  2. "نموذج" يجيب 0 (سدد) عن كل مقترض اختبار. اكتب مصفوفة الالتباس الخاصة به ودقته.
  3. أي النموذجين يجد عددا أكبر من المقترضين الذين لم يسددوا؟

التمرين 3: إنتروبيا التقسيم الأول (نحو 5 دقائق)

للمقترضين العشرة في المثال المحلول وللسؤال fico ≤ 677، احسب إنتروبيا الجانب الأيسر (3 لم يسددوا، و1 سدد)، وإنتروبيا الجانب الأيمن (1 لم يسدد، و5 سددوا)، والإنتروبيا الموزونة لهما. إنتروبيا الجذر 0.971.

التمرين 4: في Colab، على القروض (نحو 10 دقائق)

  1. شغل الدفتر حتى خطوة التقسيم، مع random_state=101 ورابط البيانات أعلاه.
  2. درب DecisionTreeClassifier(max_depth=5, random_state=101). واطبع مصفوفة الالتباس الخاصة به، ودقته على الاختبار، ودقته على التدريب.
  3. قارنها بالشجرة التي نمت باستخدام max_depth=None في الجزء 3.
  4. أعد تشغيل الحلقة على عدد الأشجار مع max_depth=5. ما نطاق الدقة الذي تحصل عليه؟

الإجابات

الإجابة 1

  1. يضم الجذر 3 لم يسددوا و5 سددوا: G = 1 - ((3/8)^2 + (5/8)^2) = 1 - (9/64 + 25/64) = 30/64 = 0.4688.
  2. يرسل fico ≤ 679.5 المقترضين P1 وP2 وP3 إلى اليسار (2 لم يسددا، و1 سدد)، والمقترضين من P4 إلى P8 إلى اليمين (1 لم يسدد، وهو P6، و4 سددوا).
    • GL = 1 - ((2/3)^2 + (1/3)^2) = 4/9 = 0.4444
    • GR = 1 - ((1/5)^2 + (4/5)^2) = 8/25 = 0.32
    • Gw = (3/8) * 0.4444 + (5/8) * 0.32 = 0.1667 + 0.2 = 0.3667
  3. يرسل int.rate ≤ 0.13015 المقترضين P4 وP5 وP7 وP8 إلى اليسار (وكلهم سددوا)، والمقترضين P1 وP2 وP3 وP6 إلى اليمين (3 لم يسددوا، و1 سدد).
    • GL = 0
    • GR = 1 - ((3/4)^2 + (1/4)^2) = 0.375
    • Gw = (4/8) * 0 + (4/8) * 0.375 = 0.1875
  4. تختار الشجرة int.rate ≤ 0.13015، لأن 0.1875 أقل من 0.3667. والانخفاض هو 0.4688 - 0.1875 = 0.2813. وهو أيضا أفضل العتبات المرشحة الـ14 كلها، وDecisionTreeClassifier(max_depth=1) المدرب على هذه الصفوف الثمانية يطرح هذا السؤال بالضبط، بقيم جيني 0.46875 و0 و0.375.

هذه المرة يفوز سعر الفائدة: فأفضل خاصية تعتمد على الصفوف الموجودة في العقدة.

الإجابة 2

  1. ضبط الصنف 1: 12 / (11 + 12) = 12 / 23 = 0.52. واستدعاء الصنف 1: 12 / (431 + 12) = 12 / 443 = 0.03.
  2. الإجابة "سدد" عن الجميع تعطي:
التنبؤ 0التنبؤ 1
الفعلي 024310
الفعلي 14430

دقة هذا النموذج 2431 / 2874 = 0.8459، مقابل 2432 / 2874 = 0.8462 للغابة. 3. تجد الغابة 12 من 443، ولا تجد الإجابة الثابتة أحدا. الفرق بين الدقتين مقترض واحد، وكلا النموذجين شبه عديم الفائدة في العثور على المتعثرين.

الإجابة 3

  • اليسار، p1 = 3/4 وp0 = 1/4: H = -(0.75 * log2(0.75) + 0.25 * log2(0.25)) = 0.3113 + 0.5 = 0.8113.
  • اليمين، p1 = 1/6 وp0 = 5/6: H = -((1/6) * log2(1/6) + (5/6) * log2(5/6)) = 0.4308 + 0.2192 = 0.6500.
  • الموزونة: (4/10) * 0.8113 + (6/10) * 0.6500 = 0.3245 + 0.3900 = 0.7145. والانخفاض عن الجذر هو 0.971 - 0.7145 = 0.2565.

تعطي scikit-learn مع criterion='entropy' قيم عدم النقاء الثلاث نفسها لهذا التقسيم: 0.971 و0.8113 و0.65.

الإجابة 4

  • الشجرة مع max_depth=5:
التنبؤ 0التنبؤ 1
الفعلي 0239635
الفعلي 143112
  • الدقة على الاختبار (2396 + 12) / 2874 = 0.8379؛ والدقة على التدريب 0.845.
  • سجلت الشجرة غير المحدودة 1.0 على التدريب و0.724 على الاختبار. وتقييد العمق يضيق الفجوة بين النتيجتين: فالشجرة الضحلة لم تعد تحفظ مقترضي التدريب. لكنها لا تجد إلا 12 من 443 متعثرا، مثل الغابة.
  • مع max_depth=5، تعطي الحلقة على 5 إلى 100 شجرة دقة بين 0.8434 و0.8459: فالغابات الـ20 كلها تقف عند مستوى الإجابة "سدد" عن الجميع.