Trần Quốc Việt
← All posts

2026-04-22

Banking Domain

Scorecard 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

View
Lang

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ếnBin (nhóm)Điểm
Thu nhập0 – 5 triệu/tháng−25
5 – 15 triệu/tháng+10
> 15 triệu/tháng+35
Lịch sử quá hạnKhô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)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

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  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   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ánModel phù hợp
Final approve/decline (regulated)Scorecard
Pre-screening, tier phân loạiXGBoost / LightGBM
Fraud detectionXGBoost + rules
Portfolio analytics, pricingBấ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.


Bài liên quan / Related posts