Mục lục
- Mục tiêu bài học
- Standardization là gì
- Công thức Z-score
- Tính chất của output
- StandardScaler trong sklearn
- Parameter with_mean và with_std
- Inspect scaler sau khi fit
- Vì sao with_mean=False cho sparse data
- So sánh Standardization vs Min-Max
- Outlier và RobustScaler
- Khi nào dùng Standardization
- Pitfall thường gặp
- Helper scale() — vì sao không dùng
- Code thực hành
- Bài tập
- Bài tiếp theo
Mục tiêu bài học
Sau khi đọc bài này, bạn sẽ:
- Hiểu công thức Z-score và tại sao output có mean = 0, std = 1.
- Dùng được
StandardScalerđúng quy ước fit-on-train. - Đọc được
scaler.mean_,scaler.scale_,scaler.var_. - Biết khi nào set
with_mean=Falsecho sparse data. - Phân biệt được Standardization, Min-Max và RobustScaler.
- Tránh được pitfall data leak khi scale test set.
Standardization là gì
Standardization (còn gọi là Z-score normalization) là biến đổi mỗi feature về dạng có trung bình bằng 0 và độ lệch chuẩn bằng 1. Tên gọi Z-score đến từ thống kê: giá trị mới đo "x cách mean bao nhiêu đơn vị std".
Khái niệm variance và standard deviation đã được giới thiệu ở Series 1 (Toán nền tảng) — bài 22. Ở đây ta dùng chúng làm công cụ chuẩn hoá feature trước khi đưa vào model.
Standardization KHÔNG bound output vào range cố định như Min-Max. Sau khi standardize, giá trị có thể là -3.2, 0.5, +4.1 tuỳ vào dữ liệu gốc — đa phần nằm trong khoảng [-3, +3] nếu phân phối gần chuẩn (do quy tắc 3-sigma).
Công thức Z-score
Với mỗi giá trị \( x \) của một feature:
\[ z = \frac{x - \mu}{\sigma} \]Trong đó:
- \( \mu \) là mean của feature đó, tính TRÊN TRAIN SET.
- \( \sigma \) là standard deviation của feature đó, cũng tính TRÊN TRAIN SET.
Hai con số \( \mu \) và \( \sigma \) là statistics mà scaler học được trong bước fit. Khi áp dụng cho test set (hoặc dữ liệu mới khi inference), ta dùng chính \( \mu \) và \( \sigma \) đã fit từ train, không tính lại từ test. Đây là điểm bắt buộc để tránh data leak — sẽ nói kỹ ở mục pitfall.
Áp dụng công thức trên cho từng feature riêng (column-wise), không gộp giữa các feature.
Tính chất của output
Sau khi standardize, feature mới \( z \) có:
- \( \mu(z) = 0 \) — trung bình bằng 0 (do trừ đi \( \mu \) gốc).
- \( \sigma(z) = 1 \) — độ lệch chuẩn bằng 1 (do chia cho \( \sigma \) gốc).
- KHÔNG có range cố định — output không bị bound vào
[0, 1]hay[-1, +1].
Nếu feature gốc gần phân phối chuẩn \( \mathcal{N}(\mu, \sigma^2) \), thì sau khi standardize phân phối mới gần \( \mathcal{N}(0, 1) \) — còn gọi là standard normal distribution. Nếu feature gốc bị skew (lệch), Z-score vẫn giữ nguyên skew đó: công thức chỉ trừ và chia (affine transform), không đổi hình dạng phân phối.
Một số bài viết cũ nói "Standardization làm dữ liệu trở thành normal distribution" — sai. Standardize chỉ đưa mean về 0 và std về 1; nếu gốc đã lệch hoặc có multi-modal, sau khi standardize vẫn vậy.
StandardScaler trong sklearn
Class StandardScaler nằm trong sklearn.preprocessing, tuân theo transformer interface chuẩn (fit / transform / fit_transform):
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit trên train, transform luôn
X_test_scaled = scaler.transform(X_test) # CHỈ transform, dùng mean/std từ train
Quy tắc giống mọi transformer khác trong sklearn:
fithoặcfit_transformchỉ gọi trên train.transformgọi trên cả train (nếu chưa) và test.- Lưu lại object
scalerđể inference sau này còn dùng cùng \( \mu \), \( \sigma \).
Parameter with_mean và with_std
StandardScaler có hai tham số chính:
with_mean=True(mặc định) — trừ mean (bước \( x - \mu \)).with_std=True(mặc định) — chia std (bước \( / \sigma \)).
Tổ hợp thường gặp:
with_mean=True, with_std=True— standardization đầy đủ (mặc định).with_mean=False, with_std=True— chỉ chia std, giữ nguyên vị trí của 0. Dùng cho sparse matrix.with_mean=True, with_std=False— chỉ center về mean 0 (mean-centering), giữ scale gốc.with_mean=False, with_std=False— không làm gì cả (no-op).
from scipy.sparse import csr_matrix
from sklearn.preprocessing import StandardScaler
X_sparse = csr_matrix([[0, 0, 3], [0, 2, 0], [1, 0, 0]])
scaler = StandardScaler(with_mean=False) # bắt buộc cho sparse
X_sparse_scaled = scaler.fit_transform(X_sparse)
Nếu cố gọi StandardScaler(with_mean=True).fit(X_sparse) sẽ raise ValueError: Cannot center sparse matrices....
Inspect scaler sau khi fit
Sau khi fit, scaler lưu các statistics dưới các thuộc tính kết thúc bằng dấu gạch dưới (theo quy ước sklearn):
scaler.mean_— array shape(n_features,), mean của từng feature.scaler.scale_— array shape(n_features,), std của từng feature (chính là \( \sigma \) dùng để chia).scaler.var_— array shape(n_features,), variance (=scale_ ** 2).scaler.n_features_in_— số feature lúc fit.scaler.n_samples_seen_— số sample đã thấy.
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
X, _ = load_iris(return_X_y=True)
scaler = StandardScaler().fit(X)
print("mean_ :", scaler.mean_)
print("scale_:", scaler.scale_)
print("var_ :", scaler.var_)
# mean_ : [5.843 3.057 3.758 1.199]
# scale_: [0.825 0.434 1.759 0.760]
# var_ : [0.681 0.189 3.096 0.577]
Nếu with_mean=False thì scaler.mean_ = None; nếu with_std=False thì scaler.scale_ = None.
Vì sao with_mean=False cho sparse data
Sparse matrix (CSR, CSC trong scipy.sparse) chỉ lưu các phần tử khác 0 — đa số phần tử là 0 và không tốn bộ nhớ. Ví dụ TF-IDF của 100k document × 50k từ vựng có thể chỉ lưu vài chục triệu số thay vì 5 tỷ.
Khi trừ mean (\( x - \mu \)) với \( \mu \neq 0 \), những giá trị 0 ban đầu sẽ trở thành \( -\mu \) — tức là khác 0. Hệ quả:
- Mọi entry phải lưu rõ ràng → ma trận trở thành dense.
- RAM bùng nổ (vd 100k × 50k floats ≈ 40 GB).
- Phần lớn pipeline (linear model, naive bayes...) đang tận dụng sparsity sẽ chậm hẳn.
Cách giải quyết của sklearn: chỉ chia std, không trừ mean (with_mean=False). Cách này giữ nguyên các vị trí 0, vẫn cân scale giữa các feature có magnitude lệch nhau, nhưng feature mới không có mean = 0. Đây là trade-off được chấp nhận trong NLP và recommender system khi dữ liệu sparse.
So sánh Standardization vs Min-Max
| Tiêu chí | Standardization | Min-Max |
|---|---|---|
| Công thức | \( (x - \mu) / \sigma \) | \( (x - x_{\min}) / (x_{\max} - x_{\min}) \) |
| Statistics dùng | mean, std | min, max |
| Range output | Không bound | [0, 1] (mặc định) |
| Nhạy outlier nhẹ | Ít hơn (mean/std bị ảnh hưởng từ tốn) | Rất nhạy (1 max lớn kéo cả scale) |
| Giữ shape phân phối | Có (chỉ shift + rescale) | Có (chỉ shift + rescale) |
| Dùng nhiều với | Linear/Logistic, SVM, PCA | NN với activation bounded, image pixel |
Điểm chung quan trọng: cả hai đều là affine transform theo trục mỗi feature (\( z = a \cdot x + b \)). Affine transform không làm đổi shape phân phối — nếu gốc bị skew phải, sau scaling vẫn skew phải.
Khi nào dùng cái nào — chốt ở Bài 9.
Outlier và RobustScaler
Standardization ít nhạy outlier hơn Min-Max nhưng vẫn nhạy: một giá trị quá lớn vẫn kéo mean và std lệch đi. Khi data có outlier nặng, các phương án thường dùng:
- Cắt / clip outlier trước khi standardize (vd cap ở percentile 99).
- RobustScaler — dùng median và IQR thay cho mean và std.
- Biến đổi phi tuyến (log, Box-Cox, Yeo-Johnson) — đề cập ở các bài sau.
Công thức RobustScaler:
\[ x' = \frac{x - \text{median}(x)}{\text{IQR}(x)} \]với IQR = \( Q_3 - Q_1 \) (khoảng giữa percentile 25 và 75). Median và IQR không bị 1-2 outlier kéo nên scale ổn định hơn khi data dirty.
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler() # mặc định quantile_range=(25.0, 75.0)
X_scaled = scaler.fit_transform(X_train)
print("center_:", scaler.center_) # median của từng feature
print("scale_ :", scaler.scale_) # IQR của từng feature
Kết quả của RobustScaler KHÔNG có mean = 0, std = 1 — chỉ có median = 0 và IQR = 1. Đừng kỳ vọng giống StandardScaler.
Khi nào dùng Standardization
Bài 9 sẽ chốt cây quyết định đầy đủ. Trước mắt, Standardization là lựa chọn mặc định cho:
- Linear Regression / Logistic Regression — gradient descent hội tụ nhanh hơn khi feature có cùng scale; coefficient cũng so sánh được giữa các feature.
- SVM với kernel RBF / polynomial — khoảng cách giữa sample phụ thuộc scale, không standardize thì feature có magnitude lớn lấn át phần còn lại.
- PCA — phân rã covariance matrix, eigenvalue dựa trên variance; feature scale to sẽ chiếm hết các component đầu nếu không standardize.
- KNN / K-Means — dựa trên khoảng cách Euclid, đòi hỏi feature cùng scale (có thể dùng Min-Max thay thế).
- Neural network — standardize input giúp activation function (sigmoid, tanh) làm việc trong vùng dốc, học ổn định hơn. Ý tưởng này được mở rộng thành
BatchNormbên trong từng layer.
Khi nào không cần: tree-based model (Decision Tree, Random Forest, Gradient Boosting, XGBoost, LightGBM, CatBoost) — tree chia theo threshold, scale không ảnh hưởng kết quả. Standardize hay không cũng cho cùng accuracy (sai khác là noise).
Pitfall thường gặp
Lỗi 1 — fit trên test set (data leak):
# SAI: fit_transform trên test => leak mean/std của test
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test) # SAI
# ĐÚNG
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # chỉ transform
Fit lại trên test sẽ dùng \( \mu \), \( \sigma \) của test → metric trên test bị thổi phồng vì model "thấy" thống kê của tập đáng lẽ chưa biết.
Lỗi 2 — quên scale khi predict:
model.fit(X_train_scaled, y_train)
# SAI: quên transform input mới
y_pred = model.predict(X_new_raw)
# ĐÚNG
X_new_scaled = scaler.transform(X_new_raw)
y_pred = model.predict(X_new_scaled)
Tốt nhất bọc scaler + model vào Pipeline để khỏi nhớ:
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
pipe.fit(X_train, y_train) # tự fit scaler + model
pipe.predict(X_new_raw) # tự transform + predict
Lỗi 3 — quên inverse khi predict target đã scaled: nếu standardize cả y (vd regression target khác scale lớn), nhớ inverse_transform trước khi báo cáo metric ở scale gốc.
Lỗi 4 — fit lại scaler ở inference: production phải serialize scaler đã fit (joblib / pickle) và load lại — không fit lại mỗi request.
Helper scale() — vì sao không dùng
Sklearn có function sklearn.preprocessing.scale(X) — fit + transform trong 1 dòng, KHÔNG trả về scaler object:
from sklearn.preprocessing import scale
X_scaled = scale(X) # tiện nhưng KHÔNG có mean_, scale_ để dùng lại
Vấn đề: không có cách lấy lại \( \mu \), \( \sigma \) để áp dụng cho test set. Chỉ phù hợp cho phân tích nhanh trong notebook khi không cần inference. Trong pipeline production, luôn dùng StandardScaler class.
Code thực hành
1. Standardize iris và verify mean ≈ 0, std ≈ 1.
import numpy as np
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
X, _ = load_iris(return_X_y=True)
scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)
print("mean_ (train):", scaler.mean_)
print("scale_(train):", scaler.scale_)
print("mean after :", X_scaled.mean(axis=0)) # ~ [0, 0, 0, 0]
print("std after :", X_scaled.std(axis=0)) # ~ [1, 1, 1, 1]
Sai số khoảng 1e-15 đến 1e-16 là floating-point bình thường, không phải bug.
2. So sánh StandardScaler vs RobustScaler khi có outlier:
import numpy as np
from sklearn.preprocessing import StandardScaler, RobustScaler
rng = np.random.default_rng(42)
x = rng.normal(loc=10, scale=2, size=100)
x = np.concatenate([x, [500.0]]) # 1 outlier rất lớn
X = x.reshape(-1, 1)
std = StandardScaler().fit(X)
rob = RobustScaler().fit(X)
print("StandardScaler mean_ :", std.mean_[0], "scale_:", std.scale_[0])
print("RobustScaler center:", rob.center_[0], "scale_:", rob.scale_[0])
# Outlier kéo mean lên ~14.7 và std lên ~49 (StandardScaler).
# Robust giữ center quanh 10 và scale ~ 2.7 (IQR) — sát thực tế hơn.
Sau khi scale 99 sample đầu (không kể outlier), RobustScaler cho phân bố tập trung hơn quanh 0; StandardScaler cho phân bố bị nén lại vì std lớn bất thường.
3. Pipeline scaler + model — quy ước production:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
pipe.fit(X_train, y_train)
print("Test accuracy:", pipe.score(X_test, y_test))
Bài tập
Bài 1: Standardize iris bằng StandardScaler. In X_scaled.mean(axis=0) và X_scaled.std(axis=0). Giá trị thực tế là bao nhiêu? Vì sao không phải chính xác 0 và 1?
Bài 2: Tự sinh một mảng 1000 phần tử normal(0, 1) rồi thêm 1 phần tử bằng 1000. Standardize bằng StandardScaler; standardize lại bằng RobustScaler. So sánh phân phối của 1000 phần tử ban đầu sau khi scale (vẽ histogram nếu có matplotlib). Phương án nào cho histogram gần normal(0, 1) hơn?
Bài 3: Fit StandardScaler trên iris, in mean_, scale_, var_. Tự verify var_ == scale_ ** 2. Tính lại mean_ và scale_ bằng np.mean(X, axis=0) và np.std(X, axis=0) — kết quả có khớp không?
Bài 4 (nâng cao): Tạo sparse matrix bằng scipy.sparse.random(1000, 50, density=0.05). Thử StandardScaler(with_mean=True).fit(X_sparse) và đọc thông báo lỗi. Sau đó dùng with_mean=False, kiểm tra X_sparse_scaled vẫn là sparse không (thuộc tính .nnz).
Bài 5: So sánh accuracy của LogisticRegression trên iris có scaler vs không scaler. Cho cùng max_iter và random_state. Trên iris (dataset nhỏ, scale các feature khá tương đồng), khác biệt thường rất nhỏ — quan sát xem có khác không.
Bài tiếp theo
Bài 9: Khi nào dùng Normalization, khi nào dùng Standardization — chốt cây quyết định chọn scaling method theo từng model và đặc tính data: bounded vs unbounded, outlier nhiều / ít, sparse / dense, tree-based hay không.
Tài liệu tham khảo
- Scikit-Learn — StandardScaler API reference
- Scikit-Learn — RobustScaler API reference
- Scikit-Learn — User Guide: Standardization, or mean removal and variance scaling
- Scikit-Learn — Compare the effect of different scalers on data with outliers
- Scikit-Learn — Common pitfalls: inconsistent preprocessing
- Wikipedia — Standard score (Z-score)
- Wikipedia — Feature scaling
