بسیاری از مدلهای یادگیری ماشین درون محیط نوتبوک عملکردی درخشان دارند، اما به محض انتقال به محیط عملیاتی یا بازآموزی روی دادههای جدید با افت شدید دقت یا خطاهای ناشناخته روبهرو میشوند. ریشهی اصلی این چالش معمولاً پراکندگی مراحل پیشپردازش، نشت داده (Data Leakage) در هنگام مقیاسبندی و ناسازگاری رفتار دادهها بین مراحل آموزش و سرویسدهی است.
راهحل استاندارد برای خروج از نوتبوک و ورود به دنیای مهندسی محصول، استفاده از پایپلاینها (Pipelines) است. یک پایپلاین تمام گامهای تمیزسازی، جایگذاری مقادیر مفقود، کدگذاری ویژگیها و آموزش مدل را درون یک شیء واحد و بازتولیدپذیر کپسوله میکند تا خروجی نهایی بدون ریسک خطای انسانی آمادهی استقرار شود.
در این درس یاد میگیرید که چطور پایپلاینهای حرفهای را با استفاده از scikit-learn و ColumnTransformer پیادهسازی کنید، جلوی نشت داده را در اعتبارسنجی متقاطع بگیرید و با ابزارهایی مثل MLflow و DVC فرایند آزمایشها و نسخهبندی داده را به شکل ساختاریافته مدیریت کنید.
مدل بهخودیخود محصول نیست؛ پایپلاین محصول است. پایپلاین همهچیز را از دادهی خام تا پیشبینیِ مستقرشده در بر میگیرد و تکتک مراحل آن باید بازتولیدپذیر باشند.
اهداف یادگیری
- ساخت یک پایپلاین یادگیری ماشین (Machine Learning Pipeline) از صفر که جایگذاری مقادیر مفقود (imputation)، مقیاسبندی (scaling)، انکودینگ (encoding) و آموزش مدل را درون یک شیء واحد و بازتولیدپذیر زنجیر کند
- شناسایی سناریوهای نشت داده (Data Leakage) و توضیح اینکه پایپلاینها با برازش (fit) ترنسفورمرها صرفاً روی دادههای آموزشی، چطور جلوی این نشت را میگیرند
- ساخت یک
ColumnTransformerبرای اعمال پیشپردازشهای متفاوت روی ویژگیهای عددی و دستهای - پیادهسازی سریالسازی (serialization) پایپلاین و نشان دادن اینکه همان پایپلاینِ برازششده نتایج یکسانی در آموزش و محیط عملیاتی تولید میکند
صورت مسئله
نوتبوکی دارید که دادهها را بارگذاری میکند، مقادیر مفقود را با میانه پر میکند، ویژگیها را مقیاسبندی میکند، مدلی را آموزش میدهد و دقت آن را چاپ میکند. همهچیز کار میکند؛ پس آن را تحویل میدهید.
یک ماه بعد، کسی مدل را دوباره آموزش میدهد و نتایج متفاوتی میگیرد. میانه روی کل مجموعه داده—از جمله دادههای تست—محاسبه شده بود (نشت داده). پارامترهای مقیاسبندی ذخیره نشده بودند؛ در نتیجه، استنتاج (inference) از آمارههای متفاوتی استفاده میکند. کدهای مهندسی ویژگی بین بخش آموزش و سرویسدهی کپی-پیست شده بودند و این دو نسخه کمکم از هم فاصله گرفتهاند. در محیط عملیاتی، مقداری جدید به یک ستون دستهای اضافه شده که انکودر قبلاً هرگز آن را ندیده است.
این سناریوها فرضی نیستند؛ اینها رایجترین دلایل شکست سیستمهای یادگیری ماشین در محیط عملیاتی هستند. پایپلاینها با بستهبندی تمام مراحل تبدیل درون یک شیء واحد، مرتب و بازتولیدپذیر، همهی این مشکلات را برطرف میکنند.
مفهوم
پایپلاین چیست
پایپلاین دنبالهای مرتب از تبدیلات داده است که به یک مدل ختم میشود. هر مرحله خروجی مرحلهی قبل را به عنوان ورودی دریافت میکند. کل پایپلاین فقط یک بار روی دادههای آموزشی برازش میشود. در زمان استنتاج، همین پایپلاینِ برازششده دادههای جدید را تبدیل میکند و پیشبینیها را تحویل میدهد.
پایپلاین تضمین میکند:
- تبدیلات صرفاً روی دادههای آموزشی برازش میشوند (بدون نشت داده)
- دقیقاً همان تبدیلات در زمان استنتاج اعمال میشوند
- کل شیء را میتوان به عنوان یک آرتیفکت واحد سریالسازی و مستقر کرد
- اعتبارسنجی متقاطع (Cross-Validation) پایپلاین را به ازای هر بخش (fold) جداگانه اعمال میکند و مانع نشتهای پنهان میشود
نشت داده: قاتل خاموش
نشت داده زمانی رخ میدهد که اطلاعات مجموعهی تست یا دادههای آینده، فرآیند آموزش را آلوده کند. پایپلاینها جلوی رایجترین اشکال آن را میگیرند.
دارای نشت (نادرست):
X = df.drop("target", axis=1)
y = df["target"]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test = X_scaled[:800], X_scaled[800:]
y_train, y_test = y[:800], y[800:]
مقیاسساز دادههای تست را دیده است؛ در نتیجه میانگین و انحراف معیار شامل نمونههای تست هم میشوند. این موضوع تخمین دقت را بهطور کاذب بالا میبرد.
درست:
X_train, X_test = X[:800], X[800:]
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
با استفاده از پایپلاین اصلاً نیازی نیست نگران این موضوع باشید؛ پایپلاین خودش همهچیز را خودکار مدیریت میکند.
پایپلاین در sklearn
کلاس Pipeline در scikit-learn ترنسفورمرها و یک تخمینگر (estimator) را به هم زنجیر میکند و متدهای .fit()، .predict() و .score() را در اختیارتان میگذارد که همهی مراحل را به ترتیب اجرا میکنند.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
("scaler", StandardScaler()),
("model", LogisticRegression()),
])
pipe.fit(X_train, y_train)
predictions = pipe.predict(X_test)
وقتی pipe.fit(X_train, y_train) را فراخوانی میکنید:
- مقیاسساز متد
fit_transformرا روی X_train اجرا میکند - مدل متد
fitرا روی X_train مقیاسبندیشده فراخوانی میکند
وقتی pipe.predict(X_test) را فراخوانی میکنید:
- مقیاسساز متد
transform(و نه fit_transform) را روی X_test اجرا میکند - مدل متد
predictرا روی X_test مقیاسبندیشده فراخوانی میکند
مقیاسساز در طول برازش هرگز دادههای تست را نمیبیند؛ کل ماجرا دقیقاً همین است.
ابزار ColumnTransformer: پایپلاینهای متفاوت برای ستونهای متفاوت
مجموعه دادههای واقعی شامل ستونهای عددی و دستهای هستند که هر کدام پیشپردازش خاص خود را میخواهند. ColumnTransformer این مسئله را حل میکند.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
numeric_pipe = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
])
categorical_pipe = Pipeline([
("impute", SimpleImputer(strategy="most_frequent")),
("encode", OneHotEncoder(handle_unknown="ignore")),
])
preprocessor = ColumnTransformer([
("num", numeric_pipe, ["age", "income", "score"]),
("cat", categorical_pipe, ["city", "gender", "plan"]),
])
full_pipeline = Pipeline([
("preprocess", preprocessor),
("model", GradientBoostingClassifier()),
])
تنظیم handle_unknown="ignore" در OneHotEncoder برای محیط عملیاتی نقشی حیاتی دارد. وقتی دستهای جدید ظاهر میشود (مثلاً شهری که مدل هرگز ندیده است)، برنامه به جای کرش کردن، یک بردار صفر تولید میکند.
ردگیری آزمایشها
پایپلاین آموزش را بازتولیدپذیر میکند، اما باید اتفاقات بین آزمایشهای مختلف را هم ردگیری کنید: چه هایپرپارامترهایی استفاده شدند، از کدام نسخهی دیتاست استفاده شد، متریکها چه بودند و کدام نسخه از کد در حال اجرا بود.
MLflow رایجترین راهکار متنباز در این حوزه است:
import mlflow
with mlflow.start_run():
mlflow.log_param("max_depth", 5)
mlflow.log_param("n_estimators", 100)
mlflow.log_param("learning_rate", 0.1)
pipe.fit(X_train, y_train)
accuracy = pipe.score(X_test, y_test)
mlflow.log_metric("accuracy", accuracy)
mlflow.sklearn.log_model(pipe, "model")
هر بار اجرا همراه با پارامترها، متریکها، آرتیفکتها و کل مدل ثبت میشود. میتوانید اجراها را با هم مقایسه کنید، هر آزمایشی را بازتولید کنید و هر نسخهای از مدل را به مرحلهی استقرار برسانید.
Weights & Biases (wandb) همین قابلیتها را در قالب یک داشبورد ابری در اختیارتان میگذارد:
import wandb
wandb.init(project="my-pipeline")
wandb.config.update({"max_depth": 5, "n_estimators": 100})
pipe.fit(X_train, y_train)
accuracy = pipe.score(X_test, y_test)
wandb.log({"accuracy": accuracy})
نسخهبندی مدل
پس از ردگیری آزمایشها، باید نسخههای مدل را مدیریت کنید. کدام مدل در محیط عملیاتی است؟ کدام در استیجینگ (staging) است؟ کدام مدل هفتهی قبل بود؟
بخش رجیستری مدل (Model Registry) در MLflow امکانات زیر را فراهم میکند:
- ردگیری نسخه: هر مدل ذخیرهشده یک شماره نسخه دریافت میکند
- تغییر وضعیتهای مرحلهای: برچسبهای «Staging» ،«Production» و «Archived»
- جریان کاری تأیید: مدلها باید پیش از ورود به محیط عملیاتی صراحتاً تأیید شوند
- بازگشت به نسخه قبل (Rollback): امکان بازگشت آنی به نسخهی قبلی
نسخهبندی داده با DVC
کد با git نسخهبندی میشود؛ داده هم باید نسخهبندی شود، اما git توان مدیریت فایلهای حجیم را ندارد. DVC (کنترل نسخه داده یا Data Version Control) این مشکل را برطرف میکند.
dvc init
dvc add data/training.csv
git add data/training.csv.dvc data/.gitignore
git commit -m "Track training data"
dvc push
ابزار DVC دادههای واقعی را در یک فضای ذخیرهسازی راهدور (S3، GCS یا Azure) ذخیره میکند و یک فایل کوچک .dvc را که حاوی هش دادههاست در git نگه میدارد. وقتی به یک commit مشخص در git برمیگردید، دستور dvc checkout دقیقاً همان دادهای را که استفاده شده بود بازیابی میکند.
این یعنی هر commit در git هم کد و هم داده را پین میکند؛ یعنی بازتولیدپذیری کامل.
آزمایشهای بازتولیدپذیر
یک آزمایش بازتولیدپذیر به 4 فاکتور نیاز دارد:
- سیدهای تصادفی ثابت: تنظیم seed برای numpy، random و فریمورک مورد استفاده (torch، sklearn)
- پین کردن وابستگیها: استفاده از requirements.txt یا poetry.lock با نسخههای دقیق
- دادههای نسخهبندیشده: استفاده از DVC یا ابزارهای مشابه
- فایلهای کانفیگ: قرار دادن همهی هایپرپارامترها در یک کانفیگ و پرهیز از هاردکد کردن آنها
import numpy as np
import random
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
try:
import torch
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
except ImportError:
pass
از نوتبوک تا پایپلاین عملیاتی
مسیر پیشرفت معمول به این صورت است:
- اکتشاف در نوتبوک: آزمایشهای سریع، تصویرسازیها و ایدههای اولیهی ویژگیها
- استخراج توابع: انتقال پیشپردازش، مهندسی ویژگی و ارزیابی به ماژولهای مجزا
- ساخت پایپلاین: زنجیر کردن تبدیلات در یک Pipeline از sklearn یا کلاسی اختصاصی
- مدیریت کانفیگ: انتقال همهی هایپرپارامترها به یک فایل کانفیگ YAML یا JSON
- ردگیری آزمایشها: اضافه کردن لاگگیری با MLflow یا wandb
- اعتبارسنجی داده: بررسی اسکیما، توزیعها و الگوهای مقادیر مفقود قبل از آموزش
- تستها: تستهای واحد (Unit tests) برای ترنسفورمرها و تستهای یکپارچگی (Integration tests) برای کل پایپلاین
- استقرار: سریالسازی پایپلاین، قرار دادن آن در قالب یک API (با FastAPI یا Flask) و کانتینریسازی
اشتباهات رایج در پایپلاین
| اشتباه | چرا بد است | راهکار |
|---|---|---|
| برازش روی کل داده قبل از جداسازی | نشت داده | استفاده از Pipeline به همراه cross_val_score |
| مهندسی ویژگی بیرون از پایپلاین | تفاوت تبدیلات در آموزش نسبت به سرویسدهی | قرار دادن همهی تبدیلات داخل Pipeline |
| مدیریت نکردن دستههای ناشناخته | کرش کردن در محیط عملیاتی روی مقادیر جدید | استفاده از OneHotEncoder(handle_unknown="ignore") |
| هاردکد کردن نام ستونها | خرابی برنامه با تغییر اسکیما | استفاده از فهرست نام ستونها از داخل کانفیگ |
| عدم اعتبارسنجی داده | پیشبینیهای اشتباهِ بیسروصدا روی دادههای خراب | اضافه کردن بررسی اسکیما قبل از پیشبینی |
| عدم تطابق آموزش و سرویسدهی (Training/serving skew) | دیدن ویژگیهای متفاوت توسط مدل در محیط پروداکشن | استفاده از یک شیء پایپلاین واحد برای هر دو بخش |
پیادهسازی عملی
کد موجود در فایل code/pipeline.py یک پایپلاین یادگیری ماشین کامل را از صفر میسازد:
مرحلهی 1: ترنسفورمر اختصاصی
class CustomTransformer:
def __init__(self):
self.means = None
self.stds = None
def fit(self, X):
self.means = np.mean(X, axis=0)
self.stds = np.std(X, axis=0)
self.stds[self.stds == 0] = 1.0
return self
def transform(self, X):
return (X - self.means) / self.stds
def fit_transform(self, X):
return self.fit(X).transform(X)
مرحلهی 2: پایپلاین از صفر
class PipelineFromScratch:
def __init__(self, steps):
self.steps = steps
def fit(self, X, y=None):
X_current = X.copy()
for name, step in self.steps[:-1]:
X_current = step.fit_transform(X_current)
name, model = self.steps[-1]
model.fit(X_current, y)
return self
def predict(self, X):
X_current = X.copy()
for name, step in self.steps[:-1]:
X_current = step.transform(X_current)
name, model = self.steps[-1]
return model.predict(X_current)
مرحلهی 3: اعتبارسنجی متقاطع با پایپلاین
این کد نشان میدهد چطور اعتبارسنجی متقاطع همراه با پایپلاین جلوی نشت داده را میگیرد: مقیاسساز برای دادههای آموزشیِ هر بخش (fold) به طور جداگانه برازش میشود.
مرحلهی 4: پایپلاین کامل برای محیط عملیاتی با sklearn
یک پایپلاین کامل با ColumnTransformer، چندین مسیر پیشپردازش و یک مدل که همراه با اعتبارسنجی متقاطع اصولی و لاگگیری آزمایشها آموزش داده میشود.
خروجی این درس
این درس این موارد را تولید میکند:
- فایل
outputs/prompt-ml-pipeline.md-- مهارتی برای ساخت و دیباگ پایپلاینهای یادگیری ماشین - فایل
code/pipeline.py-- یک پایپلاین کامل از پیادهسازی پایهای تا کار با sklearn
تمرینها
-
پایپلاینی بسازید که یک دیتاست با 3 ستون عددی و 2 ستون دستهای را مدیریت کند. با استفاده از
ColumnTransformer، برای ستونهای عددی از جایگذاری با میانه + مقیاسبندی، و برای ستونهای دستهای از جایگذاری با پرتکرارترین مقدار + One-Hot Encoding استفاده کنید. مدل را با اعتبارسنجی متقاطع 5 بخشی (5-fold) آموزش دهید. -
عمداً نشت داده ایجاد کنید: مقیاسساز را قبل از تقسیم دادهها، روی کل مجموعه داده برازش کنید. امتیاز اعتبارسنجی متقاطع دارای نشت را با امتیاز پایپلاین بدون نشت مقایسه کنید. این اختلاف چقدر است؟
-
پایپلاین خود را با
joblib.dumpسریالسازی کنید. آن را در اسکریپت دیگری لود کرده و پیشبینیها را اجرا کنید. بررسی کنید که خروجی پیشبینیها کاملاً یکسان باشد. -
یک ترنسفورمر اختصاصی به پایپلاین اضافه کنید که برای دو ویژگی عددی مهمتر، ویژگیهای چندجملهای با درجهی 2 (degree 2) بسازد. این ترنسفورمر باید در کجای پایپلاین قرار بگیرد؟
-
ابزار MLflow را برای ردگیری پایپلاین راهاندازی کنید. 5 آزمایش با هایپرپارامترهای مختلف اجرا کنید. از رابط کاربری MLflow (دستور
mlflow ui) برای مقایسهی اجراها و انتخاب بهترین مدل استفاده کنید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوهای عامیانه | معنای دقیق فنی |
|---|---|---|
| پایپلاین (Pipeline) | «زنجیرهای از تبدیلات + مدل» | دنبالهای مرتب از ترنسفورمرهای برازششده و یک مدل که به صورت یک واحد یکپارچه برای جلوگیری از نشت داده اعمال میشوند |
| نشت داده (Data leakage) | «درز اطلاعات تست به آموزش» | استفاده از اطلاعات خارج از مجموعهی آموزشی برای ساخت مدل که تخمینهای عملکرد را بهطور کاذب افزایش میدهد |
| تبدیلکنندهی ستونها (ColumnTransformer) | «پیشپردازش متفاوت برای هر ستون» | ابزاری که پایپلاینهای متفاوتی را روی زیرمجموعههای مختلفی از ستونها اعمال و نتایج را ترکیب میکند |
| ردگیری آزمایشها (Experiment tracking) | «لاگ کردن اجراها» | ثبت پارامترها، متریکها، آرتیفکتها و نسخههای کد برای هر دور از اجرای آموزش |
| ابزار MLflow | «ردگیری و استقرار مدلها» | پلتفرم متنباز برای ردگیری آزمایشها، رجیستری مدل و استقرار |
| ابزار DVC | «Git برای دادهها» | سیستم کنترل نسخه برای فایلهای حجیم داده که هشها را در git و دادهها را در مخزن راهدور نگه میدارد |
| رجیستری مدل (Model registry) | «کاتالوگ نسخههای مدل» | سیستمی که نسخههای مدل را همراه با برچسبهای مرحلهای (staging، پروداکشن، آرشیوشده) ردگیری میکند |
| عدم تطابق آموزش و سرویسدهی (Training/serving skew) | «توی نوتبوک که کار میکرد» | تفاوت در نحوهی پردازش دادهها بین مرحلهی آموزش و استنتاج که باعث خطاهای خاموش میشود |
| بازتولیدپذیری (Reproducibility) | «کد یکسان، نتیجهی یکسان» | توانایی رسیدن به نتایج دقیقاً یکسان با استفاده از کد، داده و تنظیمات مشابه |
برای مطالعهی بیشتر
- مستندات Pipeline در scikit-learn -- مرجع رسمی پایپلاین
- مستندات MLflow -- ردگیری آزمایشها و رجیستری مدل
- مستندات DVC -- نسخهبندی داده
- مقالهی بدهی فنی پنهان در سیستمهای یادگیری ماشین (Sculley et al., 2015) -- مقالهای بنیادین دربارهی پیچیدگی سیستمهای یادگیری ماشین
- بهترین روشهای یادگیری ماشین در گوگل: قوانین یادگیری ماشین -- راهکارهای کاربردی برای مهندسی یادگیری ماشین در محیط عملیاتی
