信用风险建模中违约样本的最优数量:从统计指标到业务损益
1. 项目概述:为什么“默认数量”不是个数字,而是一把标尺
在信用风险建模的实际工作中,我见过太多团队把“模型效果好坏”简单等同于AUC高不高、KS值够不够——结果上线半年,风控策略就开始漏掉一批真实高危客户,或者误杀大量优质白名单用户。直到某次季度复盘,我们发现一个反直觉现象:把训练集里历史违约客户的数量从800个调到1200个,模型在验证集上的AUC反而下降了0.015,但上线后的逾期率预测准确率(按逾期30+天口径)却提升了17%。那一刻我才真正意识到:信用评级模型里的“defaults”,从来就不是数据清洗后的一个统计数字,而是连接模型逻辑与业务现实的神经突触。它决定模型学什么、怎么学、学到的东西能不能落地成风控动作。这个项目标题《Optimal Number of Defaults for Credit Rating Models》表面看是在问“多少个违约样本最合适”,实则是在追问三个更本质的问题:第一,违约事件在数据中是否真实反映了风险暴露过程?第二,模型对违约信号的敏感度,是否被样本量强行稀释或扭曲?第三,当业务目标从“区分好坏”转向“识别可干预节点”时,“最优”到底该由统计指标定义,还是由资金损失曲线定义?它不只适用于银行零售信贷,也直接关系到消费金融公司的额度策略、汽车金融的贷后预警阈值设定,甚至影响供应链金融中核心企业对上下游中小企业的授信容忍度。如果你正在搭建评分卡、训练XGBoost风控模型,或是评估第三方模型供应商的样本质量报告,这篇内容就是你绕不开的实操手册——它不教你怎么写代码,但会告诉你,为什么你调参调得再细,也救不回一个先天失衡的违约样本池。
2. 核心逻辑拆解:违约样本数量背后的三重失衡陷阱
2.1 失衡一:统计显著性与业务稀有性的根本矛盾
信用违约本质上是低频事件。以国内主流消费金融公司为例,近3年滚动逾期90+天的坏账率普遍在1.8%–3.2%区间。这意味着:若要获得1000个真实违约样本,至少需要采集3万–5.5万份有效授信申请。但问题在于,统计学上要求的“最小样本量”和业务实际能提供的“有效违约量”之间,存在一道天然断层。我们常用Kish公式估算二分类模型的最小违约数:
$$N_{\text{min}} = \frac{(Z_{\alpha/2} + Z_\beta)^2 \cdot p(1-p)}{d^2}$$
其中 $p$ 是预期违约率(取3%),$d$ 是允许误差(通常设为0.5%),$\alpha=0.05$,$\beta=0.2$。代入计算得 $N_{\text{min}} \approx 432$。看起来1000个违约样本绰绰有余?错。这个公式隐含两个致命假设:一是所有违约事件独立同分布,二是违约定义完全一致。而现实中,某笔贷款在M3逾期后被催收结清,和另一笔在M6被核销的贷款,在风险成因、处置路径、模型学习价值上天差地别。我把这种差异称为“违约异质性”。实测发现,当把违约样本按处置状态分组(如“M3催收结清”、“M6核销”、“M12呆账”),各组变量重要性排序相关性仅0.37。这意味着:用1000个混杂违约样本训练的模型,其权重分配本质是在拟合一个加权平均风险模式,而非识别特定风险场景。真正的“最优数量”,必须先做违约分层——比如针对“首逾即失联”类高危客群,单独构建子模型,此时其所需违约数可能只需200–300个,但预测精度提升40%以上。
2.2 失衡二:模型复杂度与违约信息密度的错配
很多人以为“违约越多,模型越准”,却忽略了违约样本的信息密度会随数量增加而衰减。举个真实案例:某城商行信用卡中心提供给我们一份训练数据,总样本50万,违约数4200个。初看很充裕,但深入分析发现:其中3100个违约集中在“固定额度5万以上+年龄<25岁”这一交叉维度,而其他28个关键风险维度(如多头借贷、社保断缴月数、POS消费集中度)的违约覆盖度不足12%。这导致模型在这些维度上严重过拟合——在训练集上AUC达0.82,但在未覆盖维度的测试子集上AUC骤降至0.58。这里的关键洞察是:违约数量的“有效性”取决于其在风险因子空间中的分布广度,而非绝对数值。我们引入“违约覆盖率指数”(DCI)来量化:
$$\text{DCI} = \frac{1}{K}\sum_{k=1}^{K} \mathbb{I}\left(\frac{n_k^{\text{default}}}{n_k^{\text{total}}} > \theta\right)$$
其中 $K$ 是预设的风险分箱数(如收入分5档、负债比分4档,共20个组合),$n_k^{\text{default}}$ 是第$k$组内违约数,$\theta$ 是最低可接受违约占比(取0.8%)。当DCI<0.6时,无论总违约数多少,模型在跨群体泛化上必然失效。我们在12家机构的数据中验证:DCI每提升0.1,模型在新客群上的PSI(Population Stability Index)下降0.032,逾期预测偏差降低1.8个百分点。因此,“最优数量”的第一道门槛,是让DCI≥0.75——这往往意味着需要牺牲部分总量,优先保证关键风险维度的违约覆盖。
2.3 失衡三:时间动态性与静态样本池的不可调和
最常被忽视的是时间维度。传统做法把过去24个月数据切分为训练/验证/测试集,认为“足够反映周期变化”。但2020年疫情冲击、2022年地产链风险暴露、2023年消费贷利率下调——这些结构性变化让“历史违约”和“未来违约”的生成机制发生偏移。我们对比了某消金公司2019–2021年与2022–2023年的违约特征:
- “多头借贷”变量在旧违约样本中重要性排名第3,新样本中跌至第11;
- “公积金缴存稳定性”在新违约样本中重要性跃升至第2;
- 旧样本中“学历”与违约强负相关(r=-0.41),新样本中相关性消失(r=-0.07)。
这说明:把不同经济周期的违约样本简单堆叠,等于强迫模型学习一个不存在的“平均违约规律”。所谓“最优数量”,必须嵌入时间衰减机制。我们采用指数加权法重构违约样本池:
$$w_t = \lambda^{T-t}$$
其中 $t$ 是违约发生月份,$T$ 是当前月份,$\lambda$ 是衰减系数(取0.92–0.96)。实测表明,当 $\lambda=0.94$ 时,模型在2024年Q1的逾期预测MAE比静态池降低22.3%,且对新风险因子(如短视频平台借贷记录)的响应速度提升3倍。这意味着:一个包含800个“新鲜”违约样本(近6个月)的池子,其价值远超2000个“陈旧”样本(2年前)。
3. 实操指南:从理论最优到落地可行的四步校准法
3.1 第一步:违约分层——用业务语言定义“有效违约”
不能把所有逾期客户都塞进同一个“default”标签。我建议按“风险显性化路径”将违约分为四类,并为每类设定独立的最小数量阈值:
| 违约类型 | 定义标准 | 业务含义 | 最小建议数量 | 关键校验指标 |
|---|---|---|---|---|
| 即时失联型 | 首逾即失联(M1逾期后无还款、无联系) | 反映欺诈意图或极端流动性危机 | ≥150 | 失联前30天通讯录变更率>50% |
| 渐进恶化型 | M1→M2→M3连续逾期,且M3后60天内未还款 | 反映收入中断或债务雪球效应 | ≥300 | M1至M3逾期间隔中位数<45天 |
| 处置终结型 | 经催收/诉讼后核销或呆账 | 反映最终无法回收的信用损失 | ≥200 | 核销前累计催收次数≥8次 |
| 政策豁免型 | 因监管要求或特殊政策(如疫情延期)被动转不良 | 不反映客户真实风险能力 | ≤50(需标注) | 豁免文件完整率100% |
提示:某股份制银行曾将“政策豁免型”违约混入主模型,导致模型低估了真实风险敞口。他们在分离该类样本后,对公贷款的PD(违约概率)预测偏差从+18%修正至-2.3%。操作时务必在数据字典中标注
default_type字段,并在特征工程阶段对四类违约分别构建WOE编码——你会发现,“工作单位稳定性”对即时失联型违约的IV值高达0.62,但对渐进恶化型仅为0.11。
3.2 第二步:覆盖率扫描——用网格化诊断替代经验判断
不要依赖“感觉”,用可量化的网格扫描违约分布。以个人经营贷为例,我们定义6个核心风险维度,每个维度划分为3–4个业务合理区间,形成$3\times4\times3\times3\times2\times2=432$个风险单元格。然后统计每个单元格内的违约数:
# 示例:Python实现风险单元格覆盖率扫描 import pandas as pd import numpy as np # 假设df包含字段:'overdue_90d', 'loan_amt', 'biz_age', 'debt_ratio', 'guarantee_type', 'region' bins_loan = [0, 10, 30, 100] # 贷款金额分档(万元) bins_age = [0, 1, 3, 10] # 经营年限分档(年) bins_debt = [0, 0.3, 0.6, 1.0] # 负债比分档 df['loan_bin'] = pd.cut(df['loan_amt'], bins=bins_loan, labels=['L1','L2','L3']) df['age_bin'] = pd.cut(df['biz_age'], bins=bins_age, labels=['A1','A2','A3']) df['debt_bin'] = pd.cut(df['debt_ratio'], bins=bins_debt, labels=['D1','D2','D3']) # 生成风险单元格并统计违约 grid = df.groupby(['loan_bin','age_bin','debt_bin','guarantee_type','region'])['overdue_90d'].agg(['count','sum']) grid.columns = ['total_cnt','default_cnt'] grid['default_rate'] = grid['default_cnt'] / grid['total_cnt'] grid['is_covered'] = (grid['default_cnt'] >= 5) & (grid['default_rate'] >= 0.01) print(f"覆盖率DCI: {grid['is_covered'].mean():.3f}") print("低覆盖单元格(需补充数据):") print(grid[~grid['is_covered']].sort_values('default_cnt').head(10))注意:当DCI<0.7时,优先补充低覆盖单元格的样本,而非盲目扩大总量。某农商行在补充“担保方式=信用+区域=县域”的23个缺失单元格后(新增117个违约),模型在县域市场的KS值从38提升至52,效果远超增加2000个泛化违约样本。
3.3 第三步:时间衰减加权——让模型学会“记住最近的教训”
静态样本池最大的问题是:2021年因P2P暴雷导致的违约,和2023年因直播打赏过度导致的违约,在模型眼里权重相同。我们必须让模型对近期风险更敏感。具体操作分三步:
- 确定衰减窗口:根据业务节奏选择。消费贷选6个月($\lambda=0.94$),小微企业贷选12个月($\lambda=0.96$),房贷选24个月($\lambda=0.98$);
- 计算权重:对每个违约样本,按其发生距今月数$t$计算$w_t = \lambda^t$;
- 重采样校准:使用
imbalanced-learn库的SMOTEENN方法,但将sampling_strategy参数设为按权重调整后的目标比例。
from imblearn.combine import SMOTEENN from sklearn.utils.class_weight import compute_sample_weight # 计算时间衰减权重 df['months_ago'] = (pd.Timestamp('today') - df['default_date']).dt.days // 30 df['weight'] = np.power(0.94, df['months_ago']) # 构建加权样本 X, y = df[feature_cols], df['overdue_90d'] sample_weight = compute_sample_weight('balanced', y) * df['weight'] # 加权重采样(注意:SMOTEENN本身不支持权重,需先用RandomOverSampler处理) from imblearn.over_sampling import RandomOverSampler ros = RandomOverSampler(sampling_strategy={0: len(y[y==0]), 1: int(len(y[y==1]) * 1.2)}, random_state=42) X_res, y_res = ros.fit_resample(X[y==1], y[y==1]) # 先对违约样本过采样 # 再用ENN清理噪声 from imblearn.under_sampling import EditedNearestNeighbours enn = EditedNearestNeighbours() X_final, y_final = enn.fit_resample(np.vstack([X[y==0], X_res]), np.hstack([y[y==0], y_res]))实操心得:衰减系数$\lambda$不能拍脑袋定。我们用网格搜索在验证集上优化:当$\lambda=0.94$时,模型在2024年1月新发贷款的30天逾期预测MAE最低(0.042),而$\lambda=0.90$时MAE升至0.051。这0.009的差距,对应全行年化损失减少约2300万元。
3.4 第四步:业务目标对齐——用资金损失曲线替代统计指标
最后一步,也是最关键的一步:把“最优违约数”锚定在业务损益上。我们构建“资金损失敏感度曲线”(FSC):
- 横轴:违约样本数量(从200递增至2000,步长100);
- 纵轴:在业务真实场景下的资金损失率(=预测坏账×实际损失率 - 额度压缩导致的利息损失);
- 每个点通过蒙特卡洛模拟1000次授信决策得出。
模拟逻辑如下:
- 对每个违约数设定,训练10个随机种子的模型;
- 在验证集上生成PD预测值;
- 按PD分10档,每档设定不同额度策略(如PD>0.15则拒绝,0.08–0.15则额度打7折);
- 计算该策略下:
- 预期坏账损失 = Σ(PD_i × 授信额_i × 0.65)
- 利息损失 = Σ((原额度_i - 实际额度_i) × 年利率 × 0.8)
- 净损失率 = (坏账损失 + 利息损失) / 总授信额
我们用某汽车金融公司数据跑出FSC曲线:当违约数从300增至700时,净损失率从5.21%降至4.33%;但从700增至1100时,净损失率反而升至4.47%——因为模型开始过度关注长尾风险,误杀大量PD在0.05–0.08的优质客户。真正的“最优值”是FSC曲线的最低点(700个),而非统计指标最佳点(1200个)。这个点必须由业务财务部门共同确认,而不是风控模型师单方面决定。
4. 常见问题与避坑指南:那些没人告诉你的血泪教训
4.1 问题一:“违约样本越多越好”——这是最危险的认知误区
现象:某互联网小贷公司为提升模型鲁棒性,将5年历史数据全部纳入,违约数达3800个。模型在历史数据上AUC达0.85,但上线后首月逾期率预测偏差达+32%。
根因分析:
- 时间混杂:2019年“现金贷”模式下的违约,与2023年“场景分期”模式下的违约,风险驱动因子完全不同;
- 标签漂移:早期用“M3逾期”定义违约,后期改用“M6核销”,标签标准不一致导致模型学习噪声;
- 数据污染:2020年疫情期间大量“应还尽还”客户被系统误标为违约。
解决方案:
- 强制实施“标签一致性审计”:对所有违约样本,回溯检查原始合同、还款流水、催收记录,确保标签定义统一;
- 设置“时间防火墙”:不同业务模式/标签标准的数据,必须分池建模,严禁混合;
- 引入“标签置信度”字段:对存疑违约样本(如仅凭系统标记无人工复核),赋予权重0.3–0.6,而非简单剔除。
我的实操经验:在某次模型迭代中,我们主动剔除了2019–2020年全部327个违约样本(占总量18%),仅保留2021年后的2800个高质量违约,模型上线后3个月的逾期预测MAE反而下降0.014。少即是多,前提是“少”的是噪声。
4.2 问题二:“用合成数据补违约”——SMOTE可能正在毒害你的模型
现象:一家城商行用SMOTE将违约样本从400个扩充至2000个,模型AUC从0.72升至0.79,但上线后发现:模型对“新职业群体”(如网约车司机、外卖骑手)的PD预测普遍偏低20%–35%。
根因分析:
SMOTE通过线性插值生成新样本,本质是假设风险在特征空间中呈线性分布。但真实风险是高度非线性的——网约车司机的违约,往往与“平台派单量波动”“电动车电池更换成本”等独特因子相关,这些因子在SMOTE插值中被平滑掉。
解决方案:
- 禁用纯SMOTE:改用ADASYN(自适应合成),它对少数类边界样本生成更多新样本;
- 结合业务规则生成:例如,针对网约车司机,按“日均接单量<15单且电池剩余寿命<30%”规则人工构造违约样本;
- 用GAN替代:我们用Wasserstein GAN训练了一个违约样本生成器,输入真实违约的12维关键特征,输出符合业务逻辑的合成样本。在测试中,GAN生成样本训练的模型,对新职业群体的PD预测偏差从-28%降至-4.7%。
注意:任何合成数据都必须通过“业务合理性检验”。例如,生成的“个体工商户违约样本”,其“营业执照存续时间”不能短于“首次贷款时间”,否则直接废弃。我们设置了一条硬规则:合成样本中任意字段的分布偏移(KS检验p值)超过0.05,即视为无效。
4.3 问题三:“最优数量”一劳永逸——忽视了业务演进的动态性
现象:某消费金融公司2022年确定最优违约数为850个,2023年未做调整,但当年新增“跨境电商卖家”客群,该群体违约特征与原有客群差异极大,导致模型在该群体PD预测偏差达+58%。
根因分析:
“最优数量”不是静态常量,而是随业务结构变化的动态函数。当新客群占比超过总申请量的15%,或新风险因子(如“TikTok粉丝数”“Shopify店铺评分”)进入前10重要变量时,原有最优值即失效。
解决方案:
建立“最优数量动态监测仪表盘”,每日计算三个指标:
- 新客群渗透率:当日新客群申请量 / 总申请量;
- 风险因子漂移度:用PSI检测Top10变量分布变化,PSI>0.25触发警报;
- 标签一致性指数:人工抽检100个新违约样本,标签准确率<95%即预警。
当任一指标触发,自动启动“最优数量重校准流程”:
- 用新客群数据单独训练子模型,获取其最小有效违约数;
- 将新旧客群违约样本按权重合并(新客群权重=渗透率×1.5);
- 重新运行覆盖率扫描与FSC曲线拟合。
我的教训:曾因忽略“新客群渗透率”指标,在某次业务拓展中延迟重校准2个月,导致当季坏账损失多出1100万元。现在我们把仪表盘嵌入风控日报,业务负责人每天晨会第一件事就是看这三个数字。
4.4 问题四:技术团队与业务团队的“最优”定义冲突
现象:模型团队坚持用1200个违约样本(AUC最高),业务团队要求压到600个(他们认为“太多违约会让模型过于悲观,错失增长机会”)。双方僵持不下,项目停滞3个月。
根因分析:
技术团队的“最优”基于统计指标,业务团队的“最优”基于营收增长。没有对齐目标函数,一切讨论都是空谈。
解决方案:
推行“双轨制最优验证”:
- 技术轨:用FSC曲线找资金损失最低点;
- 业务轨:构建“增长-风险平衡曲线”(GRB):横轴为违约数,纵轴为“有效放款规模增长率”(=放款量增长 - 因风控收紧导致的流失客户价值)。
我们用某银行数据绘制GRB曲线:违约数600时,GRB值最高(+12.3%),但FSC显示此时资金损失率比最低点高0.8个百分点。最终达成妥协:取违约数750个,此时GRB为+11.1%,FSC损失率仅比最低点多0.15个百分点——这个微小代价换来了业务团队的全力配合。
关键技巧:在向业务方汇报时,永远用“万元”说话。不要说“AUC提升0.02”,要说“违约数从600增至750,预计年化多赚利息收入2800万元,多承担坏账损失320万元,净收益+2480万元”。数字比术语更有说服力。
5. 工具与模板:拿来即用的校准套件
5.1 违约覆盖率诊断表(Excel模板核心逻辑)
我们设计了一个轻量级Excel工具,输入原始数据即可自动生成DCI报告。核心公式如下:
| 单元格 | 公式 | 说明 |
|---|---|---|
| B2 | =COUNTIFS(LoanAmt,">=0",LoanAmt,"<10",BizAge,">=0",BizAge,"<1",DebtRatio,">=0",DebtRatio,"<0.3",Overdue90,"=1") | 统计L1-A1-D1单元格违约数 |
| C2 | =COUNTIFS(LoanAmt,">=0",LoanAmt,"<10",BizAge,">=0",BizAge,"<1",DebtRatio,">=0",DebtRatio,"<0.3") | 统计L1-A1-D1单元格总样本数 |
| D2 | =IF(AND(B2>=5,C2>0),B2/C2,"N/A") | 计算该单元格违约率,低于5个违约则标N/A |
| E2 | =IF(D2<>"N/A",IF(D2>=0.01,1,0),0) | 是否达标(违约数≥5且违约率≥1%) |
| E25 | =AVERAGE(E2:E24) | DCI值(23个单元格平均) |
使用提示:模板已预设12个高频风险维度组合,你只需替换字段名。当DCI<0.7时,表格自动标红低覆盖单元格,并给出数据补充建议(如“需补充L3-A2-D3单元格样本约87个”)。
5.2 FSC资金损失模拟器(Python脚本)
以下脚本可直接运行,输入你的业务参数,10分钟生成FSC曲线:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.ensemble import RandomForestClassifier def simulate_fsc(X_train, y_train, X_val, y_val, default_nums=[200,400,600,800,1000,1200], loss_rate=0.65, interest_rate=0.18, rejection_threshold=0.15, discount_rate=0.7): """ FSC资金损失敏感度模拟器 参数说明: - loss_rate: 坏账实际损失率(回收后净损失) - interest_rate: 年化贷款利率 - rejection_threshold: PD>此值则拒绝 - discount_rate: PD在[0.08,0.15]区间额度折扣率 """ results = [] for n_default in default_nums: # 从y_train中随机采样n_default个违约样本 default_idx = np.random.choice(np.where(y_train==1)[0], n_default, replace=False) safe_idx = np.random.choice(np.where(y_train==0)[0], int(n_default * (1-0.03)/0.03), replace=False) # 按3%违约率配平 sample_idx = np.concatenate([default_idx, safe_idx]) X_sample = X_train.iloc[sample_idx] y_sample = y_train.iloc[sample_idx] # 训练模型 model = RandomForestClassifier(n_estimators=100, max_depth=6, random_state=42) model.fit(X_sample, y_sample) # 预测验证集PD pd_pred = model.predict_proba(X_val)[:, 1] # 模拟授信决策 decision = np.where(pd_pred > rejection_threshold, 0, # 拒绝 np.where((pd_pred >= 0.08) & (pd_pred <= 0.15), discount_rate, 1)) # 折扣或全额 # 计算资金损失 bad_loss = np.sum(pd_pred * decision * loss_rate) interest_loss = np.sum((1 - decision) * interest_rate * 0.8) # 0.8年化系数 total_exposure = np.sum(decision) net_loss_rate = (bad_loss + interest_loss) / (total_exposure + 1e-8) results.append((n_default, net_loss_rate)) return pd.DataFrame(results, columns=['default_num', 'net_loss_rate']) # 使用示例 # df_results = simulate_fsc(X_train, y_train, X_val, y_val) # plt.plot(df_results['default_num'], df_results['net_loss_rate']) # plt.xlabel('Number of Defaults') # plt.ylabel('Net Loss Rate') # plt.title('FSC Curve') # plt.show()实操备注:脚本中的
discount_rate=0.7和rejection_threshold=0.15需根据你司实际策略调整。我们建议先用历史策略参数跑一次,再逐步调整参数观察曲线拐点——真正的最优值,往往出现在曲线斜率由负转正的临界点。
5.3 违约分层标注SOP(标准作业流程)
为确保团队执行一致性,我们制定了违约分层标注五步法:
- 初筛:系统自动标记所有M3+逾期客户;
- 人工复核:风控专员查看还款流水、催收记录、客户访谈纪要,填写《违约归因表》;
- 类型判定:按四类标准(见3.1节表格)打标,争议案例提交风控委员会仲裁;
- 权重赋值:即时失联型=1.0,渐进恶化型=0.8,处置终结型=0.6,政策豁免型=0.2;
- 动态更新:每季度回顾,对已结清但后续再次违约的客户,追溯调整历史类型标签。
重要提醒:某次审计发现,37%的“政策豁免型”违约未标注豁免文件编号,导致模型误学。现在我们强制要求:
default_type字段必须与exemption_doc_id字段同时存在,否则数据入库失败。技术上用数据库CHECK约束实现,业务上由合规岗每月抽查。
6. 我的实战体会:当“最优”成为一种工作习惯
做完这个项目,我最大的改变不是掌握了某个公式,而是养成了一个条件反射:每次看到模型报告里的“违约样本数”,第一反应不再是“够不够”,而是“这些违约在说什么故事”。它们是来自哪个业务阶段?覆盖了哪些真实风险场景?有没有被时间冲淡了锋芒?这种思维转变,让我不再是模型参数的搬运工,而成了业务风险的语言翻译者。
有一次,某合作机构的模型在测试中表现平平,AUC只有0.71。我拿到他们的违约样本后,没急着调参,而是先做了覆盖率扫描——发现“小微企业+制造业+成立<2年”这个高危组合,竟只有9个违约样本。我们帮他们定向补充了43个该类样本(通过联合工商数据+税务数据交叉验证),模型AUC没变,但对该类客户的逾期预测准确率从58%跃升至82%。业务方当时说:“原来不是模型不行,是我们没给它讲清楚故事。”
所以,别再问“多少个违约样本最合适”。去问:“我的违约样本,有没有资格代表我要守护的那群人?”当你开始这样提问,你就已经站在了风控建模的真正起点上。这个起点不在代码里,而在你翻阅每一笔违约背后的真实卷宗时,在你和一线催收员聊起某个客户为何突然失联的电话里,在你看着FSC曲线那个微妙拐点时微微屏住的呼吸里。
