Scikit-Learn Pipeline у 2026: Інженерія Ознак та Питання на Співбесідах

Повний посібник з Scikit-learn Pipeline та ColumnTransformer. Охоплює інженерію ознак, запобігання витоку даних та питання зі співбесід з машинного навчання.

Scikit-learn Pipeline та інженерія ознак у Python

Конвеєри scikit-learn перетворюють хаотичний код попередньої обробки на відтворювані, готові до продакшену робочі потоки. З версією 1.9, випущеною в червні 2026, Pipeline та ColumnTransformer залишаються фундаментом кожного серйозного проєкту машинного навчання, тепер з моніторингом через callback-и, покращеною HTML-візуалізацією та підтримкою Array API.

Порада для співбесіди

Інтерв'юери часто запитують: "Як запобігти витоку даних під час крос-валідації?" Відповідь — конвеєри. Підгонка кроків попередньої обробки всередині циклу CV гарантує, що тестові дані ніколи не впливають на рішення про масштабування чи кодування.

Чому Конвеєри Усувають Витік Даних

Витік даних відбувається, коли інформація з тестового набору впливає на попередню обробку. Типова помилка: підгонка StandardScaler на всьому наборі даних перед поділом. Масштабувальник вивчає середнє та дисперсію з тестових зразків, штучно завищуючи результати крос-валідації.

Конвеєри вирішують цю проблему, об'єднуючи трансформери та естиматори в єдиний об'єкт, який підганяє всі кроки разом:

python
# pipeline_basics.py
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# Create a pipeline that scales features and trains a classifier
pipeline = Pipeline([
    ('scaler', StandardScaler()),  # Step 1: Normalize features
    ('classifier', LogisticRegression(max_iter=1000))  # Step 2: Train model
])

# cross_val_score fits the scaler separately for each fold
# No data leakage: test fold is never seen during scaler.fit()
scores = cross_val_score(pipeline, X, y, cv=5, scoring='accuracy')
print(f"Mean accuracy: {scores.mean():.3f} (+/- {scores.std():.3f})")

Об'єкт Pipeline реалізує методи fit, predict та score, що робить його взаємозамінним з будь-яким естиматором scikit-learn. Це означає, що GridSearchCV, RandomizedSearchCV та всі утиліти крос-валідації працюють без модифікацій.

ColumnTransformer для Змішаних Типів Даних

Реальні набори даних містять числові стовпці (вік, зарплата) та категоріальні стовпці (країна, тип_продукту). ColumnTransformer застосовує різну попередню обробку до різних підмножин стовпців, а потім об'єднує результати:

python
# column_transformer_example.py
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
import pandas as pd

# Sample dataset with mixed types
df = pd.DataFrame({
    'age': [25, 30, None, 45],
    'salary': [50000, 60000, 75000, None],
    'country': ['US', 'UK', 'DE', 'US'],
    'education': ['Bachelor', 'Master', 'PhD', 'Bachelor']
})

# Define numeric preprocessing: impute missing values, then scale
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# Define categorical preprocessing: impute with mode, then one-hot encode
categorical_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# Combine transformers with ColumnTransformer
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, ['age', 'salary']),
        ('cat', categorical_transformer, ['country', 'education'])
    ],
    verbose_feature_names_out=True  # Prefix output names with transformer name
)

# Fit and inspect output shape
X_transformed = preprocessor.fit_transform(df)
print(f"Transformed shape: {X_transformed.shape}")
print(f"Feature names: {preprocessor.get_feature_names_out()}")

Параметр verbose_feature_names_out (розширений у версії 1.6 для підтримки рядків та callable) керує найменуванням вихідних ознак. Встановлення його в True додає префікс з іменем трансформера до кожної ознаки, запобігаючи конфліктам імен, коли кілька трансформерів генерують схожі назви стовпців.

Автоматичний Вибір Стовпців з make_column_selector

Жорстко закодовані назви стовпців ламаються, коли набори даних змінюються. make_column_selector автоматично вибирає стовпці за типом даних:

python
# automatic_column_selection.py
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.preprocessing import StandardScaler, OneHotEncoder

# Select columns by dtype: no hardcoded column names
preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), make_column_selector(dtype_include='number')),
        ('cat', OneHotEncoder(handle_unknown='ignore'),
         make_column_selector(dtype_include='object'))
    ],
    remainder='passthrough'  # Keep unprocessed columns as-is
)

# Works with any DataFrame that has numeric and object columns
X_processed = preprocessor.fit_transform(df)

Параметр remainder контролює, що відбувається зі стовпцями, які не відповідають жодному трансформеру. Варіанти включають 'drop' (за замовчуванням), 'passthrough' (зберегти без змін) або трансформер для застосування до решти стовпців.

Готовий до співбесід з Data Science & ML?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Інженерія Ознак Всередині Конвеєрів

Конвеєри виходять за межі попередньої обробки та включають кроки інженерії ознак. Користувацькі трансформери успадковують від BaseEstimator та TransformerMixin:

python
# custom_transformer.py
from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np

class DateFeatureExtractor(BaseEstimator, TransformerMixin):
    """Extract year, month, day, and weekday from datetime columns."""
    
    def __init__(self, date_column='date'):
        self.date_column = date_column
    
    def fit(self, X, y=None):
        # No fitting required for date extraction
        return self
    
    def transform(self, X):
        X = X.copy()
        dates = pd.to_datetime(X[self.date_column])
        
        # Extract temporal features
        X['year'] = dates.dt.year
        X['month'] = dates.dt.month
        X['day'] = dates.dt.day
        X['weekday'] = dates.dt.weekday
        X['is_weekend'] = (dates.dt.weekday >= 5).astype(int)
        
        # Drop original date column
        return X.drop(columns=[self.date_column])
    
    def get_feature_names_out(self, input_features=None):
        # Required for Pipeline.get_feature_names_out() to work
        return ['year', 'month', 'day', 'weekday', 'is_weekend']

# Integrate into a full pipeline
full_pipeline = Pipeline([
    ('date_features', DateFeatureExtractor(date_column='signup_date')),
    ('preprocessor', preprocessor),
    ('model', LogisticRegression())
])

Реалізація get_feature_names_out дозволяє новому HTML-представленню в scikit-learn 1.9 відображати назви вихідних ознак, що спрощує налагодження та документацію.

Налаштування Гіперпараметрів Між Кроками Конвеєра

GridSearchCV налаштовує параметри по всіх кроках конвеєра, використовуючи конвенцію найменування крок__параметр:

python
# pipeline_gridsearch.py
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier

# Build pipeline with tunable components
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(random_state=42))
])

# Define parameter grid: note the double underscore syntax
param_grid = {
    'preprocessor__num__imputer__strategy': ['mean', 'median'],
    'classifier__n_estimators': [100, 200],
    'classifier__max_depth': [10, 20, None]
}

# GridSearchCV handles all combinations automatically
grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=5,
    scoring='f1_weighted',
    n_jobs=-1
)

grid_search.fit(X_train, y_train)
print(f"Best params: {grid_search.best_params_}")
print(f"Best score: {grid_search.best_score_:.3f}")

Подвійне підкреслення (__) надає доступ до вкладених параметрів. Для ColumnTransformer шлях включає ім'я трансформера: preprocessor__num__imputer__strategy досягає параметра strategy імпутера всередині числового трансформера.

Кешування Трансформерів з Параметром memory

Складна попередня обробка на великих наборах даних може бути повільною. Параметр memory кешує підігнані трансформери на диск:

python
# cached_pipeline.py
from sklearn.pipeline import Pipeline
from tempfile import mkdtemp
import joblib

# Create a cache directory
cachedir = mkdtemp()

# Pipeline caches intermediate steps
cached_pipeline = Pipeline(
    [
        ('preprocessor', preprocessor),  # Cached after first fit
        ('classifier', RandomForestClassifier())
    ],
    memory=cachedir  # Or use joblib.Memory for more control
)

# First fit: slow (computes all transformations)
cached_pipeline.fit(X_train, y_train)

# Second fit with same X_train: fast (loads cached transformers)
cached_pipeline.fit(X_train, y_train)  # Skips preprocessor fitting

Кешування особливо корисне під час пошуку гіперпараметрів, коли та сама попередня обробка застосовується до багатьох конфігурацій моделі. Кінцевий естиматор (класифікатор або регресор) ніколи не кешується, лише проміжні трансформери.

Моніторинг Тренування за Допомогою Callback-ів (scikit-learn 1.9)

Callback API у версії 1.9 додає моніторинг прогресу до конвеєрів:

python
# callback_monitoring.py
from sklearn.callback import ProgressBar, ScoringMonitor
from sklearn.linear_model import LogisticRegression

# Display progress during GridSearchCV
with ProgressBar():
    grid_search = GridSearchCV(
        pipeline,
        param_grid,
        cv=5,
        n_jobs=1  # Progress bars require single-threaded execution
    )
    grid_search.fit(X_train, y_train)

# Monitor scoring metrics per iteration
with ScoringMonitor(X_val, y_val, scoring='accuracy') as monitor:
    logreg = LogisticRegression(solver='lbfgs', max_iter=500)
    logreg.fit(X_train, y_train)
    print(f"Scores per iteration: {monitor.scores_}")

Callback-и працюють з Pipeline, StandardScaler, LogisticRegression (solver LBFGS) та всіма класами search CV. Ця експериментальна функція забезпечує видимість тривалих операцій підгонки без користувацького коду логування.

FeatureUnion для Паралельної Екстракції Ознак

FeatureUnion запускає кілька трансформерів паралельно та об'єднує їх виходи горизонтально:

python
# feature_union_example.py
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, f_classif

# Combine PCA and univariate selection
feature_union = FeatureUnion([
    ('pca', PCA(n_components=5)),
    ('select', SelectKBest(f_classif, k=10))
])

# Pipeline: preprocess, combine features, classify
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('features', feature_union),
    ('classifier', LogisticRegression())
])

# Output contains 5 PCA components + 10 selected features = 15 features
pipeline.fit(X_train, y_train)

Версія 1.7.2 додала валідацію, що гарантує, що всі трансформери повертають 2D виходи. Це виявляє помилки на ранній стадії, коли користувацький трансформер випадково повертає 1D масив.

Серіалізація та Розгортання

Конвеєри серіалізуються за допомогою joblib, зберігаючи всі підігнані параметри:

python
# model_deployment.py
import joblib

# Save the entire pipeline (preprocessing + model)
joblib.dump(pipeline, 'churn_predictor.pkl')

# Load in production
loaded_pipeline = joblib.load('churn_predictor.pkl')

# Predict on new data (same preprocessing automatically applied)
new_data = pd.DataFrame({'age': [32], 'salary': [65000], ...})
predictions = loaded_pipeline.predict(new_data)

Серіалізований файл містить все: логіку селектора стовпців, підігнані середні та дисперсії масштабувальника, категорії енкодера та ваги моделі. Розгортання вимагає лише scikit-learn та joblib, без користувацького коду попередньої обробки.

Сумісність Версій

Конвеєри, серіалізовані в scikit-learn 1.8, можуть не завантажуватися коректно в 1.9, якщо вони використовують застарілі параметри. Завжди фіксуйте версію scikit-learn у продакшені та тестуйте оновлення перед розгортанням.

Питання на Співбесідах про Scikit-Learn Pipeline

П: Що таке витік даних і як конвеєри його запобігають?

Витік даних відбувається, коли інформація з валідаційного або тестового набору впливає на тренування моделі. Класичний приклад: підгонка StandardScaler на всьому наборі даних перед поділом. Середнє та стандартне відхилення масштабувальника включають тестові зразки, штучно завищуючи результати крос-валідації.

Конвеєри запобігають цьому, інкапсулюючи попередню обробку всередині циклу крос-валідації. Коли cross_val_score викликає pipeline.fit(), кожен fold підганяє масштабувальник лише на тренувальних даних. Статистика тестового fold ніколи не витікає в попередню обробку.

П: Як отримати доступ та модифікувати параметри вкладених кроків конвеєра?

Використовується синтаксис з подвійним підкресленням: pipeline.set_params(classifier__n_estimators=200). Для ColumnTransformer включається ім'я трансформера: pipeline.set_params(preprocessor__num__scaler__with_mean=False). Метод get_params() повертає всі вкладені параметри як плоский словник, що корисно для інспекції та логування.

П: Коли використовувати FeatureUnion, а коли ColumnTransformer?

ColumnTransformer застосовує різні трансформери до різних стовпців одного входу. FeatureUnion застосовує різні трансформери до всього входу та об'єднує горизонтально. ColumnTransformer використовується для гетерогенної попередньої обробки стовпців (числові vs категоріальні). FeatureUnion використовується для аугментації ознак, де кілька методів екстракції (PCA, поліноміальні ознаки, доменні екстрактори) створюють комплементарні ознаки з тих самих даних.

П: Як налагоджувати конвеєр, що не працює?

Встановіть verbose=True на конвеєрі, щоб бачити час виконання кроків. Отримати доступ до проміжних результатів можна через pipeline.named_steps['ім'я_кроку'].transform(X) для підігнаного конвеєра. У scikit-learn 1.9 HTML-представлення відображає підігнані атрибути та назви вихідних ознак, спрощуючи налагодження в Jupyter notebooks. Для глибшої інспекції використовується pipeline[:-1].fit_transform(X, y), щоб отримати вихід безпосередньо перед кінцевим естиматором.

Чек-ліст для Продакшену ML Конвеєрів

  • Фіксація версії scikit-learn у requirements для уникнення проблем серіалізації
  • Використання make_column_selector замість жорстко закодованих назв стовпців, коли набори даних можуть змінюватися
  • Встановлення handle_unknown='ignore' на OneHotEncoder для обробки нових категорій у продакшені
  • Кешування дорогої попередньої обробки параметром memory під час розробки
  • Валідація форми виходу конвеєра та назв ознак у тестах перед розгортанням
  • Збереження get_feature_names_out() разом з моделлю для аналізу важливості ознак
  • Використання Pipeline всередині GridSearchCV, ніколи навпаки, для забезпечення правильної поведінки CV

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Щоденний виклик

Чи знайдеш ти помилку в Data Science & ML?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 2 вересня 2026 р.

Теги

#scikit-learn
#machine-learning
#python
#pipeline
#feature-engineering

Поділитися

Пов'язані статті