信用卡 PD 建模实战:从违约概率预测到银行信用评分体系
一、PD 究竟是什么?
PD,全称 Probability of Default,表示在指定时间范围内,借款人发生预先定义的违约事件的概率。
例如,一名信用卡客户未来 12 个月的 PD 为 3%,意味着模型估计该客户在未来 12 个月内发生违约的概率为 3%。
它并不是说客户一定会违约,更不是说银行会损失信用额度的 3%。
在银行信用风险管理中,通常将 PD 与另外两个参数结合使用:
| 参数 | 含义 | 解决的问题 |
|---|---|---|
| PD | 违约概率 | 客户未来是否可能违约? |
| LGD | 违约损失率 | 违约后最终损失多少比例? |
| EAD | 违约风险暴露 | 违约发生时有多少风险敞口? |
最简单的预期损失公式是:
[
EL=PD\times LGD\times EAD
]
假设一名信用卡客户未来一年的 PD 为 5%,违约时预计有 20,000 元风险暴露,LGD 为 80%。
那么简化预期损失为:
[
EL=5%\times80%\times20000=800
]
即预期损失 800 元。
需要注意,这只是简化的单期公式。如果用于正式会计减值计量,还需要考虑违约发生时间、逐期 EAD、LGD、折现以及前瞻性经济情景等因素。
1. 先区分违约和逾期
这是 PD 建模最容易混淆的问题。
银行信用卡业务中,客户可能出现以下状态:
正常还款
|
v
逾期 1~29 天
|
v
逾期 30~59 天
|
v
逾期 60~89 天
|
v
逾期 90 天及以上
|
v
更严重的逾期或其他处置状态
按照巴塞尔信用风险内部评级方法的一般违约认定框架,违约不仅包括重大信用义务逾期超过 90 天,还包括银行判断债务人不太可能全额偿还债务的情形。零售信贷在特定监管安排下还存在适用差异。
因此:
逾期 30 天不必然等于违约,未达到 90 天逾期也不意味着一定没有发生监管意义上的违约。
在实际建模中,标签必须根据业务用途确定。
例如:
- 贷中预警模型:预测未来 3 个月是否进入 M2+。
- 信用评分模型:预测未来 12 个月是否发生违约。
- 行为风险模型:预测未来一定时间是否进入严重逾期状态。
- IFRS 9 减值模型:估计用于 12 个月或整个存续期 ECL 计算的违约风险。
这些模型都可以使用类似的机器学习方法,但预测的事件和时间范围并不相同。
二、案例一:利用历史还款行为预测信用卡短期风险
1. 业务背景
第一个案例使用经典的 Default of Credit Card Clients 数据集。
该数据集来自中国台湾地区信用卡客户,包含 30,000 条客户记录、23 个预测变量,以及一个目标标签。
特征覆盖 2005 年 4 月至 9 月的历史还款行为,目标是预测客户下一月是否发生违约支付。
这也是信用卡行为评分(Behavior Score)中非常典型的研究场景。
主要字段包括:
| 字段 | 含义 |
|---|---|
LIMIT_BAL |
客户信用额度 |
PAY_0 |
最近一个月的还款状态 |
PAY_2~PAY_6 |
此前五个月的还款状态 |
BILL_AMT1~BILL_AMT6 |
最近六个月的账单金额 |
PAY_AMT1~PAY_AMT6 |
最近六个月的还款金额 |
default.payment.next.month |
下个月是否违约支付 |
30,000 个客户中有 6,636 个正样本,占比约 22.12%。
这里有一个非常关键的区别:
这个数据集的目标是下一月违约支付,并不是严格定义的未来 12 个月 90+ DPD 违约。
因此,我们可以利用它研究短期信用风险预测,却不能直接把模型输出当作银行监管资本计量中的一年期 PD。
2. 为什么首先使用 Logistic Regression?
虽然现在有 XGBoost、LightGBM 等更复杂的算法,但在信用风险评分领域,逻辑回归依然非常具有代表性。
其核心公式为:
[
PD(x)=\frac{1}{1+e^{-z}}
]
其中:
[
z=\beta_0+\sum_{i=1}^{n}\beta_i x_i
]
也就是说,模型首先将客户的各项风险特征线性组合,再通过 Sigmoid 函数转换成 0~1 之间的概率。
Logistic Regression 的优势在于结构简单、易于复核,而且能够明确分析变量与模型输出之间的关系。
实际银行评分卡经常将变量分箱并进行 WOE 编码,再使用逻辑回归建模。这次先采用原始数值特征及衍生变量建立基线模型,不展开完整的 WOE 评分卡流程。
3. Python 实践
安装依赖:
pip install pandas numpy scikit-learn
前往 Hugging Face 的 scikit-learn/credit-card-clients 数据集页面,或者在 UCI Machine Learning Repository 搜索 Default of Credit Card Clients。
下载并准备 UCI_Credit_Card.csv,然后运行:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
roc_auc_score,
brier_score_loss,
precision_score,
recall_score
)
# 1. 读取数据
df = pd.read_csv("UCI_Credit_Card.csv")
target = "default.payment.next.month"
# 2. 选择信用卡行为特征
# 不使用ID及人口统计变量
pay_cols = [
"PAY_0", "PAY_2", "PAY_3",
"PAY_4", "PAY_5", "PAY_6"
]
bill_cols = [f"BILL_AMT{i}" for i in range(1, 7)]
paid_cols = [f"PAY_AMT{i}" for i in range(1, 7)]
cols = ["LIMIT_BAL"] + pay_cols + bill_cols + paid_cols
X = df[cols].copy()
y = df[target].astype(int)
# 3. 构造衍生特征
# 六个月内出现还款延迟的次数
X["late_months_6m"] = (
df[pay_cols] >= 1
).sum(axis=1)
# 历史最严重的还款延迟状态
X["max_delay"] = df[pay_cols].max(axis=1)
# 最近账单相对于信用额度的比例
X["utilization"] = (
df["BILL_AMT1"]
/ df["LIMIT_BAL"].replace(0, np.nan)
).clip(lower=0, upper=3)
# 最近实际还款额相对于账单金额的比例
X["payment_ratio"] = (
df["PAY_AMT1"]
/ df["BILL_AMT1"].clip(lower=1)
).clip(lower=0, upper=5)
# 4. 划分训练集、验证集、测试集
X_dev, X_test, y_dev, y_test = train_test_split(
X, y,
test_size=0.2,
stratify=y,
random_state=42
)
X_train, X_valid, y_train, y_valid = train_test_split(
X_dev, y_dev,
test_size=0.25,
stratify=y_dev,
random_state=42
)
# 5. 建立逻辑回归模型
model = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
("lr", LogisticRegression(max_iter=2000))
])
model.fit(X_train, y_train)
# 6. 预测风险概率
valid_pd = model.predict_proba(X_valid)[:, 1]
test_pd = model.predict_proba(X_test)[:, 1]
# 7. 基于验证集确定高风险筛选阈值
# 以筛选约20%的客户为示例
threshold = np.quantile(valid_pd, 0.80)
test_flag = (test_pd >= threshold).astype(int)
# 8. 评估模型
print("AUC:", roc_auc_score(y_test, test_pd))
print("Brier:", brier_score_loss(y_test, test_pd))
print(
"Precision:",
precision_score(y_test, test_flag, zero_division=0)
)
print(
"Recall:",
recall_score(y_test, test_flag, zero_division=0)
)
# 9. 按预测风险分成10组
result = pd.DataFrame({
"pd": test_pd,
"default": y_test.to_numpy()
})
result["risk_group"] = pd.qcut(
result["pd"].rank(method="first"),
q=10,
labels=False
) + 1
summary = result.groupby("risk_group").agg(
sample_count=("default", "size"),
avg_pred_pd=("pd", "mean"),
actual_bad_rate=("default", "mean")
)
print(summary)
4. 如何理解模型结果?
这段代码不仅输出 AUC,还会把客户按照预测概率划分成十个风险组。
假设某次实验得到如下分组结果:
| 风险组 | 平均预测概率 | 实际违约支付率 |
|---|---|---|
| 第 1 组 | 3.1% | 3.5% |
| 第 2 组 | 6.0% | 5.6% |
| 第 3 组 | 8.7% | 9.1% |
| 第 4 组 | 11.2% | 11.8% |
| 第 5 组 | 15.4% | 15.0% |
| 第 6 组 | 19.5% | 20.3% |
| 第 7 组 | 24.1% | 23.6% |
| 第 8 组 | 29.8% | 30.4% |
| 第 9 组 | 37.6% | 37.0% |
| 第 10 组 | 65.8% | 65.7% |
以上是教学用的假设数据,并非上述代码的实测结果。
如果模型有效,通常希望看到两个现象。
第一,风险组越高,实际坏样本率总体越高。这说明模型能够区分不同风险水平的客户。
第二,各风险组的平均预测概率与实际坏样本率比较接近。这说明模型输出的概率具有较好的校准程度。
这两件事并不相同。一个模型可能非常擅长把高风险客户排在前面,却把真实风险为 10% 的客户预测成 30%。
它的排序能力可能不错,但概率估计并不可靠。
还要注意,该公开数据集只包含一个历史观察窗口,没有完整的逐月建模快照。因此,代码采用随机划分作为教学验证;真正的银行行为评分模型,应优先使用跨时间验证来检验未来月份的表现。
三、案例二:利用征信行为预测未来两年的严重逾期风险
1. 业务背景
第二个案例使用 Kaggle 的经典信用评分竞赛 Give Me Some Credit。
这是一个更接近中长期信用风险预测的任务:根据客户当前的征信与负债情况,预测其未来两年是否发生严重逾期。
公开训练集包含约 150,000 条样本。
需要明确的是,它并非纯粹的信用卡数据集,而是同时涉及信用卡、个人信用额度、分期贷款和房地产贷款等信息。我们使用它来展示信用卡相关的循环额度与客户整体偿债行为如何共同影响风险。
主要变量包括:
| 特征 | 含义 |
|---|---|
RevolvingUtilizationOfUnsecuredLines |
信用卡及其他无担保循环额度的使用比例 |
NumberOfTime30-59DaysPastDueNotWorse |
历史 30~59 天逾期次数 |
NumberOfTime60-89DaysPastDueNotWorse |
历史 60~89 天逾期次数 |
NumberOfTimes90DaysLate |
历史 90 天及以上逾期次数 |
DebtRatio |
负债相关支出比例 |
MonthlyIncome |
月收入 |
NumberOfOpenCreditLinesAndLoans |
当前开放的贷款和信用账户数量 |
SeriousDlqin2yrs |
是否在未来两年发生严重逾期 |
模型目标变量是 SeriousDlqin2yrs。
这里有两个值得关注的地方。
第一,模型观察期从一个月延长到两年。
风险预测不仅要识别客户近期是否会发生还款问题,还要判断其未来较长时期的偿债风险。
第二,当前存在的风险状态与未来发生的新违约事件并不完全相同。
数据中有历史 90 天以上逾期次数,但没有完整的当前违约认定状态,也没有逐月违约轨迹。因此,它适合研究未来两年严重逾期事件预测,却不能在未经额外处理的情况下直接作为银行正式的一年期监管 PD 样本。
2. 为什么尝试梯度提升树?
前一个案例使用 Logistic Regression。
第二个案例使用 HistGradientBoostingClassifier,这是 scikit-learn 提供的基于直方图算法的梯度提升树实现。
它适合处理表格数据,能够学习复杂的非线性关系和特征交互。
例如:
- 循环额度使用率较高,并不一定意味着客户会违约。
- 如果同时存在历史逾期次数多、收入较低、负债比例较高等情况,风险可能明显不同。
- 不同负债水平下,同样的收入变化可能对应不同的信用风险。
梯度提升树可以自动学习部分此类复杂关系,而不需要完全依赖人工编写交互项。
3. Python 实践:训练并校准 PD 模型
进入 Kaggle 的 Give Me Some Credit 竞赛页面,下载 cs-training.csv。
本案例除了训练模型,还增加概率校准。原因很简单:如果最终需要输出 PD,我们不仅希望模型能够正确排序,还希望其预测概率与实际事件频率相匹配。
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.calibration import CalibratedClassifierCV
from sklearn.metrics import (
roc_auc_score,
average_precision_score,
brier_score_loss
)
# 1. 读取公开数据
df = pd.read_csv("cs-training.csv")
df = df.drop(
columns=["Unnamed: 0"],
errors="ignore"
)
target = "SeriousDlqin2yrs"
# 2. 处理明显异常的字段值
# 年龄为0视为缺失
df.loc[df["age"] <= 0, "age"] = np.nan
# 特殊逾期次数编码,暂视为异常缺失
delay_cols = [
"NumberOfTime30-59DaysPastDueNotWorse",
"NumberOfTime60-89DaysPastDueNotWorse",
"NumberOfTimes90DaysLate"
]
for col in delay_cols:
df.loc[df[col].isin([96, 98]), col] = np.nan
# 3. 准备特征与标签
X = df.drop(columns=[target])
y = df[target].astype(int)
# 4. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
stratify=y,
random_state=42
)
# 5. 构建梯度提升树
base_model = HistGradientBoostingClassifier(
max_iter=180,
max_leaf_nodes=15,
learning_rate=0.05,
min_samples_leaf=100,
l2_regularization=1.0,
random_state=42
)
# 6. 通过交叉验证进行概率校准
# Sigmoid calibration
model = CalibratedClassifierCV(
estimator=base_model,
method="sigmoid",
cv=3
)
model.fit(X_train, y_train)
# 7. 预测未来两年严重逾期事件的概率
pred_pd = model.predict_proba(X_test)[:, 1]
# 8. 模型评价
auc = roc_auc_score(y_test, pred_pd)
ap = average_precision_score(y_test, pred_pd)
brier = brier_score_loss(y_test, pred_pd)
print(f"AUC: {auc:.4f}")
print(f"AP: {ap:.4f}")
print(f"Brier: {brier:.4f}")
# 9. 风险分层与概率校准检验
result = pd.DataFrame({
"pred_pd": pred_pd,
"actual": y_test.to_numpy()
})
result["risk_group"] = pd.qcut(
result["pred_pd"].rank(method="first"),
q=10,
labels=False
) + 1
summary = result.groupby("risk_group").agg(
n=("actual", "size"),
mean_pd=("pred_pd", "mean"),
actual_default_rate=("actual", "mean")
)
print(summary)
4. 概率校准为什么重要?
假设某个模型把 1,000 名客户的预测 PD 都设为 10%。
如果该类客户最终有大约 100 人发生了模型定义的违约事件,那么预测概率在这一组上是比较准确的。
如果实际有 250 人发生事件,那么即使模型 AUC 很高,预测概率仍然明显低估了真实风险。
这就是信用评分模型中必须区分的两个维度:
- Discrimination(区分能力): 模型能否将坏客户排在好客户前面?
- Calibration(校准能力): 预测为 10% 风险的客户,实际事件发生率是否接近 10%?
AUC 主要评价前者,Brier Score 和分组预测值与实际坏样本率的比较则可以辅助评价后者。
本案例采用 Sigmoid Calibration,通过交叉验证产生模型预测并估计概率校准关系,减少在同一批样本上训练基础模型和校准模型带来的偏差。
不过,概率校准并不是一次完成后就永久有效。如果未来客户结构、经济环境或整体坏账率变化,模型仍然需要重新验证。
另外,公开竞赛数据没有充分的时间字段,示例只能随机划分。银行正式建模时,还需要观察样本外时间稳定性,并明确客户样本是否处于模型的适用范围。
四、为什么机器学习模型输出不一定等于银行最终使用的 PD?
这是从数据科学走向银行信用风险建模时,必须理解的一个问题。
假设我们使用上述数据训练出模型,对某客户得到:
客户 A
模型预测违约概率:6.5%
是否意味着银行就可以把 6.5% 直接写入内部评级系统?
不一定。
因为我们首先需要回答:这个 6.5% 到底是什么概率?
它可能是下个月违约支付的概率,也可能是未来两年严重逾期的概率,或者是特定样本结构下发生某个事件的条件概率。
而银行真正用于风险管理和参数计量的 PD,往往有明确的违约定义、预测期限、样本范围和校准目标。
1. 模型预测 PD:Point-in-Time PD
Point-in-Time(PIT)PD 更强调当前或未来一段时期的风险状况。
例如,经济增长放缓、失业率上升或客户负债水平恶化时,即使客户的长期风险等级没有发生很大变化,其短期预测 PD 也可能上升。
PIT PD 的变化通常更敏感地反映当前环境。
它常用于动态风险监控,也可作为预期信用损失测算中具有前瞻性的风险参数基础。
2. 跨周期 PD:Through-the-Cycle PD
Through-the-Cycle(TTC)PD 则更强调跨经济周期的长期风险水平。
其目的是减少短期经济波动对风险评级的直接影响,让相同风险等级在不同经济环境下具有相对稳定的长期含义。
在监管资本内部评级方法下,零售风险池的 PD 估计通常需要考虑具有代表性的长期历史违约经验,并依据适用监管要求校准至长期平均一年期违约率。
因此,某个机器学习模型在特定年份训练出的原始违约概率,即使样本外 AUC 很高,也不必然满足监管资本 PD 的使用要求。
3. 12 个月 PD 和 Lifetime PD
在预期信用损失计量中,还需要区分 12 个月 PD 与存续期 PD。
例如:
| 参数 | 含义 |
|---|---|
| 1M PD | 未来一个月违约概率 |
| 12M PD | 未来十二个月内违约概率 |
| 24M PD | 未来二十四个月内违约概率 |
| Lifetime PD | 剩余预计存续期内的累计违约概率 |
它们不是简单的倍数关系。
尤其不能直接认为:
[
PD_{12M}=\frac{PD_{24M}}{2}
]
因为客户未来各个月份的违约风险可能不同,存活至各期的客户范围也会发生变化。
如果要从逐期违约风险构建累计 PD,可以使用离散时间生存分析的思想。
设 (h_t) 是客户在前 (t-1) 期尚未违约的条件下,第 (t) 期发生违约的概率:
[
PD(1,T)=1-\prod_{t=1}^{T}(1-h_t)
]
例如,假设未来三个月的条件违约概率分别为 1%、2% 和 3%。
那么:
[
PD(1,3)=1-(0.99\times0.98\times0.97)
]
约为 5.89%。
这才是将多个时期的条件违约风险转化为累计违约概率的一种合理方式。
五、信用卡 PD 模型究竟应该看哪些指标?
在技术实践中,AUC 往往最受关注,但在实际银行应用中远远不够。
| 指标 | 主要作用 | 应重点关注什么 |
|---|---|---|
| AUC | 区分好坏客户 | 排序能力 |
| KS | 好坏样本累计分布的最大差异 | 风险区分能力 |
| Brier Score | 概率预测误差 | 概率准确性 |
| 分组实际坏账率 | 检验风险分层与校准 | 预测概率和实际结果是否一致 |
| PSI | 监控特征或评分分布漂移 | 客群结构变化 |
| OOT AUC | 跨时间区分能力 | 模型未来表现 |
| O/E | 实际违约数与预测违约数之比 | 整体预测偏差 |
其中 O/E(Observed / Expected)尤其适合帮助理解 PD 校准。
假设银行对某个风险组合预测:
- 客户数量:10,000 人。
- 平均 PD:4%。
- 预测违约人数:400 人。
- 实际违约人数:520 人。
那么:
[
O/E=\frac{520}{400}=1.30
]
即实际违约人数比预测值高 30%。
这提示模型整体存在低估风险的可能性,但还不能立即断定模型错误,因为短期随机波动、样本结构变化和经济环境差异都可能造成偏离。
如果这种差异在多个观察期持续存在,就需要进一步检查模型校准和适用性。
六、实际银行如何构建信用卡 PD 样本?
在两个公开数据集上完成训练,只是了解 PD 建模的第一步。
如果要真正开发银行内部的信用卡 PD 模型,必须从样本构建开始解决问题。
1. 明确观察时点
例如,我们选择每个月月底作为观察时点:
2024-01-31:提取客户特征
|
v
观察未来12个月
|
v
2025-01-31:确定是否违约
对于每一个观察时点,模型只能使用当时已经可获得的信息。
可用变量包括历史还款表现、当前用信情况、信用额度、已有征信记录和最近若干月交易行为等。
但不能把观察时点之后才发生的逾期、催收或账户状态变化加入预测特征。
2. 构造多个历史批次
真实建模通常会使用多个历史观察时点:
2023-01-31 → 未来12个月违约标签
2023-02-28 → 未来12个月违约标签
2023-03-31 → 未来12个月违约标签
...
2024-12-31 → 未来12个月违约标签
这样可以获得不同客户、不同月份和不同经济环境下的训练样本。
但要特别注意,12 个月表现期意味着最近 12 个月的样本可能尚未完全成熟。
对于尚未成熟的观察样本,不能简单地把暂时没有违约的客户全部标记为正常。
此外,同一个客户可能在多个观察时点重复出现。这些样本不是完全独立的随机记录,训练与验证时需要考虑客户相关性、观察期重叠,以及违约后的样本处理规则。
3. 明确建模客群
信用卡客户可能处于不同的账户状态:
- 正常还款客户。
- 当前已出现轻度逾期的客户。
- 当前严重逾期但尚未达到违约认定的客户。
- 已经发生违约的客户。
如果研究的是“未来新发生违约的概率”,应明确期初已违约客户的排除规则。
也可以针对不同客群开发单独的行为风险模型,而不是强行把全部客户放入同一个训练集。
因为正常客户与已经进入严重逾期阶段的客户,其风险形成机制可能存在显著差异。
4. 如何选择算法?
| 算法 | 优点 | 局限 |
|---|---|---|
| Logistic Regression | 结构简单、易解释、易校准 | 对复杂非线性关系表达能力有限 |
| XGBoost | 擅长非线性和特征交互 | 需要关注复杂度与概率校准 |
| LightGBM | 表格数据训练效率较高 | 对数据质量及参数设置较敏感 |
| Random Forest | 稳定、易于建立基线 | 大规模预测可能带来内存开销 |
| 生存分析模型 | 可刻画逐期条件违约风险 | 数据结构与验证更复杂 |
对于银行 PD 建模,比较实用的技术路线是:
先训练 Logistic Regression 作为基线,再尝试 XGBoost 或 LightGBM。如果复杂模型获得了更好的区分能力,进一步检查其概率校准、时间稳定性和不同风险分组的预测偏差。
如果最终用于监管或会计参数计量,还需要根据特定用途完成口径转换、长期或前瞻性校准及相应验证。
还有一点不能忽视:信用模型使用的特征应具备合法性与合理性。人口统计变量、可能产生不公平影响的代理变量及其使用方式,需要根据适用法规和内部治理要求审查。
七、总结
信用卡 PD 建模表面上是一个二分类问题,但实际应用时,模型输出必须能够回答三个问题:
第一,究竟预测什么违约事件?
是下个月还款失败、未来三个月 M2+、未来十二个月监管意义上的违约,还是未来两年的严重逾期?
第二,模型能否正确区分风险并给出可靠概率?
AUC 高只能说明模型具有较好的排序能力,不代表预测概率已经准确。概率校准、分组验证与时间外验证同样重要。
第三,预测结果最终用于什么业务?
授信审批、额度调整、贷中预警、监管资本和预期信用损失计量,对 PD 的定义、预测期限和校准方式可能存在明显区别。
本文两个案例分别展示了短期行为风险预测与中长期严重逾期预测的基本实践,也说明了为什么不能把一个公开数据集中的分类标签直接等同于银行正式使用的 PD。
PD 模型的核心价值,不只是识别谁更可能违约,而是让不同客户、不同时间和不同经济环境下的风险水平,能够被合理地量化、比较和使用。
从技术上看,训练一个二分类器并不难。真正体现信用风险建模能力的,是样本设计、违约定义、概率校准、跨周期分析,以及最终参数与业务用途的一致性。
- 点赞
- 收藏
- 关注作者
评论(0)