简介:本资源是一套面向机器学习初学者与科研人员的Matlab回归建模实战方案,聚焦多输入单输出(MISO)场景下的高精度预测需求,通过遗传算法(GA)自动优化随机森林(RF)超参数,显著提升模型泛化能力与稳定性。压缩包共23个文件,含17个核心Matlab脚本(如主程序main.m、GA优化模块Objfun.m与ranking.m、RF训练/预测接口regRF_train.m及mexw64加速文件)、3张可视化结果图(含预测曲线与误差分布)、1个结构化Excel数据集,整体仅216KB,轻量易部署。已有748人下载学习,代码逻辑清晰、注释完备,完整覆盖数据预处理、GA种群初始化与进化、RF超参寻优、交叉验证、多指标评估(R²、MAE、MSE、RMSE、MAPE)及结果可视化全流程,支持一键运行与数据替换,特别适合算法原理理解、课程设计或小规模科研项目快速复现。 先说几句题外话。这类“遗传算法优化随机森林”的项目,在工程和学术里出现频率非常高。原因很简单:随机森林本身是个不赖的模型,但它的超参数——尤其是树的数量、叶子节点最小样本数——对最终回归精度影响很大。手调太累,网格搜索又太笨,于是大家自然想到用遗传算法去自动找参数。这篇我把自己实现的完整流程、核心代码、踩过的坑一次性写清楚,代码和数据组织方式都按可直接复现的标准来。适合有Matlab基础、正在做回归预测任务的本科生、研究生,以及需要快速搭建预测模型的工程师。
1. 项目整体设计与思路拆解
1.1 随机森林回归到底在做什么
随机森林的本质是Bagging思想加决策树:训练时随机有放回地抽取样本子集,同时在每个节点分裂时随机挑选一部分特征,训练出大量决策树;预测时把所有树的输出取平均。对于回归任务,这个平均操作能把单棵决策树的高方差压下去,所以随机森林在中等规模数据集上表现相当稳,不太容易过拟合。
但问题也出在“稳”字上。树的数量如果太少,模型不充分;太多则训练成本高,而且收益边际递减。叶子节点最小样本数如果设成1,单棵树会为了拟合个别样本长得很深,虽然随机森林整体能缓解过拟合,但方差依然偏大。而如果我们把这个值拉得太高,树又太浅,模型欠拟合。特征选择数量也类似,太小会让树之间太相似,太大则失去了随机性带来的去相关效果。
这些参数组合起来,靠肉眼和经验很难快速找到最优解。这就是要用优化算法去搜索的动机。
1.2 为什么选遗传算法而不是网格搜索
很多人第一反应是网格搜索,毕竟Matlab里fitrensemble配OptimizeHyperparameters就能自动调参,或者用贝叶斯优化。但网格搜索最明显的问题是指数爆炸:每个参数取10个值,3个参数就是1000次训练,每次训练还要交叉验证,在小数据集上勉强能忍,数据量稍微大一点就非常痛苦。贝叶斯优化虽然智能,但它在处理混合类型参数空间时经常因为核函数设定不当而收敛得很怪,而且对新手来说黑箱感太强,出了问题不好排查。
遗传算法在这类问题上有两个实实在在的优点:
一是它对参数类型不敏感。随机森林的超参数里有整数(树的数量、叶子最小样本数),有整数范围(特征选择数),GA可以非常自然地用整数编码,而且Matlab的ga自带Integer constraints处理。
二是它有全局搜索能力。GA通过种群并行搜索,交叉和变异操作能跳出局部最优。虽然它不一定保证找到全局最优,但在这个问题里,“一个足够好的参数组合”比“数学上最优的参数组合”更重要。
当然GA也不是银弹。它的缺点是收敛慢、每次结果有随机性。所以实操中我的策略是:先用GA大致确定一个较优区域,再在这个区域附近用细粒度搜索或直接接受GA结果。这个思路在后面的程序里有体现。
1.3 整体流程拆解
整个项目按以下链路组织:
- 第1步:读取数据,完成训练集与测试集划分。
- 第2步:定义适应度函数。个体是随机森林的参数组合(树数量、最小叶子数、特征选择数),适应度是该参数组合在训练集上做K折交叉验证得到的均方根误差。
- 第3步:调用遗传算法迭代寻优,得到最优参数。
- 第4步:用最优参数在完整训练集上重新训练随机森林模型。
- 第5步:在测试集上评估,计算R²、RMSE、MAE、MAPE等指标,画对比图和误差图。
这里有一个关键设计点:为什么适应度要用交叉验证而不是直接在训练集上训练?
因为如果我们用训练集拟合误差作为适应度,随机森林很容易选出一组让模型“背答案”的参数(例如最小叶子设为1),泛化能力反而差。交叉验证相当于在训练集内部再切一刀,模拟“没见过的新数据”,这样选出来的参数才更可靠。
2. 环境准备与数据组织
2.1 Matlab环境与工具箱要求
这个项目实现依赖两个工具箱:
- Statistics and Machine Learning Toolbox:提供TreeBagger,这是Matlab里最经典的随机森林接口。
- Global Optimization Toolbox:提供ga函数,也就是遗传算法求解器。
版本方面,建议R2018b以上。我实测过R2019b、R2021b、R2023a都能正常跑通。如果版本太老,TreeBagger的某些参数名可能不兼容(比如NumPredictorsToSample在更早版本里叫NVarToSample)。
需要特别提一句:这些工具箱不是Matlab基础包自带的。很多人下载的是精简版或者破解版,装完之后发现没有全局优化工具箱,跑ga会直接报错Undefined function 'ga'。解决方法是安装完整版,或者在MathWorks官网下载工具箱单独安装。如果实在没有Global Optimization Toolbox,也可以自己写一个简单的二进制/实数遗传算法,但稳定性不如内置函数。这篇文章按工具箱齐全来写。
2.2 数据格式与读取方式
常规做法是把数据放在Excel或CSV中,最后一列是输出Y,前面所有列是输入X。比如你的输入特征是8个维度,样本量是500条,那Excel就是500行9列,前8列是特征,第9列是标签。
读取代码非常简单:
data = xlsread('数据集.xlsx'); X = data(:, 1:end-1); Y = data(:, end);这里有个新手容易犯的错:没有先做数据清洗。如果Excel里有空单元格或者文本标题,xlsread会返回NaN,导致后面TreeBagger直接报错。稳妥的做法是先在Excel里把表头删掉,然后检查数据里有没有NaN:
data(any(isnan(data), 2), :) = []; X = data(:, 1:end-1); Y = data(:, end);另外,如果数据的量纲差异非常大(比如一个特征在0.01级别,另一个在上万级别),有人会习惯性先做归一化。但随机森林是树模型,它的分裂只依赖特征的相对顺序和阈值,不依赖特征的尺度,所以归一化对随机森林本身没有帮助,这个跟神经网络不一样。
2.3 训练集与测试集划分
划分比例我习惯用80%训练、20%测试,样本量少的时候也会考虑75%/25%。划分类似下面的代码:
rng(42); % 固定随机种子,保证实验可复现 n = size(X, 1); idx = randperm(n); trainNum = round(0.8 * n); trainIdx = idx(1:trainNum); testIdx = idx(trainNum+1:end); X_train = X(trainIdx, :); Y_train = Y(trainIdx); X_test = X(testIdx, :); Y_test = Y(testIdx);为什么用randperm而不是直接用前80%?因为原始数据的顺序很可能有某种规律(比如按时间排序、按类别聚集),如果直接切前80%,训练集和测试集分布可能不一致,导致评估结果失真。随机打乱之后划分,能降低这种风险。
还有一点,固定随机种子这个动作,很多人在调参阶段完全忽略。但GA本身有随机性,数据划分也有随机性,如果不固定种子,每次跑出来的结果都不一样,你根本无法判断参数调整带来的提升是真提升还是随机波动。所以rng(42)不是可有可无,是必须的。
3. 核心算法实现原理
3.1 随机森林的核心参数与Matlab接口
Matlab里随机森林的接口有好几个:TreeBagger、fitrensemble、fitcensemble(分类)。回归场景我用的是TreeBagger,它在老项目中兼容性最好,输出也直观。核心参数如下:
NumTrees:决策树数量。取值范围通常20到200。太少不稳定,太多训练慢,且提升幅度递减。MinLeafSize:叶子节点最小样本数。默认是1,但对于回归问题,尤其是噪声比较大的数据,1很容易过拟合。比较合理的范围是1到20。NumPredictorsToSample:每个节点分裂时随机选择的特征数。回归问题默认是特征总数的1/3左右。这个参数决定了树的随机性强度。Method:必须设为'regression',否则TreeBagger默认做分类。
训练一个随机森林模型只需要一行:
model = TreeBagger(numTrees, X_train, Y_train, ... 'Method', 'regression', ... 'MinLeafSize', minLeaf, ... 'NumPredictorsToSample', nVar);预测时注意,TreeBagger返回的是一个cell数组,需要转换:
pred = predict(model, X_test); pred = cell2mat(pred);这个cell2mat很容易被漏掉。很多人第一次用predict的时候直接拿它跟Y_test做运算,结果报错或者算出来是NaN,就是因为忘了转换。
3.2 遗传算法的参数编码与适应度函数设计
GA-RF的核心,是把随机森林的3个超参数编码成一个个体。这里我把个体设计成3维整数向量:
- 第1维:numTrees,树的数量,范围我设为[10, 200]。
- 第2维:minLeaf,最小叶子数,范围[1, 30]。
- 第3维:numPredictorsToSample,分裂特征数,范围[1, size(X,2)]。
适应度函数的目标是让交叉验证误差最小。我用的适应度指标是均方根误差(RMSE),因为它和原始数据同一个量纲,解释起来更直观。
适应度函数代码如下(单独保存为gaRF_fitness.m):
function rmse = gaRF_fitness(params, X, Y) numTrees = round(params(1)); minLeaf = round(params(2)); numPred = round(params(3)); % 保证参数在合法范围内 numTrees = max(10, min(200, numTrees)); minLeaf = max(1, min(30, minLeaf)); numPred = max(1, min(size(X, 2), numPred)); % 5折交叉验证 cv = cvpartition(size(X, 1), 'KFold', 5); rmseSum = 0; for i = 1:cv.NumTestSets trIdx = cv.training(i); teIdx = cv.test(i); model = TreeBagger(numTrees, X(trIdx, :), Y(trIdx), ... 'Method', 'regression', ... 'MinLeafSize', minLeaf, ... 'NumPredictorsToSample', numPred); pred = predict(model, X(teIdx, :)); pred = cell2mat(pred); rmseSum = rmseSum + sqrt(mean((pred - Y(teIdx)).^2)); end rmse = rmseSum / cv.NumTestSets; end这里有几个细节需要解释:
一是为什么个体内部加了一个边界约束。GA在交叉和变异后可能产生超出范围的个体,比如树的数量变成负值、特征选择数超过特征总数,这种情况必须在进入适应度计算之前就修正,否则TreeBagger直接报错。
二是为什么用5折而不是10折。交叉验证折数越多,每次训练数据越多,评估越稳定,但计算量也越大。在GA迭代里适应度函数会被调用几百上千次,如果每算一次都要10折训练,整体耗时直接爆炸。5折是我在稳定性和耗时之间取的一个平衡点。如果数据量特别大,甚至可以降到3折。
三是为什么用RMSE而不是MSE。MSE在量纲上是原始数据标签的平方,比如标签范围是0到100,MSE可能到几百上千,看着数值很大很容易焦虑。RMSE把结果拉回原量纲,比如MSE=225,RMSE=15,你立刻知道平均误差在15个单位左右,直观得多。
3.3 GA参数设定与寻优策略
ga函数调用本身不复杂,关键是要设置好种群大小和迭代代数。我用的配置如下:
nvars = 3; lb = [10, 1, 1]; ub = [200, 30, size(X_train, 2)]; IntCon = [1, 2, 3]; options = optimoptions('ga', ... 'PopulationSize', 30, ... 'MaxGenerations', 40, ... 'Display', 'iter', ... 'UseParallel', false, ... 'PlotFcn', @gaplotbestf); [bestParams, bestRMSE] = ga(@(params) gaRF_fitness(params, X_train, Y_train), ... nvars, [], [], [], [], lb, ub, [], IntCon, options);解释一下这里每个配置:
PopulationSize设为30。太小搜索能力不足,太大每代计算量飙升。30是中等规模数据集上比较稳妥的起点。MaxGenerations设为40。加上种群30,意味着最多评估1200个个体。每个个体要做5折交叉验证,也就是最多6000次随机森林训练。这个量级在几百样本的数据集上,通常几分钟能跑完。IntCon设为[1,2,3],表示三个变量都必须取整数。如果不设置这个,ga默认按实数搜索,而随机森林的超参数必须是整数,四舍五入后可能破坏搜索的连续性。UseParallel,多核并行执行适应度评估能大幅提速,但要注意并行池的启动时间在小数据量时可能得不偿失。后面问题排查部分细说。
有个容易忽略的细节:ga的目标函数必须只有一个输入参数,所以用匿名函数@(params) gaRF_fitness(params, X_train, Y_train)把额外的训练数据传进去。这是Matlab优化工具箱非常经典的写法。
4. 完整实操流程与代码解析
4.1 主程序框架搭建
主程序按逻辑分成几大块,我强烈建议你不要把所有代码写在一个文件里,而是按功能拆分成脚本加函数。项目文件结构如下:
GA_RF_Regression/ ├── main.m # 主脚本 ├── gaRF_fitness.m # 适应度函数 ├── train_RF.m # 用最优参数训练最终模型 ├── evaluate_model.m # 计算评价指标并绘图 └── 数据集.xlsx # 输入数据main.m的核心逻辑如下:
%% 1. 数据加载与预处理 clear; clc; close all; data = xlsread('数据集.xlsx'); data(any(isnan(data), 2), :) = []; X = data(:, 1:end-1); Y = data(:, end); %% 2. 划分训练集与测试集 rng(42); n = size(X, 1); idx = randperm(n); trainNum = round(0.8 * n); X_train = X(idx(1:trainNum), :); Y_train = Y(idx(1:trainNum)); X_test = X(idx(trainNum+1:end), :); Y_test = Y(idx(trainNum+1:end)); %% 3. GA优化随机森林参数 nvars = 3; lb = [10, 1, 1]; ub = [200, 30, size(X_train, 2)]; IntCon = [1, 2, 3]; options = optimoptions('ga', ... 'PopulationSize', 30, ... 'MaxGenerations', 40, ... 'Display', 'iter', ... 'PlotFcn', @gaplotbestf); fitnessFunc = @(params) gaRF_fitness(params, X_train, Y_train); [bestParams, bestRMSE] = ga(fitnessFunc, nvars, [], [], [], [], ... lb, ub, [], IntCon, options); fprintf('最优参数: 树数量=%d, 最小叶子数=%d, 特征选择数=%d\n', ... round(bestParams(1)), round(bestParams(2)), round(bestParams(3))); fprintf('交叉验证RMSE=%.4f\n', bestRMSE); %% 4. 用最优参数训练最终模型 numTrees = round(bestParams(1)); minLeaf = round(bestParams(2)); numPred = round(bestParams(3)); finalModel = TreeBagger(numTrees, X_train, Y_train, ... 'Method', 'regression', ... 'MinLeafSize', minLeaf, ... 'NumPredictorsToSample', numPred); %% 5. 测试集预测与评估 pred_test = predict(finalModel, X_test); pred_test = cell2mat(pred_test);4.2 训练与评估脚本
train_RF.m其实已经集成在主程序里了,如果希望单独封装,可以这样:
function model = train_RF(X_train, Y_train, numTrees, minLeaf, numPred) model = TreeBagger(numTrees, X_train, Y_train, ... 'Method', 'regression', ... 'MinLeafSize', minLeaf, ... 'NumPredictorsToSample', numPred); end评估部分我建议单独写一个脚本,因为需要同时算多个指标、画多个图:
%% 计算评价指标 y_mean = mean(Y_test); SS_res = sum((Y_test - pred_test).^2); SS_tot = sum((Y_test - y_mean).^2); R2 = 1 - SS_res / SS_tot; RMSE = sqrt(mean((Y_test - pred_test).^2)); MAE = mean(abs(Y_test - pred_test)); MAPE = mean(abs((Y_test - pred_test) ./ Y_test)) * 100; fprintf('R2 = %.4f\n', R2); fprintf('RMSE = %.4f\n', RMSE); fprintf('MAE = %.4f\n', MAE); fprintf('MAPE = %.4f%%\n', MAPE);绘图部分我常画三张图:
第一张是测试集真实值与预测值的对比曲线。横轴是样本序号,纵轴是目标值,画两条曲线,一条真实值一条预测值。这个图能直观看出预测跟随趋势的能力。
figure; plot(Y_test, 'b-o', 'LineWidth', 1); hold on; plot(pred_test, 'r-^', 'LineWidth', 1); legend('真实值', '预测值'); xlabel('样本序号'); ylabel('目标值'); title('测试集真实值与预测值对比'); grid on;第二张是散点图,横轴真实值,纵轴预测值,同时画一条y=x的参考线。如果散点密集分布在参考线附近,说明预测精度高。如果整体偏离参考线,说明存在系统性偏差。
figure; scatter(Y_test, pred_test, 40, 'filled'); hold on; plot([min(Y_test), max(Y_test)], [min(Y_test), max(Y_test)], 'r--', 'LineWidth', 1.5); xlabel('真实值'); ylabel('预测值'); title('测试集真实值与预测值散点图'); axis equal; grid on;第三张是误差分布图。我一般画预测残差的直方图,观察误差是否集中在0附近,以及有没有明显的厚尾。
figure; residual = Y_test - pred_test; histogram(residual, 20); xlabel('预测误差'); ylabel('频次'); title('预测误差分布直方图'); grid on;4.3 特征重要性分析
随机森林一个很实用的副产品是特征重要性。TreeBagger在训练完后可以通过OOBPermutedPredictorDeltaError属性查看每个特征被随机置换后误差的增加量,增加越大说明该特征越重要。
figure; bar(finalModel.OOBPermutedPredictorDeltaError); xlabel('特征编号'); ylabel('重要性得分'); title('随机森林特征重要性');这个图对数据分析和论文写作特别有价值。很多时候你不仅能得到预测结果,还能判断哪些输入变量真正驱动了目标变量,这个信息单独拿出来就能形成一套分析结论。
4.4 完整程序包结构说明
上面我给出了完整的代码框架。在实际交付时,还需要注意数据文件命名不要用中文和空格,最好统一用英文字母。比如“数据集.xlsx”这个名字在Windows上没问题,但如果代码给别人在Linux或Mac的Matlab上跑,编码可能出问题。
完整的程序包里还应包含一个README.txt,简要说明每个文件的功能、Matlab版本要求、工具箱要求。这样别人拿到之后不需要反复问你怎么跑。
5. 关键参数调试与个人经验
5.1 参数取值范围怎么定
这部分我踩过不少坑。第一次做GA-RF的时候,我把树的数量范围设到[1, 500],最小叶子设到[1, 100],想着范围越大越保险。结果跑了半小时还没收敛,而且GA经常把树的数量的解落在边界值上,说明搜索空间太大,在有限的代数内根本找不到有效区域。
后来我总结出一套经验值,按数据量来定:
| 样本量 | 树数量范围 | 最小叶子范围 | 特征选择范围 |
|---|---|---|---|
| 小于500 | [10, 100] | [1, 10] | [1, 特征数] |
| 500~2000 | [20, 200] | [1, 20] | [1, 特征数] |
| 大于2000 | [50, 300] | [2, 30] | [最大(1,特征数/3), 特征数] |
具体到代码里就是调整lb和ub。另外,特征选择数的上界不用设太大,因为当它等于特征总数时,随机森林就退化成普通Bagging,树之间的相关性增大,整体性能反而下降。
5.2 优化耗时与结果稳定性
GA每次运行结果会有波动,这是遗传算法的随机性决定的,不是你的代码有问题。我实测在相同数据集上连续跑10次GA,每次找到的最优RMSE可能有1%~3%的浮动。这是正常的,但如果你想在论文里写“最优参数”,可能需要多次运行取最好的一组。
控制波动有几种办法:
- 固定rng。在ga之前加一行rng(0),这样每次运行都按同一个随机序列搜索,结果完全一致。但注意,这会失去GA的随机探索意义,一般用于最终复现。
- 增加种群大小和代数。把PopulationSize从30提到50,MaxGenerations从40提到60,稳定性会好很多,但耗时增加约两倍。
- 多次运行取最优。跑5次GA,每次独立随机种子,记录每次的最优参数和适应度,最后取适应度最好的那一组。这个方法最稳,但也是最费时间的。
对大多数中等规模回归任务,我的建议是:先固定rng跑一次,看收敛曲线是否已经平缓。如果最后十几代best fitness还在明显下降,说明代数不够,加大MaxGenerations。如果已经平缓,直接接受结果即可。
5.3 模型效果不佳时的扩展思路
如果GA优化完之后,测试集R²还是不太理想,不要急着堆更多数据。先看以下三个方向:
第一个方向是数据层面。检查Y的分布是否极端偏态。比如Y跨越几个数量级,最大值和最小值差100倍,随机森林在这种数据上很容易被大值主导,预测小值时偏差很大。一个简单的处理是对Y取对数后再训练,预测完再指数还原。
第二个方向是特征层面。如果特征数量很多且有不少噪声特征,随机森林虽然有一定的鲁棒性,但噪声特征过多依然会稀释有效特征的重要性。可以考虑先用特征重要性筛选一遍,把得分很低的特征剔除后再跑GA。我自己实测过,剔除后不仅模型效果略有提升,训练时间也下降不少。
第三个方向是模型层面。随机森林虽然稳,但在非线性强、有周期性规律的数据上,往往不如梯度提升树(如XGBoost、LightGBM)。GA-RF这套框架完全可以平移过去改成GA-XGBoost,只需要把适应度函数里的TreeBagger换成XGBoost的Matlab接口即可。参数编码也类似,只是要改一下超参数维度。
6. 常见问题与排查技巧实录
6.1 报错与解决方法速查表
我把这个项目最常遇到的报错和解决方法整理成一张表,这些错误我在调试过程中基本都遇到过:
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
| Undefined function 'ga' | 没装Global Optimization Toolbox | 安装工具箱,或改用自写遗传算法 |
| Undefined function 'TreeBagger' | 没装Statistics and Machine Learning Toolbox | 安装统计机器学习工具箱 |
| Predictor names must be a character array or cell array of character vectors | 训练数据包含NaN或非数值列 | 用isnan删除含NaN的行,确保X全为数值 |
| Cell contents reference from a non-cell array object | 忘了对predict结果做cell2mat | 加一行pred = cell2mat(pred); |
| Number of trees must be a positive integer | GA变异后产生非法值 | 在适应度函数入口做边界修正 |
| The number of predictors to sample must not exceed the number of predictors | 个体第3维超过特征总数 | 同样在适应度函数入口做边界修正 |
| Out of memory | 树太多或数据太大 | 降低NumTrees范围,或减少交叉验证折数 |
6.2 预测结果全在平均值附近的问题
这个现象很典型:预测值的方差明显小于真实值,曲线图上看预测值像被“压缩”到均值附近。根本原因是随机森林在回归时,所有决策树的预测结果取平均,相当于做了一次平滑,天然会把极值削弱。如果数据本身噪声大,或者树的多样性不足,这种“均值回归”现象会更明显。
缓解措施有这么几个:
一是调小最小叶子数。MinLeafSize越小,单棵树拟合越充分,极端值保留得越多,预测值方差会变大。代价是过拟合风险上升。GA的作用就是在这之间找平衡点。
二是检查特征选择数。如果NumPredictorsToSample太接近特征总数,树之间相关性过高,森林的多样性下降,平均之后更容易向均值收缩。
三是考虑换模型。如果数据本身极值重要且占比不高,随机森林很难完美预测极值。这时候可以对比一下GA优化的支持向量回归(SVR)或者高斯过程回归,它们对极值的处理通常更好。
6.3 运行效率优化技巧
最后说性能。数据量在几百这个级别,GA-RF跑起来毫无压力。但如果你是上千甚至上万条数据,每代30个个体乘以5折交叉验证,每折都要训练一棵几百棵树的随机森林,整体耗时是以分钟甚至小时计的。
我常用的优化手段有三个:
第一个是降折数。把5折交叉验证改成3折,训练次数减少40%,评估稳定性损失在可接受范围内。GA是在搜索相对较优的参数,不需要精确到小数点后三位。
第二个是并行计算。在optimoptions里加'UseParallel', true,Matlab会自动并行评估种群中的个体。注意,用之前要先开并行池,parpool。而且TreeBagger训练本身是单线程的,并行化主要是在多个个体之间并行,所以核心数越多收益越明显。
第三个是减少适应度函数里的冗余计算。比如交叉验证的cvpartition对象,在每一轮适应度评估时都重新生成。对于固定的训练集,完全可以在主程序里先算好,然后用匿名函数传进适应度。这样做能省掉一部分重复计算。不过说实话,如果数据集不大,这点优化感知不明显,我更多是把它当作一个工程好习惯去遵守。
还有一个容易被忽略的点:如果你把GA的最大代数设得很大,但收敛曲线早就平缓了,纯属浪费时间。我建议每次调试时打开'PlotFcn', @gaplotbestf,盯一下收敛曲线,看到它变平就说明GA基本榨不出更多信息了,可以手动终止或者直接信任当前结果。
最后再分享一个实用习惯。GA跑完拿到最优参数后,第一时间把它保存到文本或者变量里,方便之后再次使用:
save('best_params.mat', 'bestParams', 'bestRMSE');下次想跳过GA直接训练,就从mat文件里读参数,省掉重复寻优的时间。这个习惯在论文复现时尤其重要,因为你不可能每次跑实验都先花几分钟去重新搜索参数。
这个项目整体做下来,我个人最大的体会是:GA-RF真正难的地方不在写代码,而在对“什么参数值得搜、搜到什么程度该停”有判断。遗传算法用起来不难,难的是别把搜索空间定得太大、不要把交叉验证折数弄得太高,也别指望它每次都给你一个不可思议的提升。只要把这几点稳住,这套流程就是可靠且高效的回归预测方案。
本文还有配套的精品资源,点击获取