خط أنابيب AutoML
«التعلم الآلي الآلي» في البرامج التعليمية يعيش على خادم تستأجره. يشغّل هذا المشروع الفكرة ذاتها على حاسوبك المحمول: طيار آلي صغير يأخذ صفوفًا خام، وينظّفها بخط أنابيب متسلسل، ويُسابق حفنة نماذج بمعايرة متقاطعة سليمة، ويضبط الواعدة منها ببحث شبكي، ويصدّر الفائز المتسلسل الذي يعيد تحميله في أي مكان. على الطريق يعلّم الانضباط الذي ترمّزه مكتبات ML الحقيقية: تقسيم التدريب/الاختبار يُقرَّر قبل أي ضبط، والملء والقياس يتعلّمان من بيانات التدريب فقط، والبحث الشبكي المضبوط على المعايرة قد يخالف مجموعة الاختبار ، هذا المشروع يجعل الثلاثة صالحة للملاحظة ببيانات صغيرة مكتوبة باليد. المجموعة اصطناعية (إحصاءات حركة شبكة تترابط مع حالة سليمة/غير سليمة)، فكل رقم في هذا الدليل قابل للتكرار من بذرة ثابتة.
هذا يفترض pandas وsklearn أساسيًا وبعض numpy. إنه مشروع اختياري غير مُقيَّم ، راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة والنامية. يثبّت حزمتين (pandas, scikit-learn) ، وuv يجعل ذلك سهلًا.
🎯 ما ستفعله
- توليد مجموعة من 400 صف قابل للتكرار مع ضجيج وسوم محقون وتقسيمها 75/25 بتقسيم طبقي.
- لف خط أنابيب معالجة يملأ بالوسيط ويوحّد بالمعيار وملاءمته على ميزات التدريب.
- مسابقة الانحدار اللوجستي وشجرة قرار وk-NN بـ
cross_val_score. - ضبط الأشجار والجيران الواعدة بـ
GridSearchCVومقارنتها بخط المعايرة الأساسي. - تصدير الخط الابتدائي النهائي بـ
joblibوإعادة تحميله كمتنبئ باحتمالات.
أين تُشغّل هذا
محليًا باستخدام uv هو المسار الموصى به. أمر واحد يثبّت كل شيء:
uv init automl-pipeline && cd automl-pipeline
uv add pandas scikit-learn joblib
Google Colab وKaggle Notebooks وBinder يشغّلون كل خطوة دون تعديل ، المنصتان تحملان pandas وscikit-learn مثبتين مسبقًا. البيانات الاصطناعية والبذور الثابتة تجعل ناتج الدفتر متطابقًا عبر الأجهزة.
الإعداد
كل ما تحتاجه قبل أول صف.
جهّز المشروع
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_split300/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 ، الرقم الذي تثق به عند النشر؟
- شغّل
GridSearchCV9 تشكيلات شجرة قبل أن تختار واحدًا. كل تشكيل نظر إلى الطيات نفسها؛ واختيار أفضل معايرة يعني أنك «اختبرتَ» 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.loadPipelineعاملًا مع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 خارج المتصفح. 🎓
أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.