Thành thật mà nói, feature engineering mới là thứ quyết định chất lượng của một mô hình machine learning - chứ không phải kiến trúc phức tạp hay số lượng tham số. Thống kê cho thấy nó chiếm tới 80% thời gian làm việc của một data scientist, và mình cũng chẳng ngạc nhiên gì. Tin vui là từ bản scikit-learn 1.8 (phát hành tháng 12/2025), cả quy trình này có thể tăng tốc tới 15 lần nhờ hỗ trợ Array API với PyTorch và CuPy. Trong bài viết này, mình sẽ đi sâu vào hai công cụ bắt buộc phải nắm vững trong năm 2026: Pipeline và ColumnTransformer.
Feature Engineering Là Gì, Và Vì Sao Nó Quan Trọng?
Nói đơn giản, feature engineering là quá trình biến dữ liệu thô thành các đặc trưng (feature) có ý nghĩa giúp mô hình học tốt hơn. Có một câu nói kinh điển trong giới ML mà mình rất thích: "Một mô hình đơn giản với feature tốt sẽ luôn đánh bại một mô hình phức tạp với feature kém". Càng làm lâu trong nghề, mình càng thấy câu này đúng.
Các tác vụ phổ biến thường xoay quanh:
- Xử lý missing value: điền giá trị thay thế hoặc loại bỏ
- Scaling: chuẩn hóa thang đo giữa các cột số
- Encoding: chuyển dữ liệu dạng chuỗi (categorical) thành số
- Binning, polynomial features: tạo feature mới từ feature cũ
- Feature selection: chọn ra các feature quan trọng nhất
Vấn đề đau đầu nhất khi làm tay? Đó là data leakage - khi thông tin từ tập test vô tình lọt vào tập train. Mình đã chứng kiến không ít trường hợp mô hình đạt AUC 0.98 trên validation rồi "tụt dốc không phanh" khi lên production chỉ vì lỗi này. Và Pipeline của scikit-learn, may mắn thay, giải quyết triệt để chuyện đó.
Có Gì Mới Trong Scikit-learn 1.8 (Tháng 12/2025)?
Phiên bản 1.8 phát hành ngày 10/12/2025 mang đến những thay đổi khá... cách mạng cho feature engineering. Hãy cùng điểm qua.
1. Hỗ Trợ Array API Chính Thức (Thử Nghiệm)
Trước đây, muốn đưa pipeline của scikit-learn lên GPU, bạn phải viết lại toàn bộ bằng cuML hoặc RAPIDS. Đúng là một cực hình, phải không? Với Array API, bạn chỉ cần truyền trực tiếp PyTorch tensor hoặc CuPy ndarray vào estimator - và nó sẽ tự động chạy trên GPU. Thế thôi.
Các estimator đã hỗ trợ Array API trong 1.8:
preprocessing.StandardScalerpreprocessing.PolynomialFeatureslinear_model.RidgeCV,RidgeClassifierCVmixture.GaussianMixturecalibration.CalibratedClassifierCVnaive_bayes.GaussianNB- Nhiều hàm trong
sklearn.metrics(ROC curve, precision_recall_curve...)
2. Hiệu Năng Tăng Tới 15x
Theo benchmark của Probabl (Quansight), việc offload các bước preprocessing lên GPU bằng Array API có thể nhanh hơn CPU tới 15 lần trên dataset lớn. Và điều thú vị là ngay cả khi chạy PyTorch trên CPU cũng đã nhanh hơn NumPy - nhờ multi-threading mặc định. Đúng là win-win.
3. Một Vài Nâng Cấp Đáng Chú Ý Khác
DictionaryLearning,SparseCoder,MiniBatchDictionaryLearningcó thêm phương thứcinverse_transformHistGradientBoostingcho phép truyền trực tiếpX_val,y_valcho early stopping- Fit
Lasso,ElasticNetvới sparse input nhanh hơn nhờ loại bỏ tính toán thừa - Hỗ trợ Python 3.10 đến 3.13 (bản 1.7.2 đã hỗ trợ Python 3.14)
Cài Đặt Môi Trường 2026
# Yêu cầu Python 3.10+ (khuyến nghị 3.12)
pip install --upgrade scikit-learn==1.8.0 pandas numpy
# Để dùng GPU qua Array API
pip install torch # Dùng PyTorch CUDA hoặc MPS (Apple Silicon)
pip install cupy-cuda12x # Nếu dùng NVIDIA CUDA 12.x
pip install array-api-compat
Sau khi cài xong, kiểm tra một chút cho chắc:
import sklearn
import torch
print(sklearn.__version__) # 1.8.0
print(torch.cuda.is_available()) # True nếu có GPU NVIDIA
print(torch.backends.mps.is_available()) # True nếu Apple Silicon
Pipeline Trong Scikit-learn: Nền Tảng Của Feature Engineering
Tại Sao Phải Dùng Pipeline?
Pipeline gói toàn bộ chuỗi biến đổi dữ liệu + mô hình thành một đối tượng duy nhất. Mình liệt kê ra ba lý do khiến bạn bắt buộc phải dùng nó:
- Chống data leakage: Khi dùng
cross_val_score, mỗi fold chỉ fit transformer trên train set - Dễ deploy: Chỉ cần
picklemột object thay vì 5-10 object rời rạc - Grid search dễ dàng: Tune cả hyperparameter của preprocessor và model cùng lúc
Ví Dụ Pipeline Đơn Giản
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.impute import SimpleImputer
pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
("classifier", LogisticRegression(max_iter=1000)),
])
# Fit và dự đoán như một mô hình bình thường
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
Chỉ vậy thôi. Một dòng fit, một dòng predict - và không có chỗ nào cho data leakage chen vào.
ColumnTransformer: Xử Lý Dữ Liệu Dạng Hỗn Hợp
Dữ liệu thực tế hiếm khi chỉ có một loại (và đó là điều làm feature engineering thú vị hơn hẳn lý thuyết). Bạn thường có:
- Cột số cần
StandardScaler - Cột categorical cần
OneHotEncoder - Cột text cần
TfidfVectorizer
ColumnTransformer cho phép áp dụng từng transformer lên từng nhóm cột riêng biệt, rồi gộp kết quả lại. Gọn gàng, rõ ràng.
Ví Dụ Thực Tế Với Dataset Titanic
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import train_test_split
# Load dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
# Xác định nhóm cột
numeric_features = ["Age", "Fare", "SibSp", "Parch"]
categorical_features = ["Pclass", "Sex", "Embarked"]
# Pipeline cho cột số
numeric_pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])
# Pipeline cho cột categorical
categorical_pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
])
# Gộp lại bằng ColumnTransformer
preprocessor = ColumnTransformer(transformers=[
("num", numeric_pipe, numeric_features),
("cat", categorical_pipe, categorical_features),
], remainder="drop")
# Pipeline cuối cùng
clf = Pipeline(steps=[
("preprocessor", preprocessor),
("model", HistGradientBoostingClassifier(random_state=42)),
])
X = df.drop(columns=["Survived", "Name", "Ticket", "Cabin", "PassengerId"])
y = df["Survived"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf.fit(X_train, y_train)
print(f"Do chinh xac: {clf.score(X_test, y_test):.4f}")
Mẹo Nhỏ: Dùng make_column_transformer Cho Ngắn Gọn
from sklearn.compose import make_column_transformer, make_column_selector
preprocessor = make_column_transformer(
(numeric_pipe, make_column_selector(dtype_include="number")),
(categorical_pipe, make_column_selector(dtype_include=["object", "category"])),
)
make_column_selector tự động chọn cột dựa trên dtype - nên bạn không phải liệt kê tay nữa. Cực kỳ hữu ích khi dataset có vài chục cột.
Tăng Tốc GPU Với Array API (Tính Năng Mới 1.8)
Okay, đây là phần mình mong chờ nhất ở scikit-learn 1.8. Bạn có thể chạy preprocessing trên GPU mà không cần đổi thư viện - và cảm giác lần đầu thấy nó hoạt động thật sự khá "đã".
Bật Array API Dispatch
import sklearn
import torch
from sklearn.preprocessing import StandardScaler
# Bat che do Array API toan cuc
sklearn.set_config(array_api_dispatch=True)
# Tao tensor PyTorch tren GPU
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
X_gpu = torch.randn(1_000_000, 50, device=device)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_gpu)
# Ket qua van la tensor tren GPU!
print(type(X_scaled)) # torch.Tensor
print(X_scaled.device) # cuda:0
Với CuPy (NVIDIA GPU)
import cupy as cp
import sklearn
from sklearn.linear_model import RidgeCV
sklearn.set_config(array_api_dispatch=True)
X_cp = cp.random.randn(500_000, 20)
y_cp = cp.random.randn(500_000)
model = RidgeCV(alphas=[0.1, 1.0, 10.0])
model.fit(X_cp, y_cp)
print(f"Alpha toi uu: {model.alpha_}")
Benchmark CPU Vs GPU
import time
import numpy as np
import torch
import sklearn
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.pipeline import Pipeline
N, D = 2_000_000, 30
X_np = np.random.randn(N, D).astype(np.float32)
# CPU voi NumPy
pipe_cpu = Pipeline([
("poly", PolynomialFeatures(degree=2, include_bias=False)),
("scaler", StandardScaler()),
])
t0 = time.time()
pipe_cpu.fit_transform(X_np)
print(f"CPU NumPy: {time.time()-t0:.2f}s")
# GPU voi PyTorch
sklearn.set_config(array_api_dispatch=True)
X_gpu = torch.from_numpy(X_np).to("cuda")
pipe_gpu = Pipeline([
("poly", PolynomialFeatures(degree=2, include_bias=False)),
("scaler", StandardScaler()),
])
t0 = time.time()
pipe_gpu.fit_transform(X_gpu)
torch.cuda.synchronize()
print(f"GPU PyTorch: {time.time()-t0:.2f}s")
Trên chiếc NVIDIA RTX 4090 mà mình test, pipeline này chạy nhanh hơn CPU khoảng 10-15 lần với dataset 2 triệu hàng. Mức tăng tốc này đủ để bạn thay đổi hoàn toàn cách làm việc với big data.
Grid Search Với Pipeline Và ColumnTransformer
Tune siêu tham số của cả preprocessor và model cùng lúc - bằng cú pháp __ (hai dấu gạch dưới):
from sklearn.model_selection import GridSearchCV
param_grid = {
"preprocessor__num__imputer__strategy": ["median", "mean"],
"preprocessor__cat__onehot__drop": [None, "first"],
"model__learning_rate": [0.01, 0.1, 0.3],
"model__max_depth": [3, 5, 7],
}
grid = GridSearchCV(
clf,
param_grid,
cv=5,
scoring="roc_auc",
n_jobs=-1,
verbose=2,
)
grid.fit(X_train, y_train)
print(f"Best params: {grid.best_params_}")
print(f"Best AUC: {grid.best_score_:.4f}")
Để ý format một chút: <step_name>__<sub_step>__<param_name>. Đây chính là lý do bạn nên đặt tên rõ ràng cho mỗi step - chứ đừng đặt "step1", "step2" rồi sau này ngồi đoán.
Feature Engineering Nâng Cao
1. Tạo Feature Từ Hàm Tùy Chỉnh Với FunctionTransformer
from sklearn.preprocessing import FunctionTransformer
import numpy as np
def log_transform(X):
return np.log1p(np.abs(X))
log_pipe = Pipeline([
("log", FunctionTransformer(log_transform, validate=False)),
("scaler", StandardScaler()),
])
2. Feature Selection Trong Pipeline
from sklearn.feature_selection import SelectKBest, f_classif
clf_with_selection = Pipeline([
("preprocessor", preprocessor),
("selector", SelectKBest(score_func=f_classif, k=10)),
("model", HistGradientBoostingClassifier(random_state=42)),
])
3. Kết Hợp Nhiều Feature Set Với FeatureUnion
Khác với ColumnTransformer (chia cột), FeatureUnion áp dụng nhiều transformer lên toàn bộ dữ liệu rồi nối kết quả lại. Cái này rất hữu ích cho text - ví dụ TF-IDF kết hợp với độ dài văn bản:
from sklearn.pipeline import FeatureUnion
from sklearn.feature_extraction.text import TfidfVectorizer
text_features = FeatureUnion([
("tfidf", TfidfVectorizer(max_features=5000)),
("length", FunctionTransformer(lambda x: np.array([[len(s)] for s in x]))),
])
4. Thư Viện Bổ Sung: Feature-engine Và Featuretools
Ngoài scikit-learn ra, năm 2026 có một vài thư viện rất đáng chú ý:
- Feature-engine: hàng loạt transformer chuyên biệt (outlier, discretization, date-time), tương thích 100% với Pipeline scikit-learn
- Featuretools: tự động hóa feature engineering cho dữ liệu quan hệ qua thuật toán Deep Feature Synthesis (DFS)
- TSFresh: trích xuất hàng trăm feature cho time series - tự động hết
10 Best Practice Feature Engineering Năm 2026
- Luôn giữ bản gốc dữ liệu: Clone DataFrame bằng
df.copy()trước khi biến đổi - Không bao giờ fit trên full dataset: Luôn fit trên train, chỉ transform trên test
- Dùng Pipeline ngay từ đầu: Tiết kiệm thời gian và tránh rò rỉ dữ liệu
- Đặt tên step rõ ràng: Giúp grid search và debug dễ dàng hơn nhiều
- Set
random_state: Đảm bảo tái tạo được kết quả - Log version: Ghi lại
sklearn.__version__vào artifact MLflow - Kiểm tra NaN sau transform: Dùng
np.isnan(X).any() - Ưu tiên
HistGradientBoosting: Hỗ trợ NaN sẵn, không cần imputer trong nhiều trường hợp - Bật Array API cho dataset > 1M hàng: Lợi ích rõ rệt khi data lớn
- Viết unit test cho custom transformer: Đặc biệt là khi dùng
FunctionTransformer
Lỗi Thường Gặp Và Cách Khắc Phục
Lỗi: "Found unknown categories"
Xuất hiện khi tập test có giá trị categorical không có trong train. Giải pháp rất đơn giản:
OneHotEncoder(handle_unknown="ignore")
Lỗi: Data Leakage Khi Cross-Validation
Nếu bạn scale trước khi train_test_split, xin chia buồn - bạn đã làm rò rỉ thông tin test set rồi. Luôn đặt scaler trong Pipeline để cross_val_score tự xử lý đúng chỗ.
Lỗi: Sparse Matrix Không Tương Thích
Một số transformer trả về sparse matrix, một số trả dense. Từ scikit-learn 1.7 trở đi, bạn nên set OneHotEncoder(sparse_output=False) nếu model không hỗ trợ sparse.
Lỗi: inspection.partial_dependence Gặp Lỗi Dtype
Từ 1.7 trở đi, partial_dependence không chấp nhận integer dtype cho feature số nữa. Convert sang float trước khi gọi - không thì "toang" cả notebook.
Câu Hỏi Thường Gặp (FAQ)
Scikit-learn 1.8 có bắt buộc phải dùng GPU không?
Không. Array API là tùy chọn và hiện vẫn ở trạng thái thử nghiệm. Bạn vẫn chạy bình thường với NumPy trên CPU. Chỉ khi bạn set sklearn.set_config(array_api_dispatch=True) và truyền tensor PyTorch/CuPy thì mới dùng GPU.
Khác biệt giữa Pipeline và ColumnTransformer là gì?
Pipeline nối các bước tuần tự - output của bước trước là input của bước sau. Còn ColumnTransformer chạy song song nhiều transformer trên các nhóm cột khác nhau rồi gộp kết quả. Thông thường bạn sẽ đặt ColumnTransformer bên trong Pipeline, để vừa có xử lý song song vừa có chuỗi tuần tự.
Pandas DataFrame hay NumPy array tốt hơn cho Pipeline?
Tùy. Dùng pandas DataFrame khi bạn cần tên cột (để ColumnTransformer chọn theo tên) và muốn xài make_column_selector. Dùng NumPy khi tốc độ là ưu tiên hàng đầu. Từ 1.8 trở đi, bạn cũng có thể dùng PyTorch tensor hoặc CuPy array để chạy GPU.
Làm sao biết cột nào quan trọng sau Pipeline?
Dùng pipeline.named_steps["model"].feature_importances_ kết hợp với pipeline.named_steps["preprocessor"].get_feature_names_out() để lấy tên feature sau biến đổi. Scikit-learn 1.8 giữ ổn định API này và áp dụng cho hầu hết transformer.
Có nên dùng Polars thay vì Pandas với ColumnTransformer?
Scikit-learn hỗ trợ Polars DataFrame qua Array API và chuẩn __dataframe__ protocol. Nếu dataset lớn hơn 1GB RAM, Polars nhanh hơn Pandas 3-5 lần cho bước load và filter. Tuy nhiên (và đây là điểm quan trọng), ColumnTransformer với tên cột vẫn ổn định hơn khi dùng cùng pandas.
Làm sao deploy Pipeline lên production?
Dùng joblib.dump(pipeline, "model.joblib") để lưu và joblib.load() để tải. Nhớ ghi lại version scikit-learn vì model có thể không tương thích giữa các version khác nhau. Với môi trường production, bạn cũng có thể cân nhắc chuyển sang ONNX qua thư viện sklearn-onnx để tách rời khỏi Python runtime.
Kết Luận
Feature engineering vẫn là kỹ năng quan trọng bậc nhất của data scientist trong năm 2026 - ngay cả khi AutoML và LLM đang phát triển chóng mặt. Với bộ ba Pipeline + ColumnTransformer + Array API của scikit-learn 1.8, bạn có trong tay:
- Code sạch, dễ maintain, chống data leakage
- Tăng tốc tới 15x miễn phí trên GPU có sẵn
- Grid search mạnh mẽ cho toàn bộ workflow
- Deploy đơn giản với một object duy nhất
Hãy bắt đầu áp dụng ngay vào dự án tiếp theo của bạn. Từ bỏ thói quen xử lý feature bằng những script rời rạc, và chuyển sang Pipeline ngay từ hôm nay - mình đảm bảo bạn sẽ không muốn quay lại cách cũ nữa. Thời gian tiết kiệm được sẽ dành cho việc quan trọng hơn: hiểu data và cải thiện mô hình.