☰
基于随机森林与ID3决策树的贷款审批模式识别系统解析
2026/10/11 17:05:48 网站建设 项目流程

简介:一份围绕随机森林算法的模式识别系统完整设计文档,面向机器学习初学者和信贷审批、金融风控等场景的开发者,解决如何用集成学习完成特征筛选、决策树构建与贷款分类预测的问题。压缩包共1个doc文件,仅154KB,内容集中无冗余附件,已有161人浏览/学习。文档从随机森林思想入手,依次给出系统流程图、MATLAB R2019a实验环境、1500个训练样本与500个测试样本的7维特征说明,以及基于特征重要性的降维过程和ID3建树细节;特征筛选部分依据袋外误差率挑选最优特征集,决策树节点随机选取5个特征进行分裂,最终由30棵树投票输出分类结果。同时提供带注释的MATLAB程序,包括randperm随机选特征、randi抽样、statistics投票统计和准确率输出,读者可直接复现并扩展为其他模式识别任务。

1. 随机森林算法模式识别系统:让30棵ID3决策树给贷款申请投票

先说一个反直觉结论:随机森林在教材里通常被当作“黑匣子”来讲,但这个基于随机森林算法的模式识别系统恰恰把匣子拆开给你看——它只用了一个ID3分裂函数,循环30次组装森林,在500条银行贷款测试数据上拿到了91.6%的准确率。整套实现基于MATLAB R2019a,训练数据共1500条,每笔贷款被编码成7个离散字段,包括年龄、收入、房产、信用、性别、城乡和最终审批意见。对于刚接触集成学习、又不想被sklearn封装拦住的人,这份代码和文档正好是最短路径之一;对已经写过很多分类模型的工程师来说,它把决策树、随机森林、投票机制里容易被封装隐藏掉的中间变量全部暴露在工作区里,可以随时扒开看树是怎么长出来的。

2. 随机森林为什么要配ID3:选型逻辑与三个关键参数

2.1 随机森林的随机性从哪里来:自助采样和特征子集

随机森林稳定的关键不在“多棵树”,而在两处随机性。第一处是有放回抽样,也就是bootstrap:每棵树不拿全部样本,而是从原始训练集里随机抽一部分,抽完再放回,保证下一棵树还能抽到同样的样本。被抽中的样本组成这棵树的专属训练集,没被抽中的样本天然成为袋外数据,可以拿来验证这棵树。第二处随机性在特征选择上:每棵树的每个分裂节点不是从全部特征里找最优,而是先随机挑出一个特征子集,再在子集里选信息增益最大的那个特征来分裂。

这套系统的两个随机源都是显式写在代码里的。抽样这部分是:

Sample_num = randi([1,1500],1,sn); % 在1到1500之间随机抽取sn个下标,允许重复 SData = S(Sample_num,:); % 用这些下标组装一棵树的训练集

逻辑说明:randi([1,1500],1,sn)生成一个1行sn列的整数数组,每个数都在1到1500之间,因为是有放回抽样,所以重复值是被允许的。输出矩阵SData的行会存在重复,这正是bootstrap的本意。参数sn=900对应原始1500个样本的60%,这是文档里设定的抽样比例,和实际工程里常用比例基本一致。如果你把sn改成1500,每棵树都会看到几乎全部样本,树之间的差异变小,投票的容错能力反而下降;把sn降到300,单棵树能力又太弱,森林会偏向随机猜测。

特征子集的抽取是:

Feature = randperm(6,5); % 从6个特征中随机选5个,顺序也随机

逻辑说明:randperm(6,5)返回6个整数里随机挑选的5个不重复值,顺序是打乱的。注意这里是每棵树固定抽5个特征,并不是每个节点重新抽一次。和sklearn里按节点抽max_features的做法相比,这算一种简化。6个原始特征去掉标签列正好剩6个可分裂属性,从中取5个,意味着每棵树最多只会有一个特征完全缺席。这种做法的好处是保留了单棵树的判断力,不会因为特征子集太小而出现大量空分支。

2.2 为什么用ID3而不是C4.5或CART:离散标签下的信息增益

决策树的每个内部节点都代表一次“按特征划分子集”的判断。这套系统选择的是ID3,分裂标准是信息增益。信息增益的定义是:父节点熵减去按某个特征划分后各子节点的条件熵,差值越大,说明这个特征把类别区分得越干净。每次分裂都挑信息增益最大的特征,这是ID3的贪心策略。

代码里计算熵的核心函数是Exp:

function I = Exp(CN,A) CNRowNum = CN_sta(CN,A); % 统计当前样本集合里各类别数量 n = length(A); I = 0; for i = 1:3 if CNRowNum(i) > 0 P(i) = CNRowNum(i) / n; I = I - P(i) * log2(P(i)); end end end

参数说明:CN是第7列的标签数据,A是参与计算的行号集合,CN_sta统计这些行里类别1、类别2、类别3各占多少。循环里按信息熵公式I = -∑ p·log2(p)累加。当一个集合中所有样本都属于同一类别时,某一项P(i)=1、其余为0,熵是0,说明集合完全纯净。

avg_entropy函数再按某特征的不同取值把样本切分,对每个子集分别求熵并加权汇总。信息增益就是父节点熵减去这个加权熵。ID3天然只处理离散属性,连续值需要额外做阈值扫描,这是ID3没有内置的机制。原文档最后也明确说,这套系统只适用于属性值是标签值也就是离散值的类型,如果训练集中含连续属性,训练出的决策树会存在严重过拟合。这正是这个项目选ID3而不是CART的原因:贷款数据全是0/1/2/3这种离散编码,ID3直接用信息增益就能做得干净又高效。

2.3 三个关键参数:sn、tn、以及特征子集大小

这套系统真正需要调的参数并不多,关键就三个。

参数代码位置取值作用
sn主程序抽样行900每棵树的有放回抽样样本数
tn主程序森林行30森林中决策树数量
特征子集randperm(6,5)5每棵树最多使用的特征数

tn=30在随机森林里属于中等规模。树太少,投票受个别异常树影响大;树太多,训练时间线性上涨,但准确率往往在50棵以后进入平台期。这个项目数据量不大,30棵树在普通笔记本上运行只需几秒,是效率和稳定的平衡点。复现时可以把tn改成10、50、100各跑一遍,看准确率曲线的变化,体感会更直观。

特征子集选5而不选更小的值,和贷款数据特征少直接相关。6个特征里如果每次只抽2~3个,单棵树的平均判别能力会明显变弱,需要更多树来补偿。这里选5是保留单棵树能力的策略,让每棵树的差异主要来自样本抽样而不是特征缺失。特征子集该取多大,实践里多少有点玄学,但回到这个数据规模,5是合理值。如果换到几十个特征的数据集,特征子集一般按sqrt(M)或M/3来取,不能照抄5。

3. 七列贷款数据与特征提取:离散字段如何变成随机森林的输入

3.1 贷款数据集的字段映射与编码含义

数据是这套系统的“食物”,字段含义理解偏了,后面跑出来的91.6%就只是一个数字。原始数据集包含1500条训练样本和500条测试样本,每条样本由7个字段组成。前6列是特征,第7列是标签。

列号取值业务含义
11 / 2 / 3青年 / 中年 / 老年
21 / 2 / 0收入一般 / 收入最高 / 收入低
31 / 0有房 / 没房
41 / 2 / 3信用很好 / 信用好 / 信用一般
51 / 0男 / 女
61 / 0城市 / 农村
71 / 2 / 3同意贷款 / 还需考虑 / 不同意

注意第二列的编码顺序是乱的:0代表低收入,1代表一般,2代表最高,它不表示数值大小,只是类别标签。所有特征都采用这种离散编码,ID3的信息增益计算才有意义。如果你拿自己的离散数据集来套用,第一件事就是检查每一列的取值是否真的具有类别含义,不要把连续型的数值字段直接填进来。

3.2 特征重要性与袋外误差率:递归剔除的基本流程

原文档描述的特征提取过程并不是一次性选好特征,而是一个迭代式的特征选择流程:先计算每个特征的重要性,按降序排序,确定剔除比例后,每次剔除一批最不重要的特征,用剩余特征重新训练,并计算对应的袋外误差率,直到剩余特征个数达到预设的m值。最后从所有候选特征集里挑出袋外误差率最低的那一个作为最终特征集。

两个概念需要展开。第一个是特征重要性,随机森林里通常有两种计算方式:一种是基于基尼不纯度下降量的累计,另一种是基于袋外数据误差的上升幅度。原文档没有给出具体公式,但你按流程走就能理解:每次被剔除的,都是当前集合里判别贡献最小的那一批特征。第二个是袋外误差率。因为训练每一棵树时是有放回抽样,每棵树大约会遗漏约36.8%的训练样本,这些没有参与该树训练的样本,天然可以作为这棵树的验证集。整个森林对袋外数据预测的错判比例,就是袋外误差率。

这套流程的优点是不依赖独立测试集就能做特征选择,每一步都留下一个候选特征集和对应的袋外误差率。实际编码时你不需要改动ID3的训练逻辑,只需要在外层加一个循环,控制Feature变量从全量特征逐步收缩到目标长度。本项目里每个样本去掉标签列正好6个特征,特征子集取了5个,等于做了一次近似筛选;如果你想严格复现文档里的递归剔除,就需要自己补上袋外误差的统计环节,这点后面避坑部分还会再提。

训练中对未选入特征的处理方式也值得注意:

[CHA, ~] = setdiff(CLASSPNUM, Feature); % 找出没被选中的特征编号 DValue(:, CHA) = 0; % 没被选中的特征列强制置0

逻辑说明:CLASSPNUM是当前树全部可用的特征编号,Feature是随机选中的那5个特征编号,setdiff取差集,得到被排除的特征编号。把这一列的所有值都置0,相当于是“软禁用”:因为这一特征在每个样本上都取同样的值,它的信息增益必然是0,永远不会被选为分裂属性。这个做法比直接删列省事,矩阵维度保持不变,后续运算不需要动态调整下标。

3.3 读取Excel数据与精度对齐:xlsread和roundn的配合

原始数据存储成Excel格式,MATLAB用xlsread读取。训练和测试分开加载:

S = xlsread('loan_train.xls'); % 1500条训练样本 T = xlsread('loan_test.xls'); % 500条测试样本

读取后有一个容易被忽略的精度操作,同时出现在ID3函数和测试程序里:

DValue = roundn(S(:,1:6), -1); % 训练数据截断到1位小数 TData = roundn(T(:,1:end-1), -1);% 测试数据同样截断到1位小数

逻辑说明:贷款数据理论上都是整数编码,但xlsread从Excel解析时,如果单元格格式带小数或经过公式计算,读进来可能变成类似0.999999这样的浮点伪值。roundn(x,-1)把数据四舍五入到1位小数,实际上是把浮点误差抹平。虽然它不能把0.99999恢复成1,但结合后续type_sta、vote里的等值判断逻辑,可以避免因浮点尾数不同导致分支匹配失败。参数-1表示保留到小数点后1位,如果你的数据本身是整数,这个操作不会改变原始值;如果数据是两位小数编码,你需要相应调整。

提示:roundn在部分MATLAB版本里属于Financial Toolbox,如果运行报错,直接改用round(DValue*10)/10,效果完全等价。

运行完整流程后,代码会把测试集原始数据和预测标签拼在一起,写出到loan_result.xls,同时用fprintf在命令行打印准确率。这个输出习惯很实用,预测结果落盘后方便后续做错分样本复盘。

4. 从抽样到投票:MATLAB随机森林核心代码怎么读

4.1 主程序:抽样、训练30棵树、保存树结构

训练主程序是整个系统的心脏。它完成三件事:从训练集抽样、用ID3构建决策树、把树结构保存在三个cell数组里。

clear all; clc; rnode = cell(3,1); % 存储每棵树的节点分裂特征编号 rchild_value = cell(3,1); % 存储每个节点的分支取值集合 rchild_node_num = cell(3,1); % 存储每个节点的分支指向的子节点下标 sn = 900; % 每棵树抽取900个训练样本 tn = 30; % 森林中决策树的棵数 S = xlsread('loan_train.xls'); for j = 1:tn Feature = randperm(6,5); % 每棵树随机选用5个特征 Sample_num = randi([1,1500],1,sn); % 有放回抽样 SData = S(Sample_num,:); % 组装本棵树训练集 [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

逻辑说明:第j次循环训练一棵树,结果以三个数组形式存入对应单元格。rnode{j,1}保存的是树j的所有节点,其中rnode{j,1}{1}是根节点;rchild_value{j,1}{1}保存根节点各分支的取值,比如[1 2]表示该特征取值为1走左分支、取值为2走右分支;rchild_node_num{j,1}{1}保存各分支跳转的子节点下标。三个数组配合使用,可以把整棵树完整读出来。

参数说明:cell(3,1)只是初始化一个3行的单元数组,循环到第30棵树时MATLAB会自动扩容,所以不用改成cell(tn,1)也能跑,但为了调试时索引更清晰,我建议直接初始化为cell(tn,1)。sn控制每棵树的样本量,tn控制森林规模,这两个参数是后续做对比实验最常改动的地方。

4.2 ID3递归函数:三个终止条件与信息增益分裂

ID3函数是整个系统的核心,它通过递归方式把决策树长出来。实际构建工作由TreeNode完成。递归函数最先处理的是终止条件:

function [node, child_value, child_node_num] = TreeNode(DValue, CN, A, ClassPNum, m) global node child_value child_node_num n = length(node); if m > 0 k = length(child_node_num{m}); child_node_num{m}(k+1) = n+1; % 把本节点登记到父亲节点的子节点列表 end if isempty(DValue) node{n+1} = []; % 条件1:样本为空,建空节点 return; end if isempty(ClassPNum) node{n+1} = find_most(CN,A); % 条件2:特征用尽,取多数类 return; end CNRowNum = CN_sta(CN,A); if length(find(CNRowNum==0)) >= 2 node{n+1} = CN(A(1)); % 条件3:所有样本同属一类 return; end

逻辑说明:三个终止条件分别对应决策树停止生长的三种经典情形。第一个是当前划分区域没有样本;第二个是可供分裂的特征已经用完,此时用多数投票的类别作为叶子节点;第三个是当前区域里所有样本类别一致,直接用该类别作为叶子。条件3的判断方式比较巧妙,它通过CN_sta统计三个类别各自数量,只要有两个类别的计数为0,就说明剩下那一个类别是唯一类别。

如果没有触发终止条件,就进入信息增益计算和分裂环节:

I = Exp(CN,A); % 父节点经验熵 for i = 1:length(ClassPNum) Entropy(i) = avg_entropy(DValue(:,ClassPNum(i)), A, CN); Gain(i) = I - Entropy(i); % 每个特征的信息增益 end if max(Gain) <= 0 node{n+1} = find_most(CN,A); % 所有增益都非正,取多数类 return; else [~, maxIdx] = max(Gain); % 用增益最大的特征做分裂,递归生成子节点 end

参数说明:ClassPNum是当前节点还能使用的特征编号集合,每分裂一层就删掉刚用过的特征,保证同一棵树上一条路径不会重复使用同一特征。avg_entropy计算的是按某特征切分后的加权条件熵,信息增益越大说明该特征越能提纯样本。这个递归函数依赖全局变量node、child_value、child_node_num,递归过程中不断往三个数组末尾追加节点,这种风格省去了层层传参,但调试时要注意全局变量会被多棵树连续写入,每棵树训练前最好清空或重新初始化。

4.3 统计投票与分支匹配:statistics和vote的协作

测试阶段的入口是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)}); % 若平票,取第一个最大值 end

逻辑说明:循环里每次调用vote,让第i棵树对当前测试样本给出一个分类结果,然后按TypeName把票数累加到TypeNum对应位置。全部树投票结束后,取票数最多的类别作为最终输出。参数tn是森林的树数量,PValue是测试样本的特征向量,它会传入vote做单棵树的路径查找。

单棵树的路径查找vote是这套代码里最容易出问题的位置。它从根节点出发,逐层匹配测试样本的特征值是否等于某个分支取值,找到就走对应子节点;如果特征取值在分支集合里不存在,它会用偏移量逐步逼近:

while ~isempty(rchild_node_num{j,1}{n}) 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); k = 0; break; end end if i == length(rchild_value{j,1}{n}) % 若这个值在当前节点不存在,则用0.1*k的步长去接近某个分支 PValue(rnode{j,1}{n}) = PValue(rnode{j,1}{n}) + 0.1*k; PValue = roundn(PValue, -1); end k = (-1)^k * (abs(k) + 1); end type = rnode{j,1}{n};

参数说明:rnode{j,1}{n}存储第j棵树第n个节点的分裂特征编号,PValue(rnode{j,1}{n})取出测试样本在这个特征上的实际取值,再和rchild_value{j,1}{n}里的各分支值比对。偏移量k按0.1、-0.2、0.3、-0.4这样交替放大,配合roundn把值拉回一位小数,制造出一个“逐渐逼近”的效果。这个策略在取值是整数的时候通常能收敛,但它同时也是整套代码里最玄学的一块,紧接着的避坑章节会重点说。

5. 避坑记录:这五个坑我在复现这套系统时踩过

5.1 连续属性混入ID3:过拟合断崖式下跌

现象:如果把数据集里某一列从离散编码换成连续值,比如把“收入一般/最高/低”换成具体月收入金额,训练阶段准确率接近98%,测试集准确率直接跌到80%以下,树结构也变得特别深。

原因:ID3对连续属性没有内置阈值搜索机制,它会把这个字段实际出现的每个数值都当作候选分裂点,等于按训练样本的精确取值硬切,几乎必然过拟合。

解决:喂给这套代码的每一列都必须是离散标签。遇到连续特征,先用discretize或按业务阈值分箱。比如收入可以切成低/中/高三档,信用分切成很好/好/一般三档,再放进框架。这是文档里明确写的边界:只适用于属性值是标签值的离散类型。

5.2 随机抽样的硬编码1500:数据行数一少就报错

现象:主程序里randi([1,1500],1,sn)直接把抽样范围写死成1500。如果你替换了训练数据文件,且行数不是1500,MATLAB会直接报“索引超出矩阵维度”。

原因:这属于课设代码常见的硬编码问题。抽样范围上限应该取数据集实际行数,而不是写死文档里的1500。

解决:把抽样那行改成动态取行数:

N = size(S,1); % 训练数据实际行数 Sample_num = randi([1,N],1,sn); % 抽样范围跟随数据行数变化

这是这套代码最该改的一处,也是把它复用到其他数据集的前提。

5.3 分支匹配的0.1步进近似:死循环与乱跳分支

现象:测试阶段偶尔出现某棵树返回的分支明显不合理,比如一个取值2.4在节点上找不到对应分支,却在循环里跳了好几次后落到一个不相干的子节点上;极端情况下会陷入较长的接近循环,导致预测变慢。

原因:vote函数的分支匹配策略是“找不到真实分支就按0.1*k的步长去逼近”,但k的符号和大小交替变化,配合roundn(x,-1)的取舍逻辑,在特征值不是整数时容易出现错误匹配。这就是整段代码里最大的黑匣子。

解决:不要依赖这种试错式逼近。更稳妥的做法是,在建树阶段把每个节点的分支取值集合保存一份,测试时直接查表;查不到就按取值间的欧氏距离找最近分支。实现并不复杂,但比0.1的步进稳定得多,也不会出现那种“再跳一步就对了”的随机行为。

5.4 文档描述和代码不一致:特征选择流程要自己补

现象:文档详细描述了“计算特征重要性、按降序排序、剔除比例、重复迭代、比较袋外误差率”的完整特征选择流程,但训练主代码里并没有实现袋外误差统计,也没有按m值做多轮特征集对比。

原因:这是课设文档的常见情况——文档描述的是设计思路,代码是实现的一个章节,特征选择环节被简化成了“每棵树随机取5个特征”,相当于固定了m=5,但没有真正做不同m值下的效果对比。

解决:如果要做严格的特征提取复现,需要在外层补一个循环:依次设m为1到6,每次在当前m下重复训练多棵森林,计算袋外误差率,最后比较选出误差最低的特征集。代码本身不提供这个架子,这是你需要自己补的一部分。

5.5 大小写与工具箱依赖:复制代码跑不通的隐藏原因

现象:从文档复制代码直接运行时,报错提示“未定义函数或变量End”,或者在roundn这一行提示函数不存在。

原因:文档在排版转换过程中把部分MATLAB关键字的大小写弄乱了,典型的是把end写成了End。MATLAB对大小写敏感,这种错误在编辑器中反而不是一眼能看出来。另外roundn依赖Financial Toolbox,部分精简安装的MATLAB没有这个函数。

解决:粘贴代码后先在编辑器里做一次全局查找替换,把End统一替换成end,再逐节运行定位报错。roundn缺失时用round(x*10)/10替代。这两个问题都属于环境层面,和算法无关,但都会在第一时间拦住你。

6. 验证与扩展:如何把这份MATLAB代码用到自己的数据集上

6.1 用“读树法”验证训练结果

在这个系统里,训练结果不只是最终的准确率数字,还包括留在三个cell数组里的完整树结构。想确认模型真的学到了合理规律,可以用“读树法”做一次人工检查:

tree = 1; % 选第1棵树来体检 root_feature = rnode{tree,1}{1}; % 根节点的分裂特征编号 root_branches = rchild_value{tree,1}{1}; % 根节点各分支的取值 root_children = rchild_node_num{tree,1}{1}; % 各分支指向的子节点编号

逻辑说明:rnode{tree,1}是这棵树的节点列表,{1}是根节点。root_branches存储的是根节点的分支取值集合,如果打印结果是[1 2],意思是当前特征取值为1的样本走一个子节点,取值为2的样本走另一个子节点。root_children里存的是对应的子节点下标,顺着下标可以一直走到叶子节点。

我一般会把每棵树的深度、根节点特征、叶子节点类别打成一张表,快速判断有没有某个特征在整片森林里完全没被用过。这种情况一旦出现,说明该特征在信息增益计算中始终排在后面,可以考虑直接剔除。这种体检比只看准确率更有价值,能发现数据和模型之间的隐藏偏差。

6.2 保存和复用模型:训练与推理解耦

训练好的森林可以直接保存成.mat文件,不用每次预测都重新训练:

save('forest_model.mat', 'rnode', 'rchild_value', 'rchild_node_num', 'tn');

下次预测新样本时只需要加载模型和调用统计函数:

load('forest_model.mat'); newData = xlsread('new_loan.xls'); newData = roundn(newData, -1); % 保持和训练时相同的精度处理 pred = zeros(size(newData,1),1); for i = 1:size(newData,1) pred(i) = statistics(tn, rnode, rchild_value, rchild_node_num, newData(i,:)); end

逻辑说明:statistics函数的参数顺序是固定的,tn必须先加载进来,因为投票循环需要知道森林里有多少棵树。预测时newData不包含第7列标签,这样正好用来验证模型在没有真实标签情况下的输出分布;把预测结果和原表拼在一起写出,就能做错分样本分析。

提示:保存模型时把Feature抽样的随机种子或参数也一并记录,方便日后复现同一组结果。

6.3 从分类到回归:同一框架如何扩展到连续值

这套框架如果直接搬到连续值预测场景,比如房价、销售额,需要做两个替换。第一,叶子节点不再存储类别,而是存储落入该节点的样本均值;第二,投票过程从“统计类别票数”改为“对所有树的输出求平均”。这就是随机森林回归算法的基本思路。原文档和代码只解决了分类问题,但你可以沿用它的森林组装框架,把find_most改成find_mean,把statistics里的计数逻辑改成均值计算,就能得到一个最小可用的回归森林。

如果你的新数据集里同时混着连续和离散特征,我的建议是先把连续列分箱转成离散编码再喂给这套代码,或者直接改用CART实现。ID3的贪心分裂对连续列天然不友好,硬塞进来的后果就是我在避坑章节里说的过拟合。判别一个数据集适不适合这套代码,最简单的方法是盯着字段映射表看:每一列是否都能用枚举值表达清楚,能,就按这套流程走;不能,就该换工具了。

最后说一句这一路走下来我自己养成的习惯:拿到任何带标签的数据,我先不急着调参,用这套源码的最小配置跑一遍,把准确率打到底,再逐层加上自己的特征筛选、交叉验证和可视化。基础准确率在合适的数据上通常能保住,但真正的坑往往不在准确率数字里,而在数据编码和分支匹配这两处。这两条理顺了,随机森林才算真正落地。希望帮到你。

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

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

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

立即咨询