2026-04-22
Banking DomainScorecard tín dụng: Tại sao ngân hàng vẫn dùng mô hình từ thập niên 80?
XGBoost chính xác hơn, nhưng regulator hỏi 'tại sao từ chối khách này?' — scorecard là câu trả lời duy nhất mà ngân hàng có thể đưa ra.
Lý do tồn tại
Interpretability
Nền tảng kỹ thuật
WOE + LogReg
Đơn vị đầu ra
Điểm (Points)
Quy tắc calibration
PDO
Tóm tắt
Scorecard tín dụng vẫn tồn tại năm 2026 không phải vì nó tốt nhất về accuracy — XGBoost hoặc LightGBM đều cho AUC cao hơn. Scorecard tồn tại vì nó là mô hình duy nhất có thể giải thích từng quyết định với regulator, với khách hàng, và với chính risk committee của ngân hàng. Bài này đi từ đầu đến cuối: WOE binning → Logistic Regression → chuyển đổi sang điểm → validate → deploy.
Câu hỏi khởi đầu: Bạn bị từ chối vay. Bạn hỏi tại sao?
Hãy tưởng tượng tình huống: khách hàng nộp đơn vay, bị từ chối, và hỏi "vì sao tôi không được duyệt?" Ngân hàng trả lời "thuật toán quyết định." Câu đó có ổn không?
Ở hầu hết thị trường, không ổn. ECOA (Equal Credit Opportunity Act) ở Mỹ, hay các quy định Basel tương tự ở châu Á, yêu cầu ngân hàng phải cung cấp adverse action notice — lý do cụ thể tại sao bị từ chối. "Thu nhập không đủ" là lý do. "Điểm ML thấp hơn threshold" không phải lý do hợp lệ cho regulator.
Scorecard giải quyết vấn đề này: mỗi biến đóng góp một số điểm, cộng lại ra điểm tổng, điểm thấp → từ chối. Ngân hàng có thể nói "bạn bị từ chối vì thu nhập thấp hơn ngưỡng và lịch sử quá hạn tháng trước" — những thứ có thể đọc được từ bảng điểm.
Scorecard là gì — giải thích bằng bảng điểm học sinh
Hãy nghĩ về bảng chấm điểm học sinh: mỗi môn cho điểm, cộng lại ra điểm tổng kết. Scorecard tín dụng hoạt động y hệt:
| Biến | Bin (nhóm) | Điểm |
|---|---|---|
| Thu nhập | 0 – 5 triệu/tháng | −25 |
| 5 – 15 triệu/tháng | +10 | |
| > 15 triệu/tháng | +35 | |
| Lịch sử quá hạn | Không quá hạn | +20 |
| Quá hạn 1–30 ngày | −10 | |
| Quá hạn > 30 ngày | −40 | |
| Thâm niên công việc | < 6 tháng | −15 |
| 6 – 24 tháng | +5 | |
| > 24 tháng | +20 |
Khách A — Thu nhập 8tr + không quá hạn + 3 năm thâm niên:
10 + 20 + 20 = 50 điểm → Approve
Khách B — Thu nhập 3tr + quá hạn tháng trước + mới đi làm 4 tháng:
−25 + (−10) + (−15) = −50 điểm → Decline
Và khi Khách B hỏi tại sao: "Thu nhập thấp hơn ngưỡng 5tr/tháng, có ghi nhận quá hạn trong 30 ngày gần đây." Rõ ràng, actionable, defensible.
5 bước xây một scorecard
Bước 1: WOE binning cho từng biến
Mỗi biến liên tục (thu nhập, tuổi, thời gian tại job) cần được phân nhóm và mã hóa bằng WOE (Weight of Evidence). WOE đo "nhóm này good/bad ratio so với toàn bộ tập huấn luyện là như thế nào."
Điều quan trọng ở bước này: monotonicity — WOE của thu nhập nên tăng đều theo thu nhập (người thu nhập cao → ít rủi ro hơn). Nếu không monotone, cần merge bins hoặc review logic nghiệp vụ.
Bước 2: Logistic Regression với WOE-encoded features
Sau khi có WOE, chạy Logistic Regression với WOE values làm input. Tại sao Logistic Regression mà không phải mô hình phức tạp hơn?
- Hệ số β (coefficient) của từng biến có thể dịch trực tiếp sang điểm
- Tuyến tính → interpretable hoàn toàn
- Regularization (L1/L2) để tránh overfitting với ít features
Sau khi train, ta có: log-odds = β₀ + β₁·WOE(income) + β₂·WOE(dpd) + β₃·WOE(tenure) + ...
Bước 3: Chuyển đổi coefficient sang điểm — PDO
PDO (Points to Double Odds) là quy ước ngành ngân hàng: "cứ tăng thêm X điểm, odds good/bad tăng gấp đôi."
PDO thông dụng nhất là 20 điểm, base score 600 tại odds 1
. Nghĩa là:- Score 600 = odds good là 1 (50% good)
- Score 620 = odds gấp đôi (67% good)
- Score 640 = odds gấp 4 lần (80% good)
- Score 580 = odds nửa (33% good)
Công thức chuyển đổi từ coefficient sang điểm cho từng bin:
Points_bin = −(β_var × WOE_bin + β₀/n_vars) × Factor + Offset
Trong đó Factor = PDO / ln(2) và Offset được tính để base score đạt đúng odds mong muốn.
Thực tế: thư viện như scorecardpy (Python) hoặc scorecard (R) làm toàn bộ bước này tự động. Điều quan trọng là hiểu PDO để điều chỉnh khi business thay đổi khẩu vị rủi ro.
Bước 4: Validate trên OOT
Sau khi có scorecard, không validate trên cùng tập train hay test thông thường. Dùng OOT (Out-of-Time) — dữ liệu từ period sau period training.
Lý do: credit model suy giảm theo thời gian. Khách hàng 2024 khác 2022. OOT trên 3–6 tháng gần nhất mới reflect được model hoạt động ra sao trong môi trường thực.
Metrics cần check:
- Gini ≥ 35% trên OOT — nếu thấp hơn nhiều so với train, có thể overfit
- KS ≥ 30 — phân biệt good/bad đủ rõ
- PSI < 0.10 giữa train và OOT population — nếu cao, sample definition có vấn đề
Bước 5: Set cutoff và bảng scorecard cuối
Cutoff không phải là điểm "tốt nhất" từ thuần toán học — nó là quyết định kinh doanh:
- Approval rate target (ví dụ: 60%)
- Risk appetite (NPL tối đa chấp nhận được)
- Vintage analysis xem score band nào có delinquency rate như thế nào
Thông thường có 3 zone: Approve (điểm cao) / Review (điểm trung) / Decline (điểm thấp). Review zone cần underwriter quyết định thủ công.
Code thực tế: Xây scorecard end-to-end với Python
import scorecardpy as sc
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
# Bước 1: WOE binning
# monotonic_binning=True enforce xu hướng đơn điệu (thu nhập cao → WOE cao)
bins = sc.woebin(
train_df, y='bad_flag',
x=['monthly_income', 'dpd_max_6m', 'job_tenure_months'],
bin_num_limit=6,
# monotonic_binning=True # bật khi cần enforce
)
# Kiểm tra WOE plot trước khi đi tiếp
sc.woebin_plot(bins['monthly_income'])
# Bước 2: Transform — LUÔN dùng bins từ train, không refit trên test
train_woe = sc.woebin_ply(train_df, bins)
oot_woe = sc.woebin_ply(oot_df, bins) # OOT validation set
feature_cols = [c for c in train_woe.columns if c.endswith('_woe')]
# Bước 3: Logistic Regression với regularization
lr = LogisticRegression(C=0.1, solver='lbfgs', max_iter=500, random_state=42)
lr.fit(train_woe[feature_cols], train_woe['bad_flag'])
# Bước 4: Chuyển về điểm — PDO=20, base=600 tại odds 1:50 (2% bad rate)
card = sc.scorecard(
bins, lr, feature_cols,
points0=600, # base score tại odds0
odds0=1/50, # odds bad:good tại base score (1 bad per 50 goods)
pdo=20 # tăng 20 điểm → odds gấp đôi
)
# Gán điểm cho từng applicant
train_score = sc.scorecard_ply(train_df, card, print_step=0)
oot_score = sc.scorecard_ply(oot_df, card, print_step=0)
# Bước 5: Validate
def gini(y_true, y_score):
return 2 * roc_auc_score(y_true, -y_score) - 1 # âm vì score cao = good
print(f"Train Gini : {gini(train_df['bad_flag'], train_score['score']):.3f}")
print(f"OOT Gini : {gini(oot_df['bad_flag'], oot_score['score']):.3f}")
# Nếu OOT Gini thấp hơn Train > 5pt → có thể overfit, review biến
Tại sao không dùng XGBoost thay scorecard?
Câu trả lời thực: nhiều ngân hàng dùng cả hai.
| Bài toán | Model phù hợp |
|---|---|
| Final approve/decline (regulated) | Scorecard |
| Pre-screening, tier phân loại | XGBoost / LightGBM |
| Fraud detection | XGBoost + rules |
| Portfolio analytics, pricing | Bất kỳ model nào |
XGBoost tốt hơn về AUC — đúng. Nhưng khi regulator yêu cầu "giải thích từng ca từ chối trong 5 năm qua," bạn cần một bảng điểm tĩnh, không phải 300 cây quyết định.
SHAP values giúp interpret XGBoost, nhưng vẫn là post-hoc explanation, không phải built-in explainability. Scorecard là built-in — không cần giải thích thêm.
Pitfalls phổ biến
Quá nhiều biến → overfitting. Scorecard production thường chỉ có 8–15 biến. Resist the urge to add everything just because IV is positive.
Không lock binning table. WOE bins được tính trên training data. Khi deploy, phải dùng đúng bảng bins đó — không recalculate trên production data. Nếu không, scorecard sẽ drift theo population.
PDO calibrate trên tập cũ. Nếu population thay đổi nhiều (ví dụ: mở rộng sang segment mới), PDO cũ không còn đúng nữa. Calibration check định kỳ là cần thiết.
Bỏ qua monotonicity. Một scorecard mà thu nhập cao hơn lại cho điểm thấp hơn sẽ không qua được risk committee review — và họ đúng khi reject nó.
Takeaway
Scorecard không lỗi thời. Nó là ngôn ngữ chung mà data scientist, risk officer, underwriter, regulator, và đôi khi cả khách hàng đều có thể đọc được.
Khi bạn xây một scorecard tốt, bạn không chỉ build một model — bạn build một hợp đồng giải trình giữa ngân hàng và tất cả những bên liên quan. Đó là lý do nó tồn tại từ thập niên 80 và sẽ còn tồn tại lâu hơn nhiều.