简介:随机森林MATLAB代码压缩包面向希望在MATLAB环境中实现随机森林分类与回归的学习者和开发者,适合机器学习课程实验、算法复现及小规模数据建模练习。包内共14个文件,约211KB,包含m函数脚本、txt示例数据、Fortran源码、dll动态库以及readme和doc说明文档;其中m文件对应分类、回归与模型打印入口,txt提供训练测试样本,dll与Fortran部分承担底层计算,便于理解从接口调用到核心实现的完整结构。该资源已有6072人学习,配套示例覆盖分类与回归两条使用路径,读者可借此观察随机森林的样本与特征采样、决策树集成输出及特征重要性查看方式,并对照说明文档梳理目录组织、依赖配置与模型评估思路,作为算法学习和代码研读的参考资料。
1. 一段跑不通的脚本,暴露了随机森林 MATLAB 代码的三个前提
很多人找随机森林 MATLAB 代码,拿到的第一份东西通常是三五行:load 一份数据,TreeBagger 建三十棵树,predict 完打印一个准确率。这段脚本在自带示例数据上能刷到 0.9 以上,换到自己的表上就翻车——要么类别变量直接报类型错误,要么准确率死死卡在多数类占比上不动,看着像模型没学,实际上是数据和接口没对齐。随机森林在 MATLAB 里上手门槛很低,但 TreeBagger 与 fitcensemble 两条路线各有硬性输入约束,树数、叶子大小、每次分裂的特征数这三个需要调整的参数又互相牵制,调错一个,结果就不可信。这篇按「选哪条路 → 数据怎么喂 → 参数怎么调 → 报错怎么查」的顺序,给一份能改能跑、踩过坑再写出来的实现说明,适合手里已经有一张特征表、打算把 RF 当基线模型的从业者,也适合课设阶段需要一份既能运行又能解释清楚的同学。
2. 三条实现路线的选型:TreeBagger、fitcensemble 与手写脚本
MATLAB 里做随机森林,本质上是三种东西:一个封装好的老牌类、一个能接进交叉验证流水线的建模函数、一份可以完全控制分裂准则的自写脚本。选错路线不是精度问题,而是后面每一步都别扭——想算 OOB 误差却发现没开预测、想接超参搜索却发现接口不兼容、想把模型导出到别的语言却只能对着对象干瞪眼。所以先把选型摆清楚。
2.1 TreeBagger:分类回归一套接口的老牌实现
TreeBagger 是最直接的入口,一行构造,自动完成自助采样(bootstrap)、随机特征子集、多树汇总投票,还内置了袋外样本(out-of-bag,OOB)的评估能力。它的价值在于「不用自己写循环」:每棵树的训练样本由有放回抽样产生,约 36.8% 的样本不进袋,这些样本天然构成一个不花钱的验证集,能在不切分数据的前提下给出误差估计。对样本量小、切出去一块验证集就心疼的场景,这一点很实用。
% X: n×p 数值特征矩阵;Y: n×1 的 categorical 标签 rng(2024); % 固定随机种子,保证结果可复现 rf = TreeBagger(200, X, Y, ... 'Method', 'classification', ... % 分类任务;回归改成 'regression' 'OOBPrediction', 'on', ... % 开启袋外预测,后面才能算 OOB 误差 'OOBPredictorImportance', 'on', ... % 同时计算置换重要性 'MinLeafSize', 5, ... % 叶节点最小样本数,控制单棵树深度 'NumPredictorsToSample', 'all', ... % 每次分裂考察的特征数 'PredictorSelection', 'allsplits', ... % 用标准 CART 穷举分裂,而非曲率检验 'InBagFraction', 1, ... % 自助采样比例,1 表示抽 n 个 'UseParallel', true); % 需要并行工具箱,否则会报错 Yfit = predict(rf, X); % 返回 cell 数组,元素是字符标签 Yfit = categorical(Yfit); % 转回 categorical 才能直接比标签 acc = mean(Yfit == Y); fprintf('训练集准确率 %.4f\n', acc);代码里几个参数值得单独说清楚。NumTrees取 200 是经验起点,不是定值,后面 4.1 会用 OOB 曲线判断到底该停在哪。MinLeafSize从默认的 1 提到 5,是给噪声留一点缓冲,叶子太纯的树在训练集上几乎满分,测试集上掉得厉害。NumPredictorsToSample设成'all'意味着每次分裂都用全部特征,这其实弱化了「随机」这一半;分类任务常见做法是取特征数的平方根取整,回归取三分之一。PredictorSelection默认是'curvature'(曲率检验,偏向能产生强交互的分裂),想做教科书式的标准 CART 就改成'allsplits',两者在小样本上的结果差异可能达到好几个百分点,别默认它俩等价。
需要留意的是predict的返回类型。分类模型返回的是 cell 数组,里面装的是字符,直接和数值标签比较会得到一个全 false 的逻辑向量,然后你会以为模型废了。转成 categorical 或者用str2double走一遍,是必须补的一步。
2.2 fitcensemble:能挂进交叉验证和超参搜索的版本
如果后面打算做 K 折交叉验证、网格搜索、或者把模型塞进一条预处理加建模的流水线,fitcensemble更顺。它把「集成方法」和「基学习器」拆开了:Method选'Bag'就是袋装,基学习器用templateTree描述,树的参数写在模板里而不是散在函数参数上。
t = templateTree( ... 'MinLeafSize', 5, ... 'NumVariablesToSample', 'all', ... % 等价于 TreeBagger 的 NumPredictorsToSample 'Surrogate', 'off'); % 关闭代理分裂,缺失值需提前处理 mdl = fitcensemble(X, Y, ... 'Method', 'Bag', ... % 袋装;'AdaBoostM2' 等是提升法 'NumLearningCycles', 200, ... % 基学习器个数,对应 NumTrees 'Learners', t, ... 'Prior', 'uniform'); % 类别先验,处理不平衡时改成 'empirical' cvmdl = crossval(mdl, 'KFold', 5); % 直接得到交叉验证模型 fprintf('5 折误差 %.4f\n', kfoldLoss(cvmdl));这套写法的好处是crossval、kfoldLoss、oobLoss、plotPartialDependence全都认这个对象,不用自己写循环去拼混淆矩阵。Prior参数也是 TreeBagger 没有直接对应物的东西,类别不平衡时把先验改成'uniform'或者传入自定义向量,往往比调阈值更省事。代价是参数名和 TreeBagger 不完全一样,NumLearningCycles对应树数、NumVariablesToSample对应每次分裂的特征数,来回切换时容易写错。
两条路线的取舍可以压成一张表:
| 维度 | TreeBagger | fitcensemble(Method='Bag') |
|---|---|---|
| 分类/回归统一接口 | 靠Method一个参数切换 | 同一个函数,靠标签类型自动判断 |
| OOB 误差 | oobError/oobLoss | oobLoss(mdl,'Mode','cumulative') |
| 交叉验证 | 需自己写 K 折循环 | 直接crossval+kfoldLoss |
| 超参自动搜索 | 不支持,需外层包 bayesopt | 支持OptimizeHyperparameters |
| 特征重要性 | OOBPermutedPredictorDeltaError | predictorImportance+ 置换法 |
| 偏依赖图 | 需手动计算 | plotPartialDependence直接出图 |
数据量大、只想要一个能跑的基线,TreeBagger 更省事;要做实验对比、要出可复现的交叉验证数字,fitcensemble的流水线更稳。
2.3 手写版:只在这三种情况下值得
自己写递归建树不是炫技,绝大多数场景纯属浪费时间。真正值得动手的情况只有三类:一是分裂准则要改,比如你需要代价敏感的分裂或者自定义的不纯度;二是模型要导出到 C 或嵌入式环境,MATLAB 的对象没法直接落地,得自己序列化成数组;三是教学或答辩场景,需要指着每一层节点解释「这棵树为什么在这里切」。
给一份最小骨架,能说明 bagging 内部到底在干什么:
function node = growTree(X, y, depth, maxDepth, minLeaf, mtry) % 单棵 CART 分类树的最小实现,省略缺失值处理与剪枝 if depth >= maxDepth || numel(unique(y)) == 1 || size(X,1) < 2*minLeaf node = struct('leaf', true, 'label', mode(y)); return; end p = size(X,2); featIdx = randperm(p, min(mtry,p)); % 随机挑 mtry 个特征,这是"随机森林"的一半 bestGain = -inf; bestFeat = 0; bestThr = 0; parentImp = giniImp(y); for f = featIdx vals = unique(X(:,f)); if numel(vals) < 2, continue; end thrCand = vals(1:end-1) + diff(vals)/2; % 相邻取值中点作候选阈值 for t = thrCand' left = X(:,f) <= t; right = ~left; if nnz(left) < minLeaf || nnz(right) < minLeaf, continue; end gain = parentImp - (nnz(left)*giniImp(y(left)) + ... nnz(right)*giniImp(y(right))) / numel(y); if gain > bestGain bestGain = gain; bestFeat = f; bestThr = t; end end end if bestFeat == 0 node = struct('leaf', true, 'label', mode(y)); return; end left = X(:,bestFeat) <= bestThr; node = struct('leaf', false, 'feat', bestFeat, 'thr', bestThr, ... 'left', growTree(X(left,:), y(left), depth+1, maxDepth, minLeaf, mtry), ... 'right', growTree(X(~left,:), y(~left), depth+1, maxDepth, minLeaf, mtry)); end function g = giniImp(y) % 基尼不纯度,分类树最常用的分裂指标 [~,~,idx] = unique(y); p = accumarray(idx, 1) / numel(y); g = 1 - sum(p.^2); end外层再套一层 bootstrap 采样和投票就是完整森林。这段代码的性能比内置实现差一到两个数量级,因为它没有向量化候选阈值、没有做排序优化,也完全没有剪枝和缺失值处理,只建议用来理解结构或者做定制化改造,不要拿它去跑十万行数据。
3. 预处理链路:把表、缺失值和类别变量对齐到 RF 的输入约定
随机森林对特征缩放不敏感,这容易让人误以为它对数据形态也没要求。实际上 MATLAB 的实现有一套明确的输入约定:矩阵还是 table、标签是 categorical 还是字符 cell、缺失值走哪条路径,任何一条不对,报错信息都含糊得让人抓瞎。
3.1 table 输入与类别变量的三种处理
最省事的做法是把整张表连同标签一起喂进去,让实现自己去识别哪一列是类别变量。table 的优势是保留了变量名,后面看特征重要性时直接输出名字,不用回头对列号。
T = readtable('features.csv', 'TextType', 'string'); labelName = 'label'; predictorNames = setdiff(T.Properties.VariableNames, labelName, 'stable'); % 字符串列转 categorical,避免被当成 p 个哑变量处理 for k = 1:numel(predictorNames) if isstring(T.(predictorNames{k})) || iscellstr(T.(predictorNames{k})) T.(predictorNames{k}) = categorical(T.(predictorNames{k})); end end X = T(:, predictorNames); Y = categorical(T.(labelName)); rf = TreeBagger(200, X, Y, 'Method', 'classification', ... 'CategoricalPredictors', 'all'); % 显式声明:非数值列按类别变量处理TextType','string'这一步容易被忽略。默认读进来是 cell 字符数组,某些版本里categorical转换会给出警告甚至失败,统一成 string 再转 categorical 就干净了。CategoricalPredictors写成'all'是让实现自己对非数值列做处理,但如果表里混进了 ID 列这种高基数变量(几千个不同取值),随机森林会把它当成一个极强的分裂特征,训练集精度飙高、测试集惨不忍睹——这类列在建表阶段就该删掉,别指望模型自己判断。
3.2 缺失值:填补、代理分裂、还是直接删
老版本的 TreeBagger 遇到 NaN 会直接抛错,新版本和fitcensemble的templateTree支持'Surrogate','all'做代理分裂,也就是在最优分裂特征缺失时用相关的替代特征顶上。代理分裂在缺失比例低于 5% 时效果不错,缺失比例一高,代理分裂本身也没得选,还是老老实实填补靠谱。
% 数值列中位数填补,类别列众数填补,同时打缺失标记 Xnum = varfun(@median, X, 'InputVariables', @isnumeric, ... 'OutputFormat', 'uniform'); for k = 1:width(X) col = X{:,k}; if isnumeric(col) miss = isnan(col); if any(miss) X{miss,k} = median(col(~miss)); % 中位数对偏态分布更稳 end else miss = isundefined(col); if any(miss) X{miss,k} = mode(col(~miss)); % 类别列用众数 end end if any(miss) X.(['miss_' X.Properties.VariableNames{k}]) = double(miss); % 缺失本身就是信息 end end补一个缺失指示列的做法在小样本上很值:缺失往往不是随机的,某个字段空着本身就带信号,把它当特征喂进去比单纯填补保留的信息更多。反过来,如果某一列缺失超过 30%,更常见的处理是直接删列,因为填补出来的值基本上等于在主要分裂上人为制造了一个众数堆积,会系统性地带偏重要性排序。
3.3 划分:分层 Hold-Out 与 K 折的对象设计
类别不平衡时最怕的就是普通随机划分:某次抽样某个少数类一个都没进训练集,模型直接学不会。cvpartition的第一参数传分类标签时默认做分层抽样,这一条能挡掉不少意外。
cp = cvpartition(Y, 'HoldOut', 0.3); % 传标签向量,自动分层 idxTrain = training(cp); idxTest = test(cp); rf = TreeBagger(200, X(idxTrain,:), Y(idxTrain), ... 'Method', 'classification', 'OOBPrediction', 'on'); Ytest = categorical(predict(rf, X(idxTest,:))); C = confusionmat(Y(idxTest), Ytest); acc = sum(diag(C)) / sum(C(:)); fprintf('测试集准确率 %.4f\n', acc);分层之后的第二个坑是顺序:cvpartition给出的是逻辑索引,如果先对整张表做了标准化、特征筛选或者缺失值填补,再划分,那么测试集的信息已经漏进训练过程,OOB 误差和测试误差会出现明显背离。正确顺序永远是「先切分、再在训练集上拟合任何变换、最后用训练集的统计量去处理测试集」。
4. 参数调整:树数、叶子大小、特征子集与自动调参
随机森林被叫「几乎不用调参的模型」,这句话只在「能跑出个结果」的前提下成立。真实项目里,树数决定训练成本,叶子大小决定过不过拟合,每次分裂的特征数决定树之间的相关性,三个参数串起来看才有效。
4.1 NumTrees:看 OOB 曲线什么时候走平
树数不是越多越好,而是「加到 OOB 误差不再明显下降」为止。判断方法很直接:开'OOBPrediction','on',用oobError取随树数累积的误差序列,画出来看拐点。
rf = TreeBagger(300, X, Y, 'Method', 'classification', ... 'OOBPrediction', 'on', 'MinLeafSize', 5); err = oobError(rf); % 长度 300,第 k 个是前 k 棵树的 OOB 误差 [~, kBest] = min(err); fprintf('OOB 最优树数 %d,误差 %.4f\n', kBest, err(kBest)); fprintf('300 棵树时误差 %.4f,进一步增加树数收益有限\n', err(end)); figure; plot(err, 'LineWidth', 1.2); grid on; xlabel('树的数量'); ylabel('OOB 分类误差');曲线在 100 到 150 棵之间通常已经形成平台,之后再加树只是线性增加训练时间。需要注意的是oobError返回的是累积误差,第 k 个元素对应前 k 棵树的投票结果,所以它天然单调性很差、会抖动——抖动幅度大的时候说明数据本身噪声重,这时候盯着最小值去取树数反而过拟合了 OOB,取平台起点更稳。300 棵是个保守上限,演示够用,生产环境按数据量自己压。
4.2 MinLeafSize:偏差与方差最直接的旋钮
叶子最小样本数是控制单棵树复杂度的主要参数。值越小,树越深,训练集误差越低,方差越大;值越大,树越浅,偏差上升,但对噪声的容忍度更好。分类任务上,1 到 10 是常见搜索区间,回归任务建议从 5 起步。
leafList = [1 3 5 10 20 50]; oob = zeros(size(leafList)); for i = 1:numel(leafList) rf = TreeBagger(150, X, Y, 'Method', 'classification', ... 'OOBPrediction', 'on', 'MinLeafSize', leafList(i)); oob(i) = oobError(rf, 'Mode', 'last'); % 只看最终树的 OOB 误差 end [bestErr, bestIdx] = min(oob); fprintf('最优 MinLeafSize = %d,OOB 误差 %.4f\n', leafList(bestIdx), bestErr);oobError(rf,'Mode','last')跟默认的累积序列不是一回事:加'Mode','last'拿到的是最终集成的单个误差值,做网格循环时用它更省事。如果这条曲线一路往下走到 1 才停,通常说明数据里的噪声不多、或者样本量够大;如果曲线在 10 附近触底再往上走,就要考虑特征里是不是混进了大量弱相关列。
4.3 NumPredictorsToSample:mtry 的经验值与实测
这是随机森林「随机」的核心参数。每次分裂只从 p 个特征里抽 m 个候选,m 越小,树之间越不相关,集成的方差降得越多,但单棵树的精度也越低。经验值是分类取 √p、回归取 p/3,这个起点在大部分数据上不会太离谱,但不是最优。
p = size(X, 2); mtryList = unique(round([sqrt(p), p/3, p/2, p])); % 经验值加两个扩展点 for i = 1:numel(mtryList) rf = TreeBagger(150, X, Y, 'Method', 'classification', ... 'OOBPrediction', 'on', 'NumPredictorsToSample', mtryList(i)); fprintf('mtry=%3d OOB误差=%.4f\n', mtryList(i), oobError(rf,'Mode','last')); end一个常见误用是把 mtry 设成 p,也就是每次分裂都用全部特征,然后把模型叫作随机森林。这时候它退化成了纯 bagging,树之间的相关性明显提高,集成降方差的效果打折,在特征高度相关的数据上尤其明显。实测中,如果 mtry=p 和 mtry=√p 的 OOB 误差差不多,选 √p 更划算,因为训练快、树之间更独立。
4.4 自动调参与并行:把网格交给 bayesopt
参数超过三个之后,手写循环的组合爆炸会吞掉大量时间。fitcensemble支持内置的贝叶斯优化,把搜索空间一次性描述清楚,让它自己找。
rng(2024); t = templateTree('Surrogate', 'off'); mdl = fitcensemble(X, Y, 'Method', 'Bag', 'Learners', t, ... 'OptimizeHyperparameters', {'NumLearningCycles', 'MinLeafSize', ... 'NumVariablesToSample'}, ... 'HyperparameterOptimizationOptions', struct( ... 'MaxObjectiveEvaluations', 30, ... % 目标函数评估次数,30 起步 'UseParallel', true, ... % 并行评估,需要并行工具箱 'Repartition', true, ... % 每轮重新划分,降低划分偏差 'Verbose', 1));Repartition',true值得开着,它让每轮评估用不同的交叉验证划分,避免某组参数恰好抽到一份「好切分」而虚高。MaxObjectiveEvaluations给 30 次是在时间和收益之间的折中,数据量大的时候可以先在小样本上搜一轮,拿到大致范围再回到全量数据上手工微调。并行开启前最好先手动起一次本地池,否则第一次调用会额外花时间初始化。
5. 常见排查:随机森林 MATLAB 代码跑不动、跑不准的五个现场
调参投入再多,也挡不住输入层面的一些低级错误。总结下来有五类高频现场,每次排查都从这几条过一遍,能省掉大量猜测。
5.1 准确率恰好等于多数类占比
现象:测试集准确率 0.83,而训练集里正类占比正好 83%,混淆矩阵显示模型把所有样本都判成了多数类。
原因:类别严重不平衡,随机森林的投票机制下少数类的票数被压过去,树在分裂时也倾向于先把多数类分干净,因为它对整体不纯度的贡献更大。这时准确率这个指标本身已经失效。
解决:三条一起上更稳——cvpartition传标签做分层抽样保证每折里少数类都有;fitcensemble把'Prior'设成'uniform',压制先验带来的偏置;评估指标换成confusionmat之外看召回率或者 F1,别只看准确率。如果少数类样本少于几百条,还要考虑过采样,但过采样必须在训练折内部做,不能在划分前对全量数据做。
5.2 标签类型报错或者预测结果全 false
现象:训练阶段报Y must be a vector,或者代码没报错但Yfit == Y全是 false。
原因:标签是字符串 cell 或者表格里的 string 列,predict返回的是字符 cell 数组,直接和数值或 categorical 标签比较,类型不匹配,比较结果全是 false,看起来像模型完全没用。
解决:训练前统一Y = categorical(T.label);预测后立刻Yfit = categorical(predict(rf, Xtest))再做比较。回归任务同理,TreeBagger的回归模型predict返回的是数值矩阵,但如果是多输出或者用了 cell 输入,也要检查维度对齐。这类问题不会给明确报错,只能靠打印前几行class(Y)和class(Yfit)定位。
5.3 内存爆掉或者训练慢到无法接受
现象:树数加到 500 之后训练要跑半小时,内存占用飙到十几 G,机器开始交换。
原因:每棵树都把完整训练集副本存进对象里,MinLeafSize太小时单棵树节点数暴涨,再加上'OOBPrediction'会额外保存袋外索引,内存是乘法级增长的。
解决:先把MinLeafSize从 1 提到 5 到 10,节点数能降一个量级;UseParallel打开用多核分摊时间,但要确认并行池的进程数不要超过物理核数,超了反而更慢;树数按 4.1 的 OOB 曲线取平台起点,别一上来就 1000 棵。特征维度超过几百列时,先做一轮基于重要性的粗筛再重新训练,比硬扛着跑全量特征划算得多。
5.4 OOB 误差很低但测试集一塌糊涂
现象:训练时 OOB 误差 0.06,交叉验证或者独立测试集误差 0.25,差距大得不正常。
原因:数据泄漏。最常见的是在划分之前对整张表做了标准化、缺失值填补、特征筛选或者 SMOTE 过采样,测试集的分布信息通过这些步骤渗进了训练过程。另一种是同一实体有多条记录被分到了训练集和测试集两侧,模型实际上在背答案。
解决:把整条流水线重排成「切分 → 训练集上拟合变换 → 应用到测试集」。如果有分组结构(同一个体多次测量、同一批次多个样本),用cvpartition的分组形式,保证整组进同一侧。改完之后 OOB 和测试误差的差距通常会收敛到 3 到 5 个百分点以内,还收不拢就要怀疑特征里混了标签派生的列。
5.5 特征重要性全是零或者与业务直觉相反
现象:OOBPermutedPredictorDeltaError返回一列数,前几个全是 0,或者排第一的是一个业务上明显无关的字段。
原因:三个可能——一是特征之间高度相关,重要性被平摊到几个相关列上,单独看每个都不高;二是标签编码或者 ID 列被当成了有效特征,模型找到了捷径;三是树太浅,某些特征根本没被用到,重要性自然为 0。
解决:先把 ID 类、时间戳类、索引类列手工剔掉,再重跑一次。相关特征的问题用分组置换重要性处理:把一组相关特征同时打乱再算精度下降,而不是逐个打乱。树太浅就按 4.2 把MinLeafSize降回来。排查时用predictorImportance和置换重要性各算一遍,两个结果差异很大的那几列,往往是需要单独看一眼的。
6. 进阶:把 OOB 误差、置换重要性和偏依赖用成决策依据
模型跑通只是起点,真正决定这份代码有没有用的是后面三件事:误差估计可不可信、特征排序能不能指导下一步采集、单特征的影响方向说不说得清。
误差估计上,我习惯同时看三个数:OOB 误差、5 折交叉验证误差、独立测试集误差。三个数互相之间的差距比绝对值更有信息量——OOB 和交叉验证差得多,说明样本顺序有结构;交叉验证和测试集差得多,说明分布漂移或者泄漏。这三个数都稳定在同一个量级,模型才算立住。
特征重要性不要只信内置那一列。手写一遍置换重要性,逻辑简单但可控:把某一列打乱,重新预测,看精度掉多少,掉得越多越重要。重复多次取均值,能压掉随机性。
baseAcc = mean(categorical(predict(rf, Xtest)) == Ytest); imp = zeros(1, size(Xtest,2)); nRepeat = 10; for j = 1:size(Xtest,2) drop = zeros(nRepeat,1); for r = 1:nRepeat Xp = Xtest; Xp{:,j} = Xp(randperm(height(Xp)), j); % 打乱第 j 列,其余不动 drop(r) = baseAcc - mean(categorical(predict(rf, Xp)) == Ytest); end imp(j) = mean(drop); end [~, order] = sort(imp, 'descend'); disp(Xtest.Properties.VariableNames(order(1:10))');randperm打乱整列是关键,不能只交换相邻行,否则相关性结构还在,重要性会被严重低估。重复 10 次的成本可以接受,如果预测很慢,降到 5 次也够看趋势。
单特征影响方向用偏依赖看,fitcensemble训练出的模型直接plotPartialDependence(mdl, j)就能出图,看的是「把第 j 列固定在不同取值上,其余列保持原样时预测概率的平均变化」。图上出现明显的台阶或者非单调折线,通常意味着树在这个特征上切出了硬阈值,这时候回头去看MinLeafSize是不是设得太小。TreeBagger 对象没有这个接口,需要自己构造网格、替换列、批量预测再取均值,代码不长但容易写错列索引,建议封装成小函数复用。
血泪经验是:这三件事里最容易被跳过的是置换重要性,因为内置的OOBPermutedPredictorDeltaError一行就能出结果,看起来已经够了。但在相关特征多的表上,内置结果和置换结果能差出完全不同的排序,而排序错了,后面所有的特征采集和工程投入都会跟着错。从那以后我每次拿到新的特征表,都强制把分组置换重要性跑一遍再下结论。
希望帮到你。
本文还有配套的精品资源,点击获取