# Vì sao hồ sơ vay bị từ chối? Dùng SHAP để giải thích, LIME để kiểm tra chéo

> Model chấm điểm tín dụng chạy rất tốt cho tới khi có người hỏi “vì sao hồ sơ này bị loại”, và câu trả lời phải đúng với chính hồ sơ ấy.

Bản gốc: https://fdetimes.net/vi/bach-khoa/giai-thich-du-doan-model-bang-shap-va-lime/

Thử hình dung email từ đội quản trị rủi ro của một công ty cho vay mà bạn đang triển khai model: "Hồ sơ 4127 bị từ chối. Khách khiếu nại. Chúng tôi cần lý do cụ thể trước thứ Sáu." Model là một XGBoost với vài chục feature, AUC đẹp, đã qua backtest. Không ai trong phòng họp trả lời được vì sao chính hồ sơ này bị loại.

Đây là một nhiệm vụ FDE rất điển hình: model đúng về mặt thống kê nhưng đứng trước một khách hàng cụ thể thì không giải thích được. Ở Mỹ, trả lời được câu hỏi này là nghĩa vụ pháp lý của bên cho vay, không phải chuyện tùy ý.

Circular 2022-03 của CFPB, ban hành ngày 26/5/2022, khẳng định yêu cầu nêu lý do từ chối theo ECOA và Regulation B áp dụng cho mọi quyết định tín dụng, dù được đưa ra bằng công nghệ gì.

CFPB còn nói rõ hơn: chủ nợ không được dùng thuật toán phức tạp nếu vì thế mà không đưa ra được lý do cụ thể và chính xác. Họ cũng không thể viện cớ rằng công nghệ mình dùng để xét hồ sơ quá phức tạp, quá khó hiểu.

Nếu bạn làm cho fintech phục vụ thị trường Mỹ, hay cho một ngân hàng đang tham khảo chuẩn này, kỹ năng ở bài này sẽ được đem ra dùng thật.

## SHAP trả lời "mỗi feature đẩy hồ sơ đi bao xa"

SHAP, do Lundberg và Lee công bố năm 2017, gán cho mỗi feature một mức đóng góp vào từng dự đoán cụ thể. Cách đọc hay dùng là hình dung model có một mức nền, tạm coi là xác suất vỡ nợ trung bình trên một tập dữ liệu tham chiếu.

Từ mức nền làm điểm xuất phát, bạn xem từng feature của hồ sơ đẩy lên hay kéo xuống bao nhiêu. Nhưng đừng mặc định cách đọc đó đúng: hãy tự kiểm tra xem mức nền cộng các đóng góp có khớp với xác suất model trả về cho hồ sơ ấy không.

Khi phép cộng khớp, bạn có một lời giải thích mà đội rủi ro tự dò lại được từng con số, và đó là thứ cần cho lý do từ chối.

Với model cây như XGBoost hay LightGBM, `shap.TreeExplainer` dùng Tree SHAP, theo tài liệu của thư viện là một phương pháp nhanh và chính xác cho model cây và tổ hợp cây.

Bạn không phải xấp xỉ, cũng không phải chờ hàng giờ.

LIME, của Ribeiro, Singh và Guestrin từ năm 2016, đi đường khác. Nó tạo ra nhiều biến thể của hồ sơ bằng cách xáo trộn giá trị, hỏi model gốc dự đoán cho từng biến thể, rồi fit một model đơn giản trên tập dữ liệu mới này.

Mẫu nào càng gần hồ sơ gốc thì càng được tính trọng số cao. LIME hỗ trợ classifier trên dữ liệu bảng, gồm cả cột số lẫn cột phân loại, nên hợp với dữ liệu tín dụng.

## Làm trọn ca 4127

Đoạn code dưới đây tính SHAP ở thang xác suất với chế độ `interventional`. Theo tài liệu SHAP, chế độ này cần một tập dữ liệu nền, nên ta lấy mẫu 500 hồ sơ từ tập train.

```python
import shap
from lime.lime_tabular import LimeTabularExplainer

background = X_train.sample(500, random_state=0)
explainer = shap.TreeExplainer(
model,
data=background,
feature_perturbation="interventional",
model_output="probability",
)
x = X_test.loc[[4127]]
sv = explainer(x)
print(sv.base_values[0], sv.values[0].sum())
```

Giả sử kết quả cho mức nền 0.12, tổng các đóng góp là +0.29, và khi so với `predict_proba` thì xác suất vỡ nợ model trả về đúng là 0.41, cao hơn ngưỡng duyệt 0.30 mà công ty đặt ra.

Tách theo feature: tỷ lệ nợ trên thu nhập 52% góp +0.14; ba lần trả chậm trong 12 tháng góp +0.11; mới làm công việc hiện tại 4 tháng góp +0.06; thu nhập góp +0.03; còn lịch sử tín dụng 9 năm kéo xuống −0.05.

Cộng lại: 0.14 + 0.11 + 0.06 + 0.03 − 0.05 = 0.29.

Phép cộng này là thứ đầu tiên bạn kiểm tra. Nếu `base_values + values.sum()` không khớp với `model.predict_proba`, hãy xem lại mình đang giải thích output nào, chẳng hạn log-odds thay vì xác suất, trước khi đọc tiếp bất cứ con số nào.

Tiếp theo chạy LIME trên cùng hồ sơ để đối chiếu:

```python
lime_exp = LimeTabularExplainer(
X_train.values,
feature_names=list(X_train.columns),
class_names=["tra_du", "vo_no"],
mode="classification",
random_state=0,
)
e = lime_exp.explain_instance(
x.values[0], model.predict_proba, num_features=5
)
print(e.as_list())
# [('dti > 0.45', 0.21), ('late_payments_12m > 2', 0.17),
#  ('employment_months <= 6', 0.08), ('credit_age_years > 8', -0.06),
#  ('income <= 9000', 0.02)]
```

## So thứ hạng và dấu, đừng so độ lớn

Output của SHAP và output của LIME vừa in ra đo bằng hai thước khác nhau. Trong ví dụ này, các giá trị SHAP đã được kiểm tra là cộng lại khớp đúng khoảng cách từ mức nền 0.12 tới dự đoán 0.41.

Trọng số LIME là hệ số của một model tuyến tính cục bộ fit trên các điều kiện như "dti > 0.45", nên cộng chúng lại chẳng ra con số nào có ý nghĩa.

Vì thế khi đối chiếu, chỉ so hai thứ: feature nào đứng đầu và feature đó đẩy theo chiều nào.

| Feature | SHAP (hạng, giá trị) | LIME (hạng, trọng số) | Khớp? |
|---|---|---|---|
| Nợ trên thu nhập | 1, +0.14 | 1, +0.21 | Có |
| Trả chậm 12 tháng | 2, +0.11 | 2, +0.17 | Có |
| Thời gian làm việc | 3, +0.06 | 3, +0.08 | Có |
| Tuổi lịch sử tín dụng | kéo xuống, −0.05 | kéo xuống, −0.06 | Có |

Với ca 4127, ba lý do đầu khớp cả thứ hạng lẫn dấu. Lúc đó bạn có thể viết cho khách: tỷ lệ nợ trên thu nhập cao, có lịch sử trả chậm gần đây, thời gian làm công việc hiện tại còn ngắn. Ba câu cụ thể, mỗi câu gắn với một con số trong hồ sơ.

**Điểm mấu chốt:** Một lý do từ chối chỉ đáng gửi đi khi nó đứng vững qua hai phương pháp và một lần đổi dữ liệu.

## Năm bước để làm ở khách hàng

Đầu tiên, hỏi xem đội rủi ro muốn giải thích ở thang nào và ngưỡng duyệt là bao nhiêu, vì một lý do chỉ có nghĩa khi đặt cạnh ngưỡng. Sau đó chạy SHAP và kiểm tra phép cộng có khớp với output thật của model không. Kế tiếp, chạy LIME với vài random seed khác nhau rồi so thứ hạng và dấu với SHAP.

Bước thứ tư hay bị bỏ qua nhất: kiểm chứng trên chính model. Thử hạ tỷ lệ nợ trên thu nhập của hồ sơ 4127 xuống mức trung vị rồi gọi lại `predict_proba`.

Xác suất phải giảm rõ rệt; nếu không giảm, lý do số một của bạn chưa chính xác như CFPB đòi hỏi. Cuối cùng, ánh xạ tên feature sang câu chữ khách đọc hiểu được, và cho đội pháp chế duyệt bảng ánh xạ này một lần thay vì duyệt từng thư.

## Ba cái bẫy khiến lý do sai mà trông vẫn đúng

Bẫy đầu tiên là feature tương quan. Tài liệu SHAP nhắc rằng giá trị SHAP dựa trên kỳ vọng có điều kiện, nên bạn phải chọn cách xử lý các feature phụ thuộc nhau, như thu nhập và số tiền vay.

Chế độ `interventional` cần tập nền; chế độ `tree_path_dependent` không cần, vì nó lấy số mẫu train đi qua mỗi lá làm phân phối nền. Hai chế độ có thể chia đóng góp khác nhau, nên hãy chốt một chế độ và ghi lại lý do chọn.

Bẫy kế tiếp là sự dao động của LIME. Theo cuốn Interpretable Machine Learning của Christoph Molnar, chọn vùng lân cận vẫn là bài toán chưa có lời giải khi dùng LIME với dữ liệu bảng. Vì vậy đừng dùng một lần chạy LIME làm bằng chứng; nếu top 3 thay đổi theo seed, nó chỉ là tín hiệu tham khảo.

Cái bẫy cuối nghiêm trọng hơn. Một nghiên cứu năm 2019 cho thấy các kỹ thuật giải thích dựa trên xáo trộn đầu vào như LIME và SHAP có thể bị một model được thiết kế đối nghịch đánh lừa, và các tác giả kết luận chúng không đáng tin.

Đó là lý do bước kiểm chứng bằng cách đổi dữ liệu thật ở trên là bắt buộc, không phải tùy chọn.

## Thể hiện kỹ năng này trong CV thế nào

Khi đọc JD của fintech hay ngân hàng, hãy để ý các cụm như "model explainability", "reason codes", "adverse action". Trong CV, đừng chỉ ghi "dùng SHAP".

Hãy viết điều bạn đã giao: một pipeline sinh lý do từ chối cho từng hồ sơ, có kiểm tra phép cộng, đối chiếu với LIME và kiểm chứng bằng counterfactual.

Nếu ứng tuyển vào mảng tín dụng, hãy chuẩn bị kể lại từng bước đó qua một hồ sơ cụ thể, kèm những con số bạn đã kiểm tra.

Lần tới có email hỏi "vì sao hồ sơ này bị loại", bạn không cần mở slide về độ chính xác của model nữa. Chỉ cần gửi phép cộng từ 0.12 lên 0.41, kèm bằng chứng rằng mỗi số hạng trong đó đều đã được kiểm chứng.

**Thử ngay tuần này:**

- Train một XGBoost trên bộ dữ liệu tín dụng công khai, chọn một hồ sơ bị từ chối và kiểm tra base value cộng tổng SHAP có khớp với xác suất dự đoán không
- Chạy LIME trên cùng hồ sơ với 5 random_state khác nhau và ghi lại top 3 lý do có giữ nguyên qua các lần chạy không
- Viết bảng ánh xạ từ tên feature sang câu lý do từ chối mà khách hàng đọc là hiểu, rồi kiểm chứng từng lý do bằng cách đổi giá trị và chạy lại model

## Nguồn

- [Circular 2022-03: Adverse action notification requirements in connection with credit decisions based on complex algorithms (CFPB)](https://www.consumerfinance.gov/compliance/circulars/circular-2022-03-adverse-action-notification-requirements-in-connection-with-credit-decisions-based-on-complex-algorithms/)

- ["Why Should I Trust You?": Explaining the Predictions of Any Classifier](https://arxiv.org/abs/1602.04938)

- [Interpretable Machine Learning – LIME (Christoph Molnar)](https://christophm.github.io/interpretable-ml-book/lime.html)

- [marcotcr/lime (GitHub README)](https://github.com/marcotcr/lime)

- [A Unified Approach to Interpreting Model Predictions](https://arxiv.org/abs/1705.07874)

- [shap.TreeExplainer — SHAP documentation](https://shap.readthedocs.io/en/stable/generated/shap.TreeExplainer.html)

- [Fooling LIME and SHAP: Adversarial Attacks on Post hoc Explanation Methods](https://arxiv.org/abs/1911.02508)
