
简介本资源是一份面向金融风控从业者、银行建模岗与数据科学学习者的实战案例资料聚焦风控决策引擎在银行信贷业务中的建模落地围绕网商银行联合贷与某农信“税金贷”两条业务线展开。内容涵盖申请评分卡、行为评分卡的设计与效果验证好坏客户定义、观察期与表现期划分、有征信与无征信模型细分方案以及KS、AUC、精确率、准确率、召回率、PSI等指标评估同时给出“税金贷”准入规则中纳税人类型、注册资本、行业类型、纳税信用等级等剔除阈值设计并展示从10余种数据源、1100个变量筛选至10个入模变量的特征工程与额度测算流水线。资源为1个PDF文件压缩包约613KB篇幅紧凑但案例完整便于快速通读。目前已有951人学习下载适合希望理解贷前贷中风险量化、规则与评分卡结合思路的读者参考借鉴。1. 从网商贷联合贷看评分卡决策引擎的落地边界决定一个风控决策引擎上限的很少是算法本身而是好坏定义和观察期怎么切。同一个逾期口径从30天改成15天KS可能动三五个点审批通过率跟着变这套东西在金融银行的信贷场景里比调参重要得多。某城商行与网商银行做联合贷双方按约定资金比例出资基于共同认可的规则审批客群限定在淘宝、天猫、口碑、B2B商户以及线下二维码收款的码商用途是生产经营周转。这种全线上产品没有客户经理介入决策引擎就是唯一的风控闸门。项目节奏很能说明问题2018年12月先上专家申请评分卡2019年9月做第一轮数据驱动迭代2020年6月新增行为评分卡2021年1月再迭代申请卡全程2位建模专家协助单项目周期平均2.5个月。接下来把这三类建模案例网商贷、税金贷、整村授信的窗口设计、变量筛选和额度测算链拆开讲适合做风控建模、决策引擎规则配置和信贷产品策略的人对照自己手上的项目看。2. 申请评分卡与行为评分卡的好坏定义与观察期切分2.1 非滚动窗口与滚动窗口的差别在哪申请评分卡的表现期是「申请时点后至少6个月」属于非滚动窗口——每个样本的观察起点是自己的申请日窗口长度固定。行为评分卡反过来观察点固定在2019年9月表现期是「观察点后6个月滚动窗口」所有账户共享同一个时间轴。这个差别直接决定了两件事一是申请卡的样本成熟度参差不齐2020年下半年进的件到跑数时还没满6个月二是行为卡可以按月份滚动打分适合做贷中预警而不是贷前准入。观察期是2019年9月上线以来至2020年12月。做申请卡时申请日太靠后的样本必须直接剔掉不然标签是「截至跑数日还没逾期」不是真的表现期干净。-- 申请评分卡样本打标非滚动窗口 -- 观察期 2019-09 ~ 2020-12表现期固定为申请日后 6 个月 WITH apply_po AS ( SELECT apply_id, cust_id, apply_date FROM loan_apply WHERE apply_date BETWEEN DATE 2019-09-01 AND DATE 2020-12-31 ), perf AS ( SELECT p.apply_id, MAX(p.overdue_days) AS max_od_days FROM repay_perf p JOIN apply_po a ON a.apply_id p.apply_id WHERE p.dt a.apply_date AND p.dt DATE_ADD(a.apply_date, INTERVAL 6 MONTH) -- 只取申请后 6 个月内 GROUP BY p.apply_id ) SELECT a.apply_id, a.cust_id, a.apply_date, COALESCE(f.max_od_days, 0) AS max_od_days_6m, CASE WHEN COALESCE(f.max_od_days, 0) 0 THEN good WHEN f.max_od_days 30 THEN bad ELSE uncertain END AS label FROM apply_po a LEFT JOIN perf f ON f.apply_id a.apply_id;逻辑要点有三处。MAX(overdue_days)取的是表现期内最坏表现不是最近一期风控里「曾经坏过」比「现在正常」重要。DATE_ADD(... INTERVAL 6 MONTH)是窗口边界改这个值等于换一个样本集必须和策略、资金回收周期对齐。uncertain不丢单独存一张表后面做迁移矩阵或者逾期1-30天的次级模型都用得上。2.2 好坏阈值与不确定账户的处理差异申请卡和行动卡在阈值上并不一致维度申请评分卡行为评分卡好客户/账户表现期内无逾期表现期内无逾期坏客户/账户逾期30天以上逾期15天以上不确定账户逾期1-30天逾期1-15天表现期申请时点后至少6个月非滚动观察点后6个月滚动窗口观察期2019年9月上线至2020年12月观察点2019年9月排除规则—观察点已结清、已销户、观察点逾期、账龄小于3行为卡把坏的定义压到15天是因为行为卡的目标是早期预警等客户到30天再报已经晚了而申请卡面对的是新客30天口径的噪声更小、更稳定。行为卡额外加的四条排除规则也值得抄观察点已结清和已销户的账户没有后续表现可看观察点已经逾期的账户属于「带病入组」会把表现期标签污染账龄小于3的账户最短还款周期都没走完样本不成熟。提示好坏阈值一旦写进模型就不能随手改。阈值变了模型分数分布、cutoff、通过率全部要重算必须和策略侧一起版本化。2.3 有征信与无征信的模型细分与三方变量回溯码商、线下收款客群里有相当比例没有可用的央行征信记录硬用一个模型会把这批人整体打到高分档拒掉。做法是拆成有征信模型和无征信模型两套无征信那条线对三方数据做回溯把能拿到的三方变量补进模型。模型细分KSAUC精确率准确率召回率PSI有征信38.8984.3567.5680.4253.320.01无征信33.5477.2361.7779.4541.760.02无征信模型KS低5个多点、召回率低11个点这是数据可得性的代价不是建模失败。判断能不能接受的方法是看业务如果无征信客群的定价和额度上做了让步这个差距可以接受如果和无征信客群走一样的额度策略就得回头看准入规则是不是要收紧。两个模型的PSI分别是0.01和0.02说明上线后分数分布基本没漂模型稳定性合格。行为评分卡的训练/验证表现是训练集KS 41.39、AUC 83.28、精确率58.18、准确率82.42、召回率49.32验证集KS 40.54、AUC 81.29、精确率56.77、准确率80.48、召回率45.76。验证集比训练集掉约1个点KS属于正常范围如果掉超过3个点优先怀疑变量里混了泄漏信息或者时间窗口重叠。2.4 行为卡的四类变量衍生行为卡的变量池分为账户类66个、余额类13个、还款类24个、逾期类132个逾期类占比最高也最吃特征工程。账户类看当前未结清借据数、账龄/贷款期限、近3个月平均额度使用率、借据金额余额类看本金余额、逾期本金余额、逾期本金余额占本金余额百分比、逾期本金余额(1)占原始贷款金额百分比还款类看本金还款(1)占上月本金余额百分比、所有还款(1)占所有应还款金额(1)百分比、本金还款金额(1-3)占所有还款金额(1-3)百分比。逾期类的衍生逻辑最有代表性import pandas as pd def overdue_features(df: pd.DataFrame) - pd.DataFrame: df 粒度一户一月字段 month_idx(1最近月), od_flag(是否逾期), od_days, od_level df df.sort_values([cust_id, month_idx]) g df.groupby(cust_id) out pd.DataFrame(indexg.size().index) # 1) 历史最大逾期天数单调性强是最稳的逾期类变量 out[od_days_max] g[od_days].max() # 2) 逾期期数 0 的月数占比近 1~3 个月 out[od_cnt_1_3_ratio] g.apply( lambda x: (x.loc[x.month_idx 3, od_flag] 0).mean() ) # 3) 逾期 0 的连续最大月份数1~12 def max_run(s): run best 0 for v in s: run run 1 if v 0 else 0 best max(best, run) return best out[od_max_run_months] g[od_flag].apply(max_run) # 4) 逾期等级连续增加的次数恶化趋势比单点逾期更早预警 out[od_upgrade_cnt] g[od_level].apply( lambda s: sum(1 for i in range(1, len(s)) if s.iloc[i] s.iloc[i - 1]) ) return out四个变量的分工od_days_max刻画历史最坏程度od_cnt_1_3_ratio刻画近期频率od_max_run_months刻画持续性od_upgrade_cnt刻画趋势。行为卡要做贷中预警趋势类变量的贡献往往比绝对值大——一个从M0滑到M1再滑到M2的客户比一个稳在M1三个月的客户危险得多。month_idx从小到大对应从近到远排序方向搞反会让连续月份数全部失效这是最常见的低级错误。3. 税金贷14条准入规则与1100到10的变量筛选流水线3.1 准入规则写成可执行的剔除阈值税金贷面向小微客群主数据源是税务包括企业基本信息、企业申报、企业征收和违法违章数据黑样本由行方提供。准入和反欺诈规则是整条流水线的第一道闸门规则要写成「字段 阈值 动作」而不是一句业务描述。序号规则英文名规则名剔除阈值1NSLXMC纳税人类型只保留一般纳税人2ZCZB注册资本xxx万3HYML_MC行业类型涉及采矿、燃气及水生产和供应业、房地产开发经营、租赁行业4XYDJ纳税信用等级C级、D级5KYRQ_YEAR_DIFF企业成立年限小于1年6NSRZTDM纳税人状态只保留正常7L12m_sbxx_zzs_cnt近2年无增值税纳税申报记录无申报8L24m_wf_WFWZLX_cnt近24个月违法违章中发生严重违法逃避纳税税款、发票违法-虚开发票、骗税、抗税09L24m_jc_WFWZLX_cnt近24个月稽查中发生严重违法同上一类010L12m_YSXSSR_sum近12个月应税销售收入xxx万11L12m_YSXSSR_pop近12个月应税销售收入增长率xx%12L3m_sb_yqsb_cnt近3个月逾期未申报月份数大于013L3m_zs_yqjn_cnt近3个月逾期未缴纳月份数大于014SP_SJ_YEAR申报时间过早只保留申请日期≥2018年第1、4、6条是硬性准入属于「不符合直接拒」不能配置成软规则第8、9条涉及严重违法走反欺诈通道命中即拒并打标第10、11条是可调的经营规模门槛需要和产品定价、目标客群规模一起压测调高拒得多、调低风险敞口大。第14条容易被忽略——申报时间过早的数据本身是历史快照放进模型会造成时间穿越。3.2 宽表设计与1100到114的清洗数据源包括工商、司法、税务、企业财务、征信、行政处罚、行内数据等十余种整理后得到1100个变量。清洗分四步剔除手机号码、姓名、日期等不可入模字段删除时间跨度少的数据删除单一值超过80%的数据剔除缺失率在40%以上的数据。做完剩下114个。import pandas as pd def clean_wide_table(df: pd.DataFrame, drop_cols, miss_thr0.4, single_thr0.8): df df.drop(columns[c for c in drop_cols if c in df.columns]) # 手机号/姓名/日期等 miss df.isna().mean() drop_miss miss[miss miss_thr].index # 缺失率 40% df df.drop(columnsdrop_miss) single {} for c in df.columns: top_ratio df[c].value_counts(normalizeTrue, dropnaFalse).iloc[0] if top_ratio single_thr: single[c] top_ratio df df.drop(columnslist(single)) # 单一值 80% return df, {drop_missing: list(drop_miss), drop_single: single}miss_thr设0.4而不是0.5是因为小微企业数据本身稀疏留太多高缺失变量会逼着模型去学缺失模式single_thr设0.8单一值过高的变量区分度几乎为零留着只增加共线性。清洗结果要落一份被剔除清单方便后续对业务解释「为什么这个字段没进模型」。3.3 VIM、IV、相关性、多重共线性四级筛选114个变量到10个走的是四级漏斗VIM特征重要性筛选、IV值筛选、相关性分析、多重共线性分析。import numpy as np import pandas as pd from statsmodels.stats.outliers_influence import variance_inflation_factor def iv_table(df, cols, targetlabel, bins10): total_bad df[target].sum() total_good len(df) - total_bad rows [] for c in cols: s df[c].fillna(-999) # 缺失单独成箱别让它被平均掉 try: b pd.qcut(s, bins, duplicatesdrop) except ValueError: b pd.cut(s, bins, duplicatesdrop) grp df.groupby(b, observedTrue)[target].agg([sum, count]) grp[good] grp[count] - grp[sum] grp[bad_r] (grp[sum] / total_bad).replace(0, 1e-6) grp[good_r] (grp[good] / total_good).replace(0, 1e-6) grp[iv] (grp[bad_r] - grp[good_r]) * np.log(grp[bad_r] / grp[good_r]) rows.append({var: c, iv: grp[iv].sum()}) return pd.DataFrame(rows).sort_values(iv, ascendingFalse) def vif_table(X: pd.DataFrame): X X.assign(const1.0) return (pd.DataFrame({ var: X.columns, VIF: [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] }) .query(var ! const) .sort_values(VIF, ascendingFalse))IV的经验阈值小于0.02基本没区分度直接剔0.02到0.1是弱变量可以留作补充0.1到0.5是强变量是主力。VIF大于10说明和其他变量线性重叠严重常见做法是先剔VIF最高的那个再重算一轮反复两三次就收敛。相关性分析用Spearman还是Pearson要看变量分布税务这类含大量零值和长尾的字段Spearman更稳。最终入模的10个变量和单变量表现序号入模变量中文名KSGiniIV1intercept截距项———2ZZL_3近3个月销售收入增长率0.51540.50860.41093CV_12销售额近12个月变异系数0.46240.53290.33024SB_zero_COUNT_3最近3个月申报为0的次数0.44130.39120.37075SUM_PAY_GROWTH_RTI近12个月纳税额增长率0.40770.43780.38536ZNJ_12近12个月滞纳金次数0.38540.31090.36217LDBL流动比率0.35210.28910.32898MANAGEMENT_COST_REVENUE管理费用/营业收入0.28890.35650.35099TOTAL_CURRENT_LIABILITY流动负债合计/营业收入0.21850.20860.338610NL_1法人年龄0.20180.18950.331911WFWZ_12M近12个月违法违章次数0.18650.17620.3226模型效果训练集KS 40.13、AUC 80.25、精确率58.08、准确率79.35、召回率49.35验证集KS 39.34、AUC 79.31、精确率56.74、准确率74.18、召回率48.86。增长率类变量销售收入增长率、纳税额增长率和波动类变量变异系数、申报为0次数占了主力这符合税务数据的特性看一个企业的经营趋势和申报连续性比看某一期的绝对规模更有判别力。法人年龄这种弱变量留在模型里主要是为了在分段上提供一点小幅单调修正不是核心驱动。4. 从基本额度到最终授信的G1~G7额度测算链4.1 基本额度的双路测算与上下限夹逼额度测算不是拍一个数而是两条路径取高、再用一个组合上限压住最后取低。逻辑是纳税贡献大的企业按纳税额给额度销售收入大的企业按销售额给额度两个都大的用组合上限约束避免额度虚高。G1 TaxPayment_avg × timesTaxPayment_avg 是年均纳税总额G2 sales_1year / 12sales_1year 是近12个月销售收入G3 max(G1, G2)G4 G2 × 2 TaxPayment_avg × 2G5 min(G3, G4)times来自放大倍数表按纳税信用评级和评分等级两个维度取值纳税信用评级 \ 评分等级abcdefA121110987B11109876评分等级a到f从优到劣同一信用评级下等级每降一档倍数减1。A档最高12倍、B档最高11倍这个差距本身就是对纳税信用等级的一种定价。4.2 三个调整系数与最终额度计算G5拿到之后乘三个系数行业系数A区分优先支持行业A1.1和选择支持行业A1.0依据行业情景分析准入表确定存量客户系数B已是行内客户B1.05默认1.0历史表现系数C按历史还贷表现调整目标是收益覆盖风险和成本初始值1.0如果上一笔没出现过一次逾期下次额度提升可以设C1.05。加权额度 G6 G5 × A × B × C有效额度 G7 G6 −企业信用贷款余额 企业主个人经营性信用贷款余额最终额度 G max(min(G7, 300万), 0)。def calc_limit(tax_avg, sales_year, grade, tax_grade, industry_a1.0, stock_b1.0, history_c1.0, corp_credit0.0, owner_credit0.0, cap3_000_000): times_tbl {A: {a: 12, b: 11, c: 10, d: 9, e: 8, f: 7}, B: {a: 11, b: 10, c: 9, d: 8, e: 7, f: 6}} times times_tbl[tax_grade][grade] g1 tax_avg * times # 纳税贡献路径 g2 sales_year / 12 # 销售收入路径 g3 max(g1, g2) # 取高照顾纳税少但营收大的企业 g4 g2 * 2 tax_avg * 2 # 组合路径上限 g5 min(g3, g4) # 取低压住虚高 g6 g5 * industry_a * stock_b * history_c g7 g6 - (corp_credit owner_credit) # 扣减企业企业主个人经营性信用敞口 return max(min(g7, cap), 0) # 封顶 300 万下界 0参数上有几个坑。corp_credit和owner_credit必须一起扣只扣企业对公敞口、不扣企业主个人经营性贷款会出现同一实控人靠个人名义多头融资绕过额度约束。cap是产品级参数改它等于改风险敞口上限要走授信政策流程。系数A、B、C 三个乘数如果同时取到上限1.1 × 1.05 × 1.05 ≈ 1.21实际是给优质客户加了21%额度压测时要按最坏组合算一遍总体敞口。max(..., 0)这个下界不能省否则扣减后出现负数会被写成负额度单据。5. 整村授信批量风控与预警线叫停线设置技巧5.1 网格化建档与评议增信的额度上限整村授信和前面两个案例最大的区别是它不是单笔审批而是先批量给一个村、一个网格一个总额度池再逐户分配。运营上靠「总对总获取名单—启动预热—外部评议内部评议—确定授信方案—授信签约—用信营销—信用村授牌」这条链路支撑体系是网格管理员主导、网点负责人协管、网格协管员和客户机动员配合配合大数据风控做分层潜在客户、暂缓客户、目标客户、新客户、授信客户再按客群做产品匹配。农户画像里最有价值的是那套评议信息负面清单10条按严重程度从轻到重排列1游手好闲、行为不良2欠债较多、被催债3赌博、吸毒、涉黄4无劳动能力含残疾、重病、呆傻5有涉诉未结案记录、涉黑涉恶、违法违纪酒驾、打架斗殴等6放高利贷、涉及民间非法集资7有犯罪前科、正在服刑或处于社区矫正期的刑满释放人员8家庭存在不良贷款、被诉讼、不良担保9家庭不和睦父母、夫妻、儿女关系10村评不良性格不佳、禀性不良、信用较差。实操上建议把1到8条做成硬规则直接拒或转人工9、10条做成评议减分项而不是硬拒——这两条主观性太强硬拒容易引发纠纷做成评分项更稳妥。额度模型是「基础额度 评议增信额度 线下走访增信额度」并且整体封顶20万。增信项包括党员增额3万平均收入增额按1万、1.5-2倍2万、2倍以上3万三档分红增额按家庭近3年人均年分红额×贷款年数×0.5村民积分增额按A级前10%给2万、B级10-20%给1万、C级20-30%给0.5万行内资产增额按近一年日均金融资产余额×贷款年数×0.5其他收入增额按近3年除工资及分红外收入平均×贷款年数×0.5另外名下住房一套10万、自用车一辆3万。多档增信项叠加时必须先算总上限再逐项截断不能每项各自封顶后相加。5.2 预警线与叫停线的参数化配置贷后管理是整村授信最容易失效的一环因为它靠人盯。把两个指标参数化后交给系统盯会稳得多整村授信不良贷款率和当月利息收回率。设置两条线超过预警线的网格管理员协同管户客户经理对风险客户逐户排查采取措施包括提前终止、只收不付超过叫停线的对网格内整村授信客群执行只收不贷。def grid_alert(npl_rate, interest_collect_rate, npl_warn0.02, npl_stop0.04, icr_warn0.95, icr_stop0.90): # 不良率越高越危险利息收回率越低越危险任一触线即按更高等级处理 if npl_rate npl_stop or interest_collect_rate icr_stop: return STOP # 叫停线整网格只收不贷 if npl_rate npl_warn or interest_collect_rate icr_warn: return WARN # 预警线逐户排查可提前终止、只收不付 return NORMAL阈值的取法不能照搬。整村授信的客群是小额分散不良率天然比城区的经营贷略高预警线设太低会导致网格管理员天天排查规则被架空设太高等于没设。比较务实的做法是拿试点网格的历史数据回算一遍让预警线大致落在「每季度会触发一到两次」的频率上这样既有人管、又管得过来。同时要留意评议增信的道德风险外部评议依赖村委和村民代表评议信息既影响额度也影响准入必须有交叉复核单点评议不能直接决定20万的顶格额度。综合评议形成的白名单库、差异化批量授信方案、场景建设和营销触达这几件事最终都要回落到同一套网格化的数据底表上否则预警线算出来的数没人认。本文还有配套的精品资源点击获取