Mục lục
- Mục tiêu bài học
- Nhắc lại 2 phương pháp đã học
- Bảng so sánh Min-Max vs Standardization
- Quy tắc chọn theo model
- Quy tắc chọn theo data
- Decision tree dạng văn bản
- RobustScaler — khi có outlier
- Scale per feature, không per sample — Normalizer khác MinMaxScaler
- Pipeline — tránh data leak
- Test cả 2 phương pháp bằng cross-validation
- Pitfall thường gặp
- Demo so sánh 4 scaler trên iris
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Phân biệt rạch ròi Min-Max (Bài 7) và Standardization (Bài 8) qua bảng so sánh.
- Áp đúng scaler theo từng model: tree-based bỏ qua scale, linear / SVM / KNN / K-Means / PCA cần Standardization, NN ảnh dùng Min-Max.
- Áp đúng scaler theo đặc tính data: outlier →
RobustScaler, sparse →MaxAbsScaler, bounded range → Min-Max, default → Standardization. - Hiểu công thức
RobustScalerdựa trên median và IQR. - Không nhầm
Normalizer(scale per row) vớiMinMaxScaler(scale per column). - Dùng
Pipelineđể tránh data leak khi scale. - Tránh các pitfall: scale target trong regression, scale binary 0/1 sau one-hot, sót cột.
Nhắc lại 2 phương pháp đã học
Bài 7 và Bài 8 đã trình bày 2 phương pháp scaling phổ biến nhất:
Min-Max Normalization (Bài 7)
Công thức: \( x' = (x - x_{\min}) / (x_{\max} - x_{\min}) \). Mặc định scale về [0, 1], có thể đổi sang [a, b] tuỳ ý. Implement bằng sklearn.preprocessing.MinMaxScaler.
Standardization / Z-score (Bài 8)
Công thức: \( x' = (x - \mu) / \sigma \). Sau scale: mean = 0, std = 1. Output không bị chặn trong khoảng cố định. Implement bằng sklearn.preprocessing.StandardScaler.
Cả 2 đều là affine transform (tịnh tiến + co giãn tuyến tính) — không thay đổi hình dạng phân phối, chỉ dịch chuyển và co giãn trục số. Nếu phân phối gốc lệch (skew), sau scale vẫn lệch như cũ. Để "duỗi" phân phối về gần normal, cần PowerTransformer (Box-Cox, Yeo-Johnson) — chủ đề khác.
Bảng so sánh Min-Max vs Standardization
Khía cạnh | Min-Max (MinMaxScaler) | Standardization (StandardScaler)
--------------------|-----------------------------|--------------------------------
Công thức | (x - min) / (max - min) | (x - mean) / std
Range output | [0, 1] (hoặc [a, b]) | không bị chặn (~ [-3, 3] nếu normal)
Mean sau scale | không cố định | 0
Std sau scale | không cố định | 1
Sensitive outlier? | RẤT — outlier kéo min/max | ÍT hơn — chỉ ảnh hưởng mean/std
Preserve shape | có (affine) | có (affine)
Cần biết min/max? | có | không (chỉ cần mean/std)
Ưu tiên khi | range bounded, NN ảnh, | linear/logistic, SVM, KNN,
| output cần chặn [0,1] | K-Means, PCA, default tabular
Điểm cần nhớ nhất: Min-Max nhạy với outlier hơn Standardization. Một sample cực trị có thể kéo max lên rất cao, làm phần còn lại bị nén về gần 0. Standardization chỉ bị ảnh hưởng qua mean và std, mức ảnh hưởng nhẹ hơn nhiều với cùng outlier.
Quy tắc chọn theo model
Trước khi chọn scaler, hỏi câu này: model có nhạy với scale của feature không? Tuỳ thuật toán, câu trả lời rất khác nhau.
Tree-based — KHÔNG cần scale
Decision Tree, Random Forest, XGBoost, LightGBM, CatBoost, Extra Trees: không cần scale. Cây quyết định split dựa trên ngưỡng feature_i < threshold — bất kỳ phép biến đổi đơn điệu nào (kể cả scaling) đều không thay đổi thứ tự, do đó không thay đổi split. Scale chỉ làm thêm bước thừa, không cải thiện accuracy.
Đây là common mistake mà nhiều người vẫn lặp lại "scale mọi thứ trước khi train". Với tree-based, bỏ qua bước này.
Linear / Logistic Regression, Ridge, Lasso — Standardization
Hai lý do:
- Coefficient interpretation: sau khi scale, hệ số
w_itrực tiếp đo "mức ảnh hưởng của feature i" và có thể so sánh giữa các feature. - Gradient descent ổn định: nếu feature có scale rất khác nhau (vd: tuổi 18–80 vs lương 10⁷), bề mặt loss bị méo, gradient descent zigzag và hội tụ chậm.
Đặc biệt với Ridge và Lasso, regularization phạt theo ||w||² hoặc ||w||₁ — nếu không scale, feature có range lớn sẽ bị phạt nặng một cách bất công.
SVM (RBF kernel) — Standardization
RBF kernel \( K(x, x') = \exp(-\gamma \|x - x'\|^2) \) dùng khoảng cách Euclid. Feature có range lớn áp đảo khoảng cách, các feature khác mất tác dụng. Standardization (hoặc Min-Max) bắt buộc.
KNN, K-Means — Standardization
Cả 2 đều distance-based (KNN: khoảng cách tới điểm láng giềng; K-Means: khoảng cách tới centroid). Cùng vấn đề: feature có range lớn quyết định khoảng cách. Scale bắt buộc — Standardization là default, Min-Max cũng được.
PCA — Standardization
PCA tìm eigenvector của ma trận covariance. Feature có variance lớn (do range lớn) chiếm phần lớn variance tổng, lấn át các feature khác. Standardization làm mọi feature có variance = 1, PCA tìm phương phương sai thực sự của dữ liệu chứ không phải đơn vị đo.
Neural Network
Tuỳ kiểu input:
- Input ảnh: Min-Max về
[0, 1](chia 255) hoặc[-1, 1]. Pixel đã bounded sẵn (0–255), Min-Max tự nhiên. - Input tabular: Standardization. Activation như ReLU, sigmoid, tanh đều hoạt động tốt với input ~
[-3, 3].
Naive Bayes — KHÔNG cần
Naive Bayes (Gaussian / Multinomial / Bernoulli) là probabilistic, ước lượng xác suất từng feature độc lập. Scale không thay đổi xác suất, do đó không cần.
Tóm gọn dưới dạng bảng:
Model | Scaler
-----------------------------------|---------------------------
Decision Tree, Random Forest, | KHÔNG cần
XGBoost, LightGBM, CatBoost |
Naive Bayes | KHÔNG cần
Linear / Logistic / Ridge / Lasso | StandardScaler
SVM (linear / RBF / poly) | StandardScaler
KNN, K-Means, hierarchical, DBSCAN | StandardScaler
PCA, LDA | StandardScaler
Neural Net (tabular) | StandardScaler
Neural Net (ảnh) | MinMaxScaler (chia 255)
Quy tắc chọn theo data
Bên cạnh model, đặc tính của data cũng quyết định scaler:
- Có outlier nặng:
RobustScaler(median + IQR). Median và IQR ít bị outlier kéo lệch so với mean và std. Xem mục 7. - Sparse data (high-dimensional, nhiều giá trị 0 — vd: TF-IDF, one-hot):
MaxAbsScalerhoặcStandardScaler(with_mean=False). Lý do: trừ mean sẽ phá tính sparse (chuyển hầu hết 0 thành non-zero), làm vỡ cấu trúc lưu trữ sparse matrix và đội bộ nhớ. - Range biết trước, bounded (vd: pixel 0–255, score 0–10): Min-Max tự nhiên.
- Distribution gần normal: Standardization khớp với giả định Gaussian của nhiều model (linear regression, naive Bayes Gaussian).
- Distribution skew nặng: Cân nhắc
PowerTransformer(Box-Cox cho data dương, Yeo-Johnson cho data có cả âm) trước khi scaling thường. Scaling đơn thuần không sửa được skew.
MaxAbsScaler chia mỗi feature cho giá trị tuyệt đối lớn nhất: \( x' = x / \max(|x|) \). Output về [-1, 1]. Đặc điểm quan trọng: không trừ mean → giữ nguyên các phần tử 0, không phá sparsity.
Decision tree dạng văn bản
Gộp tất cả vào 1 cây quyết định nhỏ, đi từ trên xuống, dừng ở nhánh đầu tiên đúng:
Model tree-based (Tree / RF / XGB / LGB / CatBoost)? → KHÔNG cần scale
Model Naive Bayes? → KHÔNG cần scale
Data sparse (TF-IDF, one-hot, count vectors)? → MaxAbsScaler hoặc StandardScaler(with_mean=False)
Có outlier nặng? → RobustScaler
Distribution skew nặng? → PowerTransformer (Box-Cox / Yeo-Johnson)
Bounded range biết trước (pixel, score 0–10)? → MinMaxScaler
Default → StandardScaler
Quy tắc này phủ được phần lớn các bài toán tabular thực tế. Khi không chắc, chọn StandardScaler rồi A/B test với scaler khác (mục 10).
RobustScaler — khi có outlier
Công thức: \( x' = (x - \text{median}) / \text{IQR} \), với \( \text{IQR} = Q_3 - Q_1 \) (interquartile range — khoảng tứ phân vị, hiệu giữa phân vị 75% và 25%).
Vì sao "robust"?
- Median chỉ phụ thuộc thứ tự, không phụ thuộc độ lớn từng giá trị. Thêm 1 outlier cực lớn không kéo median đi xa, trong khi mean dịch chuyển ngay.
- IQR bỏ qua 25% giá trị nhỏ nhất và 25% lớn nhất, chỉ tính trên 50% phần "thân" của phân phối. Outlier nằm ngoài
[Q1, Q3]không ảnh hưởng tới IQR.
Ví dụ minh hoạ: dataset [1, 2, 3, 4, 5] có mean = 3, std ≈ 1.41. Thêm 1 outlier 1000: mean nhảy lên ~169, std ≈ 372 — toàn bộ statistic "vỡ". Median trước/sau = 3 → 3.5, IQR trước/sau = 2 → 3 — gần như không đổi.
Khi nào dùng:
- Data có heavy tail hoặc outlier mà bạn không muốn xoá (vẫn là dữ liệu hợp lệ, vd: thu nhập cao, giao dịch lớn).
- Không biết có outlier hay không nhưng muốn "phòng thủ" — dùng
RobustScalerkhông gây hại đáng kể trên data sạch.
from sklearn.preprocessing import RobustScaler
import numpy as np
X = np.array([[1], [2], [3], [4], [5], [1000]]) # 1 outlier ở cuối
scaler = RobustScaler()
print(scaler.fit_transform(X).ravel())
# [-0.71, -0.43, -0.14, 0.14, 0.43, 284.43]
# 5 sample đầu nằm gần 0; outlier vẫn ở rất xa nhưng không kéo các giá trị khác lệch như MinMax.
Scale per feature, không per sample — Normalizer khác MinMaxScaler
Mọi scaler đã nói (MinMaxScaler, StandardScaler, RobustScaler, MaxAbsScaler) đều scale per feature — mỗi cột (feature) tính statistic riêng và scale riêng. Đây là default và là cái bạn cần 99% thời gian.
sklearn có thêm 1 class tên rất dễ gây nhầm: sklearn.preprocessing.Normalizer. Class này scale per sample (per row) — mỗi sample được chia cho norm L1 hoặc L2 của chính nó, kết quả mỗi sample có norm = 1.
from sklearn.preprocessing import MinMaxScaler, Normalizer
import numpy as np
X = np.array([[1, 2, 4],
[2, 4, 8]])
# MinMaxScaler: scale per column
print(MinMaxScaler().fit_transform(X))
# [[0. 0. 0.]
# [1. 1. 1.]]
# Mỗi cột scale độc lập về [0, 1]
# Normalizer (L2): scale per row, mỗi row có norm = 1
print(Normalizer(norm="l2").fit_transform(X))
# [[0.218 0.436 0.873]
# [0.218 0.436 0.873]]
# 2 row khác nhau về độ lớn, sau normalize cùng hướng nên giống nhau
Use case của Normalizer khác hẳn:
- Text classification với TF-IDF vector — chuẩn hoá để dùng cosine similarity (cosine = dot product khi cả 2 vector có norm = 1).
- Đo similarity giữa các sample mà không quan tâm độ lớn tuyệt đối, chỉ quan tâm hướng.
Quy tắc: nếu bạn đang chuẩn bị data cho model classification / regression thông thường, dùng các *Scaler. Normalizer chỉ dùng khi bạn biết rõ vì sao cần norm theo row.
Pipeline — tránh data leak
Bài 6 đã nói: scaler phải fit trên train, transform riêng train và test. Nếu fit cả train + test, statistic của test "leak" vào model — đánh giá không còn khách quan.
Code thủ công dễ sai:
# SAI: fit trên toàn bộ X (gồm cả test)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test = train_test_split(X_scaled, ...) # test đã leak vào scaler
# ĐÚNG nhưng dài: fit chỉ trên train
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
model.fit(X_train_s, y_train)
model.predict(X_test_s)
Cách an toàn: wrap scaler + model vào Pipeline. Pipeline tự gọi fit_transform trên train và transform trên test:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
("scaler", StandardScaler()),
("model", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train) # scaler fit trên train, sau đó model fit trên scaled train
print(pipe.score(X_test, y_test)) # scaler transform test (không fit lại), model predict
Lợi ích kép: code ngắn, và quan trọng hơn — không có khả năng tự bắn vào chân khi dùng cross_val_score. Pipeline áp dụng đúng quy trình cho mỗi fold. Chi tiết Pipeline ở Bài 14.
Test cả 2 phương pháp bằng cross-validation
Quy tắc ở mục 4–6 đúng cho ~90% trường hợp. Khi chọn xong vẫn nên A/B test để chắc — đặc biệt khi 2 scaler nằm gần ranh giới khuyến nghị (vd: Standard vs Robust khi không chắc data có outlier hay không).
Mẫu test:
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
scalers = {
"minmax": MinMaxScaler(),
"standard": StandardScaler(),
"robust": RobustScaler(),
}
for name, scaler in scalers.items():
pipe = Pipeline([("scaler", scaler),
("model", LogisticRegression(max_iter=1000))])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"{name:9s}: {scores.mean():.4f} ± {scores.std():.4f}")
Với data clean, khác biệt giữa scaler thường nằm trong khoảng ±0.01 accuracy — gần như không có ý nghĩa thực tế. Khi đó chọn theo nguyên tắc default (Standardization), khỏi mất thời gian tinh chỉnh. Khác biệt rõ rệt thường xảy ra khi: có outlier nặng (Robust thắng), hoặc data đã sparse (MaxAbs / no-mean thắng).
Pitfall thường gặp
- Scale target
ytrong regression. Có thể scale (vd: log-transform giá nhà), nhưng phải nhớ inverse transform trước khi tính metric như MSE / RMSE / MAE — nếu không, sai số tính trên scale đã biến đổi, không có ý nghĩa thực tế. DùngTransformedTargetRegressorcủa sklearn để tự inverse khi predict. - Quên scale 1 cột. Khi xử lý thủ công, dễ áp scaler lên một subset cột rồi quên cột còn lại. Khắc phục: dùng
ColumnTransformerđể khai báo rõ cột nào scaler nào. - Scale binary 0/1 sau one-hot (Bài 10). Cột one-hot đã có range
[0, 1]và mang ý nghĩa nhị phân. Scale chúng (đặc biệt Standardization, sẽ ra giá trị âm như -0.43 / +2.31) làm mất ý nghĩa và thường giảm performance với model linear. Để binary cột nguyên trạng, chỉ scale các cột numeric thực. - Fit scaler trên data sau khi đã chia train/test, nhưng fit trên cả 2. Đã nhắc ở mục 9 — dùng Pipeline để loại bỏ hoàn toàn risk này.
- Quên transform khi inference. Khi deploy, input từ user phải qua đúng scaler đã fit trên train. Lưu scaler bằng
joblib.dump(scaler, "scaler.pkl")cùng model. - Refit scaler khi có data mới. Mỗi lần retrain trên data mới phải fit lại scaler — không dùng scaler cũ trên data có distribution đã dịch.
Demo so sánh 4 scaler trên iris
Test LogisticRegression với 4 trường hợp: raw / MinMax / Standard / Robust. Iris là data clean, không outlier — kết quả minh hoạ luận điểm "trên data sạch, khác biệt nhỏ":
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
X, y = load_iris(return_X_y=True)
setups = {
"raw": Pipeline([("model", LogisticRegression(max_iter=1000))]),
"minmax": Pipeline([("s", MinMaxScaler()), ("model", LogisticRegression(max_iter=1000))]),
"standard": Pipeline([("s", StandardScaler()),("model", LogisticRegression(max_iter=1000))]),
"robust": Pipeline([("s", RobustScaler()), ("model", LogisticRegression(max_iter=1000))]),
}
for name, pipe in setups.items():
scores = cross_val_score(pipe, X, y, cv=5)
print(f"{name:9s}: {scores.mean():.4f}")
Output điển hình (xấp xỉ):
raw : 0.9733
minmax : 0.9667
standard: 0.9733
robust : 0.9667
Khác biệt < 0.01 — không có scaler nào "thắng" rõ. Đó là vì iris quá nhỏ và sạch (xem giới hạn ở Bài 5). Trong tình huống này, chọn Standardization theo nguyên tắc default là đủ.
Thay bằng data có outlier
Để thấy RobustScaler phát huy, thêm outlier nhân tạo:
import numpy as np
X_outlier = X.copy()
X_outlier[0, 0] = 1000 # 1 outlier cực mạnh ở feature 0
for name, pipe in setups.items():
scores = cross_val_score(pipe, X_outlier, y, cv=5)
print(f"{name:9s}: {scores.mean():.4f}")
# Quan sát: minmax tụt mạnh (do max bị kéo lên 1000),
# standard giảm nhẹ, robust gần như không đổi.
Đây là bằng chứng thực hành cho quy tắc "outlier → RobustScaler".
Bài tập
- Cho 5 model dưới đây, chọn scaler phù hợp và giải thích trong 1 câu: (a) Random Forest trên Titanic, (b) Logistic Regression trên iris có 1 cột "thu nhập" với vài outlier triệu USD, (c) KNN trên iris, (d) Neural Network nhận ảnh MNIST 28x28 grayscale, (e) Multinomial Naive Bayes trên TF-IDF của tin tức.
- Trên iris, train KNN (
n_neighbors=5) với 4 setup: raw / MinMax / Standard / Robust. So sánhcross_val_score5-fold. Scaler nào tốt nhất? Vì sao chênh lệch giữa raw và scaled lớn hơn trường hợp Logistic Regression ở mục 12? - Sinh dataset bằng
make_regression(n_samples=500, n_features=10, noise=10, random_state=42). Thêm 5 outlier giá trị lớn vào feature đầu tiên (gấp 1000 lần std). Train Ridge regression vớiStandardScalervsRobustScaler. So sánh RMSE bằng cross-validation. Scaler nào thắng? - Cho 1 dataset có 80% cột là binary one-hot và 20% cột là numeric thực. Viết code dùng
ColumnTransformerđể chỉ scale các cột numeric, để nguyên cột one-hot. Gợi ý:from sklearn.compose import ColumnTransformer. - Giải thích trong 2 câu vì sao tree-based không cần scaling. Nếu bạn vẫn cứ scale data trước khi train Random Forest, accuracy có giảm không? Có rủi ro nào khác không?
Đáp án ngắn
- (a) Không cần — tree-based. (b)
RobustScaler— có outlier mạnh. (c)StandardScaler— KNN distance-based. (d) Min-Max chia 255 về[0, 1]— ảnh bounded sẵn. (e) Không cần — Naive Bayes là probabilistic; TF-IDF cũng đã được chuẩn hoá sẵn. - KNN nhạy với scale hơn Logistic Regression. Khoảng cách Euclid trong KNN bị feature có range lớn (vd:
petal length1–7 cm vssepal width2–5 cm) áp đảo, scale làm các feature có trọng số đều nhau. Khác biệt raw vs scaled trên KNN thường lớn hơn (~0.02–0.05) so với Logistic Regression. - Ridge với
StandardScaler: RMSE tăng mạnh vì outlier kéo mean / std và làm regularization phạt sai.RobustScalergiữ RMSE gần với baseline (không outlier). from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler numeric_cols = [...] # list tên / index cột numeric ct = ColumnTransformer([ ("num", StandardScaler(), numeric_cols), ], remainder="passthrough") X_t = ct.fit_transform(X)- Cây quyết định split theo ngưỡng
feature_i < threshold; mọi phép biến đổi đơn điệu giữ nguyên thứ tự nên giữ nguyên split. Scale không làm Random Forest tệ hơn, chỉ tốn thêm thời gian compute và 1 step thừa trong pipeline — accuracy gần như không đổi.
Tóm tắt
- Min-Max scale về
[0, 1], nhạy với outlier. Standardization đưa mean = 0 / std = 1, ít nhạy hơn nhưng không bị chặn. Cả 2 là affine, không đổi shape phân phối. - Quy tắc theo model: tree-based (Decision Tree, Random Forest, XGBoost, LightGBM, CatBoost) và Naive Bayes không cần scale. Linear / Logistic / Ridge / Lasso / SVM / KNN / K-Means / PCA dùng
StandardScaler. Neural Net ảnh dùng Min-Max chia 255; tabular dùngStandardScaler. - Quy tắc theo data: outlier →
RobustScaler; sparse →MaxAbsScalerhoặcStandardScaler(with_mean=False); bounded → Min-Max; skew nặng →PowerTransformertrước; default →StandardScaler. RobustScaler: \( x' = (x - \text{median}) / \text{IQR} \). Median và IQR ít bị outlier kéo lệch hơn mean và std.- Scale per feature (cột) là default.
Normalizerscale per sample (row) — dùng cho cosine similarity, không phải để tiền xử lý feature thông thường. - Dùng
Pipeline([("scaler", ...), ("model", ...)])để tránh data leak; tự động fit trên train và transform trên test trong cross-validation. - Test cả 2 phương pháp bằng
cross_val_scorekhi không chắc. Trên data clean khác biệt thường < 1% accuracy; trên data có outlier hoặc sparse, khác biệt rõ. - Pitfall: scale target trong regression mà quên inverse, scale binary 0/1 sau one-hot, sót cột, refit scaler trên test, quên lưu scaler khi deploy.
- Bài 10 (One-Hot Encoding) sẽ giải thích cách xử lý feature dạng phân loại — và lý do không nên scale các cột one-hot được tạo ra.
- scikit-learn Docs - Preprocessing data
- scikit-learn - Compare the effect of different scalers on data with outliers
- scikit-learn Docs - MinMaxScaler
- scikit-learn Docs - StandardScaler
- scikit-learn Docs - RobustScaler
- scikit-learn Docs - MaxAbsScaler
- scikit-learn Docs - Normalizer
- scikit-learn Docs - PowerTransformer
- scikit-learn Docs - Pipeline
- scikit-learn Docs - ColumnTransformer
- scikit-learn Docs - TransformedTargetRegressor
- Wikipedia - Interquartile range
- Wikipedia - Feature scaling
