ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MATLAB手写随机森林:Bagging+ID3风控建模实战

MATLAB手写随机森林:Bagging+ID3风控建模实战 简介本资源是一份面向机器学习初学者与高校课程设计者的完整实践文档聚焦随机森林算法在银行贷款审批场景中的模式识别应用解决信贷风险分类预测这一典型二分类/多分类工程问题。文档为单个Word文件.doc大小154KB内容涵盖算法原理精讲、MATLAB R2019a环境下的全流程实现含特征重要性排序、随机抽样、ID3决策树构建、森林集成与投票机制、详细数据集说明1500训练500测试样本7维结构化特征及编码规则以及带中文注释的核心代码含randperm/randi调用、TreeNode递归实现、statistics投票统计等关键模块。已有161人下载学习读者可直接复现从数据预处理、特征筛选、多树训练到准确率评估的完整闭环获得可运行的分类器原型与清晰的工程实现逻辑特别适合作为课程设计参考或算法原理验证素材。1. 这不是“调包即用”的随机森林一个在MATLAB中手撕ID3Bagging的贷款风控系统你打开loan_train.xls看到7列数字——年龄编码、收入等级、有房否、信用分、性别、城乡标签、最终决策1/2/3。这不是标准UCI数据集没有现成的fitcensemble或TreeBagger一键封装它要求你亲手把随机森林的每一块砖垒起来从特征子集采样、有放回抽样Bootstrap、ID3节点分裂、树结构递归构建再到多树投票集成。这套代码跑在MATLAB R2019a上不依赖Statistics and Machine Learning Toolbox的高级函数而是用randperm选特征、randi抽样本、roundn做离散化、global变量存树结构——它暴露了随机森林最原始的骨架Bagging 随机特征 ID3决策树。它解决的不是学术玩具问题而是银行信贷审批中真实存在的三分类决策同意/再审/拒绝准确率91.6%的背后是特征重要性排序、袋外误差筛选、连续值四舍五入离散化等一连串必须手动控制的细节。适合想真正理解“为什么随机森林抗过拟合”、正在用MATLAB做课程设计或小规模业务建模、且需要可追溯、可调试、可教学的完整实现链路的工程师与高年级本科生。2. 随机森林的MATLAB原生实现从Bagging采样到ID3树构建随机森林的鲁棒性不来自黑箱而来自两个核心随机性样本随机Bootstrap和特征随机Feature Subsampling。本系统在MATLAB中完全显式控制这两个过程不依赖任何高级工具箱函数确保每一步都可验证、可干预。2.1 Bootstrap采样与特征子集选择控制泛化能力的源头在主循环中每棵决策树的训练起点是独立的Bootstrap样本集for j 1:tn Feature randperm(6, 5); % 从6个输入特征中随机选取5个索引1-6 Sample_num randi([1, 1500], 1, sn); % 从1500行训练集中有放回随机抽取sn900个样本索引 SData S(Sample_num, :); % 构成第j棵树的专属训练集 [node, child_value, child_node_num] ID3(SData, Feature); rnode{j,1} node; rchild_value{j,1} child_value; rchild_node_num{j,1} child_node_num; end提示randi([1,1500],1,sn)生成的是索引向量S(Sample_num,:)执行的是有放回抽样Bootstrap这直接决定了每棵树的训练数据分布差异。randperm(6,5)确保每棵树只看到全部6个特征中的5个强制特征多样性——这是降低树间相关性的关键。若此处改为randperm(6,6)所有树使用全部特征森林将退化为简单平均抗噪能力大幅下降。Feature向量不仅用于ID3分裂时的候选属性池更在TreeNode函数中被用于屏蔽未选特征CLASSPNUM [1 2 3 4 5 6]; [CHA,~] setdiff(CLASSPNUM, ClassPNum); % 找出未被选中的特征列号 DValue(:, CHA) 0; % 将未选特征列置零使其在信息增益计算中贡献为0这种“硬屏蔽”比单纯忽略更彻底避免了因浮点精度或边界值导致的意外分裂保证了特征随机性的严格实施。2.2 ID3算法的手动实现信息增益驱动的分裂逻辑ID3是本系统的决策树内核其核心是信息增益Information Gain最大化。ID3.m函数通过递归调用TreeNode完成树构建而TreeNode的分裂逻辑完全基于熵计算% 计算当前节点的期望信息量熵 I Exp(CN, A); % CN为标签列A为当前参与划分的样本行号 % 对每个候选特征计算其条件熵 for i 1:length(ClassPNum) Entropy(i) avg_entropy(DValue(:,ClassPNum(i)), A, CN); Gain(i) I - Entropy(i); % 信息增益 熵 - 条件熵 end % 选择信息增益最大的特征进行分裂 [maxG, ~] max(Gain); node{n1} ClassPNum(maxG); % 该节点分裂属性为ClassPNum(maxG) [PValue, RowNum] type_sta(DValue(:,ClassPNum(maxG)), A); % 获取该属性的所有取值及对应样本行号 child_value{n1} PValue; % 存储分裂后的分支值如信用1, 信用2, 信用3Exp函数计算熵avg_entropy计算条件熵type_sta统计属性取值分布——这三个函数共同构成了ID3的数学基础。特别注意type_sta的实现它遍历当前样本行号A对属性列DValue(:,ClassPNum(i))的每个值进行聚类返回PValue唯一取值数组和RowNum每个取值对应的样本行号集合。这个结构直接支撑了后续递归调用TreeNode时的子节点划分。2.3 树结构的存储与组织全局单元数组的工程权衡MATLAB中没有原生的指针或引用类型因此树结构采用global变量配合cell数组存储global node child_value child_node_num node cell(1000,1); % 预分配足够大的单元数组存储所有节点 child_value cell(1000,1); child_node_num cell(1000,1);node{k}存储第k个节点的分裂属性编号如2表示用第2列“收入水平”分裂child_value{k}存储该节点下各分支的取值如[1,2,0]child_node_num{k}存储各分支指向的子节点序号如[5,8,12]。这种扁平化存储牺牲了内存紧凑性但极大简化了跨函数的数据传递——TreeNode递归调用时无需传递整个树对象只需更新全局cell数组即可。对于tn30棵树、每棵树平均深度10的规模此方案在R2019a中运行稳定是MATLAB环境下兼顾可读性与可行性的务实选择。关键变量类型作用典型值示例rnodecell(30,1)存储30棵树的根节点结构rnode{1,1}{1}3第1棵树根节点用第3列“有房”分裂Feature1x5 double单棵树的随机特征子集[1,3,4,5,6]跳过第2列“收入”Sample_num1x900 double单棵树的Bootstrap样本索引[12,12,45,108,...]含重复child_node_numcell(N,1)节点k的子节点序号列表{[5,8],[12,15,18],...}3. 特征工程与模型评估离散化、重要性与袋外误差的闭环验证本系统并非简单套用算法而是在特征层面进行了针对性处理并建立了以袋外误差Out-of-Bag Error为核心的特征筛选闭环这是其在小数据集1500样本上达到91.6%准确率的关键。3.1 离散化预处理应对ID3对离散属性的刚性要求ID3算法天然要求输入特征为离散值Categorical但原始数据中“年龄”、“收入”等虽为整数编码其语义仍具序数性。系统通过roundn(DValue,-1)进行统一离散化DValue S(:,1:6); % 提取前6列特征 DValue roundn(DValue,-1); % 四舍五入到小数点后1位实际效果是保持整数因输入本为整数 CN num2str(CN); % 将标签列转为字符串适配ID3的字符串比较逻辑roundn(X,-1)对整数无影响但为未来接入真实连续变量如年龄精确到岁、收入精确到元预留了接口。更重要的是num2str(CN)将数值标签[1;2;3]转为{1;2;3}使strcmp比较成为可能——这是ID3中find_most和CN_sta函数正确统计各类别频次的前提。若省略此步CN为double型strcmp将始终返回false导致多数类判定失效。3.2 特征重要性与袋外误差双指标驱动的特征筛选系统文档明确描述了特征筛选流程“计算每个特征的重要性并按降序排序…依据特征重要性剔除相应比例的特征…选择袋外误差率最低的特征集”。虽然提供的代码未包含完整的自动筛选循环但其实现逻辑已嵌入ID3内部特征重要性由信息增益体现。在TreeNode中每次分裂选择max(Gain)该增益值即为该特征在该节点的重要性贡献。累加所有树中各节点对该特征的增益即可得全局重要性排序。袋外误差OOB每棵决策树的Bootstrap样本约含63.2%的原始样本剩余约36.8%为“袋外样本”。系统虽未显式计算OOB但statistics函数的测试逻辑可迁移至此对第j棵树用其未参与训练的样本即setdiff(1:1500, Sample_num)进行预测统计错误率即为该树的OOB误差。30棵树的平均OOB误差即为整个森林的OOB估计。注意当前代码中tn30、sn900900/15000.6接近理论Bootstrap比例。若要实现文档所述的特征筛选需在外层增加循环对不同特征子集大小m如m3,4,5,6重复构建森林并计算OOB误差最终选择OOB最低的m值。这正是工业级特征工程的标准做法——用模型自身性能反馈指导特征选择。3.3 三分类结果的严谨评估超越准确率的细节洞察测试阶段输出loan_result.xls包含原始标签与预测标签。评估代码简洁但完备gd T(:,end); % 真实标签第7列 count sum(type gd); % 统计预测正确的样本数 fprintf(共有%d个样本判断正确的有%d\n 准确率为百分之%.2f\n, len, count, count/len*100);type为预测向量double型gd为真实标签double型运算符直接进行数值比较。91.6%的准确率458/500值得肯定但需进一步分析混淆矩阵% 手动构建混淆矩阵3x3 confusion zeros(3,3); for i 1:len true_class gd(i); pred_class type(i); confusion(true_class, pred_class) confusion(true_class, pred_class) 1; end disp(混淆矩阵行真实列预测:); disp(confusion);运行此代码可发现confusion(1,1)青年且同意贷款的正确率、confusion(3,3)老年且拒绝贷款的正确率通常较高而confusion(2,1)中年却被判为“同意”或confusion(1,2)青年被判为“再审”可能暴露模型对中间类别的判别模糊性——这正是业务方最关心的风险点远比单一准确率更有决策价值。4. 投票集成与错误处理多树决策的鲁棒性保障机制随机森林的最终输出不是单棵树的武断结论而是30棵树的民主投票。statistics.m函数实现了这一核心集成逻辑并内置了针对缺失分支的容错机制确保系统在面对训练时未见过的特征组合时仍能给出合理预测。4.1 多树投票的逐层遍历vote函数的树遍历引擎statistics函数调用vote对每个测试样本进行30次独立预测function [type] vote(rnode, rchild_value, rchild_node_num, PValue, j) n 1; % 从根节点序号1开始 k 0; % 错误计数器用于容错 while ~isempty(rchild_node_num{j,1}{n}) % 当前节点有子节点非叶节点 found false; for i 1:length(rchild_value{j,1}{n}) if PValue(rnode{j,1}{n}) rchild_value{j,1}{n}(i) n rchild_node_num{j,1}{n}(i); % 进入对应子节点 found true; break; end end if ~found % 未找到匹配分支训练时该特征值未出现需容错 PValue(rnode{j,1}{n}) PValue(rnode{j,1}{n}) 0.1*k; % 微调特征值 PValue roundn(PValue, -1); % 重新离散化 k (-1)^k * (abs(k)1); % k序列0,1,-2,3,-4... end end type rnode{j,1}{n}; % 到达叶节点返回该节点存储的类别标签 end此函数模拟了决策树的推理路径从根节点出发根据测试样本PValue在分裂属性rnode{j,1}{n}上的取值查找rchild_value{j,1}{n}中是否存在匹配项。若存在沿对应子节点序号rchild_node_num{j,1}{n}(i)下行若不存在foundfalse则触发容错逻辑。4.2 容错机制解析微调重离散化的实用主义策略当测试样本的某个特征值在训练该树时从未出现例如某棵树的训练样本中“信用等级”只有1和2但测试样本出现了3vote函数不会报错或返回空而是执行PValue(rnode{j,1}{n}) PValue(rnode{j,1}{n}) 0.1*k; PValue roundn(PValue, -1);k初始为0首次容错时k1PValue对应特征值增加0.1后四舍五入很可能回到训练中出现过的邻近值如3.0→3.1→3。若仍不匹配k变为-2减去0.2尝试向下调整。这种“试探性微调”比简单取最近邻或报错更符合业务场景——信贷审批中一个微小的信用分浮动如从3到2可能改变决策系统应尝试给出最接近的合理推断而非拒绝服务。4.3 集成输出的格式化xlswrite与业务交付对接最终结果写入Excel格式严格对齐业务需求xlswrite(loan_result.xls, [T type]); % T为原始测试数据7列type为预测列第8列[T type]将预测结果作为新列追加到原始测试数据右侧形成一份完整的决策报告。银行风控人员可直接打开loan_result.xls查看每一笔申请的原始信息与系统建议无需额外数据拼接。这种“所见即所得”的输出设计体现了工程实现对下游业务流程的尊重——算法价值最终体现在可操作的交付物上而非仅是准确率数字。5. MATLAB环境下的调试与优化技巧从global陷阱到性能瓶颈突破在MATLAB R2019a中运行这套手写随机森林会遇到一些典型环境特有问题。掌握以下调试与优化技巧能显著提升开发效率与模型稳定性。5.1global变量的生命周期管理避免跨函数污染ID3.m中声明global node child_value child_node_num但若在多次运行间不重置旧树结构会残留导致新树构建失败。必须在每次主循环前清空全局状态% 在主循环开始前添加 clear global; global node child_value child_node_num; node cell(1000,1); child_value cell(1000,1); child_node_num cell(1000,1);否则第二次运行时node可能仍存有上次的1000个元素TreeNode递归中nlength(node)会得到极大值node{n1}写入位置错误。这是MATLABglobal变量最易踩的坑——它不随函数结束自动销毁需显式clear global。5.2 内存与速度优化预分配与向量化替代循环当前TreeNode大量使用for循环遍历样本行号A在大数据集上会变慢。可对type_sta函数进行向量化优化% 原循环版慢 for i 1:length(A) if strcmp(CN(A(i)),TypeName{1}) CNRowNum(1) CNRowNum(1) 1; % ... 其他分支 end % 向量化版快 CN_subset CN(A); % 提取子集 CNRowNum(1) sum(strcmp(CN_subset, 1)); CNRowNum(2) sum(strcmp(CN_subset, 2)); CNRowNum(3) sum(strcmp(CN_subset, 3));对CN_sta、find_most等高频函数应用类似改造可将单棵树构建时间缩短30%-50%。R2019a已支持高效字符串数组strcmp向量化性能优异。5.3 模型可解释性增强导出单棵树的文本结构为满足业务审计需求可添加函数将任意一棵树如rnode{1,1}导出为可读文本function print_tree(rnode, rchild_value, rchild_node_num, tree_id) fprintf(\n 第%d棵树结构 \n, tree_id); print_node(rnode{tree_id,1}, rchild_value{tree_id,1}, rchild_node_num{tree_id,1}, 1, 0); end function print_node(node, child_value, child_node_num, n, depth) indent repmat( , 1, depth); if isempty(child_node_num{n}) || isempty(node{n}) fprintf(%sLeaf: %s\n, indent, node{n}); else fprintf(%sSplit on Feature %d\n, indent, node{n}); for i 1:length(child_value{n}) fprintf(%s Branch %d %s\n, indent, i, num2str(child_value{n}(i))); if ~isempty(child_node_num{n}) i length(child_node_num{n}) print_node(node, child_value, child_node_num, child_node_num{n}(i), depth1); end end end end调用print_tree(rnode, rchild_value, rchild_node_num, 1)即可打印第一棵树的缩进结构风控经理能直观看到“信用等级1 → 同意贷款”等规则这是黑箱模型无法提供的信任基础。提示在loan_train.xls中故意修改几行数据如将“信用很好”改为“信用极好”运行后观察vote函数的容错行为这是验证系统鲁棒性的最快方法。本文还有配套的精品资源点击获取
返回列表