Mục lục
- Mở đầu Module 4 — Classification
- Classification vs Regression
- Vì sao KHÔNG dùng Linear Regression
- Logistic Regression — fix bằng sigmoid
- Tên gọi "Regression" gây nhầm
- Decision rule và threshold
- Loss function — Binary Cross-Entropy
- Decision boundary tuyến tính
- LogisticRegression trong sklearn
- Tham số quan trọng
- Inspect coefficient
- Multi-class — OvR và Softmax
- Standardize feature
- Use case thực tế
- Code Python đầy đủ
- Bài tập thực hành
- Bài tiếp theo
Mở đầu Module 4 — Classification
Module 3 (Bài 15–21) đã xử lý regression: target liên tục, ví dụ giá nhà, doanh thu, nồng độ. Loss là MSE/RMSE, metric là \( R^2 \), model là Linear, Polynomial, Ridge, Lasso.
Module 4 chuyển sang classification: target là nhãn rời rạc. Spam hay không spam. Mèo, chó, hay chim. Khách rời bỏ hay ở lại. Bài toán đổi → loss đổi, metric đổi, decision rule đổi. Nhưng pipeline tổng thể (load data, split, fit, evaluate) vẫn giống.
Bài đầu module: Logistic Regression — model classification đơn giản nhất, là baseline mọi bài toán phân loại bắt đầu. Các bài tiếp theo trong module:
- Bài 23 — Confusion Matrix: cách tóm tắt kết quả classifier.
- Bài 24 — Precision, Recall, F1.
- Bài 25 — Naive Bayes, k-NN, SVM (các classifier khác).
- Bài 26 — ROC, AUC.
Hết module sẽ có đủ công cụ để build và đánh giá một classifier hoàn chỉnh.
Classification vs Regression
Khác biệt nằm ở không gian output.
- Regression: \( \hat{y} \in \mathbb{R} \). Predict một số thực, không bị chặn. Ví dụ giá nhà 2.5 tỷ, nhiệt độ 28.3°C.
- Binary classification: \( \hat{y} \in \{0, 1\} \). Hai class, encode 0 và 1. Ví dụ spam/non-spam, churn/stay.
- Multi-class classification: \( \hat{y} \in \{0, 1, 2, \ldots, K-1\} \). \( K \) class loại trừ nhau. Ví dụ phân loại 3 species hoa iris (\( K=3 \)), 10 chữ số MNIST (\( K=10 \)).
- Multi-label: mỗi sample có thể thuộc nhiều class cùng lúc. Ví dụ tag bài báo. Đây là bài toán riêng, không bàn ở bài này.
Hệ quả khi đổi sang classification:
- Loss MSE không còn hợp lý — predict số 0.7 trong khi truth là 1 không có ý nghĩa "lệch 0.3 đơn vị" như giá nhà.
- Metric "lỗi bình phương" đổi sang accuracy, precision, recall.
- Model thường output probability trước, rồi mới threshold thành class.
Vì sao KHÔNG dùng Linear Regression
Ý tưởng "ngây thơ": encode class 0 và 1 thành số, fit Linear Regression như bình thường, threshold ở 0.5. Ba vấn đề khiến cách này hỏng:
- Output không bound trong \( [0, 1] \) — phương trình \( \hat{y} = \mathbf{w}^T \mathbf{x} + b \) có thể ra giá trị âm hoặc lớn hơn 1. Một sample predict \( \hat{y} = -0.3 \) hay \( \hat{y} = 1.8 \) không diễn giải được như probability.
- MSE không phù hợp cho probability — MSE giả định lỗi phân phối chuẩn, đều quanh predict. Với target nhị phân chỉ nhận hai giá trị 0 và 1, giả định này sai. Loss surface không lý tưởng để tối ưu cho probabilistic output.
- Outlier kéo lệch decision boundary — Linear Regression nhạy với điểm xa boundary. Thêm vài sample class 1 ở rất xa trung tâm class 1 sẽ làm boundary dịch chuyển vô lý, ngay cả khi chúng đã được phân loại đúng. Classifier không nên "lo lắng" về điểm đã đúng.
Tổng kết: cần một biến đổi ép output về (0, 1) và một loss khác MSE. Logistic Regression giải cả hai trong một bước.
Logistic Regression — fix bằng sigmoid
Giữ nguyên phần tổ hợp tuyến tính \( z = \mathbf{w}^T \mathbf{x} + b \), nhưng truyền qua hàm sigmoid (còn gọi logistic function) để ép kết quả vào khoảng \( (0, 1) \):
\[ \hat{p} = \sigma(z) = \frac{1}{1 + e^{-z}}, \quad z = \mathbf{w}^T \mathbf{x} + b \]Tính chất của \( \sigma(z) \):
- \( \sigma(z) \in (0, 1) \) với mọi \( z \in \mathbb{R} \) — diễn giải được như probability.
- \( \sigma(0) = 0.5 \), \( \sigma(\infty) \to 1 \), \( \sigma(-\infty) \to 0 \).
- Đối xứng: \( \sigma(-z) = 1 - \sigma(z) \).
- Đạo hàm gọn: \( \sigma'(z) = \sigma(z)(1 - \sigma(z)) \) — đặc tính quan trọng khi tính gradient cho training.
Sigmoid đã preview ở Series 1 bài 26 (hàm activation cho neuron). Trong Logistic Regression, nó đóng vai trò "squashing function" — ép linear score thành probability.
Diễn giải output: \( \hat{p} \) là probability ước lượng của class 1 (\( P(y=1 \mid \mathbf{x}) \)). Suy ra probability class 0 bằng \( 1 - \hat{p} \).
Tên gọi "Regression" gây nhầm
Logistic Regression có chữ "Regression" trong tên, nhưng nó là classification. Lý do lịch sử: model regress trên log-odds (giá trị liên tục \( z = \log\frac{p}{1-p} \)) chứ không phải class trực tiếp. Output cuối cùng vẫn là class rời rạc — đây là classification.
Cách định danh chuẩn:
- Output cuối là class label rời rạc → classifier.
- Output trung gian là probability liên tục → có thể dùng cho ranking, threshold tuning, calibration.
Mọi tài liệu sklearn, statlearning đều xếp LogisticRegression vào nhóm classifier (nằm trong sklearn.linear_model cùng LinearRegression chỉ vì cả hai cùng dùng linear combination trước hàm output).
Decision rule và threshold
Từ probability sang class label cần một threshold (ngưỡng quyết định). Quy tắc mặc định:
\[ \hat{y} = \begin{cases} 1 & \text{nếu } \hat{p} \geq 0.5 \\ 0 & \text{nếu } \hat{p} < 0.5 \end{cases} \]Threshold 0.5 tương đương \( z = 0 \) (do \( \sigma(0) = 0.5 \)) — sample nằm đúng trên decision boundary. Đây là lựa chọn cân đối khi hai class quan trọng như nhau.
Threshold không phải bất biến. Tuỳ bài toán có thể điều chỉnh:
- Threshold = 0.3 — model dễ predict class 1 hơn → tăng recall (bắt được nhiều positive hơn) nhưng giảm precision. Dùng khi miss positive đắt (chẩn đoán ung thư, phát hiện fraud).
- Threshold = 0.7 — model thận trọng hơn khi tuyên bố class 1 → tăng precision, giảm recall. Dùng khi false positive đắt (chặn email hợp lệ thành spam).
Sklearn mặc định trả về class theo threshold 0.5 qua .predict(). Để dùng threshold khác, gọi .predict_proba() rồi tự so sánh:
proba = model.predict_proba(X_test)[:, 1] # P(y=1)
y_pred = (proba >= 0.3).astype(int)
Bài 26 (ROC, AUC) sẽ bàn cách quét toàn bộ threshold để chọn điểm cân đối precision–recall.
Loss function — Binary Cross-Entropy
Thay vì MSE, Logistic Regression dùng Binary Cross-Entropy (còn gọi Log Loss):
\[ L(\mathbf{w}, b) = -\frac{1}{n} \sum_{i=1}^n \left[y_i \log \hat{p}_i + (1 - y_i) \log (1 - \hat{p}_i)\right] \]Đọc cách trực giác: với mỗi sample \( i \),
- Nếu \( y_i = 1 \) → loss = \( -\log \hat{p}_i \). Model predict \( \hat{p}_i \) gần 1 → loss gần 0. Predict \( \hat{p}_i \) gần 0 → loss → \( \infty \), bị phạt cực nặng.
- Nếu \( y_i = 0 \) → loss = \( -\log (1 - \hat{p}_i) \). Đối xứng: predict gần 0 thì loss thấp, predict gần 1 thì loss bùng nổ.
Tính chất quan trọng:
- Convex theo \( \mathbf{w} \) và \( b \) — có global minimum duy nhất. Gradient descent đảm bảo hội tụ về tối ưu toàn cục, không kẹt local minimum.
- Có dạng đẹp khi tính gradient — kết hợp với đạo hàm sigmoid, gradient của loss theo \( z \) gọn lại thành \( \hat{p} - y \). Tương đồng cấu trúc với gradient của Linear Regression theo MSE, dễ implement.
- Xuất phát từ Maximum Likelihood Estimation — minimize cross-entropy tương đương maximize likelihood của data dưới giả định mỗi sample là Bernoulli với parameter \( \hat{p}_i \).
Tối ưu bằng gradient descent (hoặc các biến thể Newton, L-BFGS như sklearn dùng mặc định). Không có closed-form như OLS — vì sigmoid là phi tuyến.
Decision boundary tuyến tính
Threshold mặc định 0.5 tương đương \( \sigma(z) = 0.5 \) → \( z = 0 \). Suy ra phương trình decision boundary:
\[ \mathbf{w}^T \mathbf{x} + b = 0 \]Đây là phương trình tuyến tính. Hệ quả về hình học:
- 2 feature (\( n = 2 \)) — boundary là một đường thẳng trong mặt phẳng \( (x_1, x_2) \).
- 3 feature — boundary là một mặt phẳng trong không gian 3D.
- \( n \) feature tổng quát — boundary là một hyperplane (siêu phẳng) \( (n-1) \) chiều.
Logistic Regression vì vậy chỉ phân loại tốt khi hai class linearly separable (hoặc gần linearly separable). Nó không fit được boundary phi tuyến — ví dụ vòng tròn, xoắn ốc, hình XOR. Khi quan hệ phi tuyến:
- Mở rộng feature bằng
PolynomialFeatures(Bài 20) trước Logistic — tương tự cách Polynomial Regression mở rộng Linear. Sinh ra boundary cong trong không gian gốc. - Dùng kernel SVM (Bài 25) — map ngầm vào không gian cao chiều.
- Dùng tree-based hoặc Neural Network — boundary tuỳ ý.
Trade-off: boundary tuyến tính làm model interpretable và ít overfit, nhưng đặt giới hạn capacity. Hợp lý cho baseline, nhiều bài toán tabular thực tế (đặc biệt high-dimensional) đã đủ tốt với boundary tuyến tính.
LogisticRegression trong sklearn
API tối thiểu giống mọi estimator khác trong sklearn:
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
y_pred = model.predict(X_test) # class label: 0 hoac 1
y_proba = model.predict_proba(X_test) # shape (n_samples, n_classes)
# y_proba[:, 0] = P(y=0), y_proba[:, 1] = P(y=1)
Phân biệt ba phương thức output:
predict(X)— trả về class label theo threshold 0.5.predict_proba(X)— trả về probability của mỗi class, tổng theo hàng = 1.decision_function(X)— trả về raw score \( z = \mathbf{w}^T \mathbf{x} + b \) trước khi qua sigmoid.
decision_function hữu ích khi muốn quét threshold trên ROC curve (Bài 26) — đơn điệu với probability nên cho cùng thứ tự ranking, nhưng không bị chặn nên ổn định số học hơn ở vùng cực hạn.
Tham số quan trọng
Các tham số hay tune nhất khi train LogisticRegression:
C(mặc định 1.0) — nghịch đảo của regularization strength: \( C = 1/\alpha \). \( C \) nhỏ → regularization mạnh (weight bị kéo về 0 nhiều), \( C \) lớn → regularization yếu (model gần như không regularization). Lưu ý quy ước nghịch đảo này khác vớialphaở Ridge/Lasso (Bài 21).penalty— loại regularization:"l2"(mặc định),"l1","elasticnet","none". L1 cho sparse coefficient (feature selection ngầm), L2 cho coefficient nhỏ và ổn định.solver— algorithm tối ưu:"lbfgs"(mặc định) — quasi-Newton, nhanh cho dataset vừa, hỗ trợ L2."liblinear"— coordinate descent, tốt cho dataset nhỏ và binary, hỗ trợ L1 + L2."saga"— stochastic average gradient, hỗ trợ đủ L1/L2/elasticnet, scale tốt cho dataset lớn."newton-cg","sag"— các option chuyên biệt.
class_weight— set"balanced"để tự động trọng số nghịch với tần suất class. Quan trọng khi dataset imbalanced (Bài 42 sẽ bàn kỹ).max_iter(mặc định 100) — số iteration tối đa của solver. Mặc định thường thiếu cho dataset nhiều feature hoặc khi feature chưa scale → in warning "ConvergenceWarning: lbfgs failed to converge". Tăng lên 1000 hoặc 5000.random_state— seed cho solver có randomness ("sag","saga").
Combo mặc định an toàn cho baseline: LogisticRegression(C=1.0, penalty="l2", solver="lbfgs", max_iter=1000, class_weight="balanced" nếu imbalanced).
Inspect coefficient
Sau khi fit, có thể đọc weight và bias:
model.coef_— shape(1, n_features)cho binary classification,(K, n_features)cho multi-class \( K \) class.model.intercept_— shape(1,)cho binary,(K,)cho multi-class.model.classes_— array các class label theo thứ tự sklearn dùng nội bộ (thường sorted).
Diễn giải coefficient \( w_j \) cho feature \( x_j \):
- Dấu của \( w_j \) — \( w_j > 0 \): tăng \( x_j \) làm tăng \( P(y=1) \). \( w_j < 0 \): tăng \( x_j \) làm giảm \( P(y=1) \).
- Độ lớn của \( w_j \) — chỉ so sánh được khi các feature đã scale về cùng đơn vị. Coefficient lớn không tự động nghĩa là feature quan trọng nếu feature có range khác nhau.
- Diễn giải đầy đủ qua log-odds — tăng \( x_j \) lên 1 đơn vị (giữ feature khác cố định) làm log-odds tăng đúng \( w_j \), tương đương odds nhân với \( e^{w_j} \).
Vì coefficient interpretation phụ thuộc scale, gần như luôn nên dùng StandardScaler trước Logistic Regression (mục 13).
Multi-class — OvR và Softmax
Logistic Regression vốn là model binary. Mở rộng sang \( K \) class có hai cách:
- One-vs-Rest (OvR) — train \( K \) classifier binary độc lập. Classifier thứ \( k \) phân biệt class \( k \) (positive) với tất cả class còn lại gộp lại (negative). Khi predict, mỗi classifier ra một probability, chọn class có probability cao nhất.
- Ưu điểm: dùng được mọi binary classifier như block; song song hoá dễ.
- Nhược điểm: probability của \( K \) classifier không ràng buộc tổng bằng 1, cần normalize sau.
- Multinomial (Softmax) Regression — train một model duy nhất có \( K \) bộ weight \( \mathbf{w}_k \), output qua hàm softmax:
\[
P(y = k \mid \mathbf{x}) = \frac{e^{\mathbf{w}_k^T \mathbf{x} + b_k}}{\sum_{j=1}^K e^{\mathbf{w}_j^T \mathbf{x} + b_j}}
\]
- Probability \( K \) class tự động tổng bằng 1.
- Loss là categorical cross-entropy, tổng quát hoá của binary cross-entropy.
- Thường cho kết quả tốt hơn OvR khi class có overlap.
Sklearn auto chọn dựa trên solver: "lbfgs", "newton-cg", "sag", "saga" mặc định dùng multinomial; "liblinear" chỉ hỗ trợ OvR. Sklearn 1.5+ đã loại bỏ tham số multi_class — chỉ còn auto theo solver.
Với multi-class, model.coef_ shape (K, n_features) và model.predict_proba shape (n_samples, K).
Standardize feature
Logistic Regression nhạy với scale của feature, giống Linear Regression có regularization. Hai lý do bắt buộc scale:
- Gradient descent hội tụ nhanh hơn — feature có range chênh lệch (ví dụ thu nhập 0–10⁹ và tuổi 0–100) làm loss surface kéo dài thành "thung lũng dài hẹp". Gradient zigzag, cần nhiều iteration hơn → ConvergenceWarning.
- Regularization fair — penalty L2 phạt \( \sum w_j^2 \) đối xử mỗi weight như nhau. Nếu \( x_j \) có range lớn, \( w_j \) tương ứng cần nhỏ để cân đối → bị phạt nhẹ "không công bằng". Sau scale, mọi weight cùng đơn vị → regularization có ý nghĩa.
- Coefficient interpretation đồng nhất — sau scale, độ lớn \( |w_j| \) so sánh được giữa các feature.
Pipeline mặc định nên dùng:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = make_pipeline(
StandardScaler(),
LogisticRegression(max_iter=1000),
)
pipe.fit(X_train, y_train)
Bài tập mục 16 sẽ so sánh model có và không StandardScaler để thấy chênh lệch trực tiếp.
Use case thực tế
Logistic Regression vẫn là baseline phổ biến nhất cho phân loại nhị phân trong production, kể cả khi đã có deep learning:
- Spam detection — phân loại email spam/non-spam dựa trên feature từ nội dung (TF-IDF của từ, số link, độ dài). Linear model với hàng nghìn feature sparse hoạt động tốt và nhanh.
- Medical diagnosis — dự đoán positive/negative cho một xét nghiệm dựa trên các chỉ số sinh hoá. Interpretability quan trọng — bác sĩ cần biết feature nào đẩy probability lên/xuống.
- Credit risk — dự đoán default/non-default cho khoản vay. Regulatory yêu cầu model có thể giải thích được → linear model thường được ưu tiên hơn black-box.
- CTR prediction — predict probability user click vào quảng cáo. Hàng triệu feature (user × ad × context), Logistic Regression với L1 (sparse) là chuẩn industry suốt 10+ năm trước khi deep learning thay thế ở các công ty lớn.
- Churn prediction — predict xác suất khách rời bỏ trong N ngày tới. Probability output dùng trực tiếp cho ranking và targeting.
Lý do bền bỉ: train nhanh, predict cực nhanh (chỉ là dot product), output là probability calibrated tốt (Bài 27 sẽ bàn calibration), interpretability cao, robust với feature nhiều.
Code Python đầy đủ
Demo trên iris dataset, lấy 2 class đầu (setosa vs versicolor) để minh hoạ binary trước khi sang multi-class.
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
iris = load_iris()
X, y = iris.data, iris.target
# Binary subset: class 0 (setosa) vs class 1 (versicolor)
mask = y < 2
X_bin, y_bin = X[mask], y[mask]
X_train, X_test, y_train, y_test = train_test_split(
X_bin, y_bin, test_size=0.3, random_state=42, stratify=y_bin
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
model = LogisticRegression(max_iter=1000)
model.fit(X_train_s, y_train)
print("coef: ", model.coef_) # shape (1, 4)
print("intercept:", model.intercept_) # shape (1,)
print("classes: ", model.classes_) # [0 1]
y_pred = model.predict(X_test_s)
print(f"accuracy: {accuracy_score(y_test, y_pred):.4f}")
# Binary setosa vs versicolor — linearly separable, accuracy thuong = 1.0
# Predict probability cho 5 sample dau
proba = model.predict_proba(X_test_s[:5])
print("proba:\n", proba)
# Cot 0 = P(y=0), cot 1 = P(y=1), tong moi hang = 1
# Doi threshold tu 0.5 sang 0.3 cho class 1
p1 = proba[:, 1]
y_pred_05 = (p1 >= 0.5).astype(int)
y_pred_03 = (p1 >= 0.3).astype(int)
print("threshold 0.5:", y_pred_05.sum(), "sample du doan class 1")
print("threshold 0.3:", y_pred_03.sum(), "sample du doan class 1")
Chuyển sang multi-class (cả 3 class iris):
# Multi-class: 3 class iris
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
model = LogisticRegression(max_iter=1000) # solver lbfgs => softmax
model.fit(X_train_s, y_train)
print("coef shape: ", model.coef_.shape) # (3, 4) — 3 class
print("intercept shape:", model.intercept_.shape) # (3,)
print(f"accuracy: {accuracy_score(y_test, model.predict(X_test_s)):.4f}")
proba = model.predict_proba(X_test_s[:3])
print("proba shape:", proba.shape) # (3, 3) — 3 sample, 3 class
print(proba.sum(axis=1)) # ~[1.0, 1.0, 1.0]
Quan sát hai điểm:
- Binary iris setosa vs versicolor gần như luôn ra accuracy 1.0 — đây là dataset cực dễ, dùng để debug pipeline chứ không để đánh giá độ khó.
- Multi-class với softmax cho probability của 3 class tổng bằng 1; coefficient có 3 hàng tương ứng 3 class.
Bài tập thực hành
Bài 1 — LogisticRegression multi-class trên iris. Load load_iris(), split 70/30 với stratify=y, scale bằng StandardScaler, train LogisticRegression(max_iter=1000). In accuracy trên train và test. Đọc model.coef_.shape và model.classes_. Có bao nhiêu bộ weight? Khớp với số class không?
Bài 2 — Đổi threshold trên Breast Cancer dataset. Load sklearn.datasets.load_breast_cancer() (binary, 569 sample), split 70/30, scale, train. Lấy predict_proba cho 5 sample test đầu tiên, in cột P(y=1). Áp threshold 0.3 và threshold 0.7 → đếm số sample được dự đoán là class 1 ở mỗi threshold. Số nào lớn hơn? Giải thích tại sao.
Bài 3 — Ảnh hưởng của StandardScaler. Trên iris (multi-class), so sánh hai model:
- Model A:
LogisticRegression(max_iter=100)không scale. - Model B:
StandardScaler+LogisticRegression(max_iter=100).
So sánh: (a) có warning convergence không, (b) accuracy test, (c) độ lớn của coef_. Sau đó tăng max_iter lên 5000 và lặp lại — warning có biến mất ở Model A không?
Bài 4 — Visualize decision boundary 2D. Chỉ giữ 2 feature đầu của iris (sepal length, sepal width), 2 class đầu, train LogisticRegression. Vẽ lưới điểm trong khoảng feature, predict class cho từng điểm, plot dưới dạng contour màu cùng scatter điểm train. Boundary có phải đường thẳng không? Đọc coef_ và intercept_, viết phương trình \( w_1 x_1 + w_2 x_2 + b = 0 \) → suy ra phương trình \( x_2 = f(x_1) \).
Bài 5 — So sánh L1 và L2 penalty. Trên Breast Cancer (30 feature), so sánh:
LogisticRegression(penalty="l2", C=1.0, solver="liblinear").LogisticRegression(penalty="l1", C=1.0, solver="liblinear").
Sau train, đếm số coefficient = 0 trong mỗi model. Penalty nào tạo ra sparse coefficient? Accuracy test có chênh nhiều không?
Gợi ý Bài 4: phương trình boundary là \( w_1 x_1 + w_2 x_2 + b = 0 \) → \( x_2 = -\frac{w_1}{w_2} x_1 - \frac{b}{w_2} \). Đây là đường thẳng có hệ số góc \( -w_1/w_2 \).
Bài tiếp theo
Bài 23: Confusion Matrix — accuracy không đủ để đánh giá classifier khi class imbalanced. Confusion matrix tách kết quả thành 4 nhóm (true positive, false positive, true negative, false negative), là nền cho mọi metric classification (precision, recall, F1, ROC).
Tài liệu tham khảo
- scikit-learn — Logistic Regression user guide
- scikit-learn — LogisticRegression API reference
- Wikipedia — Logistic regression
- Wikipedia — Cross-entropy
- Wikipedia — Sigmoid function
- Gareth James et al. — An Introduction to Statistical Learning (Chương 4: Classification)
- Jurafsky & Martin — Speech and Language Processing (Chương 5: Logistic Regression)
