简介:这份PDF论文面向金融风控研究者、商业银行信贷从业者及机器学习方向的学生,聚焦小微企业信贷风险评估这一难题,尝试用BP神经网络结合层次分析法构建信用评级模型,以辅助银行进行贷前风险识别与信贷决策。资源包内仅含1个PDF文件,大小约1.01MB,即论文全文,涵盖引言、文献综述、指标体系构建与实证分析等完整章节,便于系统研读方法脉络。论文以创业板机械制造业上市公司为研究对象,对小微企业重新定义,通过AHP法确定指标权重,再训练BP神经网络完成信用等级预测,并验证其拟合度与预测准确性。文中还梳理了评分法、KMV、Logistic等主流信贷风险模型,并讨论信息不对称、信贷配给等融资难成因。目前已有140人学习,适合希望将神经网络与数据建模落地到信贷风控场景的读者参考借鉴。
1. 神经网络视角下的小微企业信贷风险评估:为什么传统评分卡在长尾客户上集体翻车
做过小微企业信贷的人都有一个共同体会:同样一套评分卡,放在头部客户上准得吓人,一旦下沉到长尾客群,KS 直接掉一半。原因不复杂——小微企业普遍存在财报不规范、纳税数据稀疏、开票流水断续、关联交易隐蔽这几类问题,传统逻辑回归吃的是"特征工程喂什么就学什么",人工构造的交叉项根本覆盖不了这种高维稀疏、非线性强耦合的场景。神经网络视角下的小微企业信贷风险评估,本质上是把"人找规则"换成"网络自己找表征":用前馈神经网络、LSTM、图神经网络等结构,把工商、税务、发票、司法、征信多头数据映射成一个高维风险向量,再输出违约概率。这套方案适合两类人:一是手里已经有几万条以上样本、想替换或补强评分卡的信贷风控工程师;二是想从零搭一套可复现实验管线的算法同学。它不解决"没有数据"的问题,但能解决"有数据却挖不动"的问题。
2. 从评分卡到神经网络:小微风控的建模路线怎么选
2.1 为什么逻辑回归在小微场景会失效
逻辑回归的假设是特征与 log-odds 线性相关,且特征之间近似独立。小微企业的真实数据恰好两条都违反。举个最常见的例子:一家批发零售企业的"近 3 个月开票金额"和"下游客户集中度"单独看都不显著,但两者组合起来——开票金额高且集中度极高——往往意味着单一客户依赖,一旦对方回款延迟就爆雷。这种交互项靠人工枚举是组合爆炸,靠 WOE 分箱又会把连续信息切碎。神经网络的多层非线性激活天然能拟合这类高阶交互,这是它相对评分卡最直接的价值。
另一个现实约束是样本量。头部机构的小微样本动辄几十万,但大多数城商行、小贷公司手里只有几千到几万条正负样本,且违约样本占比常在 1%~5%。这意味着你不能直接上几十层的深网,参数量一上去就过拟合。我一般建议:样本低于 2 万条时,优先用 3~5 层的浅层前馈网络(MLP)或带嵌入层的 Wide&Deep;样本到 10 万级、且有时序行为数据时,再考虑 LSTM 或 Transformer 类结构;如果企业之间存在担保、供应链、实控人关联,图神经网络(GNN)才真正发挥价值。
2.2 三类网络结构在小微风控里的分工
把常见结构按数据形态分一下,选型就不容易乱:
| 结构 | 适配数据 | 典型用途 | 注意点 |
|---|---|---|---|
| MLP / 前馈网络 | 结构化特征表 | 替代评分卡做基础 PD 模型 | 必须做特征分桶+嵌入,否则稀疏类别炸维度 |
| LSTM / RNN | 开票、流水、还款时序 | 捕捉账期恶化趋势 | 序列长度对齐,缺失月份要显式 mask |
| GNN 图神经网络 | 担保圈、供应链、实控人关系 | 关联风险传导、团伙欺诈识别 | 边权重设计比网络结构更影响效果 |
选型的第一原则是"数据形态决定结构",不是"哪个新用哪个"。我见过不少团队一上来就堆 GNN,结果图里节点特征全是缺失值,边关系靠人工拍脑袋,效果还不如逻辑回归。正确的顺序是:先把结构化特征用 MLP 打到一个可用的基线,再逐步引入时序和图结构做增量。
2.3 一个可复现的 MLP 基线:数据到训练的最小闭环
下面这段代码是一个能直接跑通的最小闭环,用 PyTorch 实现,重点在特征处理而不是网络本身——小微风控里 80% 的功夫在特征侧。
import torch import torch.nn as nn import pandas as pd from sklearn.preprocessing import LabelEncoder, StandardScaler # 1. 读取样本:每行一个企业,label 为 0/1 违约标签 df = pd.read_csv("micro_loan_samples.csv") cat_cols = ["industry", "region", "tax_level"] # 类别型:行业、地区、纳税等级 num_cols = ["invoice_3m", "invoice_trend", "overdue_cnt", "credit_age"] # 2. 类别特征做整数编码,后续走 Embedding;数值特征标准化 for c in cat_cols: df[c] = LabelEncoder().fit_transform(df[c].astype(str)) scaler = StandardScaler() df[num_cols] = scaler.fit_transform(df[num_cols]) # 3. 记录每个类别特征的基数,Embedding 维度用 min(50, (card+1)//2) cardinalities = [df[c].max() + 1 for c in cat_cols] emb_dims = [min(50, (c + 1) // 2) for c in cardinalities] class MicroRiskMLP(nn.Module): def __init__(self, cardinalities, emb_dims, n_num): super().__init__() self.embs = nn.ModuleList([ nn.Embedding(card, dim) for card, dim in zip(cardinalities, emb_dims) ]) in_dim = sum(emb_dims) + n_num self.net = nn.Sequential( nn.Linear(in_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, 1) # 输出 logit,配 BCEWithLogitsLoss ) def forward(self, x_cat, x_num): emb_out = [emb(x_cat[:, i]) for i, emb in enumerate(self.embs)] x = torch.cat(emb_out + [x_num], dim=1) return self.net(x).squeeze(-1) model = MicroRiskMLP(cardinalities, emb_dims, len(num_cols)) criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([20.0])) # 违约样本少,加权 optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)逻辑说明:类别特征走 Embedding 是为了避免 one-hot 把维度撑爆,同时让网络自己学行业、地区之间的相似性;数值特征标准化后直接拼接。pos_weight是应对样本不平衡的关键参数,一般设成负正样本比,比如 20:1 就填 20,填太小模型会全预测为"不违约",AUC 看着还行但召回惨不忍睹。weight_decay用 1e-4 起步,小微样本噪声大,正则弱了容易记住噪声。
参数怎么调:隐藏层宽度从 128 起,样本少于 1 万就降到 64;Dropout 在 0.2~0.4 之间试,超过 0.5 通常欠拟合;学习率 1e-3 配 Adam 是稳妥起点,loss 震荡就降到 3e-4。训练时务必按时间切分训练/验证集,不能随机切——随机切会让同一企业的不同月份样本同时进训练和验证,指标虚高,这是小微风控里最隐蔽的翻车点之一。
3. 特征工程与样本构造:神经网络吃进去的到底是什么
3.1 多头数据怎么拼成一张宽表
小微企业的数据源通常分四类:工商司法(注册资本、涉诉、变更)、税务发票(开票金额、上下游集中度)、征信(负债、查询、逾期)、行为(APP 登录、还款操作)。拼宽表的核心是"以企业+时间点为粒度",每个观察点取过去 6~12 个月的窗口做聚合。常见做法是拉一个月末快照,特征窗口用[T-12m, T-1m],标签窗口用[T, T+6m]看是否违约,中间留 1 个月做表现期隔离,防止标签泄漏。
聚合时别只算均值。开票金额的"近 3 月环比趋势""最大值/均值比""连续零开票月数"这类形态特征,往往比绝对金额更有区分度。我一般会为每个核心数值字段生成 5~8 个衍生量:均值、标准差、趋势斜率、最近值、最大最小比、缺失月数。字段一多维度就上去了,这时候 Embedding + MLP 的优势才体现出来。
3.2 违约样本极少时怎么造训练集
违约率 2% 意味着 10 万样本里只有 2000 条正例,直接训练模型会偏向多数类。三种处理方式按优先级排:第一,pos_weight加权,最简单,不改数据分布;第二,欠采样多数类到 1:5~1:10,配合加权;第三,SMOTE 类过采样,但在高维稀疏特征上容易造出不真实的合成样本,我一般只在特征维度低于 50 时用。无论哪种,验证集和测试集必须保持原始分布,否则算出来的 KS、AUC 全是假的。
from imblearn.under_sampling import RandomUnderSampler # 只在训练集上做欠采样,验证/测试保持原始分布 rus = RandomUnderSampler(sampling_strategy=0.1, random_state=42) X_train_res, y_train_res = rus.fit_resample(X_train, y_train) # 采样后正负比约 1:10,再配合 pos_weight=10 做二次平衡参数说明:sampling_strategy=0.1表示少数类采样后占多数类的 10%,即正负比 1:10。这个比例不是拍脑袋,经验上 1:5 到 1:20 之间对 AUC 影响不大,但对阈值选择影响明显,比例太低会让模型在真实分布下阈值偏移。采样后一定要重新校准概率,因为欠采样改变了先验,输出概率不再等于真实违约率,上线前要用原始分布做 Platt scaling 或 isotonic 回归校准。
3.3 时序特征用 LSTM 还是手工统计量
很多团队纠结要不要上 LSTM。我的判断标准很简单:如果每个企业的行为序列长度稳定(比如固定 12 个月还款记录),且你怀疑"恶化趋势"比"当前水平"更重要,那就值得上 LSTM;如果序列长度参差、缺失严重,手工统计量(趋势斜率、波动率)加 MLP 往往更稳。LSTM 在小微场景的坑在于:序列里大量缺失月份如果直接填 0,模型会误以为"这个月没开票=经营停滞",正确做法是加 mask 通道,把"缺失"和"真实为零"区分开。
# LSTM 输入形状: (batch, seq_len, feat_dim),另加 mask 通道标记有效月份 class SeqRiskLSTM(nn.Module): def __init__(self, feat_dim, hidden=64): super().__init__() self.lstm = nn.LSTM(feat_dim + 1, hidden, batch_first=True) # +1 是 mask self.fc = nn.Linear(hidden, 1) def forward(self, x, mask): # x: 行为特征, mask: 1 表示该月有数据, 0 表示缺失 inp = torch.cat([x, mask.unsqueeze(-1)], dim=-1) out, _ = self.lstm(inp) last = out[:, -1, :] # 取最后时刻隐状态 return self.fc(last).squeeze(-1)逻辑说明:把 mask 作为额外通道喂进去,网络能学到"缺失本身也是信号"——连续多月无开票记录,本身就是风险上升的表现。取最后时刻隐状态而不是全局池化,是因为风控关心的是"当前状态",不是序列平均。hidden 设 64 够用,小微序列通常不超过 24 个月,再大容易过拟合。
4. 训练、评估与上线:指标怎么定、阈值怎么切
4.1 风控模型不能只看 AUC
AUC 衡量的是排序能力,但信贷决策关心的是"在某个通过率下,能拦住多少坏人"。所以核心指标是 KS、以及分箱后的 Lift。KS 一般要求验证集上 0.3 以上才算可用,0.4 以上算不错。但要注意:小微模型 KS 波动大,同一模型换个月份的测试集可能从 0.42 掉到 0.31,所以必须做跨时间验证(OOT,out-of-time),用最近 3 个月做样本外测试,而不是随机留出。
from sklearn.metrics import roc_auc_score, roc_curve def ks_score(y_true, y_prob): fpr, tpr, _ = roc_curve(y_true, y_prob) return max(tpr - fpr) auc = roc_auc_score(y_test, y_prob) ks = ks_score(y_test, y_prob) print(f"AUC={auc:.4f}, KS={ks:.4f}")参数说明:roc_curve返回的 fpr/tpr 逐点相减取最大即 KS。评估时至少跑三个口径:随机切分、按时间切分、按地区切分。三个口径 KS 差距超过 0.1,说明模型学到了地域或时间相关的伪特征,上线后必然衰减。
4.2 阈值不是 0.5,是业务算出来的
模型输出的是违约概率,但决策要的是"通过/拒绝"的切点。切点由业务定:假设通过率目标 70%,就把所有样本按预测概率排序,取第 70 百分位作为阈值。更严谨的做法是算期望损失——通过一个客户的预期收益 vs 违约损失,找到期望利润最大化的切点。这一步必须和业务方一起做,算法同学自己拍 0.5 是典型的踩坑。
4.3 上线后的监控看什么
模型上线不是终点。小微客群受行业景气影响大,特征分布漂移快。必须监控三类指标:一是输入特征的 PSI(population stability index),单特征 PSI 超过 0.25 就要预警;二是输出分数的分布漂移;三是实际违约率与预测违约率的偏差。我一般设一个月度巡检,PSI 超阈值就触发重训评估,别等到 KS 掉穿才反应。
5. 避坑与排查:小微神经网络风控里最容易翻车的 5 件事
现象一:训练集 AUC 0.95,测试集 0.6。原因几乎总是标签泄漏——特征窗口和标签窗口重叠了。比如用 T 时刻的开票数据预测 T 时刻之后 6 个月的违约,但开票数据里混进了违约后月份的记录。解决:严格按时间轴切窗口,特征截止日必须早于观察点,中间留表现期隔离带。
现象二:模型把所有样本都预测为不违约。原因是样本极度不平衡且没做加权,模型发现全猜多数类就能拿到 98% 准确率。解决:加pos_weight,或先欠采样再训练,同时把评估指标从 accuracy 换成 KS/AUC。
现象三:Embedding 层维度爆炸,显存不够。原因是某个类别特征基数极大,比如"企业注册地址"精确到门牌号,基数几十万。解决:对高基数类别先做频次截断,低频归为"其他",或改用哈希嵌入(hashing trick),把维度压到可控范围。
现象四:线上分数和离线对不上。常见原因是特征计算口径不一致——离线用 pandas 算的均值,线上用 SQL 算,遇到空值处理逻辑不同。解决:特征工程代码离线线上共用一套,或者用特征平台统一管理,上线前做逐特征比对。
现象五:模型效果随月份剧烈波动。原因是训练样本时间跨度太短,只覆盖了某个行业景气周期。解决:训练集至少覆盖 24 个月,包含一个完整的风险暴露周期,并在验证时专门看跨周期表现。
6. 进阶:用图神经网络捕捉担保圈风险传导
当你的模型已经能稳定跑出 KS 0.35 以上,下一步增量往往来自"关系"而不是"个体特征"。小微企业最典型的风险传导路径是担保圈和供应链:A 企业违约,会通过互保链条拖累 B、C。这类信号在单企业宽表里完全看不到,必须建图。
具体做法:节点是企业,边有三类——担保关系(权重按担保金额)、供应链上下游(权重按交易频次)、实控人关联(权重按持股比例)。节点特征就是前面 MLP 用的那套宽表特征。用两三层 GraphSAGE 或 GCN 做消息传递,让每个企业聚合邻居信息后再输出风险分。
import torch import torch.nn.functional as F from torch_geometric.nn import SAGEConv class GuaranteeGNN(torch.nn.Module): def __init__(self, in_dim, hidden=64): super().__init__() self.conv1 = SAGEConv(in_dim, hidden) self.conv2 = SAGEConv(hidden, hidden) self.fc = torch.nn.Linear(hidden, 1) def forward(self, x, edge_index, edge_weight): # edge_weight 传入担保金额归一化后的权重 x = F.relu(self.conv1(x, edge_index, edge_weight)) x = F.dropout(x, p=0.3, training=self.training) x = F.relu(self.conv2(x, edge_index, edge_weight)) return self.fc(x).squeeze(-1)逻辑说明:两层 SAGEConv 意味着每个企业能聚合到二跳邻居的信息,对应"担保圈里隔一层的关系"。edge_weight用担保金额归一化,是因为大额担保的风险传导远强于小额。训练时要注意:图里违约节点极少,正负样本在图上分布不均,建议对违约节点做邻居采样增强,否则消息传递会被大量正常节点稀释。
验证 GNN 是否真的带来增量,别只看整体 AUC,要看"有担保关系的子样本"上的表现。如果 GNN 在这部分子样本上 KS 比 MLP 高 0.05 以上,说明关系信号确实被捕捉到了;如果整体涨但子样本没涨,多半是过拟合了图结构噪声。我自己的习惯是:任何新结构上线前,先在历史违约案例上做回溯——把已知爆雷的企业喂进去,看模型在爆雷前 3 个月有没有给出高分。这个"后悔药"测试比任何离线指标都直观。希望帮到你。
本文还有配套的精品资源,点击获取