الانحدار اللوجستي
يقدم هذا القسم أول مصنف (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) |
الهدف y | Survived: 1 نجا، و0 لم ينج |
| الصنف الموجب (positive class) | الصنف 1، وهو الصنف الذي يعرض النموذج احتماله |
الاحتمال p | تقدير النموذج لاحتمال أن يكون y = 1 لهذا الراكب |
العتبة t | الحد الفاصل على p: عند p ≥ t يكون التنبؤ 1، وتحته 0 |
النموذج في ثلاث خطوات
- المجموع. اضرب كل خاصية في وزنها وأضف الحد الثابت (bias). الناتج عدد واحد هو
z. - الضغط. مرر
zعبر الدالة السينية. الناتجpيقع دائما بين 0 و1. - القرار. قارن
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 كيف تنشأ هذه الأعمدة). طبعنا الأوزان التي تعلمها النموذج وقربناها إلى ثلاث منازل عشرية:
| الخاصية | الوزن |
|---|---|
الحد الثابت 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 |
قراءة الإشارات: كون الراكبة أنثى وكونها في الدرجة الأولى يرفعان المجموع الخطي، وكون الراكب ذكرا وكونه في الدرجة الثالثة يخفضانه، وكل سنة إضافية من العمر تخفض المجموع الخطي بمقدار 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):
| العمود | القيم غير الفارغة | المفقودة |
|---|---|---|
Age | 714 | 177 |
Cabin | 204 | 687 |
Embarked | 889 | 2 |
اختر الخصائص، ورمز، واحذف الصفوف الناقصة
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، لذا لا يتغير شيء آخر.
مثال محلول: ترميز راكبين
| الصف | Sex | Pclass | Sex_female | Sex_male | Pclass_1 | Pclass_2 | Pclass_3 |
|---|---|---|---|---|---|---|---|
| 0 | male | 3 | 0 | 1 | 0 | 0 | 1 |
| 1 | female | 1 | 1 | 0 | 1 | 0 | 0 |
يحصل كل راكب على 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)
| التنبؤ: لم ينج (0) | التنبؤ: نجا (1) | |
|---|---|---|
| الفعلي: لم ينج (0) | 78 | 7 |
| الفعلي: نجا (1) | 17 | 41 |
الصفوف هي الصنف الحقيقي والأعمدة هي التنبؤ، لذا يحمل القطر التنبؤات الصحيحة:
- 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)
يبين المنحنى، لكل عتبة ممكنة، نسبة الناجين الذين وجدهم النموذج (معدل الموجبات الصحيحة، 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.3 | 68, 17, 7, 51 | 0.750 | 0.879 |
| 0.5 | 78, 7, 17, 41 | 0.854 | 0.707 |
| 0.7 | 83, 2, 30, 28 | 0.933 | 0.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 | متوسط العمر | الأعمار المفقودة التي ملئت |
|---|---|---|
| 1 | 38.23 | 30 |
| 2 | 29.88 | 11 |
| 3 | 25.14 | 136 |
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.

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

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

يحمل الرسم الأخير الدرس الأهم: فرق بضعة أجزاء من مئة بين نموذجين قد يأتي من التقسيم وحده. ولهذا يعرض الدفتر 1 أيضا نتيجة التحقق المتقاطع.
تشغيل الدفتر 2 على المكتبات الحالية
شغلنا الدفتر باستخدام pandas 3.0.6 وscikit-learn 1.9.1. وأربعة أمور تحتاج إلى انتباه:
| في الدفتر | ما يحدث الآن | الإصلاح |
|---|---|---|
cols[0] في impute_age | KeyError: 0 | cols['Age']، cols['Pclass'] |
n_jobs=4 | FutureWarning: بلا أثر منذ 1.8 | احذفه |
max_iter=100 في حلقة C | ConvergenceWarning في 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). وباستخدام ما فعله الدفتران ببيانات تيتانيك:
- سم المتغير الهدف: العمود الوحيد الذي سيتنبأ به نموذجك. وبين هل هو صنف، مثل
Survived، أم عدد، مثل سعر المنزل. - اكتب الخصائص المرشحة واحذف أعمدة المعرفات، كما حذف الدفتران
PassengerIdوNameوTicket. - احسب القيم المفقودة في كل عمود باستخدام
df.info()، ثم قرر لكل عمود: حذف العمود، أو حذف الصفوف، أو ملء القيم. - رمز كل عمود نصي ترميزا أحاديا باستخدام
pd.get_dummies. - اكتب قائمة الخصائص النهائية وأبعاد (shape) جدولك.
احتفظ بهذا الجدول: ففي الأسبوع القادم ستدرب عليه نموذجك الأول.
الخلاصة
- يحسب الانحدار اللوجستي مجموعا خطيا
z = b + w1·x1 + ... + wn·xn، ويحوله إلى احتمال بالدالة السينية، ثم يقطع الاحتمال عند عتبة. σ(0) = 0.5، لذا مع العتبة 0.5 يكون التنبؤ1عندما تكونz ≥ 0، وعندها فقط.- تحتاج النماذج إلى خصائص عددية: رمز الأعمدة النصية ترميزا أحاديا، واحذف القيم المفقودة أو املأها.
- قيم المصنف بمصفوفة الالتباس والدقة والضبط والاستدعاء ومقياس F1، على بيانات الاختبار.
- تحريك العتبة يبادل الضبط بالاستدعاء؛ والمبادلة المناسبة تعتمد على المشكلة.
- قد يضلل تقسيم واحد؛ فالتحقق المتقاطع والتقسيمات المتكررة تبين مقدار ما يمكن أن تتحرك به النتيجة.
التمارين
نحو 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.
- صبي عمره 8 سنوات يسافر في الدرجة الثانية.
- رجل عمره 30 عاما يسافر في الدرجة الأولى.
التمرين 2: قراءة مصفوفة الالتباس (نحو 5 دقائق)
عند خفض العتبة إلى 0.3، يعطي نموذج الدفتر 1 هذه المصفوفة على ركاب الاختبار الـ143:
| التنبؤ 0 | التنبؤ 1 | |
|---|---|---|
| الفعلي 0 | 68 | 17 |
| الفعلي 1 | 7 | 51 |
احسب الدقة، والضبط والاستدعاء ومقياس F1 للصنف 1. وقارنها بالقيم عند العتبة 0.5.
التمرين 3: الملء والترميز يدويا (نحو 5 دقائق)
ثمانية ركاب، اثنان منهم بلا عمر:
| الراكب | Pclass | Age | Embarked |
|---|---|---|---|
| A | 1 | 40 | C |
| B | 1 | مفقود | S |
| C | 1 | 50 | S |
| D | 2 | 30 | S |
| E | 2 | مفقود | Q |
| F | 3 | 20 | S |
| G | 3 | مفقود | S |
| H | 3 | 28 | Q |
- املأ كل عمر مفقود بمتوسط عمر درجة الراكب، كما تفعل
impute_age. - رمز
Embarkedباستخدامpd.get_dummies(..., drop_first=True). ما الأعمدة التي تظهر، وما قيمها للركاب A وE وH؟
التمرين 4: في Colab (نحو 10 دقائق)
- شغل الدفتر 1 حتى
model.score(x_test, y_test)، مع قراءة البيانات من الرابط. - اطبع احتمال النجاة للصبي في التمرين 1،
[[8, 0, 1, 0, 1, 0]]. - تنبأ بمجموعة الاختبار عند العتبة 0.4 بدلا من 0.5، واطبع مصفوفة الالتباس و
classification_report. - هل ارتفع الضبط أم الاستدعاء للصنف
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.523p ≥ 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**:
| الراكب | Embarked | Q | S |
|---|---|---|---|
| A | C | 0 | 0 |
| E | Q | 1 | 0 |
| H | Q | 1 | 0 |
أما الركاب الخمسة الآخرون فصعدوا من الميناء 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.