ML 5 — Đánh giá mô hình & chỉ số
ML 5 — Đánh giá mô hình & chỉ số
Một đội mô hình khoe với sếp: "Model phát hiện gian lận của em đạt accuracy 99.4%". Nghe rất ấn tượng — cho đến khi ai đó chỉ ra rằng chỉ 0.5% giao dịch là gian lận, nên một model chỉ luôn đoán "không gian lận" cũng đạt 99.5% accuracy mà chẳng bắt được vụ nào. Con số đẹp đã che giấu một model vô dụng.
Đây là bài học cốt lõi của đánh giá mô hình: bạn cải thiện thứ bạn đo, nên nếu đo sai, bạn tối ưu sai mục tiêu. Chọn metric là một quyết định sản phẩm và kinh doanh, không phải chi tiết kỹ thuật. Bài này đi qua cách chia dữ liệu để đo trung thực, các chỉ số cho phân loại và hồi quy, cách chọn ngưỡng quyết định theo chi phí, và cách kiểm tra xem xác suất mô hình có đáng tin để ra quyết định hay không.
Bài này nối tiếp Quy trình ML (bước "đánh giá" trong vòng lặp) và Cây & ensemble (các model ta sẽ chấm điểm). Xử lý mất cân bằng và tuning để lại cho bài Imbalanced & tuning.
Vì sao đo đúng lại quan trọng đến vậy
Metric là hàm mục tiêu ẩn của cả dự án. Bạn báo cáo F1, cả đội sẽ cày để F1 lên; bạn báo cáo accuracy, cả đội tối ưu accuracy — kể cả khi accuracy không phản ánh giá trị thật cho doanh nghiệp. Nguyên tắc: metric phải khớp với bài toán và chi phí kinh doanh của lỗi.
Muốn chọn metric đúng, hãy hỏi: lỗi nào tốn kém hơn? Bỏ sót một vụ gian lận (mất tiền thật) khác hoàn toàn với làm phiền một khách hàng thật (chặn nhầm giao dịch hợp lệ). Hai loại lỗi này có chi phí khác nhau, nên không thể gộp chung vào một con số "tỷ lệ đúng" duy nhất.
Chia dữ liệu & cross-validation
Trước khi nói đến chỉ số, phải nói đến đo trên tập nào. Nguyên tắc bất di bất dịch: không bao giờ đánh giá trên dữ liệu đã dùng để huấn luyện. Model có thể "học thuộc" tập train và cho điểm cao giả tạo (overfit).
Train / validation / test:
- Train — để model học tham số.
- Validation — để chọn siêu tham số, so sánh model, quyết định ngưỡng.
- Test — chỉ đụng vào một lần cuối cùng để ước lượng hiệu năng thật. Nếu bạn tinh chỉnh dựa trên test, test đã bị "rò rỉ" và không còn trung thực nữa.
k-fold cross-validation. Một lần chia train/val duy nhất phụ thuộc may rủi: nếu tập val vô tình chứa toàn ca dễ, điểm sẽ đẹp ảo. Cross-validation chia dữ liệu thành k phần (fold), lần lượt lấy mỗi phần làm val còn k-1 phần còn lại làm train, rồi lấy trung bình k điểm. Kết quả ổn định hơn và kèm theo độ lệch chuẩn, cho biết model dao động nhiều hay ít giữa các lần chia.
- Stratified k-fold giữ tỷ lệ lớp trong mỗi fold giống tập gốc — bắt buộc khi dữ liệu mất cân bằng, nếu không có fold có thể thiếu hẳn lớp hiếm.
- Time-series split dùng cho dữ liệu theo thời gian: luôn train trên quá khứ, đánh giá trên tương lai, không bao giờ để model "nhìn thấy" tương lai. Chia ngẫu nhiên với dữ liệu thời gian là một dạng rò rỉ tinh vi — model học từ ngày mai để đoán hôm nay.
from sklearn.model_selection import cross_val_score, StratifiedKFold # (minh hoạ)
from sklearn.ensemble import RandomForestClassifier
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(
RandomForestClassifier(random_state=42),
X_train, y_train,
cv=cv, scoring="roc_auc"
)
print(f"ROC-AUC: {scores.mean():.3f} ± {scores.std():.3f}")
# In cả trung bình lẫn độ lệch: model càng ổn định, độ lệch càng nhỏ
Cross-validation gắn thẳng vào workflow: dùng nó để so sánh các phương án trước khi chốt, rồi mới đánh giá lần cuối trên test.
Phân loại: từ ma trận nhầm lẫn
Mọi chỉ số phân loại nhị phân đều bắt nguồn từ confusion matrix — bảng đối chiếu dự đoán với thực tế:
| Thực tế: Dương | Thực tế: Âm | |
|---|---|---|
| Đoán: Dương | TP (đúng dương) | FP (báo động giả) |
| Đoán: Âm | FN (bỏ sót) | TN (đúng âm) |
- TP (True Positive): đoán dương, đúng là dương — bắt trúng gian lận.
- FP (False Positive): đoán dương, thực ra âm — báo động giả, chặn nhầm khách hàng tốt.
- FN (False Negative): đoán âm, thực ra dương — bỏ sót, để lọt vụ gian lận.
- TN (True Negative): đoán âm, đúng là âm.
Từ bốn con số này, các chỉ số quan trọng được suy ra:
Accuracy = (TP+TN) / tổng. Tỷ lệ đoán đúng nói chung. Trực quan nhưng là cái bẫy khi mất cân bằng — như ví dụ 99.5% đầu bài. Với lớp hiếm, accuracy gần như vô nghĩa.
Precision = TP / (TP+FP). Trong số những ca model báo là dương, bao nhiêu thực sự dương. Precision cao = ít báo động giả. Quan trọng khi FP đắt (mỗi lần chặn nhầm là một khách hàng bực mình gọi tổng đài).
Recall (độ nhạy) = TP / (TP+FN). Trong số những ca thực sự dương, model bắt được bao nhiêu. Recall cao = ít bỏ sót. Quan trọng khi FN đắt (bỏ sót một vụ gian lận lớn, hoặc một khoản vay sắp vỡ nợ).
F1 = trung bình điều hoà của precision và recall. Cân bằng hai chỉ số; F1 chỉ cao khi cả hai cùng cao. Dùng khi bạn cần một con số tổng hợp và cả FP lẫn FN đều đáng ngại.
Specificity = TN / (TN+FP). Tỷ lệ nhận đúng lớp âm — cặp đối xứng của recall.
ROC-AUC vs PR-AUC. Hai chỉ số này đánh giá model trên mọi ngưỡng thay vì một ngưỡng cố định:
- ROC-AUC đo khả năng phân biệt hai lớp qua đường cong TPR (recall) theo FPR. Giá trị 0.5 = đoán mò, 1.0 = hoàn hảo. Điểm mạnh: ít nhạy với tỷ lệ mất cân bằng. Điểm yếu: chính vì "phần âm" quá đông, ROC có thể trông đẹp ngay cả khi model tạo ra rất nhiều FP so với số TP hiếm hoi.
- PR-AUC (average precision) dựa trên đường cong precision-recall, tập trung vào lớp dương hiếm. Khi dữ liệu mất cân bằng mạnh và bạn quan tâm đến lớp thiểu số, PR-AUC thường phản ánh trung thực hơn những gì bạn thực sự cần đo. Lưu ý: PR-AUC nhạy với tỷ lệ mất cân bằng (baseline của nó chính bằng tỷ lệ lớp dương), nên đừng so PR-AUC giữa các tập có tỷ lệ khác nhau. Chi tiết cách xử lý mất cân bằng ở bài Imbalanced & tuning.
Log loss (cross-entropy) phạt model không chỉ khi đoán sai lớp, mà khi tự tin sai — dự đoán xác suất 0.99 cho một ca thực ra âm bị phạt rất nặng. Hữu ích khi bạn cần chất lượng của chính xác suất, không chỉ nhãn cuối.
from sklearn.metrics import classification_report, roc_auc_score, average_precision_score # (minh hoạ)
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1] # xác suất lớp dương
print(classification_report(y_test, y_pred)) # precision/recall/F1 từng lớp
print("ROC-AUC:", roc_auc_score(y_test, y_proba))
print("PR-AUC :", average_precision_score(y_test, y_proba)) # tốt cho mất cân bằng
Ngưỡng quyết định (threshold)
Đây là mắt xích hay bị bỏ qua nhất. Đa số model không xuất ra nhãn mà xuất ra xác suất (0–1). Việc biến xác suất thành nhãn cần một ngưỡng. Mặc định của thư viện là 0.5 — nhưng 0.5 hầu như không bao giờ là lựa chọn đúng cho bài toán thật.
Khi tăng ngưỡng, model kỹ tính hơn: precision tăng, recall giảm (báo dương ít hơn nhưng chắc hơn, đồng thời bỏ sót nhiều hơn). Hạ ngưỡng thì ngược lại. Đây là đánh đổi precision-recall thuần tuý, và điểm cân bằng đúng phụ thuộc vào chi phí kinh doanh của từng loại lỗi, không phải một hằng số toán học.
import numpy as np # (minh hoạ)
from sklearn.metrics import precision_recall_curve
prec, rec, thr = precision_recall_curve(y_test, y_proba)
# Ví dụ: nghiệp vụ yêu cầu recall >= 0.90 (không bỏ sót quá 10% gian lận)
ok = rec[:-1] >= 0.90
best = np.argmax(prec[:-1][ok]) # trong các ngưỡng đạt recall, chọn precision cao nhất
chosen = thr[ok][best]
print(f"Ngưỡng chọn: {chosen:.3f} | precision {prec[:-1][ok][best]:.3f}")
y_label = (y_proba >= chosen).astype(int) # áp ngưỡng nghiệp vụ, không dùng 0.5
Trong bài toán tín dụng, ngưỡng này chính là cutoff để duyệt/từ chối hồ sơ — chọn nó là bài toán tối ưu lợi nhuận, chi tiết ở Credit scorecard.
Calibration: xác suất có "đúng nghĩa xác suất" không?
Một chuyện tinh tế: model có thể phân biệt hai lớp rất tốt (ROC-AUC cao) mà xác suất nó xuất ra vẫn sai lệch. Khi model nói "xác suất vỡ nợ 20%", ta muốn điều đó nghĩa là: trong 100 hồ sơ được chấm 20%, thực tế có khoảng 20 hồ sơ vỡ nợ. Nếu thực tế là 45, xác suất đó vô dụng cho việc ra quyết định dựa trên kỳ vọng.
Đo calibration bằng:
- Reliability curve (calibration curve): vẽ xác suất dự đoán (trục X) theo tần suất thực tế (trục Y). Đường chéo 45° = hiệu chỉnh hoàn hảo; đường cong nằm dưới = model quá tự tin.
- Brier score: bình phương trung bình sai lệch giữa xác suất dự đoán và kết quả thực (0/1). Càng thấp càng tốt.
Sửa bằng hiệu chỉnh hậu kỳ với CalibratedClassifierCV:
- Platt scaling (
method="sigmoid") — hợp khi ít dữ liệu hiệu chỉnh và đường cong có dạng sigmoid. - Isotonic regression (
method="isotonic") — linh hoạt hơn, sửa được mọi biến dạng đơn điệu, nhưng dễ overfit khi ít dữ liệu.
from sklearn.calibration import CalibratedClassifierCV, calibration_curve # (minh hoạ)
from sklearn.metrics import brier_score_loss
cal = CalibratedClassifierCV(model, method="isotonic", cv=5)
cal.fit(X_train, y_train)
p_cal = cal.predict_proba(X_test)[:, 1]
print("Brier:", brier_score_loss(y_test, p_cal)) # càng thấp càng tốt
frac_pos, mean_pred = calibration_curve(y_test, p_cal, n_bins=10) # để vẽ reliability curve
Calibration đặc biệt quan trọng khi xác suất chính là đầu ra dùng để ra quyết định — ví dụ PD (Probability of Default) trong tín dụng, nơi ta nhân xác suất với số tiền để tính tổn thất kỳ vọng.
Hồi quy: đo sai số dự đoán số
Với bài toán dự đoán giá trị liên tục (giá nhà, doanh số, hạn mức), các chỉ số chính:
- MAE (Mean Absolute Error): trung bình trị tuyệt đối sai số. Cùng đơn vị với biến mục tiêu, dễ diễn giải ("sai trung bình 12 triệu"), ít nhạy với ngoại lai.
- MSE / RMSE (Root Mean Squared Error): RMSE cùng đơn vị với mục tiêu nhưng phạt lỗi lớn nặng hơn (bình phương sai số). Chọn RMSE khi các sai số lớn đặc biệt tai hại; chọn MAE khi mọi sai số quan trọng như nhau và dữ liệu có ngoại lai.
- R² (hệ số xác định): tỷ lệ phương sai được model giải thích. R²=1 hoàn hảo, R²=0 ngang với đoán trung bình, âm nghĩa là tệ hơn cả đoán trung bình. Cho cảm nhận "tốt tương đối" nhưng không cho biết sai số tuyệt đối.
- MAPE (Mean Absolute Percentage Error): sai số theo phần trăm, dễ so sánh giữa các thang đo — nhưng nổ tung khi giá trị thật gần 0 và phạt lỗi vượt/thiếu bất đối xứng.
Ngoài con số, hãy làm residual analysis: vẽ phần dư (thực tế − dự đoán) theo giá trị dự đoán. Nếu phần dư phân tán ngẫu nhiên quanh 0 là tốt; nếu có hình dạng (hình phễu, đường cong) nghĩa là model bỏ sót một cấu trúc nào đó.
Multi-class: gộp chỉ số thế nào
Với nhiều lớp, precision/recall/F1 được tính cho từng lớp rồi gộp lại theo ba cách:
- Macro: trung bình đơn giản của các lớp — mỗi lớp cân nhau, nên lớp hiếm có tiếng nói ngang lớp phổ biến. Chọn khi bạn quan tâm lớp hiếm.
- Weighted: trung bình có trọng số theo số mẫu mỗi lớp — bị chi phối bởi lớp đông.
- Micro: gộp toàn bộ TP/FP/FN rồi mới tính — với phân loại đơn nhãn, micro-F1 bằng đúng accuracy.
Cạm bẫy thường gặp
- Đánh giá trên tập train: điểm cao ảo do overfit. Luôn báo cáo trên val/test.
- Rò rỉ dữ liệu (leakage): đặc trưng vô tình chứa thông tin tương lai hoặc chính đáp án; hoặc scale/impute trước khi chia dữ liệu. Dùng
Pipelinevà fit mọi phép biến đổi chỉ trên train. - Không so với baseline: ROC-AUC 0.82 là tốt hay tệ? Phải so với một baseline ngây thơ (đoán lớp đa số, hồi quy tuyến tính đơn giản) mới biết model có tạo ra giá trị không.
- Quá tin một metric: một con số không kể hết câu chuyện. Hãy nhìn confusion matrix, đường PR, và calibration cùng lúc.
- Tập test quá nhỏ: vài trăm mẫu cho khoảng tin cậy rất rộng; chênh lệch 1-2% giữa hai model có thể chỉ là nhiễu. Cân nhắc độ lệch chuẩn từ cross-validation.
Use case thực tế: model gian lận / vỡ nợ mất cân bằng
Giả sử bài toán phát hiện gian lận, tỷ lệ dương chỉ 0.5%. Cách đánh giá đúng:
- Bỏ accuracy. Nó vô nghĩa ở đây (đoán "không gian lận" luôn đạt 99.5%).
- Metric xếp hạng model: PR-AUC, vì nó tập trung vào lớp gian lận hiếm và không bị "phần âm" khổng lồ đánh lừa như ROC-AUC.
- Chọn ngưỡng theo chi phí, không dùng 0.5. Đặt giả định: bỏ sót một vụ gian lận (FN) tốn trung bình 5 triệu; chặn nhầm một khách hàng tốt (FP) tốn 50 nghìn (chi phí xử lý + phiền khách). Vì FN đắt gấp ~100 lần FP, ta hạ ngưỡng để ưu tiên recall: chấp nhận nhiều báo động giả để không bỏ sót. Duyệt các ngưỡng trên đường PR và chọn ngưỡng tối thiểu hoá tổng chi phí kỳ vọng = FN·5tr + FP·50k.
- Kiểm calibration nếu xác suất được dùng để xếp hàng đợi ưu tiên điều tra thủ công — cần xác suất đáng tin để phân bổ nguồn lực.
- Báo cáo confusion matrix ở ngưỡng đã chọn, không chỉ một con số, để cả đội thấy đánh đổi thật.
Với model vỡ nợ tín dụng, tư duy tương tự nhưng ngưỡng chính là cutoff duyệt vay, và xác suất (PD) phải được calibrate vì nó đi vào công thức tổn thất kỳ vọng — xem Credit scorecard.
Đánh giá LLM khác gì?
Mọi thứ trên đây giả định đầu ra là nhãn hoặc số so được với đáp án đúng cố định. Đầu ra của LLM là văn bản tự do — không có một "nhãn đúng" duy nhất, và cùng một prompt có thể cho kết quả khác nhau mỗi lần. Vì thế eval LLM dùng cách chấm khác: exact/heuristic match, LLM-as-judge, đo hallucination và faithfulness. Chi tiết ở LLM eval.
Ghi nhớ
- Đo đúng thì cải thiện đúng. Metric là hàm mục tiêu ẩn; chọn nó theo chi phí kinh doanh của lỗi, không theo thói quen.
- Không bao giờ đánh giá trên train. Dùng validation để chỉnh, test một lần cuối. Cross-validation cho kết quả ổn định hơn và kèm độ dao động.
- Accuracy là cái bẫy khi mất cân bằng. Dùng precision/recall/F1, và PR-AUC cho lớp hiếm.
- Ngưỡng 0.5 hầu như luôn sai. Chọn ngưỡng theo đánh đổi precision-recall và chi phí thật.
- Calibration quyết định xác suất có dùng được để ra quyết định (PD) hay không — kiểm bằng reliability curve và Brier.
- Hồi quy: RMSE phạt lỗi lớn, MAE bền với ngoại lai, R² cho cảm nhận tương đối; luôn xem residual.
- Luôn so với baseline, nhìn nhiều chỉ số, và cẩn thận với leakage cùng tập test nhỏ.
Nguồn tham khảo:
Bài viết liên quan
Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.
Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.
Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.
Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.