Mục lục
- Mục tiêu bài học
- Vì sao phải chia data
- 3 phần và vai trò
- Tỉ lệ phổ biến
- train_test_split của sklearn
- Chia thành 3 phần — gọi hai lần
- stratify cho classification
- Time series — shuffle=False
- Cross-Validation — alternative khi data nhỏ
- Data leak qua split — pitfall
- Imbalanced dataset và rare class
- Quy ước đặt tên và shape check
- Code đầy đủ — split iris 3 phần
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Hiểu vì sao phải chia dataset trước khi train — generalization và unbiased evaluation.
- Phân biệt rõ vai trò 3 phần: train để fit, validation để tune, test để chấm điểm cuối.
- Chọn tỉ lệ chia phù hợp với kích thước dataset (60/20/20 cho data nhỏ, 98/1/1 cho data rất lớn).
- Dùng
train_test_splitcủa sklearn, hiểutest_size,random_state,shuffle,stratify. - Chia data thành 3 phần bằng cách gọi
train_test_splithai lần. - Biết khi nào không shuffle (time series) và khi nào dùng
GroupShuffleSplit(data theo group). - Tránh 3 pitfall data leak qua split: group leak, time leak, duplicate row.
Vì sao phải chia data
Mục tiêu cuối của một model ML không phải dự đoán đúng trên data đã thấy — mà dự đoán đúng trên data chưa từng thấy. Tính chất này gọi là generalization.
Nếu chấm điểm model bằng chính tập data dùng để train, kết quả gần như chắc chắn quá lạc quan: model có thể "nhớ" data huấn luyện và đạt accuracy rất cao mà không hề học được pattern. Hiện tượng này gọi là overfitting (sẽ học kỹ ở Bài 11).
Cách kiểm tra: giữ lại một phần data không đưa cho model thấy trong lúc train, dùng phần đó để đánh giá. Đó chính là test set. Sklearn chuẩn hoá quy trình này qua hàm train_test_split.
Trong thực tế, ta cần thêm một phần thứ ba — validation set — để tune model mà không động vào test set. Lý do tách 2 phần này sẽ được giải thích ở mục 3.
3 phần và vai trò
Một dataset chia thành 3 phần độc lập:
Train set (~60–80%)
- Dùng để fit tham số của model (weights, coefficients, cây quyết định…).
- Model được "nhìn thấy" cả X và y của phần này nhiều lần trong lúc train.
- Phần lớn nhất vì nhiều data hơn thường giúp model học pattern tốt hơn.
Validation set (~10–20%) — còn gọi là dev set
- Dùng để tune hyperparameter: chọn learning rate, regularization, số layer, độ sâu cây…
- Dùng để so sánh giữa các model khác nhau (Logistic vs SVM vs Random Forest) và chọn ra model tốt nhất.
- Dùng để early stopping: dừng training khi loss trên val bắt đầu tăng (signal overfit).
- Model không fit trực tiếp trên phần này, nhưng nhiều quyết định về model dựa trên kết quả ở đây — nên val set vẫn ảnh hưởng đến model cuối.
Test set (~10–20%)
- Dùng để đánh giá cuối cùng — báo cáo accuracy / F1 / RMSE của model đã chốt.
- Quy tắc vàng: dùng 1 lần duy nhất, sau khi đã xong toàn bộ quá trình tune.
- Không bao giờ quay lại sửa model dựa trên kết quả test.
Vì sao phải tách Val và Test?
Nếu chỉ có train + test, ta sẽ tune hyperparameter dựa trên kết quả test. Sau vài chục lần thử, model đã "thấy" test set gián tiếp — ta chọn cấu hình nào cho điểm cao trên test, tức là test đã trở thành một dạng training signal. Khi đó test không còn unbiased; estimate về generalization sẽ lạc quan giả tạo.
Tách val ra giải quyết vấn đề: tune hết mức trên val, giữ test "đóng băng" cho lần đánh giá cuối. Mọi quyết định liên quan đến model phải kết thúc trước khi đụng vào test set.
Tỉ lệ phổ biến
Không có tỉ lệ "đúng tuyệt đối". Lựa chọn phụ thuộc kích thước dataset và độ phức tạp của model:
- Dataset nhỏ (< 10.000 sample): 60/20/20 hoặc 70/15/15. Cần phần val và test đủ lớn để metric ổn định, không bị nhiễu vì quá ít sample.
- Dataset vừa (10.000 – 100.000): 80/10/10 thường đủ. Val và test mỗi phần vẫn có hàng nghìn sample.
- Dataset lớn (> 100.000): có thể 98/1/1. 1% của 1 triệu vẫn là 10.000 sample — đủ thống kê.
- Dataset rất lớn (LLM pretrain corpus, ImageNet, web crawl): test có khi chỉ 0.1% hoặc nhỏ hơn. Mục tiêu là giữ lại nhiều data nhất có thể cho training, vì variance của metric trên test 0.1% × 1 tỷ sample = 1 triệu sample vẫn rất thấp.
Quy tắc thực dụng: ước lượng số lượng sample tối thiểu cần cho val/test để metric ổn định. Với binary classification, vài nghìn sample là đủ để accuracy có sai số < 1%. Tự đó tính ngược ra tỉ lệ phù hợp.
Ví dụ:
- Iris (150 sample) — 60/20/20 → 90 / 30 / 30. Val và test chỉ 30 sample mỗi phần, metric sẽ rất nhiễu. Bài học: với data quá nhỏ, nên dùng cross-validation (mục 9) thay vì holdout.
- MNIST (60k train + 10k test có sẵn) — quy ước cộng đồng dùng nguyên 10k làm test, chia 50k/10k cho train/val. Tỉ lệ ~83/8/8.
- ImageNet (1.28M train + 50k val + 100k test) — tỉ lệ ~89/3.5/7, val và test cố định trong benchmark.
train_test_split của sklearn
Hàm train_test_split nằm trong module sklearn.model_selection. Đây là cách chuẩn để chia data thành 2 phần (train + test) hoặc 3 phần (gọi 2 lần — xem mục 6).
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20% cho test
random_state=42 # seed để tái lập
)
print(X_train.shape, X_test.shape) # (120, 4) (30, 4)
print(y_train.shape, y_test.shape) # (120,) (30,)
Tham số quan trọng
test_size— kích thước test set. Có thể là float (tỉ lệ 0–1, ví dụ0.2= 20%) hoặc int (số sample, ví dụ30). Nếu không truyền, mặc định là 0.25.train_size— kích thước train set. Tương tự, float hoặc int. Thường chỉ truyền một trong hai (test_sizehoặctrain_size); phần còn lại tự tính.random_state— seed cho random shuffle. Truyền số nguyên (ví dụ42) để mỗi lần chạy ra cùng một split. Bỏ qua → kết quả khác nhau giữa các lần chạy, khó debug.shuffle— mặc địnhTrue. Đảo thứ tự sample trước khi chia. ĐặtFalsecho time series (mục 8).stratify— giữ tỉ lệ class. Truyền vector label vào (stratify=y). Bắt buộc cho classification, đặc biệt khi class imbalanced (mục 7).
Chấp nhận nhiều input cùng lúc
train_test_split có thể chia nhiều mảng cùng lúc, miễn là có cùng số dòng. Ví dụ chia thêm sample weight:
X_train, X_test, y_train, y_test, w_train, w_test = train_test_split(
X, y, sample_weight, test_size=0.2, random_state=42
)
Tất cả các mảng được chia cùng index — sample thứ i trong X_train luôn tương ứng với label thứ i trong y_train.
Chia thành 3 phần — gọi hai lần
train_test_split chỉ chia được 2 phần một lần. Để có 3 phần (train + val + test), gọi 2 lần liên tiếp:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
X, y = load_iris(return_X_y=True)
# Lần 1: tách 40% ra làm "temp" (sẽ chia tiếp thành val + test)
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.4, random_state=42, stratify=y
)
# Lần 2: chia đôi temp thành val và test (mỗi phần 20%)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)
print("Train:", X_train.shape) # (90, 4)
print("Val :", X_val.shape) # (30, 4)
print("Test :", X_test.shape) # (30, 4)
Logic tỉ lệ:
- Lần 1:
test_size=0.4→ 60% train, 40% temp. - Lần 2: chia temp 50/50 → 20% val, 20% test (trên tổng).
- Kết quả: 60/20/20.
Để có tỉ lệ 70/15/15:
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)
# → 70% / 15% / 15%
Quy tắc: luôn dùng cùng random_state cho cả hai lần gọi nếu muốn split tái lập được. Nếu split phụ thuộc thời gian (lấy dữ liệu mới), nên lưu lại index của train/val/test thành file để mọi experiment dùng cùng một split.
stratify cho classification
stratify giữ tỉ lệ class trong train, val, test giống dataset gốc. Đây là tham số quan trọng nhất khi làm classification.
Vấn đề nếu không stratify
Giả sử dataset có 95% class 0, 5% class 1 (binary imbalanced). Nếu shuffle ngẫu nhiên và chia, test set có thể tình cờ chứa 99% class 0, 1% class 1 — hoặc thậm chí 100% class 0 nếu test nhỏ. Khi đó:
- Phân phối class ở test khác train → đánh giá không phản ánh đúng performance.
- Một số class hiếm có thể biến mất hoàn toàn khỏi test hoặc train.
- Metric (đặc biệt F1, precision, recall) bị bóp méo nặng.
Cách dùng
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # giữ tỉ lệ class theo y
)
Khi nào nên dùng:
- Luôn dùng cho classification, kể cả khi dataset cân bằng — chi phí gần bằng 0, lợi ích rõ ràng.
- Bắt buộc khi class imbalanced (tỉ lệ ≥ 2:1).
- Bắt buộc khi dataset nhỏ (< 1000 sample) — variance ngẫu nhiên cao.
- Không dùng cho regression (không có class). Sklearn sẽ báo lỗi nếu
stratifylà biến liên tục.
Verify stratify hoạt động đúng:
import numpy as np
# Tỉ lệ class trong gốc, train, test
for name, arr in [("gốc", y), ("train", y_train), ("test", y_test)]:
vals, counts = np.unique(arr, return_counts=True)
pct = counts / counts.sum()
print(f"{name:5s}: {dict(zip(vals, pct.round(3)))}")
# gốc : {0: 0.333, 1: 0.333, 2: 0.333}
# train: {0: 0.333, 1: 0.333, 2: 0.333}
# test : {0: 0.333, 1: 0.333, 2: 0.333}
Time series — shuffle=False
Với time series (giá cổ phiếu, sales theo ngày, log server theo phút…), không được shuffle. Lý do: thứ tự thời gian mang thông tin, và mục tiêu là dự đoán tương lai dựa trên quá khứ.
Nếu shuffle, model có thể được train trên data ngày 15/3 rồi đánh giá trên ngày 10/3 — đó là dự đoán quá khứ, không phản ánh thực tế và còn gây time leak: thông tin tương lai (sau ngày 10/3) đã "rò rỉ" vào training. Trong production, model không bao giờ có sẵn thông tin tương lai.
Cách chia đúng
import pandas as pd
from sklearn.model_selection import train_test_split
# Giả sử df đã sort theo cột "date"
df = df.sort_values("date").reset_index(drop=True)
X = df.drop(columns=["target", "date"]).values
y = df["target"].values
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
shuffle=False # giữ nguyên thứ tự
)
# Train = 80% đầu (quá khứ), Test = 20% cuối (tương lai)
Khi shuffle=False, không thể dùng stratify (sklearn báo lỗi). Đây là trade-off chấp nhận được vì time series ưu tiên thứ tự hơn cân bằng class.
TimeSeriesSplit — cross-validation cho time series
Với time series, K-Fold thông thường không phù hợp (sẽ trộn quá khứ-tương lai). Sklearn cung cấp TimeSeriesSplit tạo các fold tăng dần theo thời gian:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for fold, (train_idx, val_idx) in enumerate(tscv.split(X)):
print(f"Fold {fold}: train={train_idx[-1]+1}, val={len(val_idx)}")
# Fold 0: train=25, val=25 → train trên 25 mẫu đầu, val trên 25 mẫu kế
# Fold 1: train=50, val=25 → train trên 50 mẫu đầu, val trên 25 mẫu kế
# ...
Mỗi fold, train luôn là quá khứ và val là tương lai liền kề — mô phỏng cách deploy thực tế. Chi tiết về CV sẽ học ở Bài 39.
Cross-Validation — alternative khi data nhỏ
Khi dataset nhỏ (vài trăm tới vài nghìn sample), holdout val set có 2 vấn đề: (1) val set quá nhỏ → metric nhiễu, (2) lãng phí 10–20% data có thể dùng để train.
Giải pháp: K-Fold Cross-Validation. Không tách val set riêng, mà:
- Tách test set ra (giữ nguyên, 1 lần).
- Chia phần còn lại (train) thành K fold (thường K=5 hoặc 10).
- Lặp K lần: mỗi lần dùng K-1 fold để train, 1 fold để đánh giá.
- Trung bình metric qua K lần → ước lượng ổn định hơn so với 1 val set duy nhất.
Workflow tổng:
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
# Tách test ra trước (giữ nguyên, không động đến)
X_trainval, X_test, y_trainval, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# CV trên trainval để tune
model = LogisticRegression(max_iter=200)
scores = cross_val_score(model, X_trainval, y_trainval, cv=5)
print("CV accuracy:", scores.mean(), "+/-", scores.std())
# Sau khi chốt cấu hình, train lại trên toàn trainval rồi đánh giá test
model.fit(X_trainval, y_trainval)
print("Test accuracy:", model.score(X_test, y_test))
Cross-validation chi tiết (K-Fold, Stratified K-Fold, Leave-One-Out, nested CV) sẽ học ở Bài 39. Bài này chỉ cần nhớ: khi data nhỏ, ưu tiên CV thay vì val set riêng.
Data leak qua split — pitfall
Chia data sai cách dẫn đến data leak — thông tin từ test rò rỉ vào train, khiến đánh giá lạc quan giả tạo. Ba dạng leak phổ biến qua split:
1. Group leak
Khi nhiều dòng thuộc cùng một đối tượng (cùng patient, cùng user, cùng device…), shuffle ngẫu nhiên có thể đẩy một số dòng của cùng đối tượng vào train và một số vào test. Model "nhớ" được đối tượng đó → predict trên test rất tốt, nhưng performance trên đối tượng mới (chưa thấy bao giờ) lại tệ.
Ví dụ: y tế — mỗi patient có 10 lần khám, mỗi lần là 1 dòng. Nếu 7 dòng vào train + 3 dòng vào test, model học được "đặc điểm patient này" và predict đúng trên 3 dòng test — nhưng đó không phải generalization.
Giải pháp: GroupShuffleSplit — split theo group, không theo dòng:
from sklearn.model_selection import GroupShuffleSplit
# patient_id: array shape (n_samples,), cùng giá trị → cùng patient
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(gss.split(X, y, groups=patient_id))
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
# Verify: không patient nào xuất hiện ở cả train và test
print("Train patients:", set(patient_id[train_idx]))
print("Test patients:", set(patient_id[test_idx]))
print("Giao nhau :", set(patient_id[train_idx]) & set(patient_id[test_idx]))
# Giao nhau: set()
2. Time leak
Đã nói ở mục 8: shuffle time series khiến model "thấy tương lai". Luôn shuffle=False với time series.
3. Duplicate row
Nếu dataset có dòng trùng lặp (cùng X, cùng y), shuffle có thể đẩy bản sao này vào train, bản kia vào test. Model "nhớ" và predict đúng → leak. Cách tránh: df.drop_duplicates() trước khi split.
Ngoài 3 dạng trên, còn các dạng leak qua preprocessing (fit scaler/encoder trên toàn bộ data trước khi split) — sẽ học ở Bài 7 (feature scaling) và Bài 10 (pipeline).
Imbalanced dataset và rare class
Dataset imbalanced (ví dụ fraud detection: 99% normal, 1% fraud) làm split phức tạp hơn.
stratify giúp giữ tỉ lệ — đó là bước bắt buộc đầu tiên. Nhưng còn vấn đề: nếu class hiếm có quá ít sample (ví dụ chỉ 50 fraud trong 5000 sample), test set 20% chỉ chứa 10 fraud — quá ít để metric ổn định.
Các kỹ thuật bổ sung (sẽ học ở Bài 42):
- SMOTE — tạo synthetic minority samples bằng interpolation trong feature space. Chỉ áp dụng cho train, không bao giờ cho val/test (sẽ leak).
- class_weight — weight loss của model nhiều hơn cho class hiếm. Tham số
class_weight="balanced"có sẵn trong nhiều estimator của sklearn. - Sampling strategy — undersampling class đa số hoặc oversampling class hiếm.
Quy tắc quan trọng: mọi kỹ thuật xử lý imbalance áp dụng sau khi split, và chỉ trên train set. Val và test phải giữ phân phối thực tế để đánh giá phản ánh đúng production.
Quy ước đặt tên và shape check
Quy ước cộng đồng (sklearn, mọi tutorial, notebook Kaggle):
X_train,y_train— train set.X_val,y_val— validation set. Tên thay thế:X_dev,y_dev(DeepLearning community).X_test,y_test— test set.X_temp,y_temp— phần trung gian khi chia 2 lần (sẽ chia tiếp thành val + test).
Sau mỗi lần split, luôn in shape để verify. Đây là thói quen rẻ và bắt được hàng loạt bug:
print(f"X_train: {X_train.shape}, y_train: {y_train.shape}")
print(f"X_val : {X_val.shape}, y_val : {y_val.shape}")
print(f"X_test : {X_test.shape}, y_test : {y_test.shape}")
# Sanity check: tổng phải bằng dataset gốc
assert X_train.shape[0] + X_val.shape[0] + X_test.shape[0] == X.shape[0]
# Sanity check: số cột phải nhất quán
assert X_train.shape[1] == X_val.shape[1] == X_test.shape[1] == X.shape[1]
Hai assert trên bắt được 90% lỗi split: thiếu phần tử (do test_size sai), nhầm transpose (đảo dòng-cột), hoặc đưa nhầm biến vào split.
Code đầy đủ — split iris 3 phần
Ghép mọi thứ lại: split iris thành 60/20/20 với stratify, verify class balance, in shape, và demo GroupShuffleSplit ngắn.
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GroupShuffleSplit
# Load
X, y = load_iris(return_X_y=True)
print("Dataset gốc:", X.shape, y.shape) # (150, 4) (150,)
# Split 60/20/20 với stratify
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.4, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)
# In shape
print(f"Train: X{X_train.shape}, y{y_train.shape}") # (90, 4) (90,)
print(f"Val : X{X_val.shape}, y{y_val.shape}") # (30, 4) (30,)
print(f"Test : X{X_test.shape}, y{y_test.shape}") # (30, 4) (30,)
# Sanity check
assert X_train.shape[0] + X_val.shape[0] + X_test.shape[0] == X.shape[0]
# Verify class balance
def class_dist(name, arr):
vals, counts = np.unique(arr, return_counts=True)
pct = (counts / counts.sum()).round(3)
print(f"{name:5s}: {dict(zip(vals.tolist(), pct.tolist()))}")
class_dist("gốc", y)
class_dist("train", y_train)
class_dist("val", y_val)
class_dist("test", y_test)
# gốc : {0: 0.333, 1: 0.333, 2: 0.333}
# train: {0: 0.333, 1: 0.333, 2: 0.333}
# val : {0: 0.333, 1: 0.333, 2: 0.333}
# test : {0: 0.333, 1: 0.333, 2: 0.333}
Demo GroupShuffleSplit trên iris (giả sử mỗi 10 hoa là cùng "vườn"):
# Tạo group: 150 hoa, mỗi 10 hoa là 1 vườn → 15 vườn
groups = np.repeat(np.arange(15), 10)
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(gss.split(X, y, groups=groups))
train_gardens = set(groups[train_idx])
test_gardens = set(groups[test_idx])
print(f"Vườn train: {sorted(train_gardens)}")
print(f"Vườn test : {sorted(test_gardens)}")
print(f"Giao nhau : {train_gardens & test_gardens}")
# Giao nhau : set() → không vườn nào xuất hiện ở cả train và test
Điều cần để ý: với GroupShuffleSplit, test_size=0.2 là tỉ lệ theo group, không phải theo sample. 3 vườn × 10 hoa = 30 sample test (giống split sample, nhưng đảm bảo không leak).
Bài tập
- Chia iris thành 70/15/15 train/val/test với
stratify=y. In shape của 3 phần. Verify tổng số sample = 150. - Verify class balance ở 3 phần (gợi ý:
np.unique(y_part, return_counts=True)rồi tính phần trăm). Kết quả phải xấp xỉ 33.3% cho mỗi class. - Tạo một time series tổng hợp: 100 dòng, mỗi dòng có cột
day(0–99),Xngẫu nhiên,y = 2*X + noise. Chia 80/20 vớishuffle=False. Kiểm tra train chứaday0–79, test chứa 80–99. - Tạo dataset 200 sample với 20 group (mỗi group 10 sample). Dùng
GroupShuffleSplitchia vớitest_size=0.2. Verify không group nào xuất hiện ở cả train và test. - So sánh: chia iris bằng
train_test_split(X, y, test_size=0.2)(không stratify, không seed) — chạy 5 lần, in tỉ lệ class trong test mỗi lần. Có ổn định không?
Đáp án ngắn
train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)rồitrain_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp). Shape: (105, 4) / (22 hoặc 23, 4) / (23 hoặc 22, 4). Tổng = 150.- Cả 3 phần đều ~33.3% cho mỗi class (sai số ≤ 4% do làm tròn).
- Train:
day0–79 (80 dòng), Test:day80–99 (20 dòng). Không có giao nhau. GroupShuffleSplitchọn 4 group cho test (20% × 20), 16 group cho train. Tổng sample test = 40, train = 160.set(groups_train) & set(groups_test) == set().- Không stratify, không seed: tỉ lệ class trong test dao động ±10% giữa các lần chạy. Stratify + seed → ổn định hoàn toàn.
Tóm tắt
- Chia data để đánh giá model trên sample chưa từng thấy — kiểm chứng generalization, tránh bị lừa bởi overfit.
- 3 phần: train (fit tham số) — val (tune hyperparameter, chọn model, early stopping) — test (đánh giá cuối, dùng 1 lần).
- Tách val và test vì: nếu tune trên test → test bị "leak" gián tiếp, không còn unbiased.
- Tỉ lệ: data nhỏ 60/20/20 hoặc 70/15/15; data lớn (> 100k) có thể 98/1/1; data rất lớn có khi test < 0.1%.
train_test_splittrongsklearn.model_selection: tham sốtest_size,random_state,shuffle,stratify.- Chia 3 phần: gọi
train_test_split2 lần — lần 1 tách train ra, lần 2 chia phần còn lại thành val + test. stratify=ygiữ tỉ lệ class — luôn dùng cho classification, bắt buộc khi imbalanced hoặc data nhỏ.- Time series:
shuffle=False— train là quá khứ, test là tương lai. Không dùng đượcstratify. CV thì dùngTimeSeriesSplit. - Data nhỏ: ưu tiên K-Fold CV thay vì val set riêng — bài 39 sẽ học sâu.
- 3 pitfall data leak qua split: group leak (dùng
GroupShuffleSplit), time leak (shuffle=False), duplicate row (drop_duplicatestrước). - Imbalanced: stratify trước, rồi SMOTE hoặc
class_weightchỉ áp dụng trên train. - Quy ước biến:
X_train,X_val,X_test,y_train,y_val,y_test. Luôn in shape sau split để verify. - Bài 7 sẽ học feature scaling (Min-Max Normalization) — bước preprocessing chuẩn sau split.
- scikit-learn Docs - train_test_split
- scikit-learn Docs - Cross-validation: evaluating estimator performance
- scikit-learn Docs - GroupShuffleSplit
- scikit-learn Docs - TimeSeriesSplit
- scikit-learn Docs - StratifiedShuffleSplit
- scikit-learn Docs - Common pitfalls and recommended practices
- DeepLearning.AI - Train/Dev/Test set splits (Andrew Ng)
- Wikipedia - Training, validation, and test data sets
- Wikipedia - Data leakage
