Một mô hình phát hiện gian lận đạt accuracy 99,13% nghe rất ấn tượng, cho đến khi bạn biết rằng một chương trình chỉ in ra "không gian lận" cho mọi giao dịch cũng đạt 99,12%. Đó là lý do việc đánh giá mô hình phân loại không thể dừng ở một con số accuracy, nhất là khi dữ liệu lệch, tức một lớp hiếm hơn hẳn lớp còn lại, như gian lận, bệnh hiếm, khách rời bỏ. Bài này đi qua confusion matrix, precision, recall, F1, ROC AUC, average precision, chọn ngưỡng theo chi phí nghiệp vụ và class_weight, mỗi khái niệm đều kèm output thật chạy trên dữ liệu giả lập 20.000 giao dịch với tỉ lệ dương chỉ 0,88%.
Bài về đánh giá mô hình này nối tiếp Machine learning với scikit-learn: Pipeline, cross-validation và tuning, nơi ta dùng roc_auc và f1 mà chưa giải thích; và Hồi quy tuyến tính và gradient descent: tự cài đặt bằng NumPy. Quy trình tổng thể xem ở Machine learning là gì. Môi trường chạy lại: Apple M4, 16 GB, Python 3.10, scikit-learn 1.2.0 (ra mắt tháng 12/2022), NumPy 1.23.5. Dữ liệu hoàn toàn giả lập.
Confusion matrix: nền tảng của việc đánh giá mô hình
Với bài toán nhị phân, mỗi dự đoán rơi vào một trong bốn ô: true positive (tp, đoán dương và đúng), false positive (fp, đoán dương nhưng sai), false negative (fn, bỏ sót) và true negative (tn). Theo tài liệu Metrics and scoring của scikit-learn, confusion_matrix có hàng là lớp thật và cột là lớp dự đoán, nên ô (i, j) đếm số quan sát thật sự thuộc nhóm i nhưng bị dự đoán vào nhóm j. Với bài toán nhị phân có thể lấy bốn số bằng tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel().
| Chỉ số | Công thức | Câu hỏi nó trả lời |
|---|---|---|
| Accuracy | (tp + tn) / tất cả | Tỉ lệ dự đoán đúng, bất kể lớp |
| Precision | tp / (tp + fp) | Trong số bị báo gian lận, bao nhiêu thật sự gian lận? |
| Recall (sensitivity) | tp / (tp + fn) | Trong số gian lận thật, bắt được bao nhiêu? |
| F1 | 2·P·R / (P + R) | Trung bình điều hoà của precision và recall |
| Balanced accuracy | trung bình recall của từng lớp | Accuracy có trọng số nghịch đảo tần suất lớp |
Tài liệu mô tả trực quan: precision là khả năng của bộ phân loại không gán nhãn dương cho một mẫu âm, còn recall là khả năng tìm ra mọi mẫu dương. Hai chỉ số kéo ngược chiều nhau, nên mọi cuộc đánh giá mô hình nghiêm túc đều cần nhìn cả hai.
Đánh giá mô hình trên dữ liệu lệch: accuracy đánh lừa thế nào
Ta sinh 20.000 giao dịch với 8 đặc trưng; nhãn dương (gian lận) được tạo từ một tổ hợp của vài đặc trưng cộng nhiễu, lấy phân vị 99% làm ngưỡng. Chia 70/30 có stratify=y để giữ tỉ lệ lớp ở cả hai phần.
scikit-learn 1.2.0
n = 20000 | số mẫu dương: 176 | tỉ lệ dương: 0.88%
test: 6000 mẫu, dương: 53Tập kiểm tra chỉ có 53 mẫu dương: hãy nhớ con số này, vì nó giới hạn độ tin cậy của mọi chỉ số ở dưới. Khi đánh giá mô hình, baseline đầu tiên trong mọi dự án phân loại nên là DummyClassifier, thứ mà mô hình thật phải thắng:
dummy = DummyClassifier(strategy="most_frequent").fit(X_tr, y_tr)[Baseline: luôn đoán 'không gian lận']
accuracy = 0.9912
recall = 0.0000
balanced_accuracy = 0.5000
roc_auc = 0.5000Accuracy 0,9912, recall bằng 0: baseline không bắt được vụ gian lận nào nhưng "đúng" 99,12% vì 99,12% giao dịch bình thường. Balanced accuracy (0,5) và ROC AUC (0,5) đều phản ánh đúng rằng mô hình này ngang đoán ngẫu nhiên. Theo tài liệu scikit-learn, balanced accuracy được thiết kế để tránh điểm số phồng lên trên dữ liệu lệch: nó là trung bình recall của từng lớp.
Đánh giá mô hình thật: logistic regression
Huấn luyện một logistic regression kèm StandardScaler trong Pipeline và đánh giá ở ngưỡng mặc định. Hàm predict của logistic regression gán lớp dương khi xác suất vượt 0,5, còn tài liệu precision_recall_curve cho thấy precision và recall đổi theo ngưỡng quyết định, nên 0,5 chỉ là một điểm hoạt động trong vô số điểm có thể chọn và không có lý do để tối ưu cho nghiệp vụ của bạn.
[Logistic regression, ngưỡng 0.5]
confusion matrix (hàng=thật, cột=đoán):
[[5946 1]
[ 51 2]]
tn=5946 fp=1 fn=51 tp=2
accuracy = 0.9913
precision = 0.6667 (tp/(tp+fp) = 2/3)
recall = 0.0377 (tp/(tp+fn) = 2/53)
F1 = 0.0714
balanced_accuracy = 0.5188 | MCC = 0.1572
ROC AUC = 0.9606 | Average precision (PR AUC) = 0.3416 | AP của đoán ngẫu nhiên = 0.0088Đây là bức tranh điển hình khi đánh giá mô hình trên dữ liệu lệch. Accuracy 0,9913 chỉ nhỉnh hơn baseline 0,0001. Mô hình báo gian lận đúng 2 trên 3 lần (precision 0,667) nhưng bỏ sót 51 trên 53 vụ (recall 0,038). Trong khi đó ROC AUC lên tới 0,9606: mô hình xếp hạng rất tốt, giao dịch gian lận thường có điểm cao hơn giao dịch thường, nhưng ngưỡng 0,5 đặt quá cao nên hầu như không báo động. Vấn đề không nằm ở mô hình mà ở ngưỡng quyết định. Đánh giá mô hình cần cả hai góc nhìn: chỉ số xếp hạng (ROC AUC, average precision) và chỉ số tại một ngưỡng cụ thể (precision, recall, F1).
Báo cáo đánh giá mô hình theo từng lớp
precision recall f1-score support
binh_thuong 0.991 1.000 0.996 5947
gian_lan 0.667 0.038 0.071 53
accuracy 0.991 6000
macro avg 0.829 0.519 0.534 6000
weighted avg 0.989 0.991 0.987 6000classification_report cho thấy rõ ràng: weighted avg (0,987) bị lớp đông át hẳn, còn macro avg (0,534) là trung bình đơn giản giữa các lớp nên phản ánh lớp hiếm. Với dữ liệu lệch, khi đánh giá mô hình, hãy đọc dòng của lớp hiếm và macro avg, đừng nhìn accuracy hay weighted avg. matthews_corrcoef (MCC, 0,157 ở đây) chỉ cao khi cả bốn ô của confusion matrix đều tốt.
Ngưỡng quyết định trong đánh giá mô hình: precision đổi lấy recall
Mô hình chỉ trả về xác suất; nhãn là kết quả của việc so xác suất với một ngưỡng, và ta có thể chọn ngưỡng khác để chuyển sang điểm hoạt động khác. Hạ ngưỡng thì bắt thêm gian lận (recall tăng) nhưng cũng báo nhầm nhiều hơn (precision giảm):
[Đổi ngưỡng quyết định]
ngưỡng precision recall F1 tp fp fn
0.5 0.667 0.038 0.071 2 1 51
0.3 0.733 0.208 0.324 11 4 42
0.2 0.485 0.302 0.372 16 17 37
0.1 0.287 0.509 0.367 27 67 26
0.05 0.176 0.679 0.279 36 169 17Bảng cho thấy sự đánh đổi khi đánh giá mô hình bằng số liệu thật: ở ngưỡng 0,05, ta bắt được 36 trên 53 vụ (recall 0,679) nhưng phải xem xét thêm 169 giao dịch báo nhầm (precision 0,176). F1 đạt cao nhất (0,372) quanh ngưỡng 0,2, nhưng F1 coi precision và recall quan trọng như nhau, điều hiếm khi đúng trong nghiệp vụ.
Chọn ngưỡng theo chi phí nghiệp vụ
Câu hỏi đúng không phải "ngưỡng nào có F1 cao nhất" mà là "sai lầm nào đắt hơn". Giả sử bỏ sót một vụ gian lận tốn 50 đơn vị (tiền hoàn lại, uy tín), còn một lần kiểm tra thủ công nhầm tốn 1 đơn vị. Ta quét ngưỡng và chọn ngưỡng có tổng chi phí nhỏ nhất:
Chi phí: bỏ sót=50.0, báo nhầm=1.0
ngưỡng 0.5 : chi phí = 2551
ngưỡng 0.04 : chi phí = 918 (tp=39, fp=218, fn=14, recall=0.736, precision=0.152)
Lưu ý: ngưỡng này chọn trên tập test chỉ để minh hoạ; thực tế phải chọn trên validation/CV.Đánh giá mô hình theo chi phí: chi phí giảm từ 2.551 xuống 918 (khoảng 64%) chỉ bằng việc đổi ngưỡng, không đổi mô hình.
Hai lưu ý quan trọng. Thứ nhất, các con số chi phí 50 và 1 là giả định minh hoạ; hãy lấy từ nghiệp vụ thật. Thứ hai, trong ví dụ ta chọn ngưỡng ngay trên tập kiểm tra, vi phạm nguyên tắc chỉ chạm tập kiểm tra một lần như ở bài về Pipeline và cross-validation; thực tế phải chọn ngưỡng trên validation hoặc cross-validation. Cách đúng là lấy xác suất ngoài mẫu bằng cross_val_predict(..., method="predict_proba") trên tập huấn luyện, quét ngưỡng trên các xác suất đó, chốt ngưỡng rồi mới áp lên tập kiểm tra.
Đánh giá mô hình bằng ROC và precision-recall: hai câu chuyện
Đường ROC vẽ true positive rate (chính là recall) theo false positive rate khi thay đổi ngưỡng; ROC AUC tóm tắt nó thành một số, và tài liệu nhấn mạnh nó nhận xác suất hoặc điểm chưa ngưỡng hoá nên không cần chọn ngưỡng. Ý nghĩa của ROC AUC: xác suất một mẫu dương ngẫu nhiên có điểm cao hơn một mẫu âm ngẫu nhiên. Đường precision-recall (PR) vẽ precision theo recall, và average_precision_score tóm tắt bằng tổng có trọng số; theo tài liệu, với dự đoán ngẫu nhiên AP bằng tỉ lệ mẫu dương, nên đó là mốc so sánh đúng.
Điểm khác biệt lớn xuất hiện khi dữ liệu lệch. Mẫu âm rất đông nên false positive rate (fp chia tổng số âm) vẫn nhỏ dù fp tuyệt đối lớn, khiến ROC trông đẹp. Precision thì không bị pha loãng như vậy. Nghiên cứu của Saito và Rehmsmeier (PLOS ONE, 2015) khuyến nghị dùng biểu đồ precision-recall thay vì ROC để đánh giá bộ phân loại nhị phân trên dữ liệu lệch. Để tự kiểm chứng trong đánh giá mô hình, ta giữ nguyên mô hình và toàn bộ mẫu dương của tập kiểm tra, chỉ lấy bớt hoặc giữ mẫu âm để thay đổi tỉ lệ dương:
[ROC AUC hầu như không đổi khi tỉ lệ dương giảm, PR AUC thì giảm mạnh]
tỉ lệ dương ROC AUC AP AP ngẫu nhiên
0.2000 0.956 0.849 0.2000
0.0500 0.959 0.640 0.0500
0.0200 0.960 0.470 0.0200
0.0100 0.961 0.366 0.0100ROC AUC đứng yên ở 0,956 đến 0,961 trong khi average precision tụt từ 0,849 xuống 0,366 chỉ vì lớp dương hiếm đi. Mô hình không xấu đi; bài toán "tìm kim đáy bể" khó hơn, và chỉ PR phản ánh điều đó. Cách đọc: ROC AUC trả lời "mô hình xếp hạng tốt đến đâu", còn AP trả lời "danh sách cảnh báo của tôi sạch đến đâu ở mức recall nhất định". Với bài toán cảnh báo mà nhân sự kiểm tra có hạn, AP quan trọng hơn khi đánh giá mô hình.
Xử lý dữ liệu lệch: class_weight và ảnh hưởng đến đánh giá mô hình
Cách rẻ nhất để mô hình quan tâm lớp hiếm là đặt class_weight="balanced", tăng trọng số lỗi trên lớp dương theo tỉ lệ nghịch với tần suất. So hai mô hình ở ngưỡng 0,5:
[class_weight='balanced' (ngưỡng 0.5)]
không trọng số precision=0.667 recall=0.038 F1=0.071 ROC AUC=0.961 AP=0.342
balanced precision=0.061 recall=0.943 F1=0.115 ROC AUC=0.961 AP=0.356ROC AUC giữ nguyên 0,961, AP gần như không đổi (0,342 so với 0,356). Điều class_weight thay đổi chủ yếu là điểm hoạt động: recall nhảy từ 0,038 lên 0,943, đổi lại precision rơi từ 0,667 xuống 0,061. class_weight gần như tương đương dịch ngưỡng quyết định, nhưng làm xác suất đầu ra bị lệch (không còn được hiệu chuẩn). Nếu cần xác suất tin cậy để tính chi phí kỳ vọng, hãy giữ mô hình gốc và tune ngưỡng, hoặc hiệu chuẩn lại xác suất.
Kỹ thuật lấy mẫu lại (oversampling, undersampling) nằm ngoài scikit-learn lõi, thường dùng thư viện imbalanced-learn; nếu dùng, hãy đặt chúng trong Pipeline để chỉ áp cho phần huấn luyện của mỗi fold, nếu không sẽ rò rỉ dữ liệu như nguyên tắc ở bài về Pipeline và cross-validation.
Đánh giá mô hình bằng cross-validation và khoảng tin cậy
Một con số trên tập kiểm tra chỉ có 53 mẫu dương rất không ổn định. Ta dùng cross-validation phân tầng với nhiều metric trên tập huấn luyện để so hai mô hình, logistic regression và random forest:
[CV 5-fold phân tầng trên tập train]
LogReg roc_auc=0.945±0.012 | average_precision=0.239±0.046 | f1=0.151±0.046
RandomForest roc_auc=0.918±0.032 | average_precision=0.203±0.054 | f1=0.015±0.030Logistic regression nhỉnh hơn ở mọi metric, nhưng cần đọc cùng độ lệch chuẩn: với average precision, 0,239 ± 0,046 so với 0,203 ± 0,054 là chênh lệch nằm trong độ nhiễu. Cũng lưu ý điểm AP trong CV (0,239) thấp hơn nhiều điểm trên tập kiểm tra (0,342): với chỉ vài chục mẫu dương, kết quả đổi mạnh theo lần chia. F1 của random forest (0,015) trong đánh giá mô hình này thấp vì ở ngưỡng 0,5 nó hầu như không dự đoán dương: khi huấn luyện lại trên toàn tập train, nó dự đoán 0 mẫu dương trên 6.000 mẫu kiểm tra (output ngay dưới đây), nhưng điều đó không có nghĩa nó xếp hạng tệ; đó là lý do không nên so mô hình bằng chỉ số phụ thuộc ngưỡng khi chưa tune ngưỡng.
[Random forest, ngưỡng 0.5, tập test] số mẫu dự đoán dương = 0 / 6000 (thật có 53 dương), recall = 0.000Để đo độ bất định ngay trên tập kiểm tra, bootstrap: lấy mẫu có hoàn lại 1.000 lần từ tập kiểm tra và tính lại recall:
[Bootstrap 1000 lần] recall tập test = 0.038, khoảng tin cậy 95%: [0.000, 0.094] (chỉ 53 mẫu dương)Trong đánh giá mô hình này, khoảng tin cậy 95% rộng và chạm 0 cho thấy mức bất định lớn. Khi chỉ có 53 mẫu dương, mọi chỉ số của lớp dương đều dao động mạnh. Khi đánh giá mô hình, hãy báo cáo cả khoảng và thu thập thêm mẫu dương trước khi so sánh các mô hình gần nhau.
Quy trình đánh giá mô hình phân loại đề xuất
- Xác định chi phí nghiệp vụ của fp và fn trước khi đánh giá mô hình.
- Đặt baseline bằng
DummyClassifiervà so mọi kết quả đánh giá mô hình với nó. - Chọn chỉ số chính theo bài toán: dữ liệu lệch và quan tâm lớp hiếm thì dùng average precision và recall ở mức precision chấp nhận được; dữ liệu cân bằng thì ROC AUC và accuracy vẫn hợp lý.
- Đánh giá bằng cross-validation phân tầng, báo trung bình và độ lệch chuẩn.
- Tune ngưỡng trên validation theo chi phí, không dùng mặc định 0,5.
- Chạm tập kiểm tra một lần và báo cáo kèm khoảng tin cậy.
- Giám sát sau triển khai: tính lại các chỉ số định kỳ vì tỉ lệ lớp và phân phối đặc trưng đổi theo thời gian.
Khi nào chỉ số đánh giá mô hình thông dụng không đủ
- Nhiều lớp:
average="macro"coi mọi lớp ngang nhau,"weighted"theo tần suất; nói rõ lựa chọn trong báo cáo. - Xếp hạng theo top-k: nếu đội kiểm tra chỉ xử lý được 100 ca mỗi ngày, precision@100 hữu ích hơn mọi AUC.
- Xác suất phải đáng tin (định giá rủi ro, chi phí kỳ vọng): cần thêm chỉ số hiệu chuẩn như Brier score và đường calibration, vì AUC không đo xác suất có đúng không.
- Dữ liệu đổi theo thời gian hoặc nhóm: chia theo thời gian hoặc nhóm và đánh giá riêng từng phân khúc khách hàng.
Lỗi thường gặp khi đánh giá mô hình
- Đánh giá mô hình chỉ bằng accuracy trên dữ liệu lệch: baseline vô dụng cũng đạt 99,12% trong ví dụ trên.
- Dùng ngưỡng 0,5 mặc định rồi kết luận mô hình tệ khi recall thấp: có thể mô hình xếp hạng tốt (ROC AUC 0,96) mà chỉ ngưỡng sai.
- Chọn ngưỡng trên tập kiểm tra: rò rỉ; chọn trên validation hoặc dùng cross-validation.
- Tin ROC AUC khi lớp dương rất hiếm: nó gần như không đổi trong khi AP giảm mạnh.
- So sánh mô hình bằng chênh lệch nhỏ hơn độ lệch chuẩn hoặc trên quá ít mẫu dương.
- Nhầm chiều tham số: các hàm metric nhận
(y_true, y_pred)theo thứ tự đó; đảo chỗ làm precision và recall hoán đổi mà không báo lỗi. Với ROC AUC và AP, truyền xác suất của lớp dương (predict_proba(X)[:, 1]), không phải nhãn đã ngưỡng hoá. - Quên
stratifykhi chia dữ liệu lệch: có thể test không có mẫu dương nào.
Checklist đánh giá mô hình
- Khi đánh giá mô hình, luôn xem confusion matrix, đừng chỉ nhìn một con số.
- So với
DummyClassifier; trên dữ liệu lệch, dùng balanced accuracy, recall, precision, average precision. - Đọc ROC AUC như chất lượng xếp hạng, đọc AP như chất lượng danh sách cảnh báo.
- Chọn ngưỡng theo chi phí nghiệp vụ trên validation hoặc trên xác suất từ
cross_val_predict. class_weightchủ yếu dịch điểm hoạt động; xác suất đầu ra không còn hiệu chuẩn.- Báo cáo đánh giá mô hình kèm trung bình ± độ lệch chuẩn hoặc khoảng tin cậy; ghi số mẫu dương.
Mã nguồn đầy đủ trong code/s7-12 (eval_demo.py, output.txt, requirements.txt), chạy bằng python eval_demo.py. Mọi số liệu đánh giá mô hình trong bài là output thật từ Apple M4, 16 GB, scikit-learn 1.2.0, NumPy 1.23.5, hạt giống cố định; dữ liệu giả lập, hệ số chi phí 50 và 1 là giả định minh hoạ.
Bài tiếp theo
Feature engineering cho dữ liệu bảng: mã hoá, chuẩn hoá và rò rỉ dữ liệu. Bước tiếp theo là cải thiện thứ được đo: biến đổi đặc trưng đúng cách, tránh rò rỉ, và kiểm tra bằng các chỉ số trong bài này.



