简介:这份资源面向机器学习初学者与需要在MATLAB环境下开展分类、回归实验的研究者,提供随机森林(RF)算法的完整实现代码与配套示例。随机森林通过随机采样特征与样本构建多棵决策树,再以多数表决或均值方式整合输出,能有效提升泛化能力并降低过拟合风险。压缩包共14个文件,约211KB,以m函数脚本、txt示例数据、f底层源码、dll动态库及readme、doc文档为主,涵盖回归与分类两条调用路径,并附带示例脚本帮助快速上手。读者可借助示例数据完成数据预处理、模型训练、特征选择与结果评估,通过打印函数查看树数量与特征重要性,结合安装文档配置环境,并参考底层源码理解采样与集成细节。目前已有6047人学习下载,适合希望从代码层面掌握随机森林原理并落地实验的读者。
1. 随机森林 MATLAB 代码:从调用 TreeBagger 到亲手写出可复现的集成模型
很多人第一次搜「随机森林 MATLAB 代码」,是因为手头有一份 Excel 或 CSV 数据,想跑个回归或分类,又不想切到 Python 重搭环境。MATLAB 里其实有两套现成路径:统计与机器学习工具箱里的TreeBagger,以及 R2021a 之后引入的fitcensemble/fitrensemble配合Bag方法。前者是老牌实现,后者接口更统一。问题在于,网上流传的代码片段大多只贴三五行调用,参数默认值一跑,特征重要性对不上、袋外误差虚高、预测结果和训练集标签错位,翻车现场非常集中。这篇笔记就按「先跑通最小例子,再拆参数,最后处理真实数据的坑」这条线走,把随机森林在 MATLAB 里的落地路径讲清楚,适合已经会 MATLAB 基础语法、想把这套模型真正用起来的工程师和研究生。
2. 随机森林在 MATLAB 里的两条实现路线与选型理由
2.1 TreeBagger 与 fitcensemble 的差异到底在哪
TreeBagger是 MATLAB 里最早支持随机森林的类,属于统计与机器学习工具箱。它的核心行为是:对每棵树做自助采样(bootstrap),在每个分裂节点随机抽取一部分特征参与最优分裂搜索,最后用投票(分类)或平均(回归)聚合。fitcensemble则是后来统一集成学习接口的产物,通过Method='Bag'指定袋装,底层同样调用决策树,但返回的是ClassificationBaggedEnsemble或RegressionBaggedEnsemble对象,和predict、crossval、oobPermutedPredictorImportance这些函数的配合更顺。
选型上我一般这样判断:如果代码要长期维护、需要和crossval、hyperparameter调参流程打通,优先用fitcensemble;如果只是快速验证、或者参考的旧代码就是TreeBagger写的,那就继续用TreeBagger,没必要为了新接口重写。两者在树的数量、特征抽样数、最小叶节点这些关键参数上语义基本一致,迁移成本不高。
2.2 最小可运行示例:用自带数据集跑通分类
先不碰自己的数据,用 MATLAB 自带的fisheriris把流程走一遍。下面这段代码可以直接复制到脚本里运行。
% 加载自带鸢尾花数据集 load fisheriris X = meas; % 150x4 特征矩阵 Y = species; % 150x1 类别标签(cell 数组) % 用 fitcensemble 构建袋装森林 rng(42); % 固定随机种子,保证可复现 mdl = fitcensemble(X, Y, ... 'Method', 'Bag', ... % 袋装即随机森林 'NumLearningCycles', 100, ... % 树的数量 'Learners', templateTree( ... 'MinLeafSize', 2, ... % 叶节点最小样本数 'NumVariablesToSample', 'all')); % 每节点参与分裂的特征数 % 袋外误差估计 oobErr = oobError(mdl); fprintf('袋外分类误差: %.4f\n', oobErr(end)); % 用模型预测前 5 个样本 pred = predict(mdl, X(1:5, :)); disp(table(Y(1:5), pred, 'VariableNames', {'真实标签','预测标签'}));这段代码的逻辑是:先固定随机种子,避免每次运行结果漂移;NumLearningCycles控制树的数量,100 是常见起点;templateTree里的MinLeafSize决定树的生长深度,值越小树越深、越容易过拟合;NumVariablesToSample设为'all'表示每个节点用全部特征,这其实退化成普通袋装,真正的随机森林应该设为特征数的平方根(分类)或三分之一(回归)。最后用oobError看袋外误差,这是随机森林自带的免费验证集,不需要额外划分测试集。
提示:
NumVariablesToSample设成'all'只是为了让最小例子先跑通,实际用的时候分类任务建议设为sqrt(特征数)取整,回归任务设为特征数/3取整。
2.3 回归任务的写法与关键参数
回归场景把fitcensemble换成fitrensemble,或者用TreeBagger时把Method设为'regression'。下面用 MATLAB 自带的carbig数据集演示。
% 加载汽车性能数据 load carbig X = [Horsepower, Weight, Displacement]; % 三个数值特征 Y = MPG; % 目标:每加仑英里数 % 剔除缺失值 idx = all(~isnan([X, Y]), 2); X = X(idx, :); Y = Y(idx); % 构建回归森林 rng(7); mdl = fitrensemble(X, Y, ... 'Method', 'Bag', ... 'NumLearningCycles', 200, ... 'Learners', templateTree( ... 'MinLeafSize', 5, ... 'NumVariablesToSample', 1)); % 3 个特征,取 1 个 % 袋外均方误差 oobMSE = oobError(mdl, 'Mode', 'cumulative'); fprintf('最终袋外 MSE: %.4f\n', oobMSE(end)); % 特征重要性 imp = oobPermutedPredictorImportance(mdl); disp(table({'Horsepower';'Weight';'Displacement'}, imp, ... 'VariableNames', {'特征','重要性'}));回归任务里NumVariablesToSample一般取特征总数的三分之一,这里 3 个特征取 1。oobError的'Mode','cumulative'会返回每增加一棵树时的累计误差,方便判断树的数量是否足够。oobPermutedPredictorImportance通过打乱每个特征后看袋外误差上升多少来衡量重要性,比单纯看分裂次数更可靠。
3. 把随机森林 MATLAB 代码用到自己的数据上:预处理与调参
3.1 数据导入与缺失值、类别不平衡的处理
真实数据很少像自带数据集那样干净。常见做法是用readtable读 CSV,然后检查缺失值分布。
% 读取 CSV,假设第一行是表头 T = readtable('mydata.csv'); % 查看每列缺失比例 missingRatio = sum(ismissing(T)) / height(T); disp(table(T.Properties.VariableNames', missingRatio', ... 'VariableNames', {'变量','缺失比例'})); % 对数值列用中位数填充,类别列用众数填充 for i = 1:width(T) col = T{:, i}; if isnumeric(col) col(isnan(col)) = median(col, 'omitnan'); elseif iscategorical(col) || iscell(col) modeVal = mode(categorical(col)); col(ismissing(col)) = {char(modeVal)}; end T{:, i} = col; end缺失值处理没有万能方案。中位数填充适合数值特征分布偏斜不严重的情况;如果缺失比例超过 30%,我一般直接考虑剔除该特征,因为填充引入的偏差可能比丢掉信息更糟。类别不平衡的话,fitcensemble支持'Weights'参数,可以给少数类更高权重,或者用'Stratify'保证每棵树的采样保持类别比例。
3.2 树的数量、叶节点大小、特征抽样数怎么定
这三个参数是随机森林的核心。树的数量NumLearningCycles不是越多越好,超过一定数量后袋外误差趋于平稳,继续增加只消耗时间。我通常从 100 开始,画袋外误差曲线,看拐点在哪。
% 观察袋外误差随树数量的变化 numTrees = [10, 30, 50, 100, 200, 300, 500]; oobErrors = zeros(size(numTrees)); for i = 1:length(numTrees) rng(42); m = fitcensemble(X, Y, 'Method', 'Bag', ... 'NumLearningCycles', numTrees(i), ... 'Learners', templateTree('MinLeafSize', 2)); e = oobError(m); oobErrors(i) = e(end); end plot(numTrees, oobErrors, '-o'); xlabel('树的数量'); ylabel('袋外误差'); grid on;叶节点大小MinLeafSize控制单棵树的复杂度。分类任务常用 1 到 5,回归任务常用 5 到 10。值太小树长得深,方差大;值太大树欠拟合。特征抽样数前面说过,分类取sqrt(p),回归取p/3,这是经验起点,实际可以用hyperparameters配合贝叶斯优化搜一下。
3.3 用交叉验证替代单一袋外估计
袋外误差虽然方便,但在样本量小的时候波动大。更稳妥的做法是做 k 折交叉验证。
% 5 折交叉验证 rng(42); mdl = fitcensemble(X, Y, 'Method', 'Bag', ... 'NumLearningCycles', 200, ... 'Learners', templateTree('MinLeafSize', 2)); cvmdl = crossval(mdl, 'KFold', 5); cvLoss = kfoldLoss(cvmdl); fprintf('5 折交叉验证误差: %.4f\n', cvLoss);crossval会自动在每折上重新训练,返回的cvLoss比单次袋外误差更稳定。注意交叉验证的计算量是单次训练的 k 倍,数据量大时要有心理准备。
4. 随机森林 MATLAB 代码避坑:五个高频翻车现场
4.1 预测标签和训练标签顺序对不上
现象:用predict得到的结果和真实标签对比,准确率异常低,但袋外误差看起来正常。
原因:分类任务里Y如果是 cell 数组或 categorical,fitcensemble内部会按字母序重排类别,predict返回的也是重排后的标签。如果手动用strcmp逐条比对,顺序错位就会导致假性低准确率。
解决:统一用 categorical 类型,并且用confusionmat或loss函数计算指标,不要手动比对。
Y = categorical(Y); mdl = fitcensemble(X, Y, 'Method', 'Bag', 'NumLearningCycles', 100); pred = predict(mdl, X); acc = 1 - loss(mdl, X, Y); % 用内置 loss 避免手动比对4.2 NumVariablesToSample 设成 all 导致随机性消失
现象:每次运行结果几乎一样,特征重要性集中在少数几个特征上,模型看起来像普通决策树。
原因:NumVariablesToSample设为'all'时,每个节点都在全部特征里找最优分裂,随机森林的「随机特征子集」机制失效,树之间相关性变高,集成效果退化。
解决:分类任务设为round(sqrt(size(X,2))),回归任务设为round(size(X,2)/3),并且确保这个值至少为 1。
4.3 类别特征直接当数值传入
现象:模型能跑,但袋外误差很高,特征重要性排序和业务直觉完全相反。
原因:MATLAB 的决策树对数值特征做阈值分裂,如果类别特征用 1、2、3 编码,树会错误地认为 3 比 1 大,产生无意义的分裂。
解决:用categorical函数把类别列转成 categorical 类型,fitcensemble会自动对 categorical 做分组分裂。
T.CategoryCol = categorical(T.CategoryCol);4.4 数据量太大导致内存溢出
现象:训练集超过几十万行时,fitcensemble报内存不足,或者训练时间长达数小时。
原因:随机森林每棵树都要保存完整结构,树的数量多、叶节点小的时候,模型对象占用内存急剧上升。
解决:先降低NumLearningCycles到 50 试试袋外误差是否可接受;增大MinLeafSize限制树深;或者用TreeBagger的'OOBPrediction','on'配合'MinLeafSize'调大,减少每棵树的节点数。如果还是不够,考虑对数据做分层抽样,每棵树只用一部分数据训练。
4.5 特征重要性为负值
现象:oobPermutedPredictorImportance返回的某些特征重要性是负数。
原因:打乱该特征后袋外误差反而下降了,说明这个特征在当前模型里是噪声,打乱它相当于引入随机扰动,偶尔会让误差降低。这在特征数多、样本量小的时候很常见。
解决:负值直接当作 0 处理,不要强行解释。如果大量特征重要性为负,说明模型可能过拟合,需要增大MinLeafSize或减少树的数量。
5. 进阶技巧:用随机森林做特征筛选与模型解释
5.1 基于袋外置换重要性的递归特征消除
随机森林的特征重要性可以驱动特征筛选。思路是:先训练一个完整模型,按重要性排序,每次剔除最不重要的特征,重新训练,观察袋外误差变化,直到误差明显上升为止。
% 递归特征消除 X_curr = X; featureNames = 1:size(X, 2); history = []; while size(X_curr, 2) > 1 rng(42); m = fitrensemble(X_curr, Y, 'Method', 'Bag', ... 'NumLearningCycles', 100, ... 'Learners', templateTree('MinLeafSize', 5)); e = oobError(m); imp = oobPermutedPredictorImportance(m); history = [history; size(X_curr, 2), e(end)]; [~, worstIdx] = min(imp); X_curr(:, worstIdx) = []; featureNames(worstIdx) = []; fprintf('剩余特征数: %d, 袋外 MSE: %.4f\n', size(X_curr, 2), e(end)); end这段代码会逐步输出剩余特征数和对应的袋外误差。实际用的时候不需要跑到只剩一个特征,观察误差曲线,在误差开始明显上升的前一步停止即可。注意每次剔除后要重新固定随机种子,否则误差波动会干扰判断。
5.2 用部分依赖图看单特征对预测的影响
部分依赖图(Partial Dependence Plot)能展示某个特征取值变化时,模型预测的平均变化趋势。MATLAB 没有直接的内置函数,但可以手动实现。
% 对第 j 个特征做部分依赖 j = 1; gridVals = linspace(min(X(:,j)), max(X(:,j)), 50); pd = zeros(size(gridVals)); for k = 1:length(gridVals) X_temp = X; X_temp(:, j) = gridVals(k); pd(k) = mean(predict(mdl, X_temp)); end plot(gridVals, pd, '-o'); xlabel(sprintf('特征 %d 取值', j)); ylabel('平均预测值'); title('部分依赖图'); grid on;这段代码把第 j 个特征的所有样本值替换成网格上的固定值,其他特征保持不变,预测后取平均。得到的曲线反映该特征对输出的边际影响。如果曲线平坦,说明这个特征对预测贡献很小;如果单调上升或下降,说明有明确的正负相关。
5.3 保存模型与在新数据上预测
训练好的模型可以保存成 MAT 文件,下次直接加载使用,不用重新训练。
% 保存模型 save('rf_model.mat', 'mdl'); % 加载模型并预测新数据 loaded = load('rf_model.mat'); newX = rand(10, size(X, 2)); % 假设新数据 newPred = predict(loaded.mdl, newX); disp(newPred);保存模型时注意 MAT 文件里存的是整个对象,包括训练数据的一些元信息,文件可能比较大。如果只需要预测功能,可以考虑用compact函数压缩模型,去掉训练数据,减小体积。
compactMdl = compact(mdl); save('rf_model_compact.mat', 'compactMdl');压缩后的模型仍然支持predict,但不支持oobError和oobPermutedPredictorImportance这类需要训练数据的方法。我一般训练阶段保留完整模型,部署阶段用压缩版。
最后说个习惯:每次跑随机森林之前先rng固定种子,把参数和袋外误差记在脚本注释里。我吃过亏,隔了两周回头跑同一份数据,结果对不上,查了半天才发现是随机种子没固定。希望帮到你。
本文还有配套的精品资源,点击获取