ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BP神经网络个人信贷信用评估:MATLAB实现与74.97%准确率解析

BP神经网络个人信贷信用评估:MATLAB实现与74.97%准确率解析 简介这是一份基于BP神经网络的个人信贷信用评估MATLAB工程包主要面向金融风控入门者、机器学习课程设计与毕业设计学生。资源以德国信贷数据集为对象包含可直接运行的MATLAB主脚本、原始german.data文件及数值化处理后的german.data-numeric文件省去数据清洗与格式转换环节可从数据导入、网络构建、训练测试到正确率统计完整走通个人信贷违约分类任务。压缩包内共3个文件以MATLAB脚本和两类数据文件为主整体仅28KB结构精简、便于逐文件对照学习。代码在20次重复测试中平均正确率达74.97%最低为73.4%且迭代次数均为3次适合用于分析BP神经网络的收敛稳定性、参数设置与评估指标选择。目前已有335人学习下载对需要完成信用评估实验、理解神经网络实践流程或进行扩展改进的MATLAB学习者有直接参考价值。1. BP神经网络做个人信贷信用评估74.97%的准确率怎么来的银行审批一笔个人贷款之前最重复的工作就是给申请人的信用“打分”。这个资源把问题放到了德国个人信贷数据集上压缩包里是german.data和german.data-numeric两份数据配一套基于BP神经网络的MATLAB分类流程。按照项目摘要的信息运行main.m测试20次平均正确率74.97%最低正确率73.4%并且每次模型在第3轮迭代就收敛了。这个包适合正在做机器学习作业、想用MATLAB快速落地一个BP基线的人也适合初入风控建模、需要一个可复现对照实验的研究生。它不追求SOTA但数据、脚本、结果三者闭环能把“BP做个人信贷评估”这条链路一次走通。2. 选型与网络结构为什么BP适合24维信贷数据2.1 信贷评分本质是二分类BP的隐藏层正好补上非线性交互个人信贷信用评估落到模型层面就是一个二分类任务把每条样本映射到“好客户”或“坏客户”。German Credit数据集常见版本有1000条样本特征被整理为24个数值维度。这个规模说不上大特征维度也不算高最适合的起点就是一个单隐藏层BP网络而不是一上来上LSTM或者Transformer。深度模型擅长序列建模和长程依赖信贷评估的24个属性是静态的、与顺序无关的硬套序列模型只会让参数量爆炸1000条样本根本喂不饱。这也是为什么这个资源刻意选BP而不是更“时髦”的模型。BP真正在线性模型之上的地方是它能自动构造非线性特征组合。信用数据里单看任何一列都很难区分好坏客户贷款金额大不一定坏储蓄账户充足又不代表一定好但“金额大储蓄账户充足就业稳定”这三个条件组合起来信号就明显强得多。逻辑回归没有隐藏层只能学一个线性加权和朴素贝叶斯又假设特征条件独立但信贷数据里贷款目的和贷款金额明显联动独立性前提并不成立。BP在输入层和输出层之间加一层隐藏层把原始特征先做加权求和、再过非线性激活函数等价于在特征空间里切出一个可以弯曲的决策边界。对24维数据、1000条样本的问题这种表达能力刚好够用计算量也完全可以忽略。要不要换成随机森林或者XGBoost那是另一个维度的比较。做课程设计和模型基线时先把BP跑通拿到一个参考值再用树模型做对比才能说明提升是来自模型还是来自调参。同类资源里还有用逻辑回归做信贷评分的版本但逻辑回归看不到非线性交互遇到这个数据集时上限会明显低于BP。所以压缩包选择BP既照顾了教学意义也照顾了实验可对比性。方案参数量级对此数据集的适配点主要风险BP单隐藏层约几百非线性拟合、样本量匹配局部极小、过拟合逻辑回归25解释性好学不到非线性LSTM/Transformer很大适合序列数据样本不足、无法收敛随机森林取决于树数稳健解释成本变高2.2 网络参数初值用feedforwardnet搭一个24-10-1结构拿到24维特征后第一件事是定网络结构输入层节点数等于特征数24输出层只用1个节点就够了二分类问题用单个输出加阈值切分没必要做两个输出节点再加softmax隐藏层节点数是个超参数我习惯先把初值定成10不是因为它最优而是处于中间位置好比较后续再用6、8、12、16做小范围搜索。% 建立单隐藏层BP网络隐藏层10个节点 net feedforwardnet(10, trainlm); % 隐藏层用tansig输出层用logsig得到0~1之间的输出 net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn logsig; % 数据划分按比例随机分成训练、验证、测试 net.divideFcn dividerand; net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; net.trainParam.epochs 1000; % 最大迭代轮次 net.trainParam.goal 0.01; % 均方误差目标这里feedforwardnet(10, trainlm)的第一个参数是隐藏层神经元个数第二个是训练算法。trainlm在MATLAB里对应Levenberg-Marquardt算法适合千条级别的小样本数据收敛速度非常快后面日志里看到迭代3次就停住主要就是它的功劳。dividerand表示按比例随机切割训练、验证、测试集其中测试集完全不参与权重更新验证集用来做早停判断只有训练集真正更新权重。epochs是训练轮次上限不是实际迭代次数实际跑了多少轮要看训练返回的参数。goal是均方误差阈值误差低于这个值也会提前结束训练。为什么隐藏层不用logsig经验上隐藏层激活函数用tansig比logsig更顺原因是logsig输出范围0到1非零中心会让后一层接收到的信号全部为正权重更新方向受到限制tansig输出范围-1到1中心为0梯度更新更稳定。输出层反而刻意用logsig这样输出天然落在0到1之间可以直接当成“好客户概率”看。若将来改用两个输出节点就需要补一层softmax在MATLAB里对应softmaxLayer但目前在二分类下没有这个必要。训练函数也可以换。trainlm默认占用内存比基本梯度下降多一些但1000条数据根本构不成压力如果换成traingd那就是最原始的梯度下降需要手动调学习率迭代次数会大幅上涨trainscg是适用于中规模问题的另一种选择优势是省内存。这个资源用trainlm是从收敛速度考虑的最优选择。千万不要一上来就调net.trainParam.lr因为trainlm本身有自适应步长机制手动改学习率反而会把默认节奏打乱我早期犯过这个错改了学习率后平均准确率不升反降。3. german.data-numeric的读入与预处理24列特征怎么切、怎么归一化3.1 german.data-numeric和german.data的区别压缩包里的german.data是最原始的文本格式里面混着字符串分类字段比如贷款目的、婚姻状态、就业情况。german.data-numeric是把它转成纯数值矩阵的版本脚本真正读取的是numeric版。数值化过程会做两类操作有序分类字段映射成递进整数无序分类字段拆成多列0/1指示变量。因此文件虽然标称24个属性但严格说不是24个完全独立的原始维度其中有相当一部分列来自同一个原始字段的拆分。提醒注意加载后不要强求每一列都“阅读理解”直接当作BP输入特征即可。% 读取数值版数据集 rawData load(german.data-numeric); X rawData(:, 1:24); % 前24列是特征 y rawData(:, 25); % 最后一列是标签这里用load而不是textscan是因为german.data-numeric已经是空格分隔的同构数值矩阵load一次就能读成double矩阵。读完后先检查两件事size(X)应为1000×24unique(y)应看到标签取值常见编码是1代表好客户、2代表坏客户。切列数必须确认个别渠道下载的文件可能列数有出入直接加载后打印尺寸比盲目信任文件名靠谱。标签分布上1000条样本里好客户约700条、坏客户约300条类别不平衡不算严重直接用准确率做评估指标有一定参考性。如果以后换成其他数据集且正负比到了9比1那就要考虑在训练集里做重采样或者把判好客户的阈值调高。这里先沿用原始数据分布不做额外过采样。3.2 训练/测试划分与mapminmax先切分再归一化预处理顺序是这个压缩包里最容易踩坑的地方。常见错误做法是把1000条样本整体做归一化然后再随机切训练集和测试集这样测试集的分布信息通过归一化参数进入了训练流程测试结果会虚高。教案里有时图省事这么写但严谨复现时结果会有偏差。正确顺序是先切分再只基于训练集计算归一化参数用同一套参数去变换测试集。rng(1); % 固定随机种子后续结果可复现 idx randperm(size(X, 1)); % 随机打乱样本顺序 trainIdx idx(1:700); testIdx idx(701:1000); % mapminmax默认按行处理先转置成【特征×样本】再归一化 [XtrainN, ps] mapminmax(X(trainIdx, :), 0, 1); XtrainN XtrainN; % 测试集复用训练集的ps参数绝不能再重新mapminmax XtestN mapminmax(apply, X(testIdx, :), ps);randperm(1000)生成不重复的随机下标前700个作训练、后300个作测试这是一个比较常见的7比3分割。mapminmax的第二个参数0、1指目标区间把每维特征线性缩放后落在[0,1]。返回的ps结构保存了每维的最小值和缩放系数测试集变换时用mapminmax(apply, ..., ps)参数改为-1到1也可以tansig激活函数对[-1,1]区间的输入响应更好但技术大方向一致。为什么0/1指示列也要进mapminmax因为拆分分类字段生成的列本来就是0或1对它做线性缩放没有损伤真正需要处理的是连续列像贷款金额、年龄、贷款期限数值量级不一不缩放会使得数值大的维度在梯度计算里占据主导网络训练被少数大数值列带偏。统一对所有维度做mapminmax是省心且稳妥的选择。不要只挑“看起来大”的列处理特征缩放要面向全部输入维度。标签映射单独做一次代码也一起放出来% 原始标签1好客户2坏客户映射成逻辑0/1 y01 (y 1); yTrain01 y01(trainIdx); yTest01 y01(testIdx);布尔表达式y1把“好客户”置为1、坏客户置为0这步不做的话输出层logsig范围是0到1但标签值却是2网络会把它当回归任务去逼近2训练根本收敛不到正确区间混淆矩阵也会反着看。映射完成后unique(yTrain01)应该只看到0和1两个取值。4. 避坑与常见问题运行main.m必查的五个细节4.1 迭代都停在3次是没训练完还是已经收敛现象训练日志里epoch动不动就停在3和平时见到的几百轮训练完全不一样第一反应是脚本没跑完就中断了。实际上项目摘要里写明20次测试迭代次数均为3次准确率却稳定在73%到75%附近。原因trainlm并不是标准的沿梯度方向固定步长走它会结合近似Hessian矩阵做二阶方向修正很多小样本问题在前几步损失就大幅下降紧接着触发验证集早停或误差目标条件所以会出现epoch3就停止的情况。这是正常现象不是脚本坏了。解决不要只看epoch要看训练记录里的bestEpoch[net, tr] train(net, XTrainN, yTrain01); fprintf(实际迭代: %d\n, tr.epoch(end)); fprintf(验证最优轮次: %d\n, tr.bestEpoch);tr.epoch(end)是实际迭代轮次tr.bestEpoch是验证集误差最小的轮次。这两者通常一致或接近。如果想让训练多跑几步可以把net.trainParam.min_grad调小或把max_fail从默认6调成更大值但最终测试集准确率不一定跟着涨反而可能因为后期过拟合而下降。4.2 每次运行结果不一样随机种子设在哪一步现象同一份main.m跑两遍结果一次是73.5%一次是75.2%怀疑数据或代码有随机性没被控制。这是BP实验里最常见的一种“玄学”波动其实来源很清楚。原因MATLAB的randperm数据切分和网络权重初始化都依赖全局随机数生成器。每次MATLAB启动时随机状态不同切分出的训练集不同、网络初始权重也不同最终结果自然不同。解决在脚本最前面固定随机种子只需一行rng(20250101); % 固定随机种子使本次运行可复现固定之后同一台机器跑出来的结果应该完全一致。如果想模拟“20次不同实验”并统计区间就在循环内部每次设置不同种子for i 1:20 rng(i); % 每次使用不同的可复现种子 % 后续训练代码 end再有就是注意MATLAB的net在train调用时会继承当前随机状态完成初始化不需要额外执行init(net)强行调用反而可能让种子控制失效。4.3 标签1和2没映射到0/1混淆矩阵怎么看都是错的现象脚本跑完准确率数字还算正常但画混淆矩阵时发现好客户几乎全被判断成了坏客户矩阵对角线反了。原因上一章强调过原始标签1好客户、2坏客户。如果哪一步把y2当成了正类或在归一化前直接把标签矩阵整体做了mapminmax都会让语义反转。更常见的是输出层logsig结果在0到1之间而原始标签是2分类阈值0.5把所有输出都判成“好客户0”指标自然全乱。解决统一走一遍标签映射并在训练前后检查类别总数y01 (y 1); yTrain01 y01(trainIdx); yTest01 y01(testIdx); pred01 (net(XTestN) 0.5); C confusionmat(yTest01, pred01); disp(C);confusionmat返回2×2矩阵第一行是真实好客户的预测分布第二行是真实坏客户的预测分布。如果对角线数值明显大说明方向对了如果矩阵看起来像“反着”把标签映射符号调换再跑一次。4.4 隐藏层节点加多准确率反而下降小样本过拟合现象隐藏层节点从10改成30训练误差降得更低但测试集准确率从74%掉到70%以下。新手容易在这里反复加节点最终得到更差的模型。原因训练集只有700条样本单隐藏层节点太多时网络参数量上升相当于加强了对训练集噪声的拟合能力测试集上泛化变差。MATLAB里训练过程不会主动阻止过拟合它只是把训练误差压下去。解决先做小范围节点扫描而不是单点试错for h [6, 8, 10, 12, 16] net feedforwardnet(h, trainlm); net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn logsig; net.trainParam.showWindow false; net train(net, XtrainN, yTrain01); pred net(XtestN); acc mean((pred 0.5) yTest01); fprintf(隐藏层%2d, 准确率%.2f%%\n, h, acc * 100); end这段代码每次重新建网络、重新训练、重新测试不会累积上一次的网络状态。如果发现12和16节点准确率都低于8和10说明结构复杂度已经越过拐点回到10节点即可。若还想继续优化再考虑在损失函数里加正则项但不要先动正则项先把结构复杂度控制在合理范围。4.5 load报错找不到数据文件中文目录和空格都是坑现象把压缩包解压到带空格或中文的路径下运行main.m时load(german.data-numeric)报错提示文件找不到。原因MATLAB对路径里的空格和中文字符处理并不总稳定尤其是旧版本配合load、addpath这类函数时容易解析出错工作目录没有正确cd到解压目录也会触发同样的现象。解决执行前先确认文件位置再用安全路径操作if exist(german.data-numeric, file) ~ 2 error(数据文件不在当前目录请先cd到解压目录); end然后进入german.data-numeric所在目录把工作区切过去例如cd(E:/bp_credit)。路径建议统一用英文和数字组成不要带空格也不要放在桌面深层目录里。这个方法在连续跑了很久的项目上会有用也有人说“玄学路径”其实只是解析规则问题。5. 验证技巧用20次重复实验把74.97%复现出来项目中写测试20次平均正确率74.97%最低73.4%这是一个很值得借鉴的验证方式。单次BP实验的随机波动可能掩盖真实模型水平只看一次准确率就去调参基本等于自己骗自己。理性的做法是把整个训练流程完整跑20次每次重新打乱数据、重新初始化网络最后看平均、最低和波动范围。accs zeros(1, 20); for i 1:20 rng(i); % 每次不同且可复现 idx randperm(size(X, 1)); trainIdx idx(1:700); testIdx idx(701:1000); [XtrainN, ps] mapminmax(X(trainIdx, :), 0, 1); XtrainN XtrainN; XtestN mapminmax(apply, X(testIdx, :), ps); yTrain01 (y(trainIdx) 1); yTest01 (y(testIdx) 1); net feedforwardnet(10, trainlm); net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn logsig; net.trainParam.showWindow false; % 循环里关掉训练图 net train(net, XtrainN, yTrain01); pred net(XtestN); predLabel double(pred 0.5); accs(i) mean(predLabel yTest01); end fprintf(平均准确率: %.2f%%\n, mean(accs) * 100); fprintf(最低准确率: %.2f%%\n, min(accs) * 100);循环内部每轮都重新做数据划分、归一化和网络初始化20次结果之间相互独立。最终打印的平均值应该接近74.97%最低值接近73.4%。如果差得远优先检查两处一是mapminmax参数是否复用了ps二是标签是否映射成0/1。对应稳定复现的参数汇总如下配置项设置说明隐藏层节点数10可扫描6/8/12/16训练算法trainlm小样本收敛快隐藏层激活函数tansig非零中心问题更少输出层激活函数logsig输出0~1概率归一化范围[0,1]处理不同量级的连续特征训练/测试比例700:300与循环代码一致随机种子rng(1)等保证可复现更进一步可以统计混淆矩阵而不是只看准确率。把20次实验的混淆矩阵累积起来取平均能看到好客户和坏客户各自的召回率。信贷场景里把坏客户当成好客户放款的代价远大于把好客户误判为坏客户所以实际工程上经常会在输出概率上调整阈值把0.5改成0.4或0.6观察准确率和召回率如何移动。这也是把BP结果从“课程作业”推向“可用模型”的关键一步。我最初跑这类BP脚本时也吃过单次结果定调子的亏。第一次跑了75%高兴了很久换一次随机种子变成73.8%又以为是代码出了问题。后来才意识到神经网络本来就是随机模型不固定种子、不跑循环均值拿单次结果评估就是自己骗自己。从那以后我每次跑分类实验都强制先把20次循环基线跑出来确认稳定区间再动手调参。希望帮到你。本文还有配套的精品资源点击获取
返回列表