Logo

الانحدار اللوجستي

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

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

الانحدار اللوجستي

التنبؤ بنجاة راكب من ركاب تيتانيك (Titanic)، ومعرفة مدى ثقة النموذج في تنبؤه.

يقدم هذا القسم أول مصنف (classifier) في المقرر: الانحدار اللوجستي (logistic regression). تنبأ الانحدار الخطي (linear regression) في الأسبوع 3 بعدد، هو سعر منزل. أما الانحدار اللوجستي فيتنبأ بأحد صنفين (classes)، وهو هنا هل نجا راكب من ركاب تيتانيك (Titanic) أم لم ينج، ويعطي أيضا احتمال (probability) هذه الإجابة. ستتابع النموذج أولا يدويا على ركاب حقيقيين، ثم تجهز البيانات، وتدرب النموذج وتقيمه وتحسن إعداداته باستخدام دفتري (notebooks) الأسبوع 4.

الأهداف

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

  • شرح كيف يحول الانحدار اللوجستي خصائص (features) الراكب إلى مجموع خطي (linear score) z، ثم احتمال p، ثم صنف.
  • حساب الدالة السينية (sigmoid) والاحتمال والتنبؤ يدويا من أوزان (weights) نموذج مدرب.
  • تجهيز بيانات تيتانيك: اختيار الخصائص والهدف (target)، وترميز الأعمدة النصية ترميزا أحاديا (one-hot encoding)، ومعالجة الأعمار المفقودة.
  • تدريب LogisticRegression، ثم قراءة الدقة (accuracy) ومصفوفة الالتباس (confusion matrix) والضبط (precision) والاستدعاء (recall) ومقياس F1 (F1-score).
  • تحريك عتبة القرار (decision threshold) وشرح المبادلة بين الضبط والاستدعاء.
  • اختيار الخصائص والمتغير الهدف (target variable) لمشروع فريقك.

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

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

  • دفتران. يبني Titanic (Logistic Regression) نموذجا مختصرا على أربعة أعمدة. ويستكشف Logistic_Regression_Classification البيانات نفسها بالرسوم، ويملأ الأعمار المفقودة، ويدرس كيف يتغير مقياس F1 مع الإعدادات.
  • سيناريو من الواقع. نموذج تصنيف ثنائي (binary classification) يتنبأ هل سينجو الراكب أم لا، باستخدام مجموعة بيانات تيتانيك.
  • مرحلة المشروع (project milestone). استخراج خصائص بيانات المشروع وتحديد المتغير الهدف (Project Data Features Extraction and Target Variable Identification): في هذا الأسبوع يقرر فريقك أي الأعمدة هي الخصائص وأي عمود هو الهدف.

يستخدم الدفتران الملف نفسه: 891 راكبا، لكل راكب صف واحد، وفيه عمود Survived يحمل 1 لمن نجا و0 لمن لم ينج.

الجزءما نفعلهالزمن
1الفكرة: مجموع خطي، والدالة السينية، وعتبة، وراكب يدويا25 دقيقة
2الدفتر 1: تجهيز البيانات، والتدريب، وحساب النتيجة، والتنبؤ25 دقيقة
3التقييم: مصفوفة الالتباس، والضبط، والاستدعاء، ومقياس F1، والعتبة20 دقيقة
4الدفتر 2: الاستكشاف، والملء، والترميز، وتحسين الإعدادات؛ ومشروعك20 دقيقة
5الخلاصة، ثم تمارين مع الإجابات30 دقيقة

الجزء 1: فكرة الانحدار اللوجستي

ما الانحدار اللوجستي

الانحدار اللوجستي خوارزمية تعلم غرضها التصنيف الثنائي: للهدف قيمتان لا غير، 0 و1. وبعبارة الدفتر، لا يكتفي الانحدار اللوجستي بالتنبؤ، بل يعطي الاحتمالات أيضا. فلكل راكب يعيد احتمال نجاة بين 0 و1، ويتحدد الصنف من هذا الاحتمال.

المصطلحات على بيانات تيتانيك

المصطلحفي بيانات تيتانيك
الخصائص xأعمدة الإدخال، مثل العمر والجنس ودرجة الراكب (passenger class)
الهدف ySurvived: 1 نجا، و0 لم ينج
الصنف الموجب (positive class)الصنف 1، وهو الصنف الذي يعرض النموذج احتماله
الاحتمال pتقدير النموذج لاحتمال أن يكون y = 1 لهذا الراكب
العتبة tالحد الفاصل على p: عند p ≥ t يكون التنبؤ 1، وتحته 0

النموذج في ثلاث خطوات

  1. المجموع. اضرب كل خاصية في وزنها وأضف الحد الثابت (bias). الناتج عدد واحد هو z.
  2. الضغط. مرر z عبر الدالة السينية. الناتج p يقع دائما بين 0 و1.
  3. القرار. قارن p بالعتبة. وتستخدم LogisticRegression.predict القيمة 0.5.

أثناء fit يتعلم النموذج الأوزان والحد الثابت من صفوف التدريب. أما وقت التنبؤ فلا ينفذ إلا هذه الخطوات الثلاث.

الخطوة 1: المجموع الخطي

z = b + w1·x1 + w2·x2 + ... + wn·xn
  • من x1 إلى xn هي خصائص الراكب الواحد، وعددها n.
  • من w1 إلى wn هي الأوزان التي تعلمها النموذج، وزن لكل خاصية. وتخزنها scikit-learn في model.coef_.
  • b هو الحد الثابت، ويخزن في model.intercept_.

الوزن الموجب يرفع z، نحو النجاة. والوزن السالب يخفضه. وهذا هو المجموع الموزون نفسه في الانحدار الخطي؛ والخطوة التالية هي ما يجعل منه مصنفا.

الخطوة 2: الدالة السينية

p = σ(z) = 1 / (1 + e^(-z))

e هو الثابت 2.71828. وللدالة السينية ثلاث سمات يجب أن تتذكرها:

  • لأي قيمة z، تقع σ(z) بين 0 و1، لذا يمكن قراءتها على أنها احتمال.
  • σ(0) = 0.5: المجموع الخطي الذي يساوي صفرا يعني "لا تفضيل".
  • القيمة z الموجبة الكبيرة تعطي p قريبا من 1، والقيمة z السالبة الكبيرة تعطي p قريبا من 0.

مثال محلول: ثلاث قيم للدالة السينية

احسب σ(0) وσ(2) وσ(-2).

σ(0)  = 1 / (1 + e^0)  = 1 / (1 + 1)      = 0.5
σ(2)  = 1 / (1 + e^-2) = 1 / (1 + 0.1353) = 0.8808
σ(-2) = 1 / (1 + e^2)  = 1 / (1 + 7.3891) = 0.1192

لاحظ أن σ(2) + σ(-2) = 1: المنحنى متماثل حول النقطة (0, 0.5).

الخطوة 3: العتبة

مع العتبة الافتراضية 0.5، يكون التنبؤ 1 عندما تكون p ≥ 0.5، و0 فيما عدا ذلك. ولأن σ(0) = 0.5، فهذا يساوي السؤال: هل z ≥ 0؟ وقد تحققنا من ذلك على نموذج الدفتر: تعطي model.predict الأصناف نفسها التي يعطيها predict_proba ≥ 0.5 تماما لركاب الاختبار الـ143 جميعا.

لتجرب الخطوات الثلاث، افتح مستكشف الدالة السينية بملء الشاشة. اختر راكبا، واضغط تشغيل ليتحرك العمر من 0 إلى 80، وحول المحور الأفقي إلى z لترى المنحنى كله على شكل حرف S.

مثال محلول: راكب واحد يدويا

يدرب الدفتر LogisticRegression(random_state=0) على ست خصائص: Age وSex_female وSex_male وPclass_1 وPclass_2 وPclass_3 (ويبين الجزء 2 كيف تنشأ هذه الأعمدة). طبعنا الأوزان التي تعلمها النموذج وقربناها إلى ثلاث منازل عشرية:

الخاصيةالوزن
الحد الثابت b1.212
Age-0.039
Sex_female1.149
Sex_male-1.150
Pclass_11.200
Pclass_20.007
Pclass_3-1.207

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

يسأل الدفتر: هل ستنجو أنثى عمرها 30 عاما تسافر في الدرجة الأولى؟

الخطوة 1: متجه الخصائص (feature vector)

يكتب الدفتر هذه الراكبة على الصورة female = [[30, 1, 0, 1, 0, 0]]، بترتيب الأعمدة Age, Sex_female, Sex_male, Pclass_1, Pclass_2, Pclass_3. العمر 30، وعمود الأنثى 1، وعمود الدرجة الأولى 1، وكل عمود آخر 0.

الخطوة 2: المجموع الخطي

لا تسهم إلا الخصائص غير الصفرية:

z = 1.212 + (-0.039)(30) + 1.149(1) + 1.200(1)
  = 1.212 - 1.170 + 1.149 + 1.200
  = 2.391

الخطوة 3: الاحتمال

p = 1 / (1 + e^-2.391) = 1 / (1 + 0.0915) = 0.916

يطبع الدفتر Probability of survival: 91.6% لهذه الراكبة، وهي القيمة نفسها.

الخطوة 4: الصنف

p = 0.916 ≥ 0.5، لذا فالتنبؤ 1، أي نجت. وتعيد model.predict(female)[0] في الدفتر القيمة 1.

مثال محلول: رجل عمره 60 عاما في الدرجة الثالثة

الخطوات نفسها للراكب male = [[60, 0, 1, 0, 0, 1]]:

z = 1.212 + (-0.039)(60) + (-1.150) + (-1.207) = -3.485
p = 1 / (1 + e^3.485) = 1 / (1 + 32.622) = 0.030

التنبؤ 0، أي لم ينج. ويطبع الدفتر Probability of survival: 2.9%. والفرق الصغير سببه تقريب وزن العمر: قيمته الكاملة -0.03915، وضربه في 60 سنة يضخم خطأ التقريب. ومع الأوزان غير المقربة، z = -3.494 وp = 0.029.

الدالة السينية مع راكبي الدفتر

الجزء 2: الدفتر 1، نموذج تيتانيك مختصر

افتح الدفتر

افتح Titanic (Logistic Regression) في Colab

تقرأ الخلية الأولى Data/titanic.csv، وهو مجلد غير موجود عند فتح الدفتر في Colab. لذلك اقرأ الملف من المستودع:

import pandas as pd
 
url = ("https://raw.githubusercontent.com/"
       "jeffprosise/Machine-Learning/master/Data/titanic.csv")
df = pd.read_csv(url)
df.head()

ابحث عن القيم المفقودة

يبين df.info() وجود 891 صفا و12 عمودا. وفي ثلاثة أعمدة قيم مفقودة (missing values):

العمودالقيم غير الفارغةالمفقودة
Age714177
Cabin204687
Embarked8892

اختر الخصائص، ورمز، واحذف الصفوف الناقصة

df = df[['Survived', 'Age', 'Sex', 'Pclass']]
df = pd.get_dummies(df, columns=['Sex', 'Pclass'])
df.dropna(inplace=True)
df.head()
  • يحتفظ السطر الأول بالهدف Survived وثلاث خصائص: Age وSex وPclass.
  • تقوم get_dummies بالترميز الأحادي لعمود: تستبدله بعمود جديد لكل قيمة، يحمل 1 حيث يأخذ الصف هذه القيمة و0 فيما عدا ذلك. فيصبح Sex العمودين Sex_female وSex_male؛ ويصبح Pclass الأعمدة Pclass_1 وPclass_2 وPclass_3. يحتاج النموذج إلى أعداد، والنص male ليس عددا.
  • تحذف dropna الصفوف الـ177 التي لا عمر فيها، فيبقى 714 صفا: 424 راكبا لم ينجوا و290 راكبا نجوا.

لماذا نرمز Pclass ترميزا أحاديا وهو عدد أصلا؟ الدرجة 2 ليست "ضعف" الدرجة 1. ومع ثلاثة أعمدة منفصلة، يتعلم النموذج وزنا مستقلا لكل درجة.

في الإصدارات الحالية من pandas تحمل الأعمدة الجديدة True وFalse بدلا من 1 و0 الظاهرين في مخرجات الدفتر المحفوظة. وتقرؤها scikit-learn على أنها 1 و0، لذا لا يتغير شيء آخر.

مثال محلول: ترميز راكبين

الصفSexPclassSex_femaleSex_malePclass_1Pclass_2Pclass_3
0male301001
1female110100

يحصل كل راكب على 1 واحد فقط بين عمودي الجنس، و1 واحد فقط بين أعمدة الدرجة.

التقسيم الطبقي (stratification)

from sklearn.model_selection import train_test_split
 
x = df.drop('Survived', axis=1)
y = df['Survived']
 
x_train, x_test, y_train, y_test = train_test_split(
    x, y, test_size=0.2, stratify=y, random_state=0)
  • يحمل x أعمدة الخصائص الستة، ويحمل y الهدف.
  • يحتفظ test_size=0.2 بـ143 صفا من الصفوف الـ714 للاختبار، ويدرب على 571 صفا.
  • يبقي stratify=y نسبة الناجين نفسها في الجزأين. ففي الصفوف الـ714 كلها نجا 290 راكبا، أي بنسبة 0.406. وفي مجموعة التدريب (training set) 232 ناجيا من 571 (0.406)، وفي مجموعة الاختبار (test set) 58 من 143 (0.406).

التدريب وحساب النتيجة

from sklearn.linear_model import LogisticRegression
 
model = LogisticRegression(random_state=0)
model.fit(x_train, y_train)
model.score(x_test, y_test)

تعيد model.score الدقة على مجموعة الاختبار: 0.8322، أي أن 119 راكبا من 143 صنفوا تصنيفا صحيحا.

التحقق المتقاطع

قد يكون تقسيم واحد محظوظا أو سيئ الحظ. لذلك ينفذ الدفتر أيضا التحقق المتقاطع (cross-validation) بخمس طيات: يقطع الصفوف الـ714 إلى خمسة أجزاء، ويدرب خمسة نماذج، ويختبر كل مرة على جزء مختلف، ثم يحسب متوسط الدقات الخمس.

from sklearn.model_selection import cross_val_score
 
cross_val_score(model, x, y, cv=5).mean()

الدقات الخمس هي 0.748 و0.832 و0.783 و0.755 و0.810، ومتوسطها 0.7857. وهو أقل من 0.8322: فقد صادف أن كان تقسيم الاختبار الوحيد عندنا من التقسيمات الأسهل.

التنبؤ بركاب جدد

female = [[30, 1, 0, 1, 0, 0]]
model.predict(female)[0]                  # 1
 
probability = model.predict_proba(female)[0][1]
print(f'Probability of survival: {probability:.1%}')   # 91.6%

تعيد predict_proba عددين لكل صف: احتمال الصنف 0 واحتمال الصنف 1. ويأخذ [0][1] الصف الأول، العدد الثاني: احتمال النجاة. ومجموع عددي الصف الواحد دائما 1.

القائمة العادية ليس لها أسماء أعمدة، لذا تطبع الإصدارات الحالية من scikit-learn تحذيرا، X does not have valid feature names. والنتيجة صحيحة مع ذلك. ولإسكات التحذير، مرر DataFrame فيه الأعمدة نفسها الموجودة في x.

الجزء 3: تقييم المصنف

مصفوفة الالتباس

from sklearn.metrics import confusion_matrix
 
y_predicted = model.predict(x_test)
confusion_matrix(y_test, y_predicted)

مصفوفة الالتباس لنموذج الدفتر على 143 راكبا من ركاب الاختبار

التنبؤ: لم ينج (0)التنبؤ: نجا (1)
الفعلي: لم ينج (0)787
الفعلي: نجا (1)1741

الصفوف هي الصنف الحقيقي والأعمدة هي التنبؤ، لذا يحمل القطر التنبؤات الصحيحة:

  • 78 لم ينجوا وتنبأ النموذج بعدم نجاتهم.
  • 41 نجوا وتنبأ النموذج بنجاتهم.
  • 7 لم ينجوا لكن النموذج تنبأ بنجاتهم.
  • 17 نجوا لكن النموذج تنبأ بعدم نجاتهم.

يرسم الدفتر هذه الصورة باستخدام plot_confusion_matrix. وقد حذفت هذه الدالة من scikit-learn؛ ومع الإصدار 1.9.1 يفشل الاستيراد برسالة ImportError. استخدم ما حل محلها:

from sklearn.metrics import ConfusionMatrixDisplay
 
ConfusionMatrixDisplay.from_estimator(
    model, x_test, y_test,
    display_labels=['Perished', 'Survived'], cmap='Blues')

مثال محلول: أربعة مقاييس من مصفوفة واحدة

خذ الصنف 1، أي نجا، على أنه الصنف الموجب.

الدقة، وهي نسبة كل الركاب الذين صنفوا تصنيفا صحيحا:

accuracy = (78 + 41) / 143 = 119 / 143 = 0.832

الضبط للصنف 1: من بين الركاب الذين وصفهم النموذج بأنهم ناجون، نسبة من نجوا فعلا.

precision = 41 / (41 + 7) = 41 / 48 = 0.854

الاستدعاء للصنف 1: من بين الركاب الذين نجوا فعلا، النسبة التي وجدها النموذج.

recall = 41 / (41 + 17) = 41 / 58 = 0.707

مقياس F1، وهو عدد واحد لا يرتفع إلا إذا ارتفع الضبط والاستدعاء كلاهما:

F1 = 2 · precision · recall / (precision + recall)
   = 2 · 0.854 · 0.707 / (0.854 + 0.707) = 0.774

النموذج حذر حين يقول "نجا" (الضبط 0.854)، لكنه يفوت 17 من الناجين الـ58 (الاستدعاء 0.707).

تقرير التصنيف

from sklearn.metrics import classification_report
 
print(classification_report(y_test, y_predicted))
              precision    recall  f1-score   support
 
           0       0.82      0.92      0.87        85
           1       0.85      0.71      0.77        58
 
    accuracy                           0.83       143
   macro avg       0.84      0.81      0.82       143
weighted avg       0.83      0.83      0.83       143

يحمل الصف 1 القيم التي حسبناها للتو، مقربة إلى منزلتين عشريتين. ويفعل الصف 0 الشيء نفسه مع جعل عدم النجاة هو الصنف الموجب: الضبط 78 / (78 + 17) = 0.821 والاستدعاء 78 / (78 + 7) = 0.918. وsupport هو عدد ركاب الاختبار في كل صنف. ويحسب الصف weighted avg متوسط الصنفين موزونا بعدد ركاب الاختبار في كل منهما؛ ويستخدم الدفتر 2 قيمة F1 فيه، وهي هنا 0.83.

منحنى ROC

ينهي الدفتر تقييمه بمنحنى ROC (ROC curve). وقد حذفت plot_roc_curve أيضا؛ والصورة الحالية هي:

from sklearn.metrics import RocCurveDisplay
 
RocCurveDisplay.from_estimator(model, x_test, y_test)

منحنى ROC لنموذج الدفتر

يبين المنحنى، لكل عتبة ممكنة، نسبة الناجين الذين وجدهم النموذج (معدل الموجبات الصحيحة، true positive rate، وهو الاستدعاء) مقابل نسبة غير الناجين الذين وصفوا خطأ بأنهم ناجون (معدل الموجبات الكاذبة، false positive rate). والقطر المتقطع نموذج يخمن. والمساحة تحت المنحنى، AUC = 0.88، تلخص المنحنى كله: 1.0 تعني نموذجا مثاليا و0.5 تعني التخمين.

تحريك العتبة

تستخدم predict دائما 0.5، لكنك تستطيع اختيار أي عتبة انطلاقا من الاحتمالات:

p = model.predict_proba(x_test)[:, 1]
y_pred = (p >= 0.3).astype(int)
confusion_matrix(y_test, y_pred)

نفذنا ذلك لثلاث عتبات على ركاب الاختبار الـ143 أنفسهم:

العتبةالمصفوفة (TN, FP, FN, TP)الضبطالاستدعاء
0.368, 17, 7, 510.7500.879
0.578, 7, 17, 410.8540.707
0.783, 2, 30, 280.9330.483

الرموز TN وFP وFN وTP هي خلايا المصفوفة الأربع بترتيب القراءة: السالبات الصحيحة (true negatives)، والموجبات الكاذبة (false positives)، والسالبات الكاذبة (false negatives)، والموجبات الصحيحة (true positives).

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

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

الجزء 4: الدفتر 2، استكشاف البيانات نفسها وتنظيفها

افتح الدفتر

افتح Logistic_Regression_Classification في Colab

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

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/titanic_train.csv")
train = pd.read_csv(url)

إنه ملف الصفوف الـ891 نفسه المستخدم في الدفتر 1.

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

يرسم الدفتر مخطط عد (count plot) للعمود Survived مقسما حسب Sex:

أعداد الناجين حسب الجنس

من بين 314 امرأة نجت 233 (0.742). ومن بين 577 رجلا نجا 109 (0.189). ولهذا حصلت أعمدة Sex على أوزان كبيرة في النموذج.

ثم يرسم نسبة الركاب الناجين في كل درجة، باستخدام train.groupby('Pclass')['Survived'].mean():

نسبة الناجين حسب درجة الراكب

تنخفض النسبة من 0.630 في الدرجة الأولى إلى 0.473 في الثانية و0.242 في الثالثة.

املأ الأعمار المفقودة بدلا من حذفها

حذف الدفتر 1 الركاب الـ177 الذين لا عمر لهم. أما الدفتر 2 فيحتفظ بهم ويملأ (imputes) كل عمر مفقود بمتوسط عمر درجة ذلك الراكب، لأن المخطط الصندوقي (boxplot) للعمر حسب الدرجة يبين أن أعمار الدرجات مختلفة.

Pclassمتوسط العمرالأعمار المفقودة التي ملئت
138.2330
229.8811
325.14136
f_class_Age = pd.DataFrame(train.groupby('Pclass')['Age'].mean())
a = list(f_class_Age['Age'])
 
def impute_age(cols):
    Age = cols['Age']
    Pclass = cols['Pclass']
    if pd.isnull(Age):
        if Pclass == 1:
            return a[0]
        elif Pclass == 2:
            return a[1]
        else:
            return a[2]
    else:
        return Age
 
train['Age'] = train[['Age', 'Pclass']].apply(impute_age, axis=1)

يكتب الدفتر Age = cols[0] وPclass = cols[1]. وفي pandas 3.0 يتوقف هذا برسالة KeyError: 0، لأن cols صف تسمياته Age وPclass. أما القراءة بالاسم، كما في الكود أعلاه، فتعمل في كل الإصدارات.

مثال محلول: عمر واحد بعد الملء

الصف 5 في الملف راكب في الدرجة الثالثة بلا عمر. تستقبل impute_age القيمتين Age = NaN وPclass = 3، فتعيد a[2]، أي 25.14. وبعد الملء يصبح في Age عدد 891 قيمة، وينتقل متوسطه من 29.699 إلى 29.293.

الحذف ثم الترميز

train.drop('Cabin', axis=1, inplace=True)
train.dropna(inplace=True)
train.drop(['PassengerId', 'Name', 'Ticket'], axis=1, inplace=True)
 
sex = pd.get_dummies(train['Sex'], drop_first=True)
embark = pd.get_dummies(train['Embarked'], drop_first=True)
train.drop(['Sex', 'Embarked'], axis=1, inplace=True)
train = pd.concat([train, sex, embark], axis=1)
  • العمود Cabin مفقود في 687 صفا، لذا يحذف العمود كله.
  • ثم تحذف dropna الصفوف التي لا قيمة لها في Embarked، وعددها 2، فيبقى 889 صفا.
  • الأعمدة PassengerId وName وTicket تعرف الراكب لكنها لا تقول شيئا عاما عن النجاة، لذا فهي ليست خصائص.
  • يحذف drop_first=True أول عمود وهمي (dummy column). فلا يبقى من Sex إلا male؛ أما female فهي ببساطة male = 0. ولا يبقى من Embarked (الموانئ C وQ وS) إلا Q وS؛ والميناء C هو Q = 0 وS = 0.

يحتوي الجدول النهائي على الهدف Survived وثماني خصائص: Pclass وAge وSibSp وParch وFare وmale وQ وS.

التدريب وقراءة مقياس F1

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
 
X_train, X_test, y_train, y_test = train_test_split(
    train.drop('Survived', axis=1), train['Survived'],
    test_size=0.30, random_state=111)
 
logmodel = LogisticRegression(C=1, max_iter=1000)
logmodel.fit(X_train, y_train)

يحتفظ التقسيم بـ267 راكبا للاختبار. ويحقق هذا النموذج دقة قدرها 0.835، مع مصفوفة الالتباس [[145, 18], [26, 78]].

كيف يعتمد مقياس F1 على الإعدادات

ينفذ باقي الدفتر ثلاث حلقات. تدرب كل حلقة نماذج كثيرة وتسجل مقياس F1 في الصف weighted avg من تقرير التصنيف.

معامل العقوبة (penalty parameter). يتحكم C في التنظيم (regularization): كلما صغرت C دفع النموذج بقوة أكبر نحو أوزان صغيرة. يجرب الدفتر قيم C من 0.001 إلى 0.2 ويرسم النتيجة مقابل العقوبة 1/C.

مقياس F1 مقابل معامل العقوبة

العقوبة القوية جدا (1/C = 1000) تبقي الأوزان صغيرة إلى حد يهبط معه مقياس F1 إلى 0.63. ومع انخفاض العقوبة نحو 5 (C = 0.2)، يصعد مقياس F1 إلى ما بين 0.83 و0.85.

حجم الاختبار. تغيير نسبة الاختبار من 0.10 إلى نحو 0.79 يحرك مقياس F1 بين 0.80 و0.87.

مقياس F1 مقابل حجم مجموعة الاختبار

البذرة العشوائية (random seed) للتقسيم. تغيير random_state وحدها من 101 إلى 198، مع تثبيت كل شيء آخر، يحرك مقياس F1 بين 0.76 و0.86.

مقياس F1 مقابل البذرة العشوائية للتقسيم

يحمل الرسم الأخير الدرس الأهم: فرق بضعة أجزاء من مئة بين نموذجين قد يأتي من التقسيم وحده. ولهذا يعرض الدفتر 1 أيضا نتيجة التحقق المتقاطع.

تشغيل الدفتر 2 على المكتبات الحالية

شغلنا الدفتر باستخدام pandas 3.0.6 وscikit-learn 1.9.1. وأربعة أمور تحتاج إلى انتباه:

في الدفترما يحدث الآنالإصلاح
cols[0] في impute_ageKeyError: 0cols['Age']، cols['Pclass']
n_jobs=4FutureWarning: بلا أثر منذ 1.8احذفه
max_iter=100 في حلقة CConvergenceWarning في 183 من 200 عملية ملاءمةارفع max_iter، إلى 1000 مثلا
f1[i] = l[len(l)-2]قيم F1 نصية، فيرتب المحور حسب أول ظهورfloat(l[len(l)-2])

الصف الأخير هو الأهم. فمع القيم النصية تعامل matplotlib كل قيمة F1 على أنها فئة، وتوزعها بمسافات متساوية بترتيب أول ظهور لها. وفي رسم البذرة يصبح المحور الرأسي من الأسفل إلى الأعلى 0.83، 0.77، 0.81، 0.80، وهكذا، مما يجعل الرسم بلا معنى. والتحويل إلى float يصلح ذلك؛ وقد رسمت الأشكال أعلاه بهذه الطريقة. ورفع max_iter يغير قيم F1 في حلقة C بمقدار 0.02 على الأكثر (عند C = 0.076 من 0.83 إلى 0.81)، لذا يبقى اتجاه الرسم كما هو.

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

  • قراءة ملف بيانات الدفتر من مجلد غير موجود في Colab.
  • إدخال عمود نصي مثل Sex إلى النموذج دون ترميزه.
  • إبقاء أعمدة المعرفات (identifier columns) مثل PassengerId أو Name ضمن الخصائص.
  • نسيان أن predict_proba تعيد عمودين: [:, 1] هو احتمال الصنف 1.
  • الحكم على المصنف بالدقة وحدها؛ انظر إلى الضبط والاستدعاء للصنف الذي يهمك.
  • تتويج نموذج بسبب فرق قد يمحوه تقسيم عشوائي مختلف.

مرحلة المشروع: الخصائص والهدف

مرحلة هذا الأسبوع هي استخراج خصائص بيانات المشروع وتحديد المتغير الهدف (Project Data Features Extraction and Target Variable Identification). وباستخدام ما فعله الدفتران ببيانات تيتانيك:

  1. سم المتغير الهدف: العمود الوحيد الذي سيتنبأ به نموذجك. وبين هل هو صنف، مثل Survived، أم عدد، مثل سعر المنزل.
  2. اكتب الخصائص المرشحة واحذف أعمدة المعرفات، كما حذف الدفتران PassengerId وName وTicket.
  3. احسب القيم المفقودة في كل عمود باستخدام df.info()، ثم قرر لكل عمود: حذف العمود، أو حذف الصفوف، أو ملء القيم.
  4. رمز كل عمود نصي ترميزا أحاديا باستخدام pd.get_dummies.
  5. اكتب قائمة الخصائص النهائية وأبعاد (shape) جدولك.

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

الخلاصة

  1. يحسب الانحدار اللوجستي مجموعا خطيا z = b + w1·x1 + ... + wn·xn، ويحوله إلى احتمال بالدالة السينية، ثم يقطع الاحتمال عند عتبة.
  2. σ(0) = 0.5، لذا مع العتبة 0.5 يكون التنبؤ 1 عندما تكون z ≥ 0، وعندها فقط.
  3. تحتاج النماذج إلى خصائص عددية: رمز الأعمدة النصية ترميزا أحاديا، واحذف القيم المفقودة أو املأها.
  4. قيم المصنف بمصفوفة الالتباس والدقة والضبط والاستدعاء ومقياس F1، على بيانات الاختبار.
  5. تحريك العتبة يبادل الضبط بالاستدعاء؛ والمبادلة المناسبة تعتمد على المشكلة.
  6. قد يضلل تقسيم واحد؛ فالتحقق المتقاطع والتقسيمات المتكررة تبين مقدار ما يمكن أن تتحرك به النتيجة.

التمارين

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

التمرين 1: راكبان يدويا (نحو 10 دقائق)

استخدم الأوزان المقربة من المثال المحلول: b = 1.212، وAge -0.039، وSex_female 1.149، وSex_male -1.150، وPclass_1 1.200، وPclass_2 0.007، وPclass_3 -1.207. لكل راكب، اكتب متجه الخصائص، ثم احسب z وp والتنبؤ عند العتبة 0.5.

  1. صبي عمره 8 سنوات يسافر في الدرجة الثانية.
  2. رجل عمره 30 عاما يسافر في الدرجة الأولى.

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

عند خفض العتبة إلى 0.3، يعطي نموذج الدفتر 1 هذه المصفوفة على ركاب الاختبار الـ143:

التنبؤ 0التنبؤ 1
الفعلي 06817
الفعلي 1751

احسب الدقة، والضبط والاستدعاء ومقياس F1 للصنف 1. وقارنها بالقيم عند العتبة 0.5.

التمرين 3: الملء والترميز يدويا (نحو 5 دقائق)

ثمانية ركاب، اثنان منهم بلا عمر:

الراكبPclassAgeEmbarked
A140C
B1مفقودS
C150S
D230S
E2مفقودQ
F320S
G3مفقودS
H328Q
  1. املأ كل عمر مفقود بمتوسط عمر درجة الراكب، كما تفعل impute_age.
  2. رمز Embarked باستخدام pd.get_dummies(..., drop_first=True). ما الأعمدة التي تظهر، وما قيمها للركاب A وE وH؟

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

  1. شغل الدفتر 1 حتى model.score(x_test, y_test)، مع قراءة البيانات من الرابط.
  2. اطبع احتمال النجاة للصبي في التمرين 1، [[8, 0, 1, 0, 1, 0]].
  3. تنبأ بمجموعة الاختبار عند العتبة 0.4 بدلا من 0.5، واطبع مصفوفة الالتباس وclassification_report.
  4. هل ارتفع الضبط أم الاستدعاء للصنف 1؟ وأيهما انخفض؟

الإجابات

الإجابة 1

الصبي. متجه الخصائص هو [8, 0, 1, 0, 1, 0].

z = 1.212 + (-0.039)(8) + (-1.150) + 0.007
  = 1.212 - 0.312 - 1.150 + 0.007 = -0.243
p = 1 / (1 + e^0.243) = 1 / (1 + 1.2751) = 0.440

القيمة p أقل من 0.5، لذا فالتنبؤ 0، أي لم ينج. ويعطي النموذج المدرب، بأوزانه غير المقربة، 0.4394.

الرجل. متجه الخصائص هو [30, 0, 1, 1, 0, 0].

z = 1.212 + (-0.039)(30) + (-1.150) + 1.200
  = 1.212 - 1.170 - 1.150 + 1.200 = 0.092
p = 1 / (1 + e^-0.092) = 1 / (1 + 0.9121) = 0.523

p ≥ 0.5، لذا فالتنبؤ 1، أي نجا. ويعطي النموذج المدرب 0.5218. والراكبان كلاهما قريبان من العتبة، لذا قد يقلب تنبؤهما تغيير صغير في النموذج أو في العتبة.

الإجابة 2

accuracy  = (68 + 51) / 143 = 0.832
precision = 51 / (51 + 17) = 0.750
recall    = 51 / (51 + 7)  = 0.879
F1        = 2 · 0.7500 · 0.8793 / (0.7500 + 0.8793) = 0.8095

يقرب مقياس F1 إلى 0.810، وهو أعلى من 0.774 عند العتبة 0.5. ومقارنة بالعتبة 0.5 (الضبط 0.854، والاستدعاء 0.707)، تجد العتبة الأدنى 10 ناجين إضافيين، فيرتفع الاستدعاء، وتطلق 10 إنذارات كاذبة إضافية، فينخفض الضبط. والدقة هي نفسها، 0.832، لأن عدد الأخطاء 24 في الحالتين: هنا 17 زائد 7، وعند 0.5 كان 7 زائد 17.

الإجابة 3

لا تستخدم متوسطات الدرجات إلا الأعمار المعروفة:

  • الدرجة 1: (40 + 50) / 2 = 45، لذا يأخذ B القيمة 45.
  • الدرجة 2: لا يعرف إلا عمر D، لذا يأخذ E القيمة 30.
  • الدرجة 3: (20 + 28) / 2 = 24، لذا يأخذ G القيمة 24.

للعمود Embarked القيم C وQ وS. ومع drop_first=True يحذف أولها، C، فيكون العمودان Q و**S**:

الراكبEmbarkedQS
AC00
EQ10
HQ10

أما الركاب الخمسة الآخرون فصعدوا من الميناء S، لذا لهم Q = 0 وS = 1.

الإجابة 4

  • احتمال نجاة الصبي 43.9%، لذا تعيد predict القيمة 0.
  • مع y_pred = (model.predict_proba(x_test)[:, 1] >= 0.4).astype(int):
[[75 10]
 [10 48]]
 
              precision    recall  f1-score   support
 
           0       0.88      0.88      0.88        85
           1       0.83      0.83      0.83        58
 
    accuracy                           0.86       143
   macro avg       0.85      0.85      0.85       143
weighted avg       0.86      0.86      0.86       143
  • ارتفع استدعاء الصنف 1، من 0.71 إلى 0.83: فالنموذج يجد الآن 48 من الناجين الـ58 بدلا من 41. وانخفض الضبط، من 0.85 إلى 0.83. وعلى مجموعة الاختبار هذه ارتفعت الدقة أيضا، من 0.832 إلى 0.860، لأن عدد الأخطاء انخفض من 24 إلى 20.