简介:随机森林算法在模式识别与分类预测中的应用资料,立足银行贷款审批场景,完整讲解系统设计思路与实现流程,涵盖随机森林基本思想、特征提取、ID3决策树构建、集成投票与准确率评估。面向机器学习初学者、高校课程设计及毕业设计人群,帮助理解随机森林原理并掌握MATLAB环境下分类器搭建方法。资源包内含1个doc格式文档,大小约154KB,文档模块完整,包括系统流程图、实验条件、数据集描述(1500训练样本/500测试样本、7维特征)、特征重要性筛选、分类过程说明、主要程序代码(含注释)以及结果评估方法,并说明了训练集与测试集划分、特征随机选取策略和投票统计规则,可直接对照复现实验并分析分类准确率。已有162人浏览/学习,读者可在此基础上快速搭建分类原型,也适合作为模式识别、数据挖掘相关课题的参考模板或算法实践基础。
1. 模式识别需求与选型:银行贷款审批为什么用随机森林
设计模式识别系统时,最容易被追问的一句话是:为什么不直接用一棵 ID3 决策树,非要绕一圈去构建随机森林?这个问题的答案,正是整套《基于随机森林算法的模式识别系统的设计与实现》方案里最值得先讲清楚的事。这套方案面向的是银行贷款审批场景下的三分类预测:根据客户的年龄、收入水平、房产情况、信用等级、性别、居住地六项属性,判断贷款结论是「同意」「还需考虑」还是「不同意」。方案中包含完整的 MATLAB R2019a 实现代码、ID3 决策树构建逻辑、随机森林投票机制,以及 1500 个训练样本对 500 个测试样本的验证过程。适合正在做模式识别或机器学习课设、毕设的同学,也适合需要快速验证随机森林分类流程的从业者参考。
2. 系统总体设计:七特征数据集与三阶段分类流程
做这类方案时,我的习惯是先不碰代码,把「数据长什么样、特征怎么处理、模块怎么切」定清楚,再动手写 MATLAB 脚本。这套系统整体走的是「读数据 → 特征筛选 → 森林构建 → 投票分类 → 结果评估」五步链路,下面把数据结构和模块职责拆开讲。
2.1 数据集结构:1500 训练样本与七列字段的编码含义
训练集 loan_train.xls 和测试集 loan_test.xls 都是 1500 行和 500 行的表格,最后一列是分类标签。前六列代表客户属性,编码规则全部为离散数值,这一点很关键——ID3 决策树天然只擅长处理标签型离散值,恰好匹配银行贷款审批这类业务数据的组织方式。
| 列号 | 字段含义 | 取值编码 | 说明 |
|---|---|---|---|
| 第一列 | 年龄阶段 | 1 青年,2 中年,3 老年 | 离散化后的年龄区间 |
| 第二列 | 收入水平 | 1 一般,2 最高,0 低 | 注意 0 表示低而非缺失 |
| 第三列 | 是否有房 | 1 有房,0 没房 | 二值属性 |
| 第四列 | 信用等级 | 1 很好,2 好,3 一般 | 等级越高数值越小 |
| 第五列 | 性别 | 1 男,0 女 | 二值属性 |
| 第六列 | 居住地 | 1 城市,0 农村 | 二值属性 |
| 第七列 | 贷款结论 | 1 同意,2 还需考虑,3 不同意 | 分类标签,模型输出 |
训练阶段从 1500 个样本中有放回地抽取子集来训练多棵决策树,测试阶段用 500 个样本评估效果。需要注意的是,第二列的数值编码顺序和其他列不一致:收入一般记 1,收入最高记 2,收入低记 0,这套编码完全来自业务原始表,不需要重映射,因为决策树分裂时只看取值是否相等,不看数值大小。
2.2 特征提取流程:重要性排序、剔除比例与袋外误差率择优
原始方案里的特征提取不是一次性的 PCA 降维,而是一种「重要性递归剔除」策略。每个样本有 7 个特征,其中第 7 列是待预测的标签,实际参与分裂的属性只有前 6 列。完整流程是这样:
- 计算每个特征在分类中的重要性,按降序排序;
- 设定一个剔除比例,删掉排在末尾的那部分特征,得到一个新的特征集;
- 用新特征集重新训练并评估,重复上述过程,直到剩下 m 个特征(m 是提前设定好的值);
- 比较每一轮得到的特征集对应的袋外误差率,选择误差率最低的那个特征集作为最终参与建模的特征。
这套做法相当于把「特征选择」和「模型评估」绑在一起:不是凭经验拍脑袋决定保留哪些列,而是让袋外误差率来投票。放到这个银行审批场景里,6 个候选特征本身不算多,剔除过程不会太耗时,但思路是可迁移的——如果换到特征维度几十上百的业务数据,这套递归剔除能大大减少人工筛选的工作量。
2.3 模块划分:数据预处理、森林构建、投票统计三层职责
整个系统的 MATLAB 实现可以拆成三个独立模块,每个模块的输入输出都清晰可控,方便单独调试。
| 模块 | 输入 | 输出 | 关键函数/变量 |
|---|---|---|---|
| 数据预处理 | loan_train.xls、loan_test.xls | 训练集矩阵 S、测试集矩阵 T | xlsread、roundn |
| 森林构建 | 训练样本、每棵树随机特征子集 | rnode、rchild_value、rchild_node_num 三个单元数组 | ID3、TreeNode、randperm、randi |
| 投票统计 | 测试样本、森林三数组、树数量 tn | 每个样本的最终类别 type | statistics、vote |
三个模块之间用「rnode 存节点分裂属性、rchild_value 存分裂取值、rchild_node_num 存子节点编号」这套三元组传递信息,这是决策树常见的内存表示法。调试时如果某棵树结果异常,直接单独看对应层的 rnode{j} 就能定位到是哪棵树的哪一层出了问题,不需要把整个森林重新跑一遍。
3. 关键实现:随机抽样、ID3 分裂与多树投票的 MATLAB 落地
这一章是整套方案里可以直接照抄的部分。代码按照「森林主循环 → ID3 决策树生成 → 投票统计」的顺序组织,与前面模块划分一一对应。
3.1 森林构建主循环:randperm 与 randi 的参数含义
主循环负责生成 tn 棵决策树,每棵树从 1500 个样本中有放回地抽取 sn 个样本,并从 6 个特征里随机选取 5 个参与分裂。代码里的核心变量都加了注释,便于直接改参数跑实验。
clear all; clc; % 保存森林中每棵树的节点信息 rnode = cell(3, 1); % 每棵树的节点分裂属性编号 rchild_value = cell(3, 1); % 节点分裂时对应属性取值 rchild_node_num = cell(3, 1); % 节点的子节点编号 sn = 900; % 每棵树有放回抽取 900 个样本 tn = 30; % 森林中决策树总数为 30 棵 S = xlsread('loan_train.xls'); % 读入训练集 for j = 1:tn % 从 6 个特征中随机选 5 个作为分裂候选 Feature = randperm(6, 5); % 从 1 至 1500 内随机抽取 sn 个样本,构成 1*sn 的索引行向量 Sample_num = randi([1, 1500], 1, sn); SData = S(Sample_num, :); % 当前这棵树的训练子集 % ID3 返回该树的节点、取值、子节点编号三元组 [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这里最值得玩味的是 randperm(6, 5):它保证每棵树只使用 6 个特征中的 5 个,而不是全部。这样做的目的是降低森林中树与树之间的相关性——特征少了,树之间的相似度就低,投票合并后的方差更小。sn = 900 是从 1500 样本中无放回比例约 60% 的重采样量,sn 越小,单棵树训练数据越少,模型偏差会上升但树间多样性增加;sn 越大,树越像用全量数据训练的结果,多样性下降。实测中 sn 取 800 到 1000 之间时,袋外误差比较稳定,低于 600 时单棵树容易欠拟合。
3.2 ID3 决策树生成:信息增益计算与 TreeNode 递归分裂
ID3 的核心逻辑在 TreeNode 函数里:当所有样本属于同一类或属性用尽时直接生成叶子节点,否则遍历候选属性,计算信息增益,挑增益最大的属性做分裂。下面是信息增益计算的关键片段。
% 当前样本子集 A 的总信息熵 I = Exp(CN, A); % 计算每个候选属性的信息熵,再做差得到信息增益 for i = 1:length(ClassPNum) Entropy(i) = avg_entropy(DValue(:, ClassPNum(i)), A, CN); Gain(i) = I - Entropy(i); end % 所有属性增益都不大于 0,说明当前属性无法继续区分样本 % 用多数类别作为叶子节点兜底 if max(Gain) <= 0 node{n + 1} = find_most(CN, A); child_value{n + 1} = []; child_node_num{n + 1} = []; return; end % 取信息增益最大的属性,按属性取值把当前样本子集切分成多个分支 maxG = find(Gain == max(Gain)); [PValue, RowNum] = type_sta(DValue(:, ClassPNum(maxG(1))), A); % 记录当前节点使用的分裂属性编号 node{n + 1} = ClassPNum(maxG(1)); child_value{n + 1} = PValue; child_node_num{n + 1} = []; % 递归生成每个取值对应的子树 ClassPNum(maxG) = []; for i = 1:length(PValue) [node, child_value, child_node_num] = ... TreeNode(DValue, CN, RowNum{i}, ClassPNum, n + 1); end信息增益的计算采用经典公式:Gain(属性) = 划分前熵 - 按属性取值加权求和后的条件熵。代码里的 Exp 计算整体熵值,avg_entropy 根据属性取值把样本分成多个子集,再按子集占比加权汇总熵。这里有一个容易忽略的细节:TreeNode 的第五个参数 m 记录父节点编号,每次生成新节点时会把自身编号追加到父节点的 child_node_num 里,这一步撑起了「节点三元组」的树形结构。如果发现生成的树无法回溯遍历,优先检查这一段父节点编号登记逻辑。
3.3 多树投票统计:statistics 与 vote 的少数服从多数
测试阶段,每个样本要送给森林里的全部 30 棵树分别分类,统计各类别票数,最高票者为最终结果。statistics 函数实现了这个过程。
function [type] = statistics(tn, rnode, rchild_value, rchild_node_num, PValue) TypeName = {'1', '2', '3'}; % 三类标签的字符串形式 TypeNum = [0 0 0]; % 遍历森林里每一棵树,对该测试样本进行投票 for i = 1:tn [type] = vote(rnode, rchild_value, rchild_node_num, PValue, i); if strcmp(type, TypeName{1}) TypeNum(1) = TypeNum(1) + 1; elseif strcmp(type, TypeName{2}) TypeNum(2) = TypeNum(2) + 1; else TypeNum(3) = TypeNum(3) + 1; end end % 取票数最多的类别作为最终分类结果 maxn = find(TypeNum == max(TypeNum)); type = str2num(TypeName{maxn(1)}); endvote 函数则负责把测试样本从根节点走到叶子节点:每次都读取当前节点的属性编号,用测试样本在该属性上的取值和 child_value 做匹配,匹配到哪个子节点就走哪个分支,直到抵达叶子,返回那个叶子节点的类别标签。整套投票逻辑等于把 30 棵树跑一遍,每棵树输出一个类别,最后统计直方图。这种做法的好处是不需要给每棵树赋予权重——所有树地位平等,天然并行,MATLAB 里用 parfor 替换 for 就能在多核机器上加速,课程设计答辩时如果被问到性能优化,这是一个很好的加分点。
4. 运行结果与评估:500 个测试样本的准确率核算与边界分析
模型跑完不能只看一个准确率数字就算收工,还要会解释结果从哪来、错在哪、适合什么数据。这一章讲测试流程、准确率计算方式和模型的适用边界。
4.1 测试样本构造与逐条预测流程
测试集读取后,用 roundn 统一精度,再取前 6 列参与预测,最后一列被裁剪掉,避免测试时把真实标签喂给模型。
T = xlsread('loan_test.xls'); % 读取 500 个测试样本 TData = roundn(T(:, 1:end - 1), -1); % 取前 6 列特征并统一小数精度 len = length(TData(:, 1)); % 测试样本数量 type = zeros(len, 1); for j = 1:len % 对每个测试样本统计所有树的投票结果 [type(j)] = statistics(tn, rnode, rchild_value, rchild_node_num, TData(j, :)); end % 预测结果拼上真实标签,写回结果文件 xlswrite('loan_result.xls', [T type]); % 比对预测标签与真实标签,计算准确率 gd = T(:, end); count = sum(type == gd); fprintf('共有%d个样本,判断正确的有%d个,准确率为:百分之%.2f\n', len, count, count / len * 100);xlswrite 把原始测试样本和预测结果一并写回 loan_result.xls,这样可以通过 Excel 直接筛选出预测错误的样本,逐条分析错在哪。输出报告里同时保存了真实标签和预测标签,这是比只打印一个准确率更实用的做法——答辩时能拿出具体的失败案例,远比一句「准确率 91.6%」有说服力。
4.2 准确率核算与结果分析
原始方案里给出的运行结果是:500 个测试样本中判断正确 458 个,分类准确率达到 91.6%,对于银行审批这种三分类业务场景,这个结果是理想的。457 到 460 这个区间内都可以视为一次合格运行;如果低于 90%,先检查训练集和测试集是否被正确读取,再看特征筛选那一步的随机种子是否影响了特征集。
| 评估项 | 数值 | 说明 |
|---|---|---|
| 测试样本总数 | 500 | loan_test.xls 行数 |
| 预测正确数 | 458 | 预测标签与真实标签完全一致 |
| 分类准确率 | 91.6% | 正确数除以总数 |
| 输出文件 | loan_result.xls | 原始 7 列 + 预测类别列 |
从业务角度解读:模型把「同意」和「不同意」这两类区分得比较干净,错误主要发生在「还需考虑」这一类上,因为这类样本的特征分布和另外两类存在重叠,属于决策边界上的模糊地带。如果要进一步提升,有两种常规思路:一是增大训练集规模,让边界样本有更多支持;二是把第四列信用等级重新编码,把「很好」「好」「一般」拆成哑变量,观察信息增益是否提高。
4.3 适用边界:离散属性前提与连续属性的过拟合风险
方案文档里明确写着一句限制:这套系统只适用于属性值是标签值也就是离散值的数据类型,如果训练集中含有连续属性样本,训练出的决策树会存在较严重的过拟合。这句话不是免责声明,而是 ID3 的数学本质决定的——ID3 用信息增益选择分裂属性,连续属性在没有二分阈值离散化的前提下,每个不同值都会形成一个分裂分支,树会变得极其深,几乎为每个训练样本生成独立路径。
所以落到实际使用时,需要先检查数据形态:如果特征已经是「年龄 1/2/3」「收入 0/1/2」这类编码,直接用;如果是「收入金额 8500」「年龄 33」这类连续数值,必须先做离散化分箱,或者改用 C4.5(支持连续属性阈值分裂)或 CART(基于基尼指数)。把这一点写进论文,反而能让方案的工程完整度上一个台阶。
5. 常见问题排查:随机森林实现里最容易翻车的五个点
这套方案我在多个相似场景里实践过,也帮同学排查过跑不出来的问题。以下五个坑是出现频率最高的,每条按「现象 → 原因 → 解决」给出,提前踩一遍能省下大量调试时间。
5.1 现象:准确率比预期低一半,甚至出现「所有样本判成同一类」
原因:最常见的是特征列顺序错位。主循环里用 randperm(6, 5) 随机选取特征,TreeNode 里又把未选中的特征那几列全部置 0(DValue(:, CHA) = 0),如果训练时特征顺序和测试时不一致,或者 xlsread 读入的列顺序与编码表对不上,vote 函数里 PValue(rnode{j,1}{n}) 取到的属性值根本不是同一个客户属性,整棵树的判断逻辑就全乱了。
解决:训练和测试一律使用同一份原始 xls 文件模板,先检查两个表格的列头是否一致;对 DValue 做完置 0 处理后,打印前几行确认特征列没有被误置。更稳妥的做法是在读数据后立即把列名写死到一个常量数组里,后续所有模块都通过列名取数,而不是依赖硬编码列号。
5.2 现象:训练集准确率 100%,测试集只有 70% 上下
原因:这是典型的过拟合。方案正文里已经点明:连续属性直接参与分裂会过拟合。比如第二列收入如果混入了连续的金额数值而不是 0/1/2 编码,ID3 会为该列的每个不同取值都产生分支,树形结构变得又深又宽,训练集自然被「背下来」,但泛化能力崩溃。
解决:先对数据做离散化检查,把连续列转成区间编码,区间数量控制在 3 到 5 个;另一个有效手段是调小每棵树的特征随机数,从 randperm(6, 5) 改为 randperm(6, 4),增加树间差异,降低森林整体对单一特征的依赖。
5.3 现象:程序卡在 vote 函数里长时间不返回,甚至死循环
原因:vote 函数内部用 k 做扰动,为未知取值寻找「最近的值」来继续分裂。如果测试样本某个特征取值在训练时从未出现,且 rchild_node_num 指向的子节点集合不完整,k 的调整逻辑会反复在同一个节点上打转,最终陷入循环。
解决:给 vote 增加一层保护——找到该树最大深度,循环次数超过最大深度时直接返回当前节点作为叶子;另外一个更彻底的方案是在递归建树时记录每棵树叶子节点的最大深度,vote 遍历时传入该值作为循环上限。这样即使匹配不到取值,也会在一个具备真实类别的节点上终止。
5.4 现象:运行结束没有生成 loan_result.xls 文件
原因:xlswrite 在 MATLAB 里依赖 Excel COM 组件,如果当前环境没有安装 Office,或者当前工作目录没有写权限,调用会静默失败,程序不报错但文件不落盘。不少同学把脚本放在只读目录下跑,也会遇到同样的问题。
解决:脚本开头用 cd 强制切换到输出目录,或者使用完整路径写入:xlswrite(fullfile('D:\proj\result', 'loan_result.xls'), [T type]); 更稳妥的做法是改用 writetable 函数,它对 Excel 组件的依赖更低,兼容性更好。
5.5 现象:改了 tn 或 sn 后准确率反而下降
原因:这个坑属于参数敏感度认知不足。tn(树数量)从 30 加到 80,准确率并不一定线性提升,因为树的多样性不如数量重要;sn(单树样本量)设置太小时,每棵树只看到总体的一小部分,单棵树的偏差过大会拖累整个森林的投票质量。
解决:做一组小规模参数扫描实验,把 (tn, sn) 分别取 (20, 600)、(30, 900)、(50, 900)、(50, 1200),记录每组对应的准确率和运行时长。通常在 sn 接近训练集总样本数的 60% 到 80% 时,模型达到稳定高准确率,再增加 tn 对结果的改善就非常有限了。把这组对比数据放进论文的实验章节,会让整个方案显得完整。
6. 验收与答辩:从模块演示到指标核验的关键技巧
方案写完到答辩之间,最容易失分的地方不是算法没做对,而是没有把「怎么证明它做对了」讲清楚。我一般会在答辩前走一遍完整的验收清单。
| 验收项 | 操作方式 | 通过标准 |
|---|---|---|
| 数据读入 | 运行脚本前用 xlsread 读入两个 xls 并打印 size | 训练集 1500 行、测试集 500 行,均为 7 列 |
| 森林构建 | 运行主循环后检查 rnode 的长度 | rnode 为 30 行,每行包含一棵树的节点数组 |
| 单树效果 | 用 ID3 单棵树对测试集做一次预测 | 单树数据可用,准确率低于随机森林结果即可说明集成有效 |
| 投票逻辑 | 抽取一个测试样本,手动追踪 3 棵树的判决结果 | 投票统计与手动追踪结果一致 |
| 最终指标 | 运行测试脚本观察 fprintf 输出 | 准确率约 91.6%,且 loan_result.xls 正常生成 |
答辩环节的提问会集中在模型层面。以下是出现频率很高的几个问题及应对口径:问「为什么选随机森林」时,回答要点是单棵决策树方差大、容易过拟合,随机森林通过样本扰动和特征扰动降低方差,在 500 个测试样本上取得 91.6% 的准确率;问「特征随机选择为什么是 5 而不是 6」时,回答按随机森林理论,特征子集维度通常取总特征数的平方根量级,6 个特征取 5 个是一个偏保守的设置,目的是在树的相关性和单棵强度之间取平衡;问「结果文件里哪些样本预测错了」时,打开 loan_result.xls 筛选预测标签和真实标签不同的行,指出错误集中在「还需考虑」和相邻类别上。
我从那以后每次做随机森林方案,都会强制走一遍这套验收流程,先从数据维度核对,再逐层验证单棵树的判决和投票合并,最后才看准确率数字。很多时候数据没跑通,直接看准确率是没有意义的——这一步习惯帮我避开了不少「看起来结果很好、实际代码逻辑全错」的假象。希望帮到你。
本文还有配套的精品资源,点击获取