PyDA Course
متقدم حرّ ⏱ 120 دقيقة ⚡ +100 XP +15 نقطة لكل خطوة

خط أنابيب AutoML

Machine LearningDeveloper ToolsPandas

«التعلم الآلي الآلي» في البرامج التعليمية يعيش على خادم تستأجره. يشغّل هذا المشروع الفكرة ذاتها على حاسوبك المحمول: طيار آلي صغير يأخذ صفوفًا خام، وينظّفها بخط أنابيب متسلسل، ويُسابق حفنة نماذج بمعايرة متقاطعة سليمة، ويضبط الواعدة منها ببحث شبكي، ويصدّر الفائز المتسلسل الذي يعيد تحميله في أي مكان. على الطريق يعلّم الانضباط الذي ترمّزه مكتبات ML الحقيقية: تقسيم التدريب/الاختبار يُقرَّر قبل أي ضبط، والملء والقياس يتعلّمان من بيانات التدريب فقط، والبحث الشبكي المضبوط على المعايرة قد يخالف مجموعة الاختبار ، هذا المشروع يجعل الثلاثة صالحة للملاحظة ببيانات صغيرة مكتوبة باليد. المجموعة اصطناعية (إحصاءات حركة شبكة تترابط مع حالة سليمة/غير سليمة)، فكل رقم في هذا الدليل قابل للتكرار من بذرة ثابتة.

هذا يفترض pandas وsklearn أساسيًا وبعض numpy. إنه مشروع اختياري غير مُقيَّم ، راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة والنامية. يثبّت حزمتين (pandas, scikit-learn) ، وuv يجعل ذلك سهلًا.

🎯 ما ستفعله

  1. توليد مجموعة من 400 صف قابل للتكرار مع ضجيج وسوم محقون وتقسيمها 75/25 بتقسيم طبقي.
  2. لف خط أنابيب معالجة يملأ بالوسيط ويوحّد بالمعيار وملاءمته على ميزات التدريب.
  3. مسابقة الانحدار اللوجستي وشجرة قرار وk-NN بـcross_val_score.
  4. ضبط الأشجار والجيران الواعدة بـGridSearchCV ومقارنتها بخط المعايرة الأساسي.
  5. تصدير الخط الابتدائي النهائي بـjoblib وإعادة تحميله كمتنبئ باحتمالات.

أين تُشغّل هذا

محليًا باستخدام uv هو المسار الموصى به. أمر واحد يثبّت كل شيء:

uv init automl-pipeline && cd automl-pipeline
uv add pandas scikit-learn joblib

Google Colab وKaggle Notebooks وBinder يشغّلون كل خطوة دون تعديل ، المنصتان تحملان pandas وscikit-learn مثبتين مسبقًا. البيانات الاصطناعية والبذور الثابتة تجعل ناتج الدفتر متطابقًا عبر الأجهزة.

Open In Colab Open In Kaggle Binder

الإعداد

كل ما تحتاجه قبل أول صف.

جهّز المشروع

uv init automl-pipeline
cd automl-pipeline
uv add pandas scikit-learn joblib

الاستيرادات الثلاثة التي ستستخدمها طوال الوقت:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

✅ قائمة التحقق

  • ✅ ينجح uv run python3 -c "import pandas, sklearn, joblib".
  • ✅ تعرف أن مقاييس sklearn تأتي من sklearn.metrics، والخطوط الابتدائية تأتي من sklearn.pipeline ، كلاهما يُستورد عند الحاجة أدناه.

🤔 سؤال (أسئلة) سقراطي(ة) :

  • يعد «التعلم الآلي الآلي» بأن يختار أفضل نموذج. لكن خطًّا يضبط على نفس البيانات التي يبلّغ عنها متفائل. أين يجب أن تظهر مجموعة الاختبار وتتكرر في مسار هذا المشروع، ولماذا يتغير الجواب لو تسرّبت إلى الضبط؟
  • المجموعة اصطناعية: عنقدتان ضبابيتان في (bytes_in, bytes_out) مع قلب وسم عشوائي بنسبة 5%. ماذا يعلّمك القلب عن مجموعة اصطناعية نظيفة تمامًا كانت ستخفيه؟

الخطوة 1: ابنِ مجموعة البيانات القابلة للتكرار

كل رقم لاحق يعتمد على هذه الكتلة، لذا يجب أن تُزرع وتُوثّق وتُقسَّم بعناية.

1.1 ولّد بيانات العناقيد

👟 تلميح البداية : استخدم np.random.default_rng(7) لرسم 200 صف لكل صنف من سحابتين، ثم اقلب 5% من الوسوم عشوائيًا.

# main.py
import numpy as np
import pandas as pd

rng = np.random.default_rng(7)
n = 400
X0 = rng.normal([2.0, 2.0], 1.6, size=(n // 2, 2))   # "unhealthy" cluster
X1 = rng.normal([6.0, 6.0], 1.6, size=(n // 2, 2))   # "healthy" cluster
X = np.vstack([X0, X1])
y = np.array([0] * (n // 2) + [1] * (n // 2))
flip = rng.random(n) < 0.05
y = np.where(flip, 1 - y, y)                          # 5% label noise

df = pd.DataFrame(X, columns=["bytes_in", "bytes_out"])
df["ok"] = y
print("shape:", df.shape)
print("balance:", df["ok"].value_counts().to_dict())
print(df.head(3).round(2).to_string(index=False))

default_rng(7) هي واجهة numpy الحديثة ، البذرة الثابتة تعني رسومات متطابقة على كل جهاز. يلتقط flip = rng.random(n) < 0.05 نحو 5% من الصفوف وقلبها 1 - y، فتصبح الصنفات صعبة الفصل حقيقة عند الحدود، كما تكون حركة الشبكة الحقيقية. لاحظ أن الميزان لم يعد 200/200 بالضبط ، تنقل القلبات الوسوم، فتترك خللًا صادقًا طفيفًا.

🎯 الناتج المتوقع:

shape: (400, 3)
balance: {1: 208, 0: 192}
 bytes_in  bytes_out  ok
     2.00       2.48   0
     1.56       0.58   0
     1.27       0.41   0

🩹 إذا لم يعمل : إذا كان الميزان 200/200 بالضبط، فسطر قلب الوسم لم يَعمل (أو استُبدل rng.random(n) بمولّد RNG جديد). إذا أظهر head كسورًا مختلفة، فتختلف بذرة numpy لديك أو سطر np.vstack ، أعد فحص default_rng(7).

1.2 اقسم التدريب والاختبار أولًا

👟 تلميح البداية : اقسم بـtrain_test_split(..., test_size=0.25, random_state=7, stratify=df["ok"]) ، القسمة تحدث قبل أن يتعلم أي شيء.

# main.py (continued)
from sklearn.model_selection import train_test_split

train, test = train_test_split(df, test_size=0.25, random_state=7,
                               stratify=df["ok"])
print("train/test:", len(train), len(test))
print("test balance:", test["ok"].value_counts().to_dict())

القسمة مرة واحدة من البداية هي الانضباط الذي يُبقي بقية المشروع نزيهًا: كل مُلئٍ ومُوحِّد وطية معايرة وبحث لاحق يرى train فقط. يبقي stratify نسبة الصنف متشابهة في الجانبين حتى مع الخلل 208/192 ، فالخلط البسيط قد يعطي مجموعة اختبار سيئة الحظ.

🎯 الناتج المتوقع:

train/test: 300 100
test balance: {1: 52, 0: 48}

🩹 إذا لم يعمل : إذا انقلب الحجمان 75/25، فقد ضُبطت test_size على 0.75. إذا كان ميزان الاختبار قرب 50/50 لكن ليس تمامًا ، فهذا تقريب scikit-learn الطبقي وهو سليم.

1.3 تحقّق من البيانات والقسمة

✅ قائمة التحقق

  • df.shape == (400, 3)؛ ميزان {1: 208, 0: 192} من بذرة 7.
  • ✅ يعطي train_test_split 300/100 بتقسيم طبقي.
  • ✅ تشغيل الكتلة مرتين يعطي DataFrames متطابقة (البذرة!).

🤔 سؤال (أسئلة) سقراطي(ة) :

  • لماذا يضيف قلب الوسم متاعب بدل أن يطرحها؟ ما الذي كان سيجعله مثاليًا مصطنعًا مجموعةٌ بضجيج 0% (تخيل نتيجتها بالمعايرة على بيانات لا تتداخل عنقدتاها أبدًا) ، ولماذا يضللك ذلك بشأن نشر حقيقي؟
  • يعمل stratify على وسوم الصنف. لو كانت انحدارًا (ok مستمرًا)، لَمَا انطبق stratify. أي خاصية للهدف كانت ستحتاج حمايةً حينها، وأي وسيطة sklearn توفرها؟

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

الأرقام الخام لا تغذّي نموذجًا؛ الأرقام النظيفة المقيَّسة تفعل. تزيل الخطوة 2 القيم المفقودة وتعيد المقياس دون لمس مجموعة الاختبار أبدًا.

2.1 أدخل الفجوات وحدّد مواقعها

👟 تلميح البداية : انسخ ميزات التدريب، واثقب 10% ثقوبًا، وعدّها ، سيناريو واقعي «جهاز الاستشعار أسقط القراءات».

# main.py (continued)
feat = train[["bytes_in", "bytes_out"]].copy()
miss = np.random.default_rng(1).random(feat.shape) < 0.10   # ~10% holes
feat[miss] = np.nan
print("NaNs  bytes_in:", feat["bytes_in"].isna().sum(),
      " bytes_out:", feat["bytes_out"].isna().sum())

الفجوات تحدث بعد القسمة وفوق نسخة، فيبقى الإطاران الحقيقيان train/test كاملين ، وهنا حيث كان سيملأ خطٌّ معرّض للتسرّب بسعادة من بيانات الاختبار ويتدرّب بصمت على كل 400 صف. default_rng(1) بذرة مختلفة عن بذرة الخطوة 1، فتبقى البيانات نفسها ثابتة بينما الفجوات قابلة للتكرار في حد ذاتها.

🎯 الناتج المتوقع:

NaNs  bytes_in: 23  bytes_out: 34

🩹 إذا لم يعمل : إذا اختلفت العدّادات، فبذرة RNG أو مقارنة .random(feat.shape) تغيّرت. إذا قرأ feat كاملًا بعد الطباعة، فتعيين np.nan لم يلتصق ، تحقق أن miss منطقية ومن نفس الشكل.

2.2 سلاسل الملء ← القياس

👟 تلميح البداية : ابنِ Pipeline([("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler())]) ونفّذ fit_transform على الميزات المثقوبة.

# main.py (continued)
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler

clean = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
])
S = clean.fit_transform(feat)
print("scaled mean:", np.round(S.mean(axis=0), 4))
print("scaled std :", np.round(S.std(axis=0), 4))
print("imputed medians:", np.round(clean.steps[0][1].statistics_, 3))

خط الأنابيب تسلسل تحويلات يتعلّم فقط مما تُلائمه عليه. يملأ SimpleImputer(strategy="median") كل فجوة بوسيط عمودها، المُتعلَّم من feat؛ ثم يوحّد StandardScaler بالمعيار: المتوسط→0، الانحراف المعياري→1. اسأل لماذا الوسيط لا المتوسط للملء ، الوسيط متين أمام القمم المحقونة، بينما سينزاح المتوسط تحتها. بعد الملء والقياس تكون مصفوفة الميزات جاهزة لأي نموذج مبني على مسافة أو منظَّم.

🎯 الناتج المتوقع:

scaled mean: [ 0. -0.]
scaled std : [1. 1.]
imputed medians: [3.999 3.608]

🩹 إذا لم يعمل : إذا لم يكن المتوسط المقيَّس ‎≈0، فالمُلئِ أدّى قبل المُوحِّد أو طُبّق المُوحِّد على إطار مختلف. إذا أخطأ statistics_، فالمُلئِ لم يُلائم ، نسيت fit_transform وفعلت transform فقط.

2.3 تحقّق من خط الأنابيب

✅ قائمة التحقق

  • ✅ عدّادات bytes_in: 23, bytes_out: 34 من الفجوات المزروعة.
  • ✅ لخريج fit-transformed متوسط ‎≈ 0 وانحراف معياري ‎≈ 1 لكل عمود.
  • ✅ يحمل .steps[0][1].statistics_ وسائط الأعمدة المستخدمة للملء.

🤔 سؤال (أسئلة) سقراطي(ة) :

  • يتعلّم المُوحِّد الوسط والانحراف من train فقط. لو تعلّم من كل 400 صف، أكان سيظل ينتج درجات z صالحة؟ نعم ، صالحة لكن ملائمة على بيانات مستقبلية، وهذا هو التسرّب بالضبط الذي يضخّم نتائج المعايرة. ما الذي يتسرّب، تحديدًا، حين تساهم مجموعة الاختبار في mean_ للموحِّد؟
  • وسيط الملء 3.999 قريب من مركز عنقودة 0. إذا وصل صف اختبار بـbytes_in مفقود، أي رقم مكتسب يملؤه ، ولماذا الملء من وسيط train أفضل بدقة من الملء من صنف الصف نفسه، الذي لا يعرفه النموذج عند الاستدلال؟

الخطوة 3: سباق حديقة النماذج

المعالجة خط ابتدائي ثابت؛ النموذج اختيار. يسبق cross_val_score ثلاثة مرشحين أمناء على طية التدريب فقط.

3.1 دوّن ثلاثة نماذج

👟 تلميح البداية : ابنِ قاموس حديقة من Pipeline/مقدِّرات وبلّغ cross_val_score(...).mean() لكل نموذج على train.

# main.py (continued)
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score

zoo = {
    "logistic": make_pipeline(StandardScaler(),
                              LogisticRegression(max_iter=1000, random_state=1)),
    "tree": DecisionTreeClassifier(max_depth=4, random_state=1),
    "knn": KNeighborsClassifier(n_neighbors=15),
}
for name, est in zoo.items():
    scores = cross_val_score(est, train[["bytes_in", "bytes_out"]], train["ok"], cv=5)
    print(name, "-> mean", round(scores.mean(), 3))

لاحظ أن مجموعة الاختبار غائبة هنا: كل درجة معايرة تبادلية من 5 طيات على 300 صف تدريب، فيتدرّب كل نموذج على 240 ويُسجَّل على المحتجَز الـ60، خمس مرات. يخلط zoo في main.py خطًّا مقيَّسًا (اللوجستي، الذي يريد ميزات مقيَّسة) مع مقدِّرات خام (الشجرة وkNN، إذ تجهل الأشجار غير الحساسة للمقياس وتعيد kNN الضبط الفعليّ بنفسها عبر المسافة). لا ترى الشجرة ولا kNN بيانات مفقودة، لأنهما يأخذان الأعمدة الخام غير المملوءة ، فأنظف مقارنة للحديقة ميزات كما هي، مع ملاحظة أن طيارًا آليًا حقيقيًا كان سيغذّي كل نموذج خط الملء نفسه.

🎯 الناتج المتوقع:

logistic -> mean 0.927
tree -> mean 0.9
knn -> mean 0.917

🩹 إذا لم يعمل : إذا كانت الثلاثة ‎≈0.5، فقد التهم قلب الوسم الإشارة (تحقق من flip لبذرة 7). إذا كانت الشجرة فقط أسوأ بكثير، فـmax_depth=4 لا يلائم ذلك النموذج بشكل كافٍ بينما يتكيف الآخرون.

3.2 اقرأ السباق بنزاهة

👟 تلميح البداية : اطبع تباين مستوى الطية أيضًا ، المتوسط يخفي نموذجًا صاخبًا.

# main.py (continued)
for name, est in zoo.items():
    scores = cross_val_score(est, train[["bytes_in", "bytes_out"]], train["ok"], cv=5)
    print(name, "->", [round(s, 3) for s in scores])

متوسط 5 طيات ملخص؛ الأرقام الخمسة لكل طية هي المضمون. نموذج طياته [0.93, 0.90, 0.92, 0.91, 0.95] يقول «مستقر»، فيما يقول [1.0, 0.75, 0.98, 0.80, 1.0] «هش» حتى عند نفس المتوسط. تقسيمات العملاء المنفصلة وتقسيم الشجرة وحدود kNN كلها تطوى بشكل مختلف ، رؤية القيم الخمس تخبرك بمتوسط أي نموذج يمكنك الوثوق به.

🎯 الناتج المتوقع : 5 درجات لكل نموذج متوسطها يطابق 3.1 (مثلًا طيات اللوجستي الخمس متوسطها 0.927 ، قيم الطيات الدقيقة تختلف بإصدار sklearn؛ الـالمتوسط والترتيب لا يختلفان).

🩹 إذا لم يعمل : إذا طُبعت درجات الطية بغلافات np.float64، فذلك تجميلي ، عوّمها لناتج مرتب. إذا لم يكن عدد الطيات ‎≠5، فقد تغيّرت cv=.

3.3 تحقّق من الحديقة

✅ قائمة التحقق

  • ✅ ثلاثة نماذج سُجِّلت بمعايرة 5 طيات على train فقط؛ مجموعة الاختبار غير ملموسة.
  • ✅ الترتيب في هذه الجري: اللوجستي (0.927) > kNN (0.917) > الشجرة (0.900).
  • ✅ طُبعت درجات مستوى الطية كي لا تُوثق المتوسطات عمياء.

🤔 سؤال (أسئلة) سقراطي(ة) :

  • يفوز اللوجستي رغم أنه يريد ميزات مقيَّسة والشجرة تدور عنها ، الإشارة شبه قابلة للفصل خطيًا، واللوجستي يستغلها أفضل. لو كانت الحدود جيبية، من الثلاثة كان سيغلب غالبًا، وماذا يعني ذلك عن «أفضل نموذج» باعتباره خاصية من البيانات لا من المكتبة؟
  • اختِير n_neighbors=15 لـkNN تخمينًا. ستُضبطه الخطوة 4 ، لكن ضبط كل نموذج يهدر ساعات. أي معلومة تخبرك بفائز هذه الحديقة، وما الذي يجعل ضبط الوصيف ما زال ذا قيمة؟

الخطوة 4: اكنس المعلمات الفائقة

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

4.1 اضبط الشجرة والجيران

👟 تلميح البداية : GridSearchCV(estimator, param_grid, cv=5) على شبكة مدمجة، ثم اطبع best_params_ وbest_score_.

# main.py (continued)
from sklearn.model_selection import GridSearchCV

gs_tree = GridSearchCV(DecisionTreeClassifier(random_state=1),
                       param_grid={"max_depth": [2, 3, 5],
                                   "min_samples_leaf": [1, 5, 10]},
                       cv=5)
gs_tree.fit(train[["bytes_in", "bytes_out"]], train["ok"])
print("tree best:", gs_tree.best_params_, "cv score", round(gs_tree.best_score_, 3))

gs_knn = GridSearchCV(KNeighborsClassifier(),
                      param_grid={"n_neighbors": [3, 5, 9],
                                  "weights": ["uniform", "distance"]},
                      cv=5)
gs_knn.fit(train[["bytes_in", "bytes_out"]], train["ok"])
print("knn best:", gs_knn.best_params_, "cv score", round(gs_knn.best_score_, 3))

GridSearchCV معايرة آلية داخلك: 3×3 = 9 تشكيلات شجرة و3×2 = 6 تشكيلات kNN، كلها سُجِّلت بمعايرة 5 طيات على train ، يختار البحث التشكيلَ بأفضل متوسط درجة معايرة. والأهم أن أفضل تشكيل يُختار بمعايرة train، لا بدقة الاختبار. مختبِر جنّد نموذجه ليكون أفضل على مجموعة الاختبار لكان يضبط على ورقة الإجابات.

🎯 الناتج المتوقع:

tree best: {'max_depth': 3, 'min_samples_leaf': 1} cv score 0.903
knn best: {'n_neighbors': 3, 'weights': 'uniform'} cv score 0.937

🩹 إذا لم يعمل : إذا أظهر best_params_ أطراف الشبكة (مثل max_depth: 5)، فالشبكة خشنة جدًا في ذلك الاتجاه. إذا تجاوز cv score القيمة 0.94، فوزن distance في kNN يسحق نسخة unified في هذه المجموعة ، تحقق من best_params_.

4.2 التوتر بين المعايرة والاختبار

👟 تلميح البداية : سجّل الفائزين المضبوطين على مجموعة الاختبار المحتجَزة وقارنهم بأفضل درجات معايرة لهم.

# main.py (continued)
from sklearn.metrics import accuracy_score

for gs, name in [(gs_tree, "tree"), (gs_knn, "knn")]:
    acc = accuracy_score(test["ok"], gs.best_estimator_.predict(
        test[["bytes_in", "bytes_out"]]))
    print(name, "cv", round(gs.best_score_, 3), "-> test", round(acc, 3))

هذه فجوة النزاهة التي يعلّمها المشروع كله: تقول معايرة الشجرة المضبوطة 0.903، واختبارها 0.91؛ ومعايرة kNN 0.937 واختبارها 0.91. لا المعايرة ولا الاختبار «خطأ» ، المتوسط فوق 5 تقسيمات مبنية على التدريب، بينما يقيس الاختبار مجموعة مسحوبة واحدة ، لكن رقم الاختبار هو ما يهم للتقرير، ورقم المعايرة هو ما استخدمته للاختيار. إبلاغك عن النموذج «0.937 على المعايرة» علنًا سيكون تهويلًا له.

🎯 الناتج المتوقع:

tree cv 0.903 -> test 0.91
knn cv 0.937 -> test 0.91

🩹 إذا لم يعمل : إذا طُبعت دقة اختبار بدل 0.91، فـbest_estimator_ يختلف عن أفضل تشكيل في الشبكة (لاءمت مقدِّرًا جديدًا). إذا تباعدا المعايرة والاختبار بعنف، فبذور الطيات تجعل المعايرة مفرطة التفاؤل ، عَلِّم عليها لا تخفها.

4.3 تحقّق من الكنس

✅ قائمة التحقق

  • ✅ الشجرة مضبوطة إلى max_depth=3, min_samples_leaf=1؛ وkNN إلى n_neighbors=3, uniform.
  • ✅ طُبعت وقورنت درجتا المعايرة ودقة الاختبار المستقلة معًا.
  • ✅ قرار التصدير استخدم نتيجة الاختبار، لا سقف المعايرة.

🤔 سؤال (أسئلة) سقراطي(ة) :

  • تجاوزت معايرة kNN (0.937) اختبارها (0.91)، بينما طابقت الشجرة (0.903≈0.91). وبما أن معايرة نموذج واحد تكذب عن المستقبل، كيف كانت ستغير مجموعة تجميد ثانية ، اضبط على train، واختر على dev، وبلّغ على test ، الرقم الذي تثق به عند النشر؟
  • شغّل GridSearchCV 9 تشكيلات شجرة قبل أن تختار واحدًا. كل تشكيل نظر إلى الطيات نفسها؛ واختيار أفضل معايرة يعني أنك «اختبرتَ» 9 نماذج فعلًا. ما اسم هذا الانحياز التفاؤلي، وكيف يُبقيه معايرة متداخلة أو مجموعة dev ثابتة نزيهًا؟

الخطوة 5: صدّر الفائز وحمّلْه

المنتج النهائي للطيار الآلي أثري قابل لإعادة التحميل: نفس خط الأنابيب، نفس الحالة، جاهز لتقييم حركة مرور جديدة في عملية أخرى. تجمّد الخطوة 5 الاختيار.

5.1 لاءم احفظ خط الأنابيب النهائي

👟 تلميح البداية : عرّف Pipeline النهائي (المُوحِّد → اللوجستي)، ولاءمه على train، وسجّله على test، ثم joblib.dump.

# main.py (continued)
import joblib

final = Pipeline([("scaler", StandardScaler()),
                  ("model", LogisticRegression(max_iter=1000, random_state=1))])
final.fit(train[["bytes_in", "bytes_out"]], train["ok"])
acc = accuracy_score(test["ok"], final.predict(test[["bytes_in", "bytes_out"]]))
print("final logistic test accuracy:", round(acc, 3))

joblib.dump(final, "autopilot.joblib")
print("saved", __import__("pathlib").Path("autopilot.joblib").stat().st_size, "bytes")

اللوجستي فائز الحديقة ولم يهزمه البحث الشبكي على الاختبار، فالمنتج النهائي هو اللوجستي المقيَّس البسيط المفهوم ، نسخة ML من «الحل الممل الذي ينجح». الحفظ بـjoblib يسلسل الكائن المُلائم (المعاملات، وسائط المُوحِّد، أسماء الميزات) في كتلة صغيرة أصلية المنصة ، ليست قائمة أوزان فقط، بل كل ما يلزم للتنبؤ على حركة مرور عمرها يوم في عملية جديدة.

🎯 الناتج المتوقع:

final logistic test accuracy: 0.91
saved 1665 bytes

🩹 إذا لم يعمل : إذا كانت الدقة ‎≠ 0.91، فبذرة أو test_size انحرفت عن الخطوة 1. إذا طبع saved حجمًا أكبر وملف .joblib بحجم 0 بايت، فجرى joblib.dump قبل fit أو على كائن مختلف ، ارمِ الدالة بعد final المُلاءَم.

5.2 حمّل وتنبّأ على حركة مرور جديدة

👟 تلميح البداية : في مقتطف جديد (أو خلية جديدة)، حمّل joblib.load الكتلة وسجّل دفعة صغيرة ، مع predict_proba.

# main.py — the reload, as if a new process
import joblib
model = joblib.load("autopilot.joblib")

batch = [[2.0, 2.5], [6.0, 6.0], [4.0, 4.0]]
print("labels:", model.predict(batch).tolist())
print("probas:\n", model.predict_proba(batch).round(3))

النموذج المُعاد تحميله هو الشيء نفسه ، وسائط المُوحِّد ومعاملات اللوجستي عادت سليمة، فيعيد score على مجموعة الاختبار إنتاج 0.91. يسلّمك predict_proba ثقةً لا أصواتًا: صف [0.966, 0.034] «غير سليم» قوي، و[0.251, 0.749] «سليم» ناعم قرب الحد ، بالضبط ما يحتاجه إنسان في الحلقة قبل التصرف عند نداء قريب.

🎯 الناتج المتوقع:

labels: [0, 1, 0]
probas:
 [[0.966 0.034]
 [0.991 0.009]
 [0.251 0.749]]

🩹 إذا لم يعمل : إذا أخطأ joblib.load بعدم تطابق إصدار، فالكُتلة صُبَّت بإصدار sklearn مكسوّ ذات مستوى مختلف ، أعِد الصبّ ببيئة التحميل. إذا أعاد predict صنفاتًا غير صحيحة، فـy كان عمود نصوص؛ أبقِ الهدف رقميًا.

5.3 تحقّق من التصدير

✅ قائمة التحقق

  • ✅ يسجّل خط الأنابيب المُلاءَم 0.91 على الاختبار المحتجَز قبل ذهاب-وعودة وبعده.
  • ✅ يعيد joblib.load Pipeline عاملًا مع score وpredict_proba عاملين.
  • ✅ صفوف batch تتنبأ بعقلانية: أقصى اليسار صنفًا 0 بقوة، وأقصى اليمين صنفًا 1 بقوة، والمركز ملتبسًا.

🤔 سؤال (أسئلة) سقراطي(ة) :

  • الأثري 1.6 كيلوبايت لـ300 صف تدريب. أين «يعيش» النموذج فعلًا ، المعاملات ووسائط المُوحِّد، أم بيانات التدريب؟ إذا لم تُشحن البيانات أبدًا مع الأثري، فماذا يعني ذلك للخصوصية ولإعادة التدريب لاحقًا؟
  • أعطى predict صنفات صلبة وpredict_proba ثقة. لوحة تسأل «0.24 فرصة فشل» للصف 3 ، أكانت ستنبّه عند > 0.5؟ صِغ ما كانت ستضيفه عتبة قرار متغيرة للخط الابتدائي أبعد من النموذج.

⚠️ مآزق شائعة

  • تسريب مجموعة الاختبار إلى المعالجة. لاءم المُلئ/المُوحِّد على train فقط؛ استدعاء fit_transform على كل 400 صف يتدرّب على بيانات «ستهدرها» لاحقًا. القسمة أولًا، دائمًا.
  • الضبط على مجموعة الاختبار. GridSearchCV مع test داخل fit يخطف معرفة ورقة الإجابات. ابحث على train؛ واستكشف test مرة واحدة فقط، في النهاية.
  • value_counts بعد القلب. يجعل ضجيج وسم 5% الميزان {1: 208, 0: 192} لا 200/200. تأكيد المساواة الدقيقة تأكيد أن الضجيج لم يعمل.
  • ملء بـfit بدل fit_transform. لدى مُقيّم تدفق حي مباشر يجب أن transform بالمُلئ المُلاءَم ، فـfit على صف واحد سيتعلم الوسيط منه ويفجر.
  • الخلط بين بذرتَي RNG. تتحكم default_rng(7) بالبيانات، وdefault_rng(1) بالفجوات. تبديلهما يغيّر كل الأرقام اللاحقة؛ أبقِهما موثَّقين.
  • انحراف إصدار joblib. كُتلة صُبَّت بـsklearn 1.4 وتُحمَّل في 1.6 تعمل عادة، لكن ضمانات عَبْر-الإصدارات تنطبق على نفس الحزم المثبتة؛ joblib.dump/load في نفس البيئة هو الذهاب والعودة الآمن.

ما بنيته للتو

حلقة طيار آلي حقيقية على حاسوب محمول: بيانات اصطناعية مزروعة، وتقسيم طبقي، وخط ملء وقياس يتعلم على train فقط، وسباق نماذج ثلاثي أمناء عبر معايرة تبادلية، وبحث شبكي تتباعد أرقامه المعيارية ورقم اختباره ظاهريًا، وفائز مسلسَّل بـjoblib تعيد تحميله في أي عملية. الأفكار التي تنجو من ملامسة الإنتاج هي الحدود: تقسيم التدريب/الاختبار أولًا، والمعالجات تتعلم من train فقط، والنماذج تختار بالمعايرة وتبلغ عنها مجموعة اختبار غير ملموسة، ويُقاس الضبط مرتين (مرة للاختيار ومرة للتقرير). ذلك هو الفرق بين «نموذجي سجّل 0.93» و«نموذجي سجّل 0.91، وإليك رقم المعايرة الذي استخدمته لاختيار التشكيل».

شغّل نسخة أكمل دون أي إعداد محلي

examples/automl-pipeline/ في مستودع الدورة خط الأنابيب كاملًا كدفتر ، المجموعة، والمعالجة، والحديقة، والكنس، والتصدير، قابل للتشغيل في Colab/Kaggle/Binder. استنسخ المستودع أو افتحه في Codespace.

إلى أين تذهب من هنا

  • أضف مجموعة «dev» تجميد ثالثة: اضبط على train، واختر التشكيل على dev، وبلّغ على test ، الطريقة الموثقة لإيقاف تفاؤل الضبط دون معايرة متداخلة.
  • غذِّ كل نموذج حديقة نفس خط الملء والقياس (لا ميزات خام) وسجّل إن كانت ميزة اللوجستي هي المعالجة أم النموذج.
  • ارسم درجات طيات المعايرة كمخطط صندوق في مكتبة رسمك المفضلة ، الانتشارات تخبرك بأي نموذج هش قبل أن يُشحن.
  • لفّ الكتلة المصدَّرة في CLI صغيرة: uv run autopilot.py --model autopilot.joblib <bytes_in> <bytes_out> تطبع الوسم المتوقع والثقة.

شارك مشروعك مع الصف

بنيت شيئًا تفخر به؟ examples/student-projects/ معرض لمشاريع قدّمها طلاب آخرون ، وREADME الخاص به يحوي شرحًا كاملًا وودودًا للمبتدئين لإضافة مشروعك عبر pull request، حتى لو لم تستخدم git من قبل قط: عمل fork للمستودع، وإنشاء فرع، وتثبيت ملفاتك، وفتح الـ PR، خطوة بخطوة. لا يُفترض أي خبرة سابقة بـ git.

مرحبًا بك في كتابة Python خارج المتصفح. 🎓

أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.