Dự án ước lượng xác suất một khoản vay LendingClub đã được giải ngân bị
Charged Off trong toàn bộ vòng đời hợp đồng. Pipeline dùng nhãn
maturity-aware, chỉ sử dụng feature tại thời điểm nộp đơn, chia dữ liệu theo
thời gian, Logistic Regression có calibration và đánh giá trên một tập
Out-of-Time (OOT) hoàn toàn tách biệt.
Đầu ra chính là:
[ P(\text{Charged Off} \mid \text{Funded Loan}) \in [0, 1] ]
Phạm vi của mô hình là danh mục khoản vay lịch sử đã qua thẩm định và đã giải ngân. Mô hình không đại diện cho người nộp đơn chưa được duyệt, không tự động approve/reject, không định giá lãi suất, không tính Expected Loss và không thay thế quy trình tín dụng hoặc Adverse Action Notice.
Mermaid dưới đây là source of truth cho luồng code, cấu hình, artifact và báo
cáo. Các node tương ứng trực tiếp với module trong src/.
flowchart TD
RAW["data/raw/lendingclub_2007_2011.csv"] --> LOAD["src.data.load_data\nSchema + parse issue_d + term"]
LOAD --> MATURITY["src.data.add_maturity_columns\ncontractual maturity <= dataset_as_of_date"]
MATURITY --> TARGET["src.data.create_lifetime_target\nMATURE + Fully Paid/Charged Off"]
TARGET --> SPLIT["src.data.temporal_split_three_blocks"]
SPLIT --> TRAIN["Train\nissue_date < 2011-01-01"]
SPLIT --> CAL["Calibration\n2011-01-01 <= issue_date < 2011-04-01"]
SPLIT --> OOT["OOT Test\nissue_date >= 2011-04-01"]
TRAIN --> TRAIN_FEATURES["build_features(train)\n16 application-time features"]
CAL --> CAL_FEATURES["build_features(calibration)"]
OOT --> OOT_FEATURES["build_features(oot)"]
TRAIN_FEATURES --> CV["src.model.tune_logistic_c\nExpanding temporal CV, select C by PR-AUC"]
TRAIN_FEATURES --> FIT["src.model.make_pipeline\nImpute + scale + one-hot + Logistic Regression"]
CV --> FIT
FIT --> CALIBRATE["CalibratedRiskModel.fit\nSigmoid calibration only if Brier improves"]
CAL_FEATURES --> CALIBRATE
OOT_FEATURES --> OOT_SCORE["Calibrated model\npredict_proba on OOT only"]
CALIBRATE --> OOT_SCORE
OOT_SCORE --> EVAL["src.evaluate + src.analysis\nOOT metrics, decile, drift, cohort, slices"]
EVAL --> REPORTS["reports/*.csv + *.json"]
CALIBRATE --> ARTIFACT["artifacts/risk_model.joblib\nModel + feature contract + policy bounds"]
API_INPUT["API/UI application record"] --> SCORE["src.predict.predict\nProbability + local risk factors"]
ARTIFACT --> SCORE
SCORE --> POLICY["src.policy.get_risk_band\nLOW / MEDIUM / HIGH for display"]
SCORE --> API["app.api\nFastAPI /predict"]
SCORE --> UI["app.streamlit_app\nInteractive scoring"]
Luồng dữ liệu không được đảo thứ tự: dữ liệu phải qua maturity gate trước khi
gán target; feature được tạo sau target nhưng chỉ từ thông tin application-time;
chỉ Train được dùng để chọn C, Calibration chỉ dùng để fit/kiểm tra Sigmoid,
và OOT chỉ dùng để đánh giá cuối cùng. Inference production đọc artifact đã
được lưu, không chạy lại CV hoặc calibration.
src.data.validate_schema yêu cầu các cột đầu vào: id, loan_status,
issue_d, loan_amnt, term, emp_length, home_ownership, annual_inc,
verification_status, purpose, dti, delinq_2yrs, inq_last_6mths,
open_acc, pub_rec, revol_bal, revol_util, total_acc và
earliest_cr_line. loan_status hợp lệ là Fully Paid, Charged Off hoặc
Current; term chỉ nhận 36 hoặc 60 tháng.
Ngày maturity được tính bằng issue_date + term_months. Với
dataset_as_of_date mặc định 2016-01-01, chỉ dòng thỏa cả hai điều kiện sau
mới vào supervised set:
contractual_maturity_date <= dataset_as_of_date;loan_statuslà trạng thái cuối:Fully Paid -> 0,Charged Off -> 1.
Khoản vay Current hoặc chưa đủ maturity bị loại khỏi target nhị phân để tránh
right-censoring. Đây là maturity-based cohort filtering, không phải survival
analysis liên tục.
src.features.build_features luôn trả đúng 16 cột theo đúng thứ tự sau:
loan_amnt, term_months, emp_length_years, home_ownership,
log_annual_income, verification_status, purpose, dti, delinq_2yrs,
inq_last_6mths, open_acc, pub_rec, revol_bal, revolving_utilization,
total_acc, credit_history_years
Các biến hậu quả như total_pymnt, recoveries, last_pymnt_d,
out_prncp và loan_status nằm trong POST_OUTCOME_FEATURES. Các biến proxy
policy/giá như grade, sub_grade, int_rate, installment, addr_state
và issue_month nằm trong POLICY_PROXY_FEATURES. Hai nhóm này không được
đưa vào FEATURE_COLUMNS; policy workflow kiểm tra bất biến này tự động.
temporal_split_three_blocks dùng ba block theo issue_date:
| Block | Điều kiện | Vai trò |
|---|---|---|
| Train | < 2011-01-01 |
Fit và expanding-window CV |
| Calibration | 2011-01-01 <= date < 2011-04-01 |
Fit Sigmoid/Platt độc lập |
| OOT Test | >= 2011-04-01 |
Đánh giá cuối, không chọn model |
tune_logistic_c so sánh C = (0.01, 0.1, 1.0, 10.0) bằng PR-AUC trên các
fold temporal expanding hợp lệ. Pipeline mô hình gồm median imputation và
standardization cho numeric, most-frequent imputation và one-hot encoding cho
categorical, sau đó là Logistic Regression L2. Sigmoid calibrator chỉ được
dùng nếu Brier score trên Calibration không xấu hơn raw score.
Artifact gắn policy hiển thị rủi ro: LOW nếu p < 0.15, MEDIUM nếu
0.15 <= p <= 0.25, và HIGH nếu p > 0.25. Đây chỉ là nhóm hiển thị/xếp
hạng, không phải ngưỡng quyết định tín dụng.
Snapshot báo cáo hiện có trong reports/ cho OOT Test:
| Metric | Giá trị |
|---|---|
| PR-AUC | 0.1892 |
| ROC-AUC | 0.6574 |
| Brier score | 0.0949 |
| Capture@20% | 36.00% |
| Lift@20% | 1.80x |
| Default prevalence | 10.98% |
Đây là kết quả của artifact/report snapshot đang có, không phải cam kết hiệu năng cho dữ liệu hoặc môi trường kinh tế mới. Chi tiết phương pháp và giới hạn nằm trong MODEL_CARD.md; chính sách bảo mật nằm trong SECURITY.md.
loan-default-risk-prediction/
├── .github/workflows/
│ ├── ci.yml # Test, pip check, Docker build, API healthcheck
│ └── policy.yml # Kiểm tra repo, dữ liệu và leakage policy
├── app/
│ ├── api.py # FastAPI: health, info, predict, coefficients
│ └── streamlit_app.py # UI chấm điểm, xem metrics, decile và hệ số
├── artifacts/
│ ├── .gitkeep # Model joblib sinh cục bộ, không commit dữ liệu nhị phân
│ └── risk_model.joblib # Sinh bởi lệnh train nếu có dữ liệu
├── data/
│ ├── raw/ # CSV LendingClub đặt cục bộ, bị gitignore
│ └── external/ # Dữ liệu ngoài tùy chọn, không thuộc pipeline chính
├── reports/ # Metrics, CI, decile, drift, cohort và slice outputs
├── scripts/
│ ├── check_repo_policy.py # Static repository policy check
│ └── prepare_data.py # Kiểm tra schema và maturity trước khi train
├── src/
│ ├── analysis.py # PSI, cohort performance
│ ├── data.py # Schema, maturity gate, target, temporal split
│ ├── evaluate.py # Metrics, decile, bootstrap CI, slice metrics
│ ├── explain.py # Local contributions và global coefficients
│ ├── features.py # Feature contract 16 cột và leakage exclusion
│ ├── model.py # Preprocessing, Logistic Regression, calibration
│ ├── policy.py # Risk-band boundary dùng chung cho API/UI
│ ├── predict.py # Load artifact và inference contract
│ └── train.py # Orchestrator train -> evaluate -> artifact/reports
├── tests/ # Unit và integration tests
├── Dockerfile # Container FastAPI runtime
├── MODEL_CARD.md # Model card
├── README.md # Tài liệu này
├── SECURITY.md # Security và privacy policy
├── pyproject.toml # Package metadata và pytest config
├── requirements.txt # Runtime pins
└── requirements-dev.txt # Runtime pins + pytest/httpx
data/raw/*.csv, artifacts/*.joblib và cache local không được commit. Dataset
LendingClub không phân phối cùng repository; người dùng phải tự tải snapshot
được phép sử dụng và kiểm tra license trước khi chạy.
git clone https://github.com/haminhthong/Loan-Default-Risk-Prediction.git
cd Loan-Default-Risk-Prediction
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
# Linux/macOS
# source .venv/bin/activate
python -m pip install -r requirements-dev.txtĐặt snapshot được cấp phép tại data/raw/lendingclub_2007_2011.csv, sau đó
kiểm tra schema và maturity:
python scripts/prepare_data.pyCó thể truyền đường dẫn và as-of date:
python scripts/prepare_data.py path/to/data.csv 2016-01-01python -m src.trainCác tham số CLI chính: --data, --as-of-date, --output và --report-dir.
Lệnh train ghi artifacts/risk_model.joblib cùng các báo cáo
model_cv.csv, test_metrics.json, bootstrap_ci.json,
target_censoring.csv, decile_reliability.csv, drift_psi.csv,
cohort_performance.csv, model_coefficients.csv và các slice_*.csv nếu
phân khúc có tối thiểu 30 dòng hợp lệ.
python -m pytest -q
python scripts/check_repo_policy.pySau khi train, chạy Streamlit:
streamlit run app/streamlit_app.pyMở http://localhost:8501. API FastAPI chạy bằng:
uvicorn app.api:app --host 0.0.0.0 --port 8000API và Streamlit mặc định đọc artifacts/risk_model.joblib. Có thể đổi đường
dẫn bằng biến môi trường LOAN_RISK_MODEL_PATH; Docker dùng cùng biến này nếu
cần mount artifact ở vị trí khác.
Endpoints:
GET /health: liveness, trả thêmmodel_loaded.GET /v1/info: metadata, feature contract, metrics, policy bounds và split rows.GET /model/coefficients: hệ số toàn cục từ report.POST /predict: batch tối đa 1.000 records.POST /v1/risk/score: alias tương thích của/predict.
Nếu đặt LOAN_API_KEY, các endpoint ngoài /health yêu cầu header
X-API-Key. Ví dụ request tối thiểu:
{
"records": [
{
"loan_amnt": 10000,
"term_months": 36,
"home_ownership": "RENT",
"annual_inc": 60000,
"verification_status": "Verified",
"purpose": "debt_consolidation",
"revolving_utilization": 0.45,
"credit_history_years": 8.0
}
]
}Schema LoanApplication bắt buộc loan_amnt, term_months,
home_ownership, annual_inc, verification_status và purpose. Các trường
còn lại là optional và được phép thiếu để pipeline impute. API chỉ nhận
term_months là 36 hoặc 60; home_ownership là RENT, OWN, MORTGAGE hoặc
OTHER; verification_status là Verified, Source Verified hoặc
Not Verified. Numeric fields có validation không âm; loan_amnt và
annual_inc phải lớn hơn 0; emp_length_years nằm trong [0, 10], dti trong
[0, 100] và revolving_utilization trong [0, 1]. Field ngoài schema bị từ chối
bởi extra="forbid".
Response chuẩn chứa lifetime_chargeoff_probability, risk_band,
top_risk_factors và scored_at. Artifact phải tồn tại; nếu chưa train API
vẫn có thể healthcheck nhưng /predict sẽ trả 503.
Build image và chạy healthcheck:
docker build -t loan-default-risk:test .
docker run --rm -p 8000:8000 loan-default-risk:testVì dataset và joblib bị loại khỏi image, muốn chấm điểm bằng container cần mount artifact đã train:
docker run --rm -p 8000:8000 \
-v "${PWD}/artifacts:/app/artifacts" \
loan-default-risk:testGitHub Actions chạy trên Python 3.11 và có hai workflow:
policy.yml: kiểm tra file bắt buộc, không commit raw CSV, đúng 16 feature và không có feature leakage/proxy.ci.yml: chạy policy check, cài dependency pins,pip check, toàn bộ pytest, build Docker và smoke testGET /healthtrong container.
CI không train lại từ dataset riêng tư. Điều này giúp workflow tái lập và xanh trên checkout sạch mà không phân phối dữ liệu LendingClub.
Không thêm feature hậu outcome, không đưa PII vào log, không commit secret, CSV raw hoặc joblib. Thay đổi target, feature contract, split boundary, policy hoặc output API phải cập nhật test, Model Card và README trong cùng change. Xem SECURITY.md trước khi báo cáo lỗ hổng.
Đây là dự án nghiên cứu/kỹ thuật về credit-risk modeling trên dữ liệu lịch sử 2007-2011. Xác suất và factor giải thích chỉ phản ánh hành vi toán học của mô hình, không chứng minh quan hệ nhân quả và không thay thế quyết định nghiệp vụ của tổ chức tài chính.