Scikit-Learn Pipeline 2026: Hướng Dẫn Feature Engineering và Câu Hỏi Phỏng Vấn

Làm chủ scikit-learn pipeline với ColumnTransformer cho feature engineering. Tìm hiểu các best practice preprocessing, tránh data leakage, và chuẩn bị phỏng vấn machine learning với các ví dụ code thực tế.

Scikit-Learn Pipeline 2026: Hướng Dẫn Feature Engineering và Câu Hỏi Phỏng Vấn

Scikit-learn pipeline biến đổi code preprocessing lộn xộn thành workflow có thể tái tạo và sẵn sàng cho production. Với phiên bản 1.9 được phát hành vào tháng 6 năm 2026, PipelineColumnTransformer vẫn là nền tảng của mọi dự án machine learning nghiêm túc, giờ đây với callback monitoring, HTML visualization được cải tiến, và hỗ trợ Array API.

Interview Insight

Nhà tuyển dụng thường hỏi: "Làm thế nào để ngăn chặn data leakage trong quá trình cross-validation?" Câu trả lời là pipeline. Việc fit các preprocessing step bên trong CV loop đảm bảo dữ liệu test không bao giờ ảnh hưởng đến các quyết định scaling hoặc encoding.

Tại Sao Pipeline Loại Bỏ Data Leakage

Data leakage xảy ra khi thông tin từ test set ảnh hưởng đến preprocessing. Một lỗi phổ biến: fit StandardScaler trên toàn bộ dataset trước khi split. Scaler học mean và variance từ các mẫu test, làm tăng giả tạo điểm cross-validation.

Pipeline khắc phục điều này bằng cách nối các transformer và estimator thành một object duy nhất thực hiện fit tất cả các bước cùng nhau:

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})")

Object Pipeline implement fit, predict, và score, cho phép hoán đổi với bất kỳ estimator scikit-learn nào. Điều này có nghĩa là GridSearchCV, RandomizedSearchCV, và tất cả các tiện ích cross-validation hoạt động mà không cần chỉnh sửa.

ColumnTransformer cho Các Kiểu Dữ Liệu Hỗn Hợp

Dataset thực tế chứa các cột số (tuổi, lương) và cột phân loại (quốc gia, loại_sản_phẩm). ColumnTransformer áp dụng preprocessing khác nhau cho các tập con cột khác nhau, sau đó nối kết quả:

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()}")

Tham số verbose_feature_names_out (được cải tiến trong phiên bản 1.6 để chấp nhận string và callable) kiểm soát việc đặt tên output feature. Đặt nó thành True sẽ thêm prefix tên transformer vào mỗi feature, ngăn xung đột tên khi nhiều transformer tạo ra tên cột tương tự.

Tự Động Hóa Chọn Cột với make_column_selector

Việc hardcode tên cột sẽ hỏng khi dataset thay đổi. make_column_selector chọn cột theo dtype tự động:

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)

Tham số remainder kiểm soát điều gì xảy ra với các cột không khớp với bất kỳ transformer nào. Các tùy chọn bao gồm 'drop' (mặc định), 'passthrough' (giữ nguyên), hoặc một transformer để áp dụng cho các cột còn lại.

Sẵn sàng chinh phục phỏng vấn Data Science & ML?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Feature Engineering Bên Trong Pipeline

Pipeline mở rộng beyond preprocessing để bao gồm các bước feature engineering. Custom transformer kế thừa từ BaseEstimatorTransformerMixin:

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())
])

Việc implement get_feature_names_out cho phép HTML representation mới trong scikit-learn 1.9 hiển thị tên output feature, giúp debugging và documentation dễ dàng hơn.

Hyperparameter Tuning Trên Các Bước Pipeline

GridSearchCV tune các tham số trên tất cả các bước pipeline sử dụng quy ước đặt tên step__parameter:

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}")

Double underscore (__) truy cập các tham số lồng nhau. Đối với ColumnTransformer, đường dẫn bao gồm tên transformer: preprocessor__num__imputer__strategy truy cập tham số strategy của imputer bên trong numeric transformer.

Caching Transformer với Tham Số memory

Preprocessing phức tạp trên dataset lớn có thể chậm. Tham số memory cache các fitted transformer vào disk:

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

Caching đặc biệt hữu ích trong quá trình hyperparameter search, khi cùng một preprocessing được áp dụng cho nhiều cấu hình model. Estimator cuối cùng (classifier hoặc regressor) không bao giờ được cache, chỉ các transformer trung gian.

Monitoring Training với Callback (scikit-learn 1.9)

Callback API trong phiên bản 1.9 thêm progress monitoring vào pipeline:

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 hoạt động với Pipeline, StandardScaler, LogisticRegression (solver LBFGS), và tất cả các lớp search CV. Tính năng thử nghiệm này cung cấp khả năng quan sát các fit chạy lâu mà không cần code logging tùy chỉnh.

FeatureUnion cho Trích Xuất Feature Song Song

FeatureUnion chạy nhiều transformer song song và nối output của chúng theo chiều ngang:

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)

Phiên bản 1.7.2 thêm validation đảm bảo tất cả transformer trả về output 2D. Điều này bắt lỗi sớm khi một custom transformer vô tình trả về array 1D.

Serialization và Deployment

Pipeline serialize với joblib, bảo toàn tất cả các tham số đã fit:

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)

File được serialize chứa mọi thứ: logic column selector, mean và variance của scaler đã fit, các category của encoder, và trọng số model. Deployment chỉ yêu cầu scikit-learn và joblib, không cần code preprocessing tùy chỉnh.

Tương Thích Phiên Bản

Pipeline được serialize với scikit-learn 1.8 có thể không load đúng trên 1.9 nếu sử dụng các tham số deprecated. Luôn pin phiên bản scikit-learn trong production và test các upgrade trước khi deploy.

Câu Hỏi Phỏng Vấn về Scikit-Learn Pipeline

Q: Data leakage là gì và pipeline ngăn chặn nó như thế nào?

Data leakage xảy ra khi thông tin từ validation hoặc test set ảnh hưởng đến việc training model. Ví dụ kinh điển: fit StandardScaler trên toàn bộ dataset trước khi split. Mean và standard deviation của scaler bao gồm các mẫu test, khiến điểm cross-validation cao giả tạo.

Pipeline ngăn chặn điều này bằng cách đóng gói preprocessing bên trong vòng lặp cross-validation. Khi cross_val_score gọi pipeline.fit(), mỗi fold fit scaler chỉ trên dữ liệu training. Thống kê test fold không bao giờ rò rỉ vào preprocessing.

Q: Làm thế nào để truy cập và sửa đổi tham số của các nested pipeline step?

Sử dụng cú pháp double underscore: pipeline.set_params(classifier__n_estimators=200). Đối với ColumnTransformer, bao gồm tên transformer: pipeline.set_params(preprocessor__num__scaler__with_mean=False). Method get_params() trả về tất cả tham số lồng nhau dưới dạng dictionary phẳng, hữu ích cho việc kiểm tra và logging.

Q: Khi nào nên sử dụng FeatureUnion so với ColumnTransformer?

ColumnTransformer áp dụng các transformer khác nhau cho các cột khác nhau của cùng một input. FeatureUnion áp dụng các transformer khác nhau cho toàn bộ input và nối theo chiều ngang. Sử dụng ColumnTransformer cho preprocessing cột không đồng nhất (số so với phân loại). Sử dụng FeatureUnion cho feature augmentation, khi nhiều phương pháp trích xuất (PCA, polynomial features, domain-specific extractors) tạo ra các feature bổ sung từ cùng một dữ liệu.

Q: Làm thế nào để debug pipeline bị lỗi?

Đặt verbose=True trên pipeline để xem timing các bước. Truy cập kết quả trung gian với pipeline.named_steps['step_name'].transform(X) cho pipeline đã fit. Trong scikit-learn 1.9, HTML representation hiển thị các thuộc tính đã fit và tên output feature, đơn giản hóa debugging trong Jupyter notebook. Để kiểm tra sâu hơn, sử dụng pipeline[:-1].fit_transform(X, y) để lấy output ngay trước estimator cuối cùng.

Checklist Production cho ML Pipeline

  • Pin phiên bản scikit-learn trong requirements để tránh vấn đề serialization
  • Sử dụng make_column_selector thay vì tên cột hardcode khi dataset có thể thay đổi
  • Đặt handle_unknown='ignore' trên OneHotEncoder để xử lý category mới trong production
  • Cache preprocessing tốn kém với tham số memory trong quá trình development
  • Validate output shape và tên feature của pipeline trong test trước khi deployment
  • Lưu get_feature_names_out() cùng với model để phân tích feature importance
  • Sử dụng Pipeline bên trong GridSearchCV, không ngược lại, để đảm bảo hành vi CV đúng

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thử thách hôm nay

Bạn có tìm ra lỗi trong Data Science & ML không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 2 tháng 9, 2026

Chia sẻ

Bài viết liên quan