Dữ liệu, nhãn và ba tập không được trộn
Model không biết gì ngoài thứ đã thấy. Nhãn quyết định model học được gì, còn cách chia train/validation/test quyết định bạn có tin nổi con số đo hay không.
TL;DR: Nhãn (label) là đáp án đúng gắn với mỗi ví dụ dữ liệu — nó là spec thật mà model học theo, và nhãn sai thì model học sai một cách hoàn toàn im lặng, không compiler hay test suite nào báo lỗi. Ba tập dữ liệu đóng ba vai trò khác nhau: train chỉnh trọng số, validation được nhìn nhiều lần để chọn cấu hình, test chỉ đo đúng một lần ở cuối cùng — nhìn lại rồi quay lại chỉnh là tập test mất giá trị. Luật quan trọng nhất: tách ba tập TRƯỚC khi chạm vào dữ liệu, kể cả bước chuẩn hoá tưởng như vô hại. Dữ liệu có yếu tố thời gian phải chia theo mốc thời gian, không chia ngẫu nhiên.
Một team xây model chấm điểm rủi ro gian lận thẻ — ví dụ minh hoạ dưới đây, không phải số liệu kiểm toán thật. Họ có bảng giao dịch với nhãn is_fraud do đội vận hành gắn tay trong sáu tháng. Quy trình nghe rất chuẩn: chuẩn hoá mọi cột số trên toàn bộ bảng, xáo dữ liệu rồi cắt 80% train, 10% validation, 10% test. Model báo AUC 0.97 trên tập test nội bộ — đẹp không tì vết. Ba tuần sau khi lên production, tỉ lệ gian lận lọt qua tăng vọt: gần một phần tư nhãn is_fraud của sáu tháng đó hoá ra bị gắn nhầm vì một bug ở hệ thống chấm điểm cũ.
Không bước nào trong pipeline báo lỗi — code chạy trơn tru, review không thấy gì sai. Đây là hai nguyên nhân độc lập, không chung một gốc: nhãn sai khiến model học sai, im lặng (mục 1); còn chuẩn hoá trên toàn bộ bảng trước khi tách khiến điểm 0.97 tự nó không đáng tin, bất kể nhãn đúng hay sai (mục 3–4). Bài này trả lời hai câu hỏi tách bạch: nhãn quyết định model học được gì, và cách chia ba tập quyết định con số đo có đáng tin hay không.
Bài trước: machine learning thay luật if-else viết tay bằng luật suy ra từ dữ liệu — bạn kiểm soát hành vi chương trình bằng cách kiểm soát dữ liệu, không còn bằng cách đọc code. Câu hỏi bỏ ngỏ: luật suy ra bằng cách nào? Câu trả lời nằm ở nhãn — thứ nói cho model biết "luật đúng" trông như thế nào; nhãn sai thì luật suy ra cũng sai, giống một dòng if-else viết sai mà chẳng ai review lại.
1. Analogy — nhãn là expected output không ai review lại
Bạn quen với việc viết test: mỗi test case có một expected để so khớp với actual mà code trả về. Nếu ai đó gõ nhầm expected (viết 4 thay vì 5), test suite không hề biết đó là gõ nhầm — nó chỉ so khớp. Code bị chỉnh cho tới khi ra đúng 4 vẫn được CI báo xanh, dù logic đó sai với nghiệp vụ thật.
Nhãn trong machine learning đóng đúng vai trò của expected, ở quy mô hàng nghìn tới hàng triệu ví dụ. Loss function so khớp actual (output model) với expected (nhãn); train chỉnh trọng số cho tới khi hai bên khớp nhau nhiều nhất có thể.
| Test suite (bạn đã quen) | Machine learning |
|---|---|
expected viết tay trong file test | Nhãn gắn với mỗi ví dụ dữ liệu |
So khớp actual với expected | Loss function so output model với nhãn |
Test pass nghĩa là khớp expected, dù expected có thể sai | Model "học đúng" nghĩa là khớp nhãn, dù nhãn có thể sai |
CI xanh không phát hiện expected sai | Không phép đo tự động nào phát hiện nhãn sai khi train |
| Chỉ người đọc lại file test mới bắt được lỗi | Chỉ audit dữ liệu, hoặc theo dõi hành vi thật, mới bắt được nhãn sai |
Nhãn là "đáp án mẫu" model học theo, không phải dữ liệu mô tả. Đáp án mẫu sai thì học giỏi đến đâu cũng ra thứ sai, và bài tự chấm bằng đúng đáp án sai đó vẫn báo điểm mười.
2. Ba tập, ba vai trò khác nhau
Chia dữ liệu không phải thủ tục hình thức — mỗi tập trả lời một câu hỏi khác nhau, và trộn lẫn vai trò của chúng là nguồn lỗi phổ biến nhất khi đánh giá model.
| Tập | Câu hỏi nó trả lời | Được nhìn lại bao nhiêu lần |
|---|---|---|
| Train | Trọng số nên là bao nhiêu để khớp nhãn? | Nhìn liên tục suốt quá trình train |
| Validation | Cấu hình nào (kiến trúc, hyperparameter, ngưỡng) tốt hơn? | Nhìn nhiều lần — mỗi lần thử một phương án là một lần nhìn |
| Test | Model thật sự làm tốt tới đâu với dữ liệu chưa từng thấy? | Đúng một lần, ở cuối cùng |
Validation được nhìn nhiều lần vì đó là công cụ ra quyết định: thử cấu hình A, đo, thử cấu hình B, đo lại, chọn cấu hình tốt hơn. Nhưng "thử rồi chỉnh theo điểm đo" là con dao hai lưỡi — càng lặp nhiều vòng trên cùng một tập, quyết định của bạn càng khớp theo đặc thù riêng của đúng tập đó, không còn phản ánh khả năng tổng quát hoá.
Đây là lý do cần tập thứ ba. Chỉ có train và test, bạn buộc phải dùng test để chọn cấu hình — và ngay khi làm vậy, nó đã trở thành một validation trá hình, còn bạn hết công cụ để biết model tổng quát tới đâu. Test chỉ giữ giá trị "gương chưa từng soi" khi bị cất đi tới đúng một lần cuối cùng.
3. Thử đoán — quy trình chia dữ liệu này hỏng ở đâu?
Quay lại team gian lận thẻ ở đầu bài. Quy trình họ dùng trông rất hợp lý, đúng những gì hầu hết tutorial giới thiệu:
-- Bước 1: chuẩn hoá mỗi cột số, tính trên TOÀN BỘ bảng giao dịch
for each cot_so trong bảng:
mean <- trung_binh của cot_so, tính trên toàn bộ dữ liệu
std <- do_lech_chuan của cot_so, tính trên toàn bộ dữ liệu
cot_so <- (cot_so - mean) / std
-- Bước 2: xáo dữ liệu rồi cắt theo tỷ lệ
shuffle(du_lieu)
train <- 80% đầu
validation <- 10% tiếp theo
test <- 10% còn lại
Đọc lại hai bước trên. Một bước trong đó âm thầm phá vỡ nguyên tắc tách ba tập, dù không dòng nào báo lỗi và không con số nào trông bất thường. Viết ra bước bạn nghi ngờ nhất, và lý do, trước khi đọc tiếp.
4. Đáp án — luật tách trước khi chạm vào dữ liệu
Bước 1 là chỗ hỏng. mean và std được tính trên toàn bộ bảng — kể cả những dòng sau này rơi vào validation và test. Đem mean/std đó chuẩn hoá train, mỗi giá trị train đã mang dấu vết thống kê của dữ liệu lẽ ra train không được biết. Nhãn is_fraud không tham gia bước chuẩn hoá này — thứ rò rỉ là thống kê (mean/std) của các cột feature ở validation/test đã lọt vào phép biến đổi áp cho tập train, đây là rò rỉ dữ liệu (data leakage): thông tin từ tập lẽ ra "chưa từng thấy" lọt vào huấn luyện qua đường gián tiếp. Bài sau (Overfitting & data leakage) chẩn đoán triệu chứng của rò rỉ; ở đây chỉ cần nhớ một luật: tách ba tập TRƯỚC khi làm bất cứ điều gì với dữ liệu, rồi mới tính toán.
Quy trình đúng tách trước theo mốc thời gian — giao dịch thẻ có trục thời gian nên shuffle sai ngay từ bước này, lý do đầy đủ ở mục 5:
-- Bước 1: tách TRƯỚC theo mốc thời gian, chưa đụng tay vào giá trị nào
train <- giao dịch trước mốc thời gian X
validation <- giao dịch từ mốc X đến mốc Y
test <- giao dịch từ mốc Y trở đi -- luôn đứng sau cùng trên trục thời gian
-- Bước 2: tính thống kê CHỈ trên train
mean <- trung_binh của cot_so, tính trên train
std <- do_lech_chuan của cot_so, tính trên train
-- Bước 3: dùng ĐÚNG mean/std đó để chuẩn hoá cả ba tập
train <- (train - mean) / std
validation <- (validation - mean) / std
test <- (test - mean) / std -- test chỉ chạm vào ở đây, đúng 1 lần
Luật này không chỉ áp cho chuẩn hoá — điền giá trị thiếu, chọn feature, giảm chiều: mọi phép biến đổi "học" tham số từ dữ liệu đều phải fit trên train rồi mới áp dụng sang validation/test, không bao giờ ngược lại.

5. Dữ liệu có yếu tố thời gian phải chia theo thời gian
shuffle rồi cắt ngẫu nhiên là mặc định hợp lý khi các dòng dữ liệu độc lập nhau. Nhưng giao dịch thẻ, log request, hay bất kỳ dữ liệu có cột thời gian đều không độc lập theo cách đó — chúng có một trục thứ tự mà xáo trộn phá vỡ.
Ví dụ minh hoạ: sáu tháng giao dịch bị shuffle rồi cắt ngẫu nhiên 80/10/10 — hoàn toàn có khả năng giao dịch tháng 3 rơi vào train còn tháng 1 rơi vào test. Model được huấn luyện trên dữ liệu "tương lai" so với vài điểm nó bị đem ra đo, điều production không bao giờ có (production chỉ có quá khứ trong tay để dự đoán tương lai). Điểm đo kiểu này bị thổi phồng: model đang "nội suy" giữa hai lát thời gian nó đã thấy cả hai phía, không phải dự đoán thật.
-- SAI: chia ngẫu nhiên dữ liệu có yếu tố thời gian
shuffle(giao_dich) -- trộn lẫn tháng 1 và tháng 3 vào chung một rổ
train, test <- cắt tỷ lệ trên dữ liệu đã trộn
-- ĐÚNG: chia theo mốc thời gian
train <- giao_dich trước mốc X -- ví dụ minh hoạ: tháng 1 - tháng 2
test <- giao_dich từ mốc X trở đi -- ví dụ minh hoạ: tháng 3
Nguyên tắc chung: train luôn đứng trước validation, validation luôn đứng trước test trên trục thời gian — không ngoại lệ "xáo cho đều" khi dữ liệu có thứ tự (xem Deep Dive bên dưới).
6. Pitfall tổng hợp
❌ Nhầm 1 — chuẩn hoá/điền thiếu/chọn feature trên toàn bộ dữ liệu rồi mới tách: ✅ Luôn tách ba tập trước, fit mọi phép biến đổi trên train, rồi áp dụng sang validation/test (mục 4).
❌ Nhầm 2 — nhìn test nhiều lần, mỗi lần thấy điểm thấp lại quay về chỉnh model rồi đo lại: ✅ Test chỉ đo đúng một lần ở cuối. Muốn thử nhiều phương án, dùng validation.
❌ Nhầm 3 — shuffle ngẫu nhiên dữ liệu có cột thời gian trước khi chia:
✅ Cắt theo mốc thời gian: train luôn nằm trước, test luôn nằm sau.
7. 📚 Deep Dive
Tài liệu chính chủ:
- Google ML Crash Course — Dividing datasets — vì sao validation "mòn" dần khi dùng lại nhiều lần, test chỉ giữ giá trị đúng một lần.
- scikit-learn — Cross-validation — cơ chế rò rỉ khi dùng test để chọn hyperparameter, vì sao cần validation thứ ba.
- scikit-learn — TimeSeriesSplit — kiểu chia tôn trọng thứ tự thời gian, thay cho chia ngẫu nhiên.
Hai link scikit-learn ở đây dùng để đọc cơ chế, không phải để học API — khoá này không dạy triển khai bằng thư viện cụ thể.
8. Liên hệ các bài khác
- Bài 01 — Khi nào cần học từ dữ liệu — luật dời từ code sang dữ liệu; bài này chỉ ra chính xác dữ liệu nào (nhãn) mang luật đó.
- Bài 02 — Ba kiểu học — supervised learning cần nhãn để so khớp.
- Bài 04 — Train vs inference — ba tập dữ liệu ở bài này chỉ tồn tại trong pha train.
- Bài 05 — Overfitting & data leakage — chẩn đoán triệu chứng khi rò rỉ đã xảy ra, tiếp nối cơ chế rò rỉ vừa học ở mục 4.
- Mini-challenge — đọc mô tả hệ AI — áp dụng luật tách-trước để phát hiện lỗi chia dữ liệu thật.
9. Tóm tắt
- Nhãn đóng vai trò
expectedmà model được chỉnh để khớp theo — nhãn sai thì model học sai, im lặng, không compiler nào bắt được. - Train chỉnh trọng số; validation được nhìn nhiều lần để chọn cấu hình; test chỉ đo đúng một lần ở cuối.
- Ba tập tồn tại vì test không thể vừa "chưa từng thấy" vừa được dùng để chọn cấu hình — hai vai trò xung đột nhau.
- Luật quan trọng nhất: tách ba tập TRƯỚC khi làm bất cứ điều gì với dữ liệu, kể cả chuẩn hoá — tính thống kê trên toàn bộ rồi mới tách chính là rò rỉ dữ liệu (data leakage).
- Dữ liệu có yếu tố thời gian phải chia theo mốc thời gian: train luôn đứng trước, test luôn đứng sau, không chia ngẫu nhiên.
10. Tự kiểm tra
Q1Vì sao nhãn sai không bị bất kỳ công cụ tự động nào (compiler, test suite, hay chính quá trình train) phát hiện?▸
Nhãn đóng vai trò expected mà loss function dùng để so khớp — train chỉ tối ưu cho việc khớp với nhãn đã cho, không có cách nào biết nhãn đó có đúng thực tế hay không, giống hệt test case có expected gõ nhầm vẫn báo pass tuyệt đối. Chỉ audit dữ liệu (đối chiếu nghiệp vụ thật) mới phát hiện được.
Q2Vì sao validation được phép nhìn lại nhiều lần để chọn cấu hình, còn test thì không?▸
Validation là công cụ ra quyết định: thử cấu hình, đo, chỉnh, đo lại — nhưng mỗi vòng lặp đó làm quyết định khớp dần theo đặc thù riêng của đúng tập đó. Test chỉ giữ vai trò "chưa từng thấy" nếu chưa từng bị dùng để ra quyết định; nhìn rồi quay lại chỉnh biến nó thành một validation trá hình.
Q3Trong pseudocode ở mục 3, bước tính mean/std gây rò rỉ dữ liệu theo cơ chế nào — và vì sao đảo thứ tự hai bước lại giải quyết được?▸
mean/std tính trên toàn bộ bảng, gồm cả những dòng sau này thuộc validation/test, nên mỗi giá trị train bị chuẩn hoá đã mang dấu vết thống kê của dữ liệu lẽ ra train không được biết. Tách trước rồi mới tính mean/std chỉ trên train đảm bảo con số biến đổi chỉ đến từ đúng phần dữ liệu model được phép nhìn khi train.
Q4Một tập giao dịch sáu tháng bị shuffle ngẫu nhiên rồi cắt 80/10/10. Vì sao cách này khiến model 'nhìn thấy tương lai', và hệ quả gì khi model chạy thật ở production?▸
Shuffle xoá trục thời gian: giao dịch tháng 3 có thể rơi vào train trong khi tháng 1 rơi vào test, nên model được huấn luyện trên dữ liệu đến sau thời điểm nó bị đem ra đo — điều production không bao giờ có. Điểm test vì thế bị thổi phồng (model chỉ đang nội suy, không dự đoán thật một tương lai chưa biết); hiệu năng production thấp hơn hẳn con số đã đo.
Q5Team dùng chung một tập test để so sánh năm cấu hình model, rồi chọn cấu hình có điểm test cao nhất. Vì sao con số test cuối cùng không còn đáng tin?▸
Ngay khi test được dùng để so sánh và chọn giữa các phương án, nó đã đóng vai trò của validation chứ không còn là "chưa từng thấy" — cấu hình được chọn chính là cấu hình khớp tốt nhất với đặc thù riêng của đúng tập test đó. Cần một tập thứ hai, độc lập và chưa từng bị nhìn, để đo lại đúng một lần sau khi cấu hình đã chốt.
Bài tiếp theo: Train vs inference — hai pha, hai loại hoá đơn
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên