Nhiều dự án machine learning thất bại không vì thuật toán yếu, mà vì chọn sai bài toán, dữ liệu không đại diện, hoặc đo kết quả bằng thước sai. Bài này mở đầu phần học máy của series: machine learning là gì, nó khác lập trình thông thường ở đâu, các loại bài toán chính, vì sao phải chia dữ liệu train và test, và một dự án đi qua những bước nào. Ta không dừng ở định nghĩa mà dựng một dự án nhỏ chạy thật bằng scikit-learn 1.1.2 trên Python 3.10 (máy Apple M4, 16 GB): dự đoán khách hàng có quay lại mua trong 90 ngày, so với baseline, thấy tận mắt overfitting và rò rỉ dữ liệu. Dữ liệu là dữ liệu tổng hợp sinh bằng seed cố định, không phải khách hàng thật.
Bài dùng công cụ từ hai bài trước của series: bảng sạch ở Làm sạch dữ liệu crawl bằng pandas: chuẩn hoá, khử trùng lặp, kiểu dữ liệu và mảng, shape, broadcasting ở NumPy cho machine learning: vector hoá và broadcasting. Dữ liệu đầu vào của nhiều dự án thực tế bắt nguồn từ crawl, xem lại Crawl data bằng Python: requests, BeautifulSoup và robots.txt.
Machine learning là gì, và khác lập trình thông thường ở đâu
Trong lập trình thông thường, bạn viết quy tắc rồi đưa dữ liệu vào để ra kết quả: nếu đơn hàng trên 2 triệu và khách mới, gắn cờ kiểm tra. Trong machine learning (học máy), bạn đưa vào dữ liệu cùng đáp án đã biết, và thuật toán suy ra quy tắc từ đó. Quy tắc học được, gọi là mô hình, sau đó được áp dụng cho dữ liệu mới. Cách nhìn này tiện để quyết định khi nào dùng machine learning: khi quy tắc quá nhiều, quá mơ hồ hoặc thay đổi liên tục để viết tay, nhưng bạn có sẵn nhiều ví dụ đã gắn nhãn.
Cũng cần khiêm tốn về chỗ đứng của machine learning. Tài liệu Rules of Machine Learning của Google mở đầu bằng quy tắc đầu tiên: đừng ngại ra mắt sản phẩm mà chưa dùng machine learning. Một heuristic đơn giản, như xếp hạng theo lượt cài đặt hay lọc nhà xuất bản từng gửi thư rác, thường đưa sản phẩm đi được phần lớn quãng đường. Chỉ nên dùng machine learning khi sản phẩm thật sự cần và bạn có dữ liệu.
Các loại bài toán machine learning
Theo tài liệu nhập môn của scikit-learn, dữ liệu luôn được biểu diễn dạng bảng (n_samples, n_features): mỗi hàng là một mẫu, mỗi cột là một đặc trưng (feature). Có hai nhóm lớn, phân biệt bởi việc dữ liệu có đáp án hay không:
| Nhóm | Bài toán | Đầu ra | Ví dụ ở doanh nghiệp |
|---|---|---|---|
| Học có giám sát | Phân loại (classification) | nhãn rời rạc | đơn hàng có gian lận hay không; khách có quay lại không |
| Học có giám sát | Hồi quy (regression) | số liên tục | dự báo doanh số tuần sau; giá nhà |
| Học không giám sát | Phân cụm (clustering) | nhóm các mẫu giống nhau | chia khách hàng thành nhóm hành vi |
| Học không giám sát | Giảm chiều, ước lượng mật độ | biểu diễn gọn hơn, phát hiện điểm lạ | trực quan hoá dữ liệu, phát hiện giao dịch bất thường |
Hầu hết bài toán machine learning đầu tiên của một doanh nghiệp là học có giám sát vì dữ liệu lịch sử vốn đã có đáp án: khách đã quay lại hay chưa, đơn đã bị hoàn hay chưa. Việc đầu tiên của dự án machine learning là chuyển yêu cầu mơ hồ ("giữ chân khách hàng") thành một câu hỏi dự đoán cụ thể ("trong 90 ngày tới, khách A có mua lại không?"), kèm quyết định mà dự đoán đó phục vụ (gửi voucher cho nhóm nào).
Quy trình một dự án machine learning: sáu bước
Một dự án machine learning thường theo quy trình chuẩn của ngành khai phá dữ liệu là CRISP-DM, mô tả trong tài liệu CRISP-DM 1.0 Step-by-step data mining guide (Chapman và cộng sự, 2000) với sáu pha. Các pha không tuyến tính: kết quả ở pha sau thường buộc quay lại pha trước.
- Hiểu bài toán nghiệp vụ: mục tiêu, chỉ số thành công, chi phí khi sai. Đây là pha hay bị bỏ qua nhất.
- Hiểu dữ liệu: dữ liệu có những gì, thiếu gì, chất lượng ra sao, nhãn lấy từ đâu và có đáng tin không.
- Chuẩn bị dữ liệu cho machine learning: làm sạch, tạo đặc trưng, chia train và test. Thường chiếm phần lớn thời gian dự án; pandas và NumPy ở hai bài trước thuộc pha này.
- Xây mô hình machine learning: chọn thuật toán, huấn luyện, chỉnh tham số.
- Đánh giá: đo trên dữ liệu chưa thấy, so với baseline, và đối chiếu với mục tiêu nghiệp vụ chứ không chỉ với con số kỹ thuật.
- Triển khai và giám sát: đưa vào hệ thống thật, theo dõi hiệu quả theo thời gian.
Phần còn lại của bài đi qua pha 2 đến 5 bằng code; triển khai và giám sát sẽ có bài riêng.
Dựng dữ liệu và chia train, test trong dự án machine learning
Dự án mẫu: một cửa hàng trực tuyến muốn biết khách nào sẽ quay lại mua trong 90 ngày tới để gửi ưu đãi đúng người. Đặc trưng gồm số đơn đã mua, số ngày từ đơn cuối, giá trị đơn trung bình và việc đã từng dùng voucher; nhãn quay_lai bằng 1 nếu khách mua lại. Vì không dùng dữ liệu khách thật, ta sinh 3.000 khách tổng hợp với quan hệ giả định giữa đặc trưng và nhãn.
# --- 1. Dữ liệu: 3000 khách, nhãn quay_lai = 1 nếu mua lại trong 90 ngày ---
rng = np.random.default_rng(7)
n = 3000
so_don = rng.poisson(3, n) + 1
ngay_tu_don_cuoi = rng.integers(1, 365, n)
gia_tri_tb = rng.normal(900_000, 300_000, n).clip(50_000)
da_dung_voucher = rng.integers(0, 2, n)
logit = 0.55 * (so_don - 4) - 0.012 * (ngay_tu_don_cuoi - 120) + 0.0000004 * (gia_tri_tb - 900_000) + 0.4 * da_dung_voucher - 0.6
p = 1 / (1 + np.exp(-logit))
quay_lai = (rng.random(n) < p).astype(int)
df = pd.DataFrame({"so_don": so_don, "ngay_tu_don_cuoi": ngay_tu_don_cuoi,
"gia_tri_tb": gia_tri_tb.round(-3), "da_dung_voucher": da_dung_voucher,
"quay_lai": quay_lai})
print(df.head(3).to_string(index=False))
print("\nTỉ lệ quay lại:", round(df["quay_lai"].mean(), 3), "| số mẫu:", len(df))
X = df.drop(columns="quay_lai"); y = df["quay_lai"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
print("train:", len(X_tr), "| test:", len(X_te)) so_don ngay_tu_don_cuoi gia_tri_tb da_dung_voucher quay_lai
5 136 733000.0 0 1
2 364 1197000.0 1 0
5 276 946000.0 0 0
Tỉ lệ quay lại: 0.294 | số mẫu: 3000
train: 2250 | test: 750Khoảng 29% khách quay lại: tập mất cân bằng (lớp 1 ít hơn lớp 0). Điều này quan trọng ở bước đánh giá. Ta chia dữ liệu thành hai phần: tập huấn luyện (train) để mô hình học, tập kiểm thử (test) giữ riêng để đo khả năng tổng quát hoá. Tài liệu scikit-learn định nghĩa đúng như vậy: train set là dữ liệu mô hình học thuộc tính của nó, test set là dữ liệu tách riêng để kiểm tra những gì đã học. Tham số stratify=y giữ tỉ lệ lớp bằng nhau ở cả hai phần, và random_state cố định để chạy lại cho cùng kết quả.
Quy tắc sống còn của machine learning: chỉ chạm tập test một lần ở cuối. Mọi lựa chọn (đặc trưng, tham số, thuật toán) dựa trên tập test sẽ khiến nó mất vai trò đại diện cho dữ liệu chưa thấy.
Lưu ý: trong bài này ta in điểm test của nhiều mô hình liền nhau chỉ để minh hoạ, vì mục tiêu là cho thấy baseline và overfitting trông ra sao. Trong dự án thật, hãy chọn mô hình và tham số (như max_depth) bằng cross-validation trên tập train, như ở mục sau, rồi mới đo test đúng một lần cho mô hình đã chọn. Mã đầy đủ (kèm phần import) của các đoạn dưới đây nằm trong hai tệp project.py và leakage.py của bài.
Baseline trước, mô hình machine learning sau
Trước khi huấn luyện bất cứ thứ gì, hãy xác định điểm so sánh đơn giản nhất. Với phân loại, đó là luôn đoán lớp đa số (DummyClassifier). Mô hình machine learning chỉ có giá trị khi vượt được baseline một cách đáng kể. Ta so baseline với hồi quy logistic (đặt sau bước chuẩn hoá StandardScaler trong một Pipeline), in độ chính xác (accuracy), precision và recall trên cả train và test:
def report(name, model):
model.fit(X_tr, y_tr)
for tag, Xs, ys in (("train", X_tr, y_tr), ("test ", X_te, y_te)):
pr = model.predict(Xs)
print(f"{name:<22} {tag} acc={accuracy_score(ys, pr):.3f} "
f"precision={precision_score(ys, pr, zero_division=0):.3f} recall={recall_score(ys, pr):.3f}")
# --- 2. Baseline trước, mô hình sau ---
print("\n=== Baseline và mô hình ===")
report("Baseline (đa số)", DummyClassifier(strategy="most_frequent"))
report("Logistic regression", make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)))=== Baseline và mô hình ===
Baseline (đa số) train acc=0.706 precision=0.000 recall=0.000
Baseline (đa số) test acc=0.705 precision=0.000 recall=0.000
Logistic regression train acc=0.783 precision=0.680 recall=0.495
Logistic regression test acc=0.795 precision=0.725 recall=0.489Baseline không dùng machine learning vẫn đạt accuracy 0,705 mà không học được gì, chỉ vì 70% khách không quay lại. Nhưng recall của nó bằng 0: không tìm ra khách nào sẽ quay lại, hoàn toàn vô dụng cho mục tiêu gửi ưu đãi. Đây là minh hoạ vì sao accuracy một mình đánh lừa trên dữ liệu mất cân bằng. Hồi quy logistic đạt accuracy 0,795 trên test, cao hơn baseline khoảng 9 điểm phần trăm, và tìm được khoảng 49% khách thật sự quay lại với precision 0,725.
Một mô hình machine learning vừa phải nhưng thật sự có ích; chi tiết về precision, recall sẽ được bàn kỹ ở một bài riêng về đánh giá mô hình. Tạm hiểu: precision là tỉ lệ các dự đoán "sẽ quay lại" thật sự đúng, còn recall là tỉ lệ khách thật sự quay lại mà mô hình tìm ra được.
Overfitting: nhớ thuộc lòng chứ chưa học
Overfitting xảy ra khi mô hình khớp dữ liệu huấn luyện quá sát, thực chất là học thuộc cả nhiễu, rồi dự đoán kém trên dữ liệu mới. Tài liệu cross-validation của scikit-learn gọi việc học tham số rồi kiểm tra trên chính dữ liệu đó là một sai lầm phương pháp luận: mô hình chỉ cần lặp lại nhãn đã thấy là đạt điểm tuyệt đối nhưng không dự đoán được gì hữu ích trên dữ liệu mới. Cây quyết định không giới hạn độ sâu là ví dụ điển hình vì nó có thể chia dữ liệu đến khi mỗi lá chỉ còn một mẫu:
# --- 3. Overfitting: cây không giới hạn độ sâu ---
print("\n=== Overfitting ===")
report("Cây sâu vô hạn", DecisionTreeClassifier(random_state=0))
report("Cây giới hạn (depth=3)", DecisionTreeClassifier(max_depth=3, random_state=0))=== Overfitting ===
Cây sâu vô hạn train acc=1.000 precision=1.000 recall=1.000
Cây sâu vô hạn test acc=0.680 precision=0.455 recall=0.430
Cây giới hạn (depth=3) train acc=0.780 precision=0.688 recall=0.462
Cây giới hạn (depth=3) test acc=0.752 precision=0.634 recall=0.376Cây sâu vô hạn đạt accuracy 1,000 trên train nhưng chỉ 0,680 trên test, thậm chí thấp hơn baseline 0,705. Khoảng cách 0,32 giữa hai con số là chữ ký của overfitting. Giới hạn max_depth=3 ép cây học quy luật tổng quát hơn: train 0,780, test 0,752, khoảng cách chỉ còn 0,028. Điểm cần nhớ: accuracy trên tập huấn luyện không nói gì về chất lượng thật, chỉ khoảng cách giữa train và test mới cho biết mô hình có tổng quát hoá không. Cách chống overfitting: thêm dữ liệu, giảm độ phức tạp mô hình, đánh giá đúng cách.
Cross-validation: đánh giá ổn định hơn một lần chia
Một lần chia train và test cho con số phụ thuộc vào việc may rủi đưa mẫu nào vào đâu. Cross-validation chia tập train thành 5 phần, lần lượt giữ một phần làm kiểm thử, huấn luyện trên bốn phần còn lại, rồi lấy trung bình và độ lệch chuẩn. Cách này dùng để so sánh mô hình và chọn tham số mà vẫn chưa động vào tập test:
# --- 4. Đánh giá ổn định hơn: cross-validation 5 phần trên tập train ---
print("\n=== Cross-validation (5 phần, tập train) ===")
for name, m in (("Logistic regression", make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))),
("Cây depth=3", DecisionTreeClassifier(max_depth=3, random_state=0))):
s = cross_val_score(m, X_tr, y_tr, cv=5, scoring="accuracy")
print(f"{name:<22} acc = {s.mean():.3f} ± {s.std():.3f}")=== Cross-validation (5 phần, tập train) ===
Logistic regression acc = 0.784 ± 0.018
Cây depth=3 acc = 0.773 ± 0.009Hồi quy logistic đạt 0,784 ± 0,018, cây độ sâu 3 đạt 0,773 ± 0,009. Hai mô hình sát nhau trong khoảng dao động, nên chênh lệch 0,011 chưa đủ để kết luận mô hình nào tốt hơn. Vì vậy cần nhìn cả độ lệch chuẩn thay vì chỉ trung bình.
Rò rỉ dữ liệu: khi kết quả tốt đến mức đáng ngờ
Lỗi nguy hiểm nhất trong dự án machine learning là rò rỉ dữ liệu (data leakage). Rò rỉ dữ liệu (data leakage) là việc dùng thông tin mà khi dự đoán thật sẽ không có sẵn để xây mô hình, nên ước lượng hiệu năng trở nên quá lạc quan và mô hình kém đi khi ra production. Nguyên nhân phổ biến gồm trộn lẫn train với test, gọi fit trên dữ liệu test, và học tham số tiền xử lý (như trung bình để chuẩn hoá) từ toàn bộ dữ liệu.
Về nguyên tắc cuối cùng, tài liệu scikit-learn 1.1 nêu rõ: bước tiền xử lý như chuẩn hoá hay chọn đặc trưng cũng phải được học từ tập huấn luyện rồi mới áp dụng cho dữ liệu giữ riêng, và Pipeline giúp làm đúng điều đó khi cross-validation. Cách tránh là chia dữ liệu trước mọi bước tiền xử lý và dùng Pipeline để mỗi bước chỉ học từ phần train của từng lần chia.
Có một dạng rò rỉ khó thấy hơn: đặc trưng mang thông tin từ tương lai. Ví dụ, thêm cột "số đơn trong 90 ngày tới" vào bài toán dự đoán 90 ngày tới. Cột này không thể có tại thời điểm dự đoán, nhưng trong bảng lịch sử nó nằm sẵn. Hãy xem nó làm kết quả thay đổi thế nào:
rng = np.random.default_rng(7)
n = 3000
X = pd.DataFrame({"so_don": rng.poisson(3, n) + 1, "ngay_tu_don_cuoi": rng.integers(1, 365, n)})
logit = 0.55 * (X["so_don"] - 4) - 0.012 * (X["ngay_tu_don_cuoi"] - 120) - 0.2
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
def cv(Xs):
m = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
return cross_val_score(m, Xs, y, cv=5).mean()
print(f"Đặc trưng hợp lệ : acc = {cv(X):.3f}")
X_leak = X.assign(don_trong_90_ngay_toi=y + rng.integers(0, 2, n) * y) # chỉ biết SAU thời điểm dự đoán
print(f"Thêm đặc trưng rò rỉ : acc = {cv(X_leak):.3f} <- đẹp bất thường")Đặc trưng hợp lệ : acc = 0.780
Thêm đặc trưng rò rỉ : acc = 1.000 <- đẹp bất thườngVới đặc trưng hợp lệ, cross-validation cho 0,780. Thêm đặc trưng rò rỉ, độ chính xác nhảy lên 1,000, một kết quả hoàn hảo mà thực tế gần như không bao giờ có. Kết quả quá đẹp là dấu hiệu để nghi ngờ trước khi ăn mừng. Với mỗi đặc trưng, hãy tự hỏi: tại thời điểm hệ thống cần dự đoán, giá trị này đã tồn tại chưa?
Khi nào không nên dùng machine learning
- Quy tắc rõ ràng và ổn định: tính thuế, chiết khấu theo bậc, kiểm tra định dạng. Viết luật trực tiếp chính xác và dễ kiểm chứng hơn.
- Không đủ dữ liệu có nhãn: với machine learning, vài chục mẫu không đủ để học quy luật đáng tin; nhãn sai còn tệ hơn thiếu nhãn.
- Sai sót có hậu quả nặng và cần giải trình: quyết định về tín dụng hay y tế đòi hỏi khả năng giải thích và quy trình kiểm soát riêng.
- Một heuristic đã đủ tốt: theo quy tắc của Google ở trên, hãy bắt đầu bằng heuristic và chỉ thêm machine learning khi có bằng chứng nó cải thiện.
- Dữ liệu huấn luyện không giống dữ liệu thật: mô hình học từ quá khứ chỉ hợp lệ chừng nào tương lai còn giống quá khứ.
Lỗi thường gặp trong dự án machine learning
- Bỏ qua baseline: không biết mô hình machine learning có hơn đoán lớp đa số hay không.
- Chỉ nhìn accuracy của mô hình machine learning trên dữ liệu mất cân bằng; luôn xem thêm precision và recall.
- Chỉnh theo tập test khi làm machine learning: chọn tham số theo điểm test rồi báo cáo chính điểm đó; dùng cross-validation trên tập train.
- Chuẩn hoá trước khi chia: tính trung bình và độ lệch chuẩn trên toàn dữ liệu là một dạng rò rỉ; đặt
StandardScalertrongPipeline. - Đặc trưng từ tương lai: kiểm tra từng cột có tồn tại tại thời điểm dự đoán hay không.
- Quên đường triển khai: Google khuyên cho cả hệ thống chạy thông suốt với mô hình đơn giản trước, rồi mới nâng cấp.
- Nhầm tương quan với nhân quả: mô hình machine learning thấy khách dùng voucher hay quay lại không có nghĩa phát voucher sẽ khiến họ quay lại.
Checklist khởi động một dự án machine learning
- Với mỗi dự án machine learning, viết bài toán thành một câu dự đoán cụ thể, kèm quyết định mà dự đoán phục vụ và chi phí khi sai.
- Xác định nhãn lấy từ đâu và thời điểm dự đoán, để kiểm tra từng đặc trưng không đến từ tương lai.
- Chia train và test trước mọi bước tiền xử lý; cố định
random_state; dùngstratifykhi lớp mất cân bằng. - Đặt baseline đơn giản; mô hình phải hơn baseline trên chỉ số phản ánh mục tiêu nghiệp vụ.
- So sánh mô hình bằng cross-validation trên tập train; so khoảng cách train và test để phát hiện overfitting.
- Đọc kết quả quá đẹp như một cảnh báo; chạm tập test một lần ở cuối.
- Ghi lại phiên bản thư viện, seed và dữ liệu để tái lập kết quả.
Tóm lại, machine learning là cách để máy suy ra quy tắc từ dữ liệu có đáp án, và chất lượng của nó phụ thuộc vào bài toán đặt đúng, dữ liệu đại diện và cách đánh giá trung thực hơn là vào thuật toán cầu kỳ. Với quy trình sáu bước, một baseline và kỷ luật chia dữ liệu, bạn đã tránh được phần lớn thất bại phổ biến.
Bài tiếp theo
Bài này dùng sẵn mô hình tuyến tính mà chưa giải thích nó học thế nào. Bài kế tiếp tự cài đặt phần đó từ đầu: hàm mất mát, đạo hàm và vòng lặp cập nhật tham số bằng NumPy. Mời bạn đọc tiếp Hồi quy tuyến tính và gradient descent: tự cài đặt bằng NumPy.



