BP神经网络信贷信用评估实战:从数据准备到评分卡上线
2026/9/15 4:08:52 网站建设 项目流程

简介:这是一份基于BP神经网络的个人信贷信用评估完整实现,面向金融风控、机器学习初学者以及需要快速搭建分类模型的Matlab用户。压缩包共3个文件,包含核心脚本、已数值化数据与原始数据,整体仅28KB,结构轻量便于直接运行。执行主脚本即可完成模型训练与测试,实验结果显示20次测试平均正确率为74.97%,最低正确率73.4%,且迭代次数稳定为3次,说明模型收敛快、性能可靠。目前已有334人学习下载,适合用于课程设计、毕业设计或信贷风险评估入门实践。通过源码可学习数据预处理、BP网络构建与评估流程,也便于后续调参优化和二次开发,对理解神经网络分类任务具有实际参考价值。

1. 基于BP神经网络的个人信贷信用评估,这条技术路线为什么还能打

个人信贷风控这几年几乎被梯度提升树和深度学习包围,XGBoost、LightGBM 在结构化表格数据上统治力极强,但BP神经网络并没有退出牌桌。原因在于信用评估本质是一个非线性映射问题:输入是年龄、收入、负债比、历史逾期次数这类稀疏且高度相关的特征,输出是违约概率或信用评分。BP神经网络通过误差反向传播自动拟合特征之间的交互关系,在小样本、强先验的信贷数据集上,配合合理的正则化,依然能逼近甚至超过树模型的判别能力。更现实的原因是,评分卡监管和模型可解释性要求下,很多机构需要的是一个能稳定复现、可导出权重、可做阈值控制的概率模型,BP神经网络恰好满足这些约束。这篇文章顺着一条完整落地路径讲:从数据字段设计、BP网络结构选择,到Python训练、参数调优,再到信贷评分映射与上线监控。适合正在做风控建模、需要评估基准模型或者想把BP网络作为集成学习基学习器的读者。

2. BP神经网络做信用评估的理论与数据准备

2.1 BP神经网络结构图与信用评估的映射关系

BP神经网络的核心是输入层、隐藏层、输出层的全连接结构,加上非线性的激活函数。信用评估场景里,输入层神经元个数等于特征维度,输出层通常是一个神经元,输出违约概率。隐藏层层数和节点数决定模型的拟合能力。标准的BP算法包含两个过程:前向传播计算预测值,反向传播根据损失函数对权重求梯度,再用梯度下降更新权重。

映射关系上要注意,信用评估不是纯回归也不该写成分类器。输出层用Sigmoid激活,配合交叉熵损失,得到的是一个连续概率值,而不是直接给“好/坏”的类别。后期可以通过这个概率做排序、定阈值、换算评分卡分数。隐藏层激活一般选ReLU,但如果数据做过分箱标准化,Tanh在浅层网络里有时收敛更稳。

网络结构的选择有经验法则。输入特征个数假设是20,隐藏层节点数可以先取输入的1.5到2倍,之后用网格搜索调优。层数方面,个人信贷数据样本很少超过几十万条,特征维度也不高,两层隐藏层基本是上限,堆多了反而容易过拟合并且难以收敛。

2.2 信贷数据集字段与标签定义

常见做法是使用德国信用数据集或Lending Club的公开样本,这些数据包含字段大致如下:

字段类型典型字段处理方式
数值连续年龄、月收入、负债率、贷款金额分箱或归一化
类别离散职业、住房状态、储蓄账户、信用目的One-hot编码或WOE编码
时序统计过去逾期次数、查询次数连续化处理,截断异常值
目标标签是否违约二值化,1表示坏样本

标签的定义决定了模型上限。常见做法是把“逾期超过90天”或“核销”标记为1,其余为0。注意样本时间窗口:特征取申请时点的历史数据,标签取未来12至24个月的表现,避免用未来信息预测过去。数据集划分时不能随机混洗后直接切,而是按时间顺序切分,否则会出现特征穿越,高估模型效果。

2.2.1 缺失值与异常值处理

信贷数据缺失不是随机的,往往与申请渠道、客户资质相关。直接填充均值会抹掉这种信号。常用办法是单独生成一列“是否缺失”作为新特征,同时用中位数填充连续变量。异常值例如月收入填了999999,这类需要用分位数截断处理,超过99%分位的值拉回99%分位数。处理异常值时不要只看单变量,结合贷款金额计算负债收入比的时候,异常值会被放大,所以先截断再派生特征。

2.2.2 归一化与one-hot的取舍

BP神经网络对输入尺度敏感,如果不做归一化,梯度更新会震荡严重。连续特征要标准化到[0,1]或均值为0方差为1。类别特征直接用one-hot会增大维度,一个只有10个类别的职业字段就会增加10个维度,再加上关联性强的稀疏编码,网络参数爆炸。替代方案是WOE编码:把每个类别映射为该类别的违约对数odds,这样既保留顺序信息,压缩维度,又能让输入值落在合理的数值区间。建议在BP网络里用WOE编码或者目标编码,树模型可以通过原始label编码处理,但BP需要数值光滑的输入。

2.3 特征选择与数据平衡

特征选择上,BP网络不适合塞入过多无关特征。常用方法先用随机森林或逻辑回归初筛,保留与目标变量IV值大于0.02的特征。另外要剔除相关性大于0.7的特征对,例如“负债率”和“负债金额”高度相关,只保留其一即可。数据平衡方面,信贷数据好坏比通常20:1甚至更高,直接训练BP网络会倾向预测全为“好”。在数据准备阶段就要决定好策略:欠采样、过采样还是调权重。网络训练中更推荐调整类别权重,避免改变原始数据分布。

3. 用Python从零训练BP神经网络信用评估模型

3.1 最小可运行的NumPy版BP网络

理解BP原理之后,先手写一个最小版本,便于看清梯度流动和调试。下面代码实现了一个含一层隐藏层的BP网络,输入维度是特征数,隐藏层节点数可调,输出为违约概率。

import numpy as np class BPNet: def __init__(self, n_features, n_hidden=16, lr=0.01, reg=1e-4): # 权重初始化使用Xavier,避免梯度消失或爆炸 self.w1 = np.random.randn(n_features, n_hidden) * np.sqrt(2.0 / n_features) self.b1 = np.zeros((1, n_hidden)) self.w2 = np.random.randn(n_hidden, 1) * np.sqrt(2.0 / n_hidden) self.b2 = np.zeros((1, 1)) self.lr = lr self.reg = reg def sigmoid(self, z): return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500))) def forward(self, X): self.z1 = X.dot(self.w1) + self.b1 self.a1 = np.tanh(self.z1) # 隐藏层用tanh,输出在[-1,1]之间 self.z2 = self.a1.dot(self.w2) + self.b2 self.a2 = self.sigmoid(self.z2) return self.a2 def backward(self, X, y): m = X.shape[0] # 交叉熵损失对输出层z2的梯度 delta2 = self.a2 - y.reshape(-1, 1) # 隐藏层误差项,tanh导数为 1 - a1^2 delta1 = delta2.dot(self.w2.T) * (1 - np.power(self.a1, 2)) dw2 = self.a1.T.dot(delta2) / m + self.reg * self.w2 db2 = np.sum(delta2, axis=0, keepdims=True) / m dw1 = X.T.dot(delta1) / m + self.reg * self.w1 db1 = np.sum(delta1, axis=0, keepdims=True) / m self.w2 -= self.lr * dw2 self.b2 -= self.lr * db2 self.w1 -= self.lr * dw1 self.b1 -= self.lr * db1 def train(self, X, y, epochs=100, batch_size=32): for epoch in range(epochs): idx = np.random.permutation(len(X)) for i in range(0, len(X), batch_size): batch_idx = idx[i:i+batch_size] Xb = X[batch_idx] yb = y[batch_idx] self.forward(Xb) self.backward(Xb, yb)

这段代码的逻辑说明:forward里先线性变换再经过tanh,输出层用sigmoid把结果压到0和1之间。backwarddelta2直接等于预测值与真实值之差,这是交叉熵与sigmoid组合的数学简化结果。权重更新时加了reg正则项,本质是对权重做L2衰减,避免某些特征权重过大导致过拟合。训练时使用小批量随机梯度下降,batch_size=32在信贷数据集上速度与稳定性的平衡点。

参数说明:n_hidden隐藏层节点数,太小欠拟合,太大过拟合;lr学习率,推荐先试0.1、0.01、0.001三档;reg正则强度,可以从1e-4起步,如果训练AUC与验证AUC差距过大,加大到1e-2。手动实现版本适合教学和深度调试,但真正做信用评估时建议直接使用封装好的库。

3.2 用MLPClassifier快速验证基线效果

实际项目中不会手写BP,而是用sklearn.neural_network.MLPClassifier快速跑通基线。下面演示在标准化后的特征矩阵上训练,并输出AUC。

from sklearn.neural_network import MLPClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score import pandas as pd # df为已清洗好的特征表,X为特征列,y为标签列 X = df.drop('label', axis=1).values y = df['label'].values scaler = StandardScaler() X = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) mlp = MLPClassifier( hidden_layer_sizes=(32, 16), activation='relu', solver='adam', alpha=0.0001, batch_size=128, max_iter=300, early_stopping=True, n_iter_no_change=10, validation_fraction=0.15, random_state=42 ) mlp.fit(X_train, y_train) y_pred_proba = mlp.predict_proba(X_test)[:, 1] auc = roc_auc_score(y_test, y_pred_proba) print(f"Validation AUC: {auc:.4f}")

这里hidden_layer_sizes=(32, 16)表示两层隐藏层,第一层32个节点,第二层16个节点。solver='adam'是自适应学习率优化器,比原始SGD收敛快,但要注意它自带动量,学习率参数实际会被动态调整。alpha就是L2正则化系数,与手写代码里的reg对应。early_stopping=True配合validation_fraction,训练时会自动从训练集切出15%作为验证集,当连续10轮验证损失不改善时停止训练,这是防止过拟合最直接的一招。

3.3 训练集/验证集划分与早停设置

信贷数据建模中,训练和验证集划分不能只依赖stratify按标签分层,还要考虑时间维度。例如用2023年1月到6月的数据训练,用7月到9月的数据验证,再用10月到12月的数据测试。如果样本量不够,再退化为随机分层抽样。

早停参数n_iter_no_changetol有联动关系。tol默认是1e-4,表示验证损失相对改善小于这个值就认为不再提升。在信贷数据上,损失曲线往往在50轮之后进入平台期,n_iter_no_change建议设在10到20之间,太小容易在局部波动中过早停止,太大会浪费训练时间。还可以把max_iter提高到一个边界值,例如500,防止max_iter触顶时模型还没收敛。

4. 信贷场景下的模型评估、阈值选择与样本不均衡处理

4.1 评估指标:AUC、KS与提升度

信用评估模型不能用准确率衡量,因为坏样本占比可能只有5%,全部预测为好准确率就是95%,毫无意义。核心指标是AUC和KS。

AUC是ROC曲线下面积,衡量模型把随机的好样本排在坏样本前面的概率。AUC在0.7以上具备基本区分能力,0.8以上算优秀。KS统计量是坏样本累积分布减去好样本累积分布的绝对差最大值,公式为:

KS = max(TPR - FPR)

KS通常大于0.3可用,大于0.5需要警惕是否过拟合。下面代码计算KS值并画出分箱下的累积分布。

import pandas as pd import numpy as np # pred_df包含预测概率和真实标签 pred_df = pd.DataFrame({'prob': y_pred_proba, 'label': y_test}) # 按概率分成10个分数段,从高到低排序 pred_df['bin'] = pd.qcut(pred_df['prob'], 10, duplicates='drop') grouped = pred_df.groupby('bin', as_index=False)['label'].agg(['mean', 'count']) grouped['bad_pct'] = grouped['mean'] grouped['good_pct'] = 1 - grouped['mean'] grouped['cum_bad'] = grouped['bad_pct'].cumsum() grouped['cum_good'] = grouped['good_pct'].cumsum() grouped['ks'] = np.abs(grouped['cum_bad'] - grouped['cum_good']) ks_value = grouped['ks'].max() print(f"KS = {ks_value:.4f}")

这段代码用分位数分箱代替逐样本累积计算,每组样本量更平均,KS值不会因个别极端概率而失真。注意duplicates='drop',当概率取值重复过多导致分位数不足10箱时,自动减少箱数。

4.2 阈值选择与业务成本衡量

模型输出概率后,需要设定一个阈值来判定好坏。阈值不是固定0.5,而是根据业务成本计算。假设拒绝一个好客户损失获客成本C1,批准一个坏客户损失本金C2,那么最优阈值应满足:

阈值近似 = C1 / (C1 + C2)

例如,坏客户平均损失2000元,好客户获客成本200元,阈值应约为 200 / (200+2000) = 0.0909。也就是说概率超过9.1%就可以拒绝。这个计算很粗糙,但指引了方向。实际调阈值时,看混淆矩阵在验证集上的表现,选择一个单人头的成本最低点。可以利用下面的代码搜索最优阈值:

from sklearn.metrics import confusion_matrix thresholds = np.arange(0.05, 0.6, 0.01) best_th, best_cost = 0.5, float('inf') # 假设单笔好客户盈利G, 坏客户亏损L G, L = 500, 2000 for th in thresholds: y_pred = (y_pred_proba >= th).astype(int) tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel() # 总成本 = 拒绝好客户损失(-fp) + 批准坏客户损失(tp的误放) cost = fn * G + fp * L if cost < best_cost: best_cost = cost best_th = th print(f"Best threshold = {best_th:.2f}, min cost = {best_cost:.2f}")

这里的tn, fp, fn, tp四个值中,fn是把好客户拒绝掉的样本数,fp是把坏客户放过的样本数。成本函数中,拒绝好客户损失的是潜在盈利,批准坏客户损失的是本金,两边权重不同。这种阈值搜索方法比固定0.5更符合信贷业务。注意阈值确定后,需要在独立的测试集上重新验证一遍成本和KS,避免过拟合验证集。

4.3 样本不均衡的应对策略

信贷坏样本占比低,直接训练BP网络会导致输出概率整体偏小,模型无法区分好坏。常见做法有三种:

  1. 调整样本权重:给坏样本的损失乘以权重,权重通常设为好坏比。例如好坏比20:1,坏样本权重设为20。MLPClassifier里传入class_weight='balanced'即可,底层会自动按比例调整。
  2. 过采样:用SMOTE生成合成的坏样本。注意SMOTE要在训练集上执行,验证集保持原始分布,否则评估结果会虚高。
  3. 欠采样:随机丢弃多数类样本。适合样本量很大的情况,缺点会丢掉有用信息。

在BP神经网络中,优先选择调整样本权重,理由是不改变原始数据分布,训练后输出概率可以按照真实好坏比进行校准。相比之下,SMOTE在特征维度较高时很容易生成噪声点,需要谨慎检查合成样本是否符合业务逻辑。如果训练AUC高但验证AUC低,先怀疑是否在验证集上也做了过采样。

4.4 参数网格搜索与正则化

BP网络的超参数组合很多,隐藏层节点数、学习率、正则系数、批量大小。建议用随机搜索而不是全网格,因为维度太多。下面是用RandomizedSearchCV做一轮搜索的代码:

from sklearn.model_selection import RandomizedSearchCV param_dist = { 'hidden_layer_sizes': [(16, 8), (32, 16), (64, 32), (128,)], 'alpha': [0.0001, 0.001, 0.01, 0.1], 'learning_rate_init': [0.001, 0.01, 0.05], 'batch_size': [64, 128, 256], 'activation': ['relu', 'tanh'], } search = RandomizedSearchCV( MLPClassifier(max_iter=300, early_stopping=True, random_state=42), param_dist, n_iter=20, cv=3, scoring='roc_auc', n_jobs=-1, random_state=42 ) search.fit(X_train, y_train) print(search.best_params_)

n_iter=20表示从参数分布里随机抽取20组组合,交叉验证3折。scoring='roc_auc'衡量排序能力而不是分类准确率。搜索出的最优参数只是参考,实际使用时要再在验证集上跑一遍,观察KS和分数分布是否稳定。正则化方面,alpha与BP网络的权重衰减直接相关,如果发现验证集KS比训练集低0.1以上,把alpha调大一个数量级,同时考虑增加early_stopping的耐心值。

5. 从训练到落地:BP信用评分模型的输出与监控

5.1 将BP网络输出转换为标准评分卡分数

信用评估常用的评分表示方法是标准分制,例如范围在300到850分,分数越高风险越低。BP网络输出的违约概率可以通过对数odds转换公式映射成分数:

score = offset + factor * log(odds) odds = (1 - p) / p

通常设定两个锚点:某个特定odds对应的基准分,以及odds翻倍时分数增加的点数。假设基准odds=1:1时分数为600,odds每翻倍分数增加20,那么:

factor = 20 / np.log(2) offset = 600 - factor * np.log(1) def prob_to_score(p): # 防止p=0或1导致log运算异常 p = np.clip(p, 1e-6, 1 - 1e-6) odds = (1 - p) / p return round(offset + factor * np.log(odds))

这段代码得到的分数满足:违约概率越高,分数越低。factor决定了分数对风险变化的敏感度,信贷业务中一般设为20或50。将验证集上每个样本的预测概率转换为分数后,再按分数段统计实际违约率,比如每50分一个档位,检查是否单调。如果某个分数段出现违约率反转,说明模型在该区域判断不稳定,需要检查对应区间的样本量是否过少。

5.2 模型上线后的漂移监控

BP网络上线后,最怕的是特征漂移和分数漂移。因为神经网络隐含层学到的特征组合是非线性的,一旦输入变量分布变化,输出的分数偏移可能比线性模型更剧烈。常见监控指标包括:

监控对象计算方式阈值
PSI(群体稳定性指标)按分数分箱,对比训练期与当期的占比差异小于0.1稳定,0.1-0.2需关注,大于0.2异常
特征缺失率逐字段统计月均值与训练期相差超过5%报警
顶部拒绝率分数低于拒绝线的人数占比连续3天上升10%触发复查

PSI的计算可以用一个函数实现:

def calculate_psi(expected, actual, bins=10): expected_pct = np.histogram(expected, bins=bins, range=(0, 1))[0] / len(expected) actual_pct = np.histogram(actual, bins=bins, range=(0, 1))[0] / len(actual) psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct)) return psi

expected用训练期模型预测的概率分布,actual用当期预测概率。PSI超过0.2时,需要回看近期特征均值是否发生跳变。BP网络没有树模型那种分裂点,可以输出一层隐藏层的激活值做漂移检测,不过这需要保存网络中间层输出,理论上代价较高。实际落地中,监控分数PSI和关键特征PSI已经足够。

5.3 用LIME解释BP网络的局部决策

BP网络缺乏可解释性,信贷审批又要求给出拒绝或批准的理由。常见做法是用LIME或SHAP在局部拟合一个可解释模型。下面以LIME为例:

import lime import lime.lime_tabular # 构造解释器,使用训练数据作为背景分布 explainer = lime.lime_tabular.LimeTabularExplainer( X_train, feature_names=feature_names, class_names=['good', 'bad'], mode='classification', discretize_continuous=True ) # 解释第i个样本的预测结果 exp = explainer.explain_instance(X_test[i], mlp.predict_proba, num_features=8) exp.show_in_notebook(show_table=True)

这里的X_train传入原始特征空间,解释器会在该样本附近做随机扰动,观察BP网络输出的变化,然后用稀疏线性模型拟合。num_features=8表示最多展示8个影响最大的特征。LIME解释的稳定性取决于扰动次数,默认500次可以适当提高到2000次。需要注意的是,LIME给出的权重只代表局部线性近似,不能当作全局特征重要性解释,但仍然可以用于生成拒绝理由,例如“收入负债比过高导致评分偏低”。

对BP网络更可靠的可解释性替代方案,是在网络之上再用逻辑回归训练一个解释性评分卡,把BP网络作为集成模型的基学习器之一。这样既保留了BP的非线性拟合能力,又有一层可审计的线性模型兜底。很多银行的实际落地就是这个思路,强烈建议在投产前同时产出可解释规则和BP网络的概率输出,以便满足合规要求。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询