Scikit-Learn Pipeline ปี 2026: คู่มือ Feature Engineering และคำถามสัมภาษณ์งาน

เชี่ยวชาญ scikit-learn pipeline ด้วย ColumnTransformer สำหรับ feature engineering เรียนรู้ best practice ของ preprocessing หลีกเลี่ยง data leakage และเตรียมตัวสัมภาษณ์ machine learning พร้อมตัวอย่างโค้ดจริง

Scikit-Learn Pipeline ปี 2026: คู่มือ Feature Engineering และคำถามสัมภาษณ์งาน

Scikit-learn pipeline เปลี่ยนโค้ด preprocessing ที่ยุ่งเหยิงให้กลายเป็น workflow ที่สามารถทำซ้ำได้และพร้อมใช้งานใน production ด้วยเวอร์ชัน 1.9 ที่เปิดตัวในเดือนมิถุนายน 2026 Pipeline และ ColumnTransformer ยังคงเป็นรากฐานของทุกโปรเจกต์ machine learning ที่จริงจัง พร้อมด้วย callback monitoring, HTML visualization ที่ปรับปรุงแล้ว และการรองรับ Array API

Interview Insight

ผู้สัมภาษณ์มักถามว่า: "จะป้องกัน data leakage ระหว่าง cross-validation ได้อย่างไร?" คำตอบคือ pipeline การ fit preprocessing step ภายใน CV loop ช่วยให้มั่นใจว่าข้อมูล test จะไม่มีอิทธิพลต่อการตัดสินใจ scaling หรือ encoding

ทำไม Pipeline ถึงขจัด Data Leakage

Data leakage เกิดขึ้นเมื่อข้อมูลจาก test set มีอิทธิพลต่อ preprocessing ข้อผิดพลาดทั่วไป: การ fit StandardScaler บนทั้ง dataset ก่อนทำการ split Scaler จะเรียนรู้ mean และ variance จากตัวอย่าง test ทำให้คะแนน cross-validation สูงเกินจริง

Pipeline แก้ปัญหานี้โดยการเชื่อม transformer และ estimator เข้าด้วยกันเป็น object เดียวที่ fit ทุกขั้นตอนพร้อมกัน:

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, และ score ทำให้สามารถสลับใช้กับ estimator ของ scikit-learn ตัวใดก็ได้ หมายความว่า GridSearchCV, RandomizedSearchCV และ utility ทั้งหมดของ cross-validation ทำงานได้โดยไม่ต้องแก้ไข

ColumnTransformer สำหรับประเภทข้อมูลแบบผสม

Dataset จริงประกอบด้วยคอลัมน์ตัวเลข (อายุ, เงินเดือน) และคอลัมน์หมวดหมู่ (ประเทศ, ประเภทสินค้า) ColumnTransformer ใช้ preprocessing ที่แตกต่างกันกับชุดย่อยของคอลัมน์ที่แตกต่างกัน จากนั้นรวมผลลัพธ์:

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 เพื่อรับ string และ callable) ควบคุมการตั้งชื่อ output feature การตั้งค่าเป็น True จะเพิ่ม prefix ชื่อ transformer ให้แต่ละ feature ป้องกันการชนกันของชื่อเมื่อหลาย transformer สร้างชื่อคอลัมน์ที่คล้ายกัน

การเลือกคอลัมน์อัตโนมัติด้วย make_column_selector

การ hardcode ชื่อคอลัมน์จะพังเมื่อ dataset เปลี่ยน make_column_selector เลือกคอลัมน์ตาม dtype โดยอัตโนมัติ:

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 ควบคุมว่าจะทำอย่างไรกับคอลัมน์ที่ไม่ตรงกับ transformer ใดๆ ตัวเลือกรวมถึง 'drop' (ค่าเริ่มต้น), 'passthrough' (เก็บไว้ไม่เปลี่ยนแปลง) หรือ transformer ที่จะใช้กับคอลัมน์ที่เหลือ

พร้อมที่จะพิชิตการสัมภาษณ์ Data Science & ML แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Feature Engineering ภายใน Pipeline

Pipeline ขยายเกินกว่า preprocessing เพื่อรวมขั้นตอน feature engineering Custom transformer สืบทอดจาก 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())
])

การ implement get_feature_names_out ทำให้ HTML representation ใหม่ใน scikit-learn 1.9 แสดงชื่อ output feature ทำให้การ debug และเอกสารง่ายขึ้น

Hyperparameter Tuning ข้ามขั้นตอน Pipeline

GridSearchCV ปรับแต่งพารามิเตอร์ข้ามทุกขั้นตอน pipeline โดยใช้หลักการตั้งชื่อ 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 (__) เข้าถึงพารามิเตอร์ที่ซ้อนกัน สำหรับ ColumnTransformer path รวมชื่อ transformer: preprocessor__num__imputer__strategy เข้าถึงพารามิเตอร์ strategy ของ imputer ภายใน numeric transformer

การ Cache Transformer ด้วยพารามิเตอร์ memory

Preprocessing ที่ซับซ้อนบน dataset ขนาดใหญ่อาจช้า พารามิเตอร์ memory cache fitted transformer ไปยัง 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 มีประโยชน์เป็นพิเศษระหว่าง hyperparameter search ที่ preprocessing เดียวกันถูกใช้กับหลายการกำหนดค่า model Estimator สุดท้าย (classifier หรือ regressor) จะไม่ถูก cache เฉพาะ transformer ระดับกลาง

การ Monitor Training ด้วย Callback (scikit-learn 1.9)

Callback API ในเวอร์ชัน 1.9 เพิ่ม progress monitoring ให้ 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 ทำงานกับ Pipeline, StandardScaler, LogisticRegression (solver LBFGS) และคลาส search CV ทั้งหมด ฟีเจอร์ทดลองนี้ให้การมองเห็นการ fit ที่ทำงานนาน โดยไม่ต้องเขียนโค้ด logging เอง

FeatureUnion สำหรับการแยก Feature แบบขนาน

FeatureUnion รันหลาย transformer แบบขนานและเชื่อม output ในแนวนอน:

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 เพิ่มการตรวจสอบเพื่อให้แน่ใจว่า transformer ทั้งหมดคืน output 2D ซึ่งช่วยจับข้อผิดพลาดตั้งแต่เนิ่นๆ เมื่อ custom transformer คืน array 1D โดยไม่ตั้งใจ

Serialization และ Deployment

Pipeline serialize ด้วย joblib รักษาพารามิเตอร์ที่ 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)

ไฟล์ที่ serialize ประกอบด้วยทุกอย่าง: logic ของ column selector, mean และ variance ของ scaler ที่ fit แล้ว, หมวดหมู่ของ encoder และ weight ของ model Deployment ต้องการเพียง scikit-learn และ joblib ไม่ต้องเขียนโค้ด preprocessing เอง

ความเข้ากันได้ของเวอร์ชัน

Pipeline ที่ serialize ด้วย scikit-learn 1.8 อาจโหลดไม่ถูกต้องบน 1.9 หากใช้พารามิเตอร์ที่ deprecated ควร pin เวอร์ชัน scikit-learn ใน production เสมอ และทดสอบการอัพเกรดก่อน deploy

คำถามสัมภาษณ์เกี่ยวกับ Scikit-Learn Pipeline

Q: Data leakage คืออะไร และ pipeline ป้องกันได้อย่างไร?

Data leakage เกิดขึ้นเมื่อข้อมูลจาก validation หรือ test set มีอิทธิพลต่อการ train model ตัวอย่างคลาสสิก: fit StandardScaler บนทั้ง dataset ก่อน split Mean และ standard deviation ของ scaler รวมตัวอย่าง test ทำให้คะแนน cross-validation สูงเกินจริง

Pipeline ป้องกันสิ่งนี้โดยการห่อหุ้ม preprocessing ภายใน loop cross-validation เมื่อ cross_val_score เรียก pipeline.fit() แต่ละ fold จะ fit scaler เฉพาะบนข้อมูล training สถิติของ test fold จะไม่รั่วไหลเข้าสู่ preprocessing

Q: จะเข้าถึงและแก้ไขพารามิเตอร์ของ nested pipeline step ได้อย่างไร?

ใช้ syntax double underscore: pipeline.set_params(classifier__n_estimators=200) สำหรับ ColumnTransformer ให้รวมชื่อ transformer: pipeline.set_params(preprocessor__num__scaler__with_mean=False) Method get_params() คืนพารามิเตอร์ซ้อนทั้งหมดเป็น dictionary แบบ flat ซึ่งมีประโยชน์สำหรับการตรวจสอบและ logging

Q: เมื่อไหร่ควรใช้ FeatureUnion เทียบกับ ColumnTransformer?

ColumnTransformer ใช้ transformer ต่างกันกับคอลัมน์ต่างกันของ input เดียวกัน FeatureUnion ใช้ transformer ต่างกันกับ input ทั้งหมดและเชื่อมในแนวนอน ใช้ ColumnTransformer สำหรับ preprocessing คอลัมน์ที่ต่างกัน (ตัวเลข vs หมวดหมู่) ใช้ FeatureUnion สำหรับ feature augmentation ที่หลายวิธีการแยก (PCA, polynomial features, domain-specific extractors) สร้าง feature เสริมจากข้อมูลเดียวกัน

Q: จะ debug pipeline ที่ล้มเหลวได้อย่างไร?

ตั้ง verbose=True บน pipeline เพื่อดู timing ของแต่ละขั้นตอน เข้าถึงผลลัพธ์ระดับกลางด้วย pipeline.named_steps['step_name'].transform(X) สำหรับ pipeline ที่ fit แล้ว ใน scikit-learn 1.9 HTML representation แสดง attribute ที่ fit แล้วและชื่อ output feature ทำให้การ debug ใน Jupyter notebook ง่ายขึ้น สำหรับการตรวจสอบลึกขึ้น ใช้ pipeline[:-1].fit_transform(X, y) เพื่อรับ output ก่อน estimator สุดท้าย

Checklist Production สำหรับ ML Pipeline

  • Pin เวอร์ชัน scikit-learn ใน requirements เพื่อหลีกเลี่ยงปัญหา serialization
  • ใช้ make_column_selector แทนการ hardcode ชื่อคอลัมน์เมื่อ dataset อาจเปลี่ยน
  • ตั้ง handle_unknown='ignore' บน OneHotEncoder เพื่อจัดการหมวดหมู่ใหม่ใน production
  • Cache preprocessing ที่หนักด้วยพารามิเตอร์ memory ระหว่าง development
  • ตรวจสอบ output shape และชื่อ feature ของ pipeline ใน test ก่อน deployment
  • เก็บ get_feature_names_out() ไว้พร้อมกับ model สำหรับการวิเคราะห์ feature importance
  • ใช้ Pipeline ภายใน GridSearchCV ไม่ใช่ทางกลับกัน เพื่อให้แน่ใจว่าพฤติกรรม CV ถูกต้อง

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Data Science & ML เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 2 กันยายน 2569

แชร์

บทความที่เกี่ยวข้อง