Danh sách bài viết

Bài 37: Overfitting và Underfitting — nhận diện qua learning curve

Overfitting (model nhớ noise, train tốt nhưng test kém) và underfitting (model quá đơn giản, cả train lẫn test đều tệ). Cách nhận diện qua bảng train/test error, learning curve, validation curve. Nguồn gốc, cách chữa cho Linear, Tree, KNN, GB, NN. Code sklearn learning_curve và validation_curve.

24/05/2026
14 phút đọc
0 lượt xem
1

Mở đầu Module 6 — Tối ưu và Đánh giá

Đây là module cuối của Series 2 (ML cổ điển), gồm 6 bài (37–42). Sau khi học xong các họ model ở Module 2–5 (Regression, Classification, Tree/Ensemble, Unsupervised), Module 6 quay lại các kỹ thuật chéo — không gắn với một thuật toán cụ thể nào mà áp dụng được cho mọi model:

  • Bài 37 — Overfitting/Underfitting và learning curve (bài này).
  • Bài 38 — Bias-Variance Tradeoff (góc nhìn lý thuyết).
  • Bài 39 — Cross-Validation, K-Fold.
  • Bài 40 — Grid Search hyperparameter.
  • Bài 41 — Random Search, Bayesian Optimization sơ lược.
  • Bài 42 — Class imbalance, SMOTE, class_weight.

Khái niệm overfit/underfit đã xuất hiện rải rác từ B6 (train/val/test split), B20 (polynomial degree cao), B21 (regularization), B22 (logistic regression). Module này deep-dive: định nghĩa chính xác, công cụ chẩn đoán (learning curve, validation curve), và cách chữa.

Mục tiêu sau bài này: nhìn vào hai biểu đồ — learning curve và validation curve — và biết model đang overfit, underfit hay balanced, và bước tiếp theo nên là gì.

2

Overfitting là gì

Overfitting là tình trạng model nhớ cả pattern thật lẫn noise của training data, dẫn đến generalize kém ra data mới.

Triệu chứng định lượng:

  • Train error rất thấp — model fit gần như hoàn hảo các điểm train (vd MSE \( \approx 0 \), accuracy 99%+).
  • Test error cao — khoảng cách (gap) train↔test lớn.
  • Coefficient/độ phức tạp internal lớn bất thường (đã thấy ở B21 với OLS không regularize).
  • Model nhạy cảm: train lại với một bootstrap khác của train set ra model rất khác.

Trực giác: noise là phần ngẫu nhiên không thể predict được từ feature. Model nhớ noise = học "câu trả lời" cho từng sample train, nhưng noise của sample test khác, nên predict sai.

Ví dụ kinh điển: fit polynomial degree 15 trên 20 sample sinh ra từ \( y = x + \epsilon \). Curve uốn lượn qua từng điểm train, nhưng giữa các điểm thì dao động dữ dội — predict trên data mới sai be bét.

3

Underfitting là gì

Underfitting là tình trạng model quá đơn giản, không đủ capacity để học pattern cơ bản trong data.

Triệu chứng:

  • Train error cao — model không fit được cả training data.
  • Test error cũng cao — và gần bằng train error.
  • Tăng training data không giúp cải thiện đáng kể.
  • Curve học (residual plot) còn pattern rõ ràng chưa được capture.

Ví dụ: fit linear regression (\( y = w_0 + w_1 x \)) lên data thực sự sinh từ \( y = \sin(x) + \epsilon \). Đường thẳng không thể uốn theo sin → cả train và test đều có MSE lớn, residual có pattern dạng sóng rõ.

Underfit thường ít được chú ý hơn overfit vì dễ phát hiện hơn (train tệ là biết ngay) và dễ chữa hơn (tăng complexity là xong). Overfit nguy hiểm vì train looks great nhưng deploy ra production thì sụp.

4

Sweet spot — cân bằng complexity

Giữa underfit (model quá đơn giản) và overfit (model quá phức tạp) có một vùng sweet spot: model đủ phức tạp để học pattern thật, nhưng không phức tạp tới mức nhớ noise.

Vẽ theo trục "complexity":

  • Complexity thấp → train error cao, test error cao, gap nhỏ (underfit).
  • Complexity tăng → cả hai cùng giảm.
  • Tới một điểm \( c^* \) → test error chạm đáy. Đây là sweet spot.
  • Complexity tiếp tục tăng → train error vẫn giảm (về 0), nhưng test error quay lên. Gap mở rộng (overfit).

Mục tiêu của tuning là tìm \( c^* \). "Complexity" là khái niệm trừu tượng, hiện thực hoá bằng các hyperparameter cụ thể tuỳ model: degree của polynomial, \( \alpha \) của Ridge/Lasso (nghịch đảo), max_depth của tree, \( K \) của KNN (nghịch đảo — K nhỏ = complex), số layer/neuron của NN, số tree của GB...

Góc nhìn lý thuyết của hiện tượng này là bias-variance tradeoff — Bài 38.

5

Nhận diện qua bảng train/test error

Cách chẩn đoán nhanh nhất: nhìn vào cặp \( (\text{train error}, \text{test error}) \). Bốn trường hợp:

  • Train thấp + Test thấp — model OK, gần sweet spot. Có thể vẫn còn cải thiện nhỏ, nhưng đang tốt.
  • Train thấp + Test caoOVERFIT. Gap rõ giữa train và test. Xem mục 9 để chữa.
  • Train cao + Test cao (gần nhau) — UNDERFIT. Xem mục 10 để chữa.
  • Train cao + Test thấp — bất thường, hiếm gặp. Nguyên nhân thường là:
    • Data leakage ngược (feature trong test "dễ" hơn train do split sai).
    • Test set quá nhỏ, may mắn.
    • Augmentation/noise mạnh chỉ áp dụng cho train.
    Khi gặp, kiểm tra pipeline split và preprocessing trước.

"Thấp" và "cao" là tương đối — so với mức error tối thiểu khả thi của bài toán (irreducible noise). Một model train MSE = 5 trên data có noise std = 0.1 đang underfit nặng; cũng MSE = 5 trên data có noise std = 5 là đã tới sweet spot.

Quy tắc thực hành: gap test − train > 5–10% accuracy (hoặc MSE test ≫ MSE train) là tín hiệu overfit. Train accuracy còn xa baseline khả thi là tín hiệu underfit.

6

Learning curve — error theo training size

Learning curve là biểu đồ:

  • Trục x — kích thước training set \( n \) (vd 10%, 30%, 50%, 70%, 100% của data).
  • Trục y — error (hoặc \( -\text{score} \)) trên hai tập:
    • Train error: lấy chính subset train hiện tại (với fold CV).
    • Validation error: cố định trên validation fold (hoặc test set).

Hình dạng ba kịch bản:

  • Underfit — hai curve hội tụ về nhau ở mức error cao. Gap nhỏ. Thêm data không giúp (curve đã phẳng). Cần tăng complexity của model.
  • Overfit — train curve thấp, validation curve cao, gap lớn. Validation curve đang giảm dần khi tăng \( n \). Thêm data có thể giúp thu hẹp gap. Regularization cũng giúp.
  • Balanced — hai curve hội tụ ở mức error thấp. Mục tiêu.

Cách dùng learning curve để quyết định:

  • Curve còn dốc xuống ở phía phải (\( n \) lớn nhất) → đầu tư thu thập thêm data sẽ có ROI.
  • Curve đã phẳng → thêm data không giúp; phải đổi model hoặc đổi feature.
  • Gap rất lớn ở \( n = n_{\max} \) → vẫn overfit; cần regularize/đơn giản hoá.
  • Train error đã rất cao ngay từ \( n \) nhỏ → underfit cấu trúc, đổi model.

Sklearn cung cấp learning_curve trong sklearn.model_selection:

from sklearn.model_selection import learning_curve
import numpy as np

train_sizes, train_scores, val_scores = learning_curve(
    estimator, X, y,
    train_sizes=np.linspace(0.1, 1.0, 10),
    cv=5, scoring="neg_mean_squared_error",
    random_state=42,
)

train_err = -train_scores.mean(axis=1)
val_err   = -val_scores.mean(axis=1)
7

Validation curve — error theo hyperparameter

Validation curve giữ training size cố định, quét một hyperparameter điều khiển complexity. Biểu đồ:

  • Trục x — hyperparameter (vd polynomial degree 1..15, max_depth 1..20, \( K \) của KNN 1..30, \( \alpha \) Ridge ở thang log).
  • Trục y — train error và validation error.

Hình dạng chuẩn (chữ U cho validation):

  • Bên trái (complexity thấp) — cả hai cao, gần nhau → underfit.
  • Giữa — validation chạm đáy → sweet spot. Đây là \( c^* \) cần chọn.
  • Bên phải (complexity cao) — train tiếp tục giảm, validation tăng → overfit.

Khác biệt với learning curve: learning curve trả lời "thêm data có giúp không"; validation curve trả lời "complexity nào tối ưu". Hai công cụ bổ trợ nhau.

Sklearn API:

from sklearn.model_selection import validation_curve

param_range = np.arange(1, 21)
train_scores, val_scores = validation_curve(
    estimator, X, y,
    param_name="max_depth",
    param_range=param_range,
    cv=5, scoring="accuracy",
)

train_acc = train_scores.mean(axis=1)
val_acc   = val_scores.mean(axis=1)
optimal   = param_range[val_acc.argmax()]

Với param nằm sâu trong pipeline, dùng dot-notation: param_name="ridge__alpha".

8

Nguồn gốc của overfit và underfit

Source của overfit:

  • Model quá phức tạp so với độ phức tạp thật của bài toán (degree polynomial cao, tree quá sâu, NN quá nhiều layer).
  • Dataset quá nhỏ — không đủ sample để pin xuống pattern thật, model phải "đoán" và đoán theo noise.
  • Số feature quá nhiều so với số sample (\( d \gg n \)) — curse of dimensionality.
  • Train quá lâu (số iteration/epoch nhiều) đối với model học tăng tiến (GBM, NN). Loss train tiếp tục giảm sau khi val loss đã chạm đáy.
  • Data leakage gián tiếp — feature chứa thông tin về target không có trong production.

Source của underfit:

  • Model quá đơn giản — vd linear cho data nonlinear, tree max_depth=1, KNN với \( K \) quá lớn.
  • Quá ít feature, thiếu feature engineering — bài toán cần feature tương tác hoặc phi tuyến.
  • Regularization quá mạnh — \( \alpha \) Ridge/Lasso lớn quá, dropout NN quá cao, kéo model về gần predict mean.
  • Train chưa đủ — đối với model gradient-based, dừng quá sớm khi loss vẫn còn giảm rõ.
  • Preprocessing sai — vd scale làm mất thông tin (clip outlier nặng), encoding sai cho categorical.
9

Cách chữa overfit

Thứ tự ưu tiên thường thấy trong thực hành:

  1. Thêm data — nếu learning curve cho thấy còn dốc xuống. Đây là cách "an toàn" nhất, không thay đổi model. Khi data thực sự đắt, cân nhắc data augmentation (xoay/lật ảnh cho CV, paraphrase cho NLP, SMOTE cho tabular imbalance — Bài 42).
  2. Đơn giản hoá model — giảm max_depth, giảm n_estimators, giảm degree polynomial, giảm số layer/neuron NN. Mỗi gia tăng complexity phải có biện minh từ validation curve.
  3. Regularization — Ridge/Lasso/ElasticNet (B21), dropout cho NN, weight decay, ccp_alpha cho tree (post-pruning).
  4. Early stopping — đối với GBM (early_stopping_rounds trong XGBoost/LightGBM, n_iter_no_change trong sklearn GradientBoosting) và NN. Theo dõi validation loss, dừng khi không cải thiện sau \( k \) epoch.
  5. Cross-validation chọn hyperparameter — không phụ thuộc vào một split duy nhất. Chi tiết Bài 39.
  6. Feature selection — bỏ feature noise, dùng Lasso, mutual information, hoặc các phương pháp wrapper. Giảm \( d \) giúp giảm overfit khi \( d \) lớn.
  7. Bagging/Ensemble — Random Forest, bagging classifier — giảm variance bằng cách trung bình hoá nhiều model.

Cảnh báo: không nên áp dụng nhiều cách cùng lúc rồi không biết cái nào hiệu quả. Thay đổi từng yếu tố, theo dõi learning curve, kết luận, rồi mới thay đổi tiếp.

10

Cách chữa underfit

  1. Tăng complexity model — tăng max_depth, thêm layer NN, dùng polynomial degree cao hơn, đổi từ linear sang tree/RF/GB.
  2. Feature engineering — thêm tương tác feature (\( x_1 \cdot x_2 \)), polynomial expansion (B20), domain-specific feature, encoding tốt hơn cho categorical, scale phù hợp (B13/B14).
  3. Giảm regularization — giảm \( \alpha \) Ridge/Lasso, giảm dropout, nới ccp_alpha. Nếu \( \alpha \) đang ép coefficient gần 0, model mất khả năng học.
  4. Train lâu hơn — đối với gradient-based: tăng số epoch, kiểm tra loss curve còn dốc xuống không.
  5. Đổi model — nếu data thực sự nonlinear mà bạn đang dùng linear, không có cách nào fix bằng tuning, phải đổi sang tree/SVM kernel/NN.
  6. Kiểm tra preprocessing — đảm bảo không vô tình clip/scale làm mất thông tin, không drop nhầm feature quan trọng.

Underfit ít gây stress vì train tệ là tín hiệu rõ ràng. Khó hơn là tình huống "underfit ẩn" — model có train R² = 0.85 tưởng tốt, nhưng baseline khả thi của bài toán là 0.97 (vd predict click-through-rate với feature đầy đủ). Đánh giá phải có baseline tham chiếu.

11

Triệu chứng theo từng model

Mỗi họ model có "đường" riêng để over/underfit:

  • Linear Regression / Logistic Regression — underfit khi data nonlinear (residual có pattern rõ). Overfit khi nhiều feature, ít sample, hoặc polynomial degree cao. Fix: Ridge/Lasso/ElasticNet (B21), feature selection, giảm degree.
  • Decision Tree — overfit dễ vì có thể grow tới khi mỗi leaf chứa 1 sample. Fix: max_depth, min_samples_split, min_samples_leaf, max_leaf_nodes, hoặc post-pruning với ccp_alpha. Underfit hiếm — chỉ khi giới hạn quá ngặt.
  • Random Forest — bagging đã giảm variance, ít overfit hơn single tree. Vẫn có thể overfit nếu mỗi tree quá sâu và \( n \) nhỏ. max_depth, min_samples_leaf, max_features là các knob chính.
  • KNN — \( K \) nhỏ (1, 3) → variance cao, overfit. \( K \) lớn (gần \( n \)) → bias cao, underfit (predict gần mean). Validation curve theo \( K \) là cách chuẩn để chọn.
  • Gradient Boosting (XGBoost, LightGBM, sklearn GBM) — overfit nếu nhiều tree + learning rate lớn + tree sâu. Fix: n_estimators hợp lý với early_stopping_rounds, learning_rate nhỏ, max_depth ngắn (3–8), subsample < 1, reg_alpha/reg_lambda.
  • SVM — \( C \) lớn (regularization yếu) → overfit. gamma lớn ở kernel RBF → overfit (decision boundary lượn sát điểm train). Tune \( (C, \gamma) \) bằng grid search.
  • Neural Network — overfit khi nhiều layer/neuron, ít data, train lâu. Fix: dropout, weight decay (L2), batch normalization, early stopping, data augmentation. Đi sâu ở Series 3 (DL).
12

Code Python — polynomial demo và learning curve

Demo 1 — Polynomial degree 1, 3, 15 trên data sinh từ \( y = \sin(2\pi x) + \epsilon \):

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.metrics import mean_squared_error

rng = np.random.default_rng(42)
n = 30
X = rng.uniform(0, 1, size=(n, 1))
y = np.sin(2 * np.pi * X.ravel()) + rng.normal(0, 0.2, size=n)

X_test = np.linspace(0, 1, 200).reshape(-1, 1)
y_test = np.sin(2 * np.pi * X_test.ravel())   # khong noise

for degree in [1, 3, 15]:
    model = make_pipeline(
        PolynomialFeatures(degree),
        LinearRegression(),
    )
    model.fit(X, y)
    mse_train = mean_squared_error(y, model.predict(X))
    mse_test  = mean_squared_error(y_test, model.predict(X_test))
    print(f"degree={degree:2d}  MSE_train={mse_train:.4f}  MSE_test={mse_test:.4f}")

Kết quả điển hình:

degree= 1  MSE_train=0.2451  MSE_test=0.2310   <- UNDERFIT
degree= 3  MSE_train=0.0379  MSE_test=0.0148   <- BALANCED
degree=15  MSE_train=0.0184  MSE_test=0.6932   <- OVERFIT

Degree 1 không uốn được theo sin → train và test cùng tệ. Degree 3 đủ uốn theo sin một chu kỳ → train và test cùng tốt. Degree 15 nhớ từng điểm train (train MSE thấp nhất) nhưng dao động dữ dội giữa các điểm → test MSE bùng nổ.

Demo 2 — Vẽ train/test MSE theo degree:

degrees = range(1, 16)
mse_train_list, mse_test_list = [], []
for d in degrees:
    model = make_pipeline(PolynomialFeatures(d), LinearRegression())
    model.fit(X, y)
    mse_train_list.append(mean_squared_error(y, model.predict(X)))
    mse_test_list.append(mean_squared_error(y_test, model.predict(X_test)))

# matplotlib
import matplotlib.pyplot as plt
plt.plot(degrees, mse_train_list, "o-", label="train")
plt.plot(degrees, mse_test_list, "s-", label="test")
plt.yscale("log"); plt.xlabel("polynomial degree"); plt.ylabel("MSE")
plt.legend(); plt.show()

Curve test có dạng chữ U: giảm rồi tăng. Đáy quanh degree 3–5 là sweet spot.

Demo 3 — Learning curve cho một model:

from sklearn.datasets import fetch_california_housing
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import learning_curve

data = fetch_california_housing()
X_h, y_h = data.data, data.target

model = DecisionTreeRegressor(max_depth=10, random_state=42)

train_sizes, train_scores, val_scores = learning_curve(
    model, X_h, y_h,
    train_sizes=np.linspace(0.1, 1.0, 8),
    cv=5, scoring="neg_mean_squared_error",
    random_state=42, n_jobs=-1,
)

train_err = -train_scores.mean(axis=1)
val_err   = -val_scores.mean(axis=1)

for n_tr, tr, va in zip(train_sizes, train_err, val_err):
    print(f"n_train={int(n_tr):6d}  MSE_train={tr:.3f}  MSE_val={va:.3f}  gap={va-tr:.3f}")

Gap giữa val và train cho biết mức overfit; gap thu hẹp khi tăng \( n \) → thêm data còn giúp. Gap không đổi → đã hết hiệu quả với data thêm, cần đổi model/regularize.

13

Bài tập thực hành

Bài 1 — Decision Tree với max_depth khác nhau trên iris. Load load_iris, train/test split 70/30. Train ba DecisionTreeClassifier(max_depth=d) với \( d \in \{1, 5, 20\} \). In train accuracy và test accuracy của mỗi cái. Cái nào underfit, cái nào overfit, cái nào balanced? Vì sao max_depth=20 không khác nhiều max_depth=5 trên iris (gợi ý: \( n \) và số class)?

Bài 2 — Validation curve cho KNN. Trên iris hoặc load_breast_cancer, quét \( K \in [1, 30] \) cho KNeighborsClassifier. Dùng validation_curve với cv=5. Plot train và validation accuracy theo \( K \). Tại đâu validation accuracy đạt max? Vùng \( K \) nhỏ là overfit hay underfit? Vùng \( K \) lớn?

Bài 3 — Linear Regression overfit khi d > n. Sinh dataset 10 sample, 50 feature random độc lập (không liên quan tới \( y \) lý thuyết):

rng = np.random.default_rng(0)
n, d = 10, 50
X = rng.normal(size=(n, d))
y = rng.normal(size=n)            # noise thuan tuy, khong co tin hieu

Fit LinearRegression. In train MSE và test MSE (sinh thêm 100 sample test theo cùng phân phối). Quan sát: train MSE \( \approx 0 \), test MSE rất lớn — overfit cực đoan. Bây giờ fit Ridge(alpha=1.0) (sau StandardScaler) — Ridge có cứu được không? Tăng \( \alpha \) lên 10, 100 thì sao?

Bài 4 — Learning curve cho Random Forest vs Linear. Trên California housing, vẽ learning curve cho LinearRegression()RandomForestRegressor(n_estimators=50, max_depth=10). So sánh hình dạng — model nào có gap lớn? Thêm data có giúp Linear không (gợi ý: Linear bias cao, variance thấp nên gap nhỏ)?

Bài 5 — Tự thiết kế chẩn đoán. Cho dataset bất kỳ (vd fetch_openml('credit-g')), chọn 1 model (vd GradientBoostingClassifier). Quy trình:

  1. Train baseline, in train+test accuracy.
  2. Vẽ learning curve.
  3. Vẽ validation curve theo n_estimators hoặc max_depth.
  4. Kết luận: model đang over/underfit/balanced? Nên làm gì tiếp theo?
  5. Thực hiện 1 thay đổi (regularize, đổi hyperparameter, thêm feature) — đo lại.

Báo cáo gồm hai biểu đồ và 3–5 câu kết luận.

Gợi ý đáp án Bài 3: LinearRegression với \( d > n \) có nhiều nghiệm exact-fit train, test MSE thường ở mức hàng chục, hàng trăm tuỳ scale của \( y \). Ridge với alpha đủ lớn (vd 10) kéo coefficient gần 0, train MSE tăng nhưng test MSE giảm mạnh — đến gần với variance của \( y \) (vì \( y \) thực ra là noise thuần).

14

Bài tiếp theo

Bài 38: Bias-Variance Tradeoff — góc nhìn lý thuyết của hiện tượng over/underfit. Phân rã test error thành \( \text{bias}^2 + \text{variance} + \text{noise} \), liên hệ trực tiếp underfit ↔ high bias, overfit ↔ high variance. Sau Bài 38, các bài 39–42 sẽ là công cụ thực hành (CV, grid search, random search, class imbalance) để áp dụng các nguyên lý này.