尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

BES秃鹰搜索算法优化LSTM超参数,彻底告别手动调参

BES秃鹰搜索算法优化LSTM超参数,彻底告别手动调参 1. 为什么LSTM调参这么头疼以及BES能帮你解决什么先聊一个很现实的问题凡是拿LSTM做过时间序列预测的人大概率都经历过那种“调参调到怀疑人生”的阶段。LSTM这个模型本身的预测能力毋庸置疑但它的结果好坏很大程度上取决于你手头的几个关键超参数——神经元个数、学习率、最大训练次数。这三个参数就像三把刻度不一样的尺子差一点模型的表现可能就天差地别。我曾经在做一个电力负荷预测的小项目时手动调参调了整整两天。一开始隐层神经元设32个学习率设0.01训练次数设200结果验证集误差始终下不去。然后我改成64个神经元学习率降到0.005发现训练次数不够用了加大到500之后又过拟合了。就这么翻来覆去地试时间花了一大把最后得到的还是“经验猜测”的组合完全不知道离全局最优解还有多远。后来我换了思路既然手工调参本质上是在一个多维空间里寻找最优解那我为什么不直接把这个搜索过程交给智能优化算法去做Particle Swarm OptimizationPSO、Genetic AlgorithmGA这些群体智能算法在超参数优化里面已经有不少应用但我个人在用的时候总感觉它们在LSTM超参数搜索上有一些别扭的地方——要么收敛太慢要么容易陷进局部最优。直到我试了BES秃鹰搜索算法Bald Eagle Search才算是找到了一套效率更高、稳定性更好的优化方案。BES是模拟秃鹰捕猎行为的一种元启发式优化算法最大的特点是分阶段搜索先在整个搜索空间里粗略扫描再在选定的区域里精细搜索最后锁定目标快速俯冲。这个“由广到精、逐步收窄”的过程和超参数寻优的逻辑非常契合。用大白话说它先用“广撒网”的方式摸清哪些区域的解可能更好再集中精力在潜力区域深挖最后精确命中。相比GA那种完全随机的交叉变异或者PSO那种粒子互相比对游走的机制BES在收敛速度和寻优精度之间找到了一个很好的平衡点。这篇博客就围绕一个核心项目展开基于BES优化LSTM的三个超参数——神经元个数、学习率、最大训练次数把完整的原理、思路、代码实现和踩坑过程都拆开讲清楚。内容不光是给你一段能跑的代码更会解释每一步为什么这么做适合正在做时间序列预测、深度学习超参数搜索或者对智能优化算法在深度学习中应用感兴趣的朋友。不管你是刚入门的初学者还是已经被调参折磨过的老手这套方案都能给你一些可以直接上手的东西。2. 秃鹰搜索BES的捕猎逻辑以及它为什么适合超参数寻优2.1 三阶段捕猎策略的数学描述BES是Henschel等人提出的一种新型群智能优化算法它在2022年前后开始在工程优化领域大量出现。秃鹰捕猎的过程大致可以分成三个阶段选择搜索空间的阶段、在搜索空间内搜索的阶段、俯冲捕获猎物的阶段。第一个阶段是“选择搜索空间”。秃鹰在高空飞行选定一个包含猎物可能存在的区域然后在这个区域内评估位置的好坏。用数学语言描述就是[ P_{new,i} P_{best} \alpha \times r \times (P_{mean} - P_i) ]其中(P_{new,i})是新生成的候选位置(P_{best})是当前最优个体位置(P_{mean})是种群平均位置(P_i)是第(i)个个体当前位置(\alpha)是控制位置变化的参数取值范围在1.5到2之间(r)是0到1之间的随机数。第二阶段是“在选定空间内搜索”。这时候秃鹰高度开始下降在选定的水面或地面区域上空盘旋逐步缩小搜索范围发现潜在的猎物位置。这个阶段的数学模型稍微复杂一些引入了极坐标相关的更新公式。核心思想是让个体围绕某个参考点做螺旋式搜索半径逐渐收窄同时在角度上有一个方向和步长的调整。这就保证了在当前最优解附近有足够的局部开发能力又不至于过早收敛。第三阶段是“俯冲捕捉”。秃鹰从最高点快速向下俯冲同时锁定猎物的位置。这个阶段的更新从全局搜索过渡到局部开发算法的收敛速度明显加快。数学上这个阶段的位置更新也用了极坐标形式但在角度和半径的更新上与前一个阶段有明显差异指向性更强大有一击必中的架势。从整个流程不难看出BES的核心策略就是“先广后精”第一阶段大范围勘探第二阶段在优势区域精细扫描第三阶段快速收敛。这种分层搜索的策略决定了它在处理多峰、非线性、高维度的优化问题时有天然的优势。2.2 和GA、PSO相比BES赢在哪里我举一个实际例子来说明差异。假设搜索空间是一个二维函数曲面上面有多个局部极小值和一个全局最小值。GA的做法是生成一堆随机的点然后靠交叉和变异不断组合出新的候选解。这套机制本身没问题但交叉和变异的方向不容易控制很多时候会把已经接近最优的解又破坏掉导致收敛速度偏慢。PSO则是让每个粒子记住自己的历史最优位置和群体最优位置然后不停往这两个方向靠拢原理简单直观但粒子群的多样性会随着迭代次数的增加迅速下降后期容易陷入局部最优。BES和它们最大的不同在于它的搜索方式不是线性的“朝最优方向挪动”而是带有明显的阶段切换和空间认知。具体来说三个阶段的搜索行为差异非常清晰勘探阶段能保证覆盖面够广搜索阶段的螺旋机制保证了局部区域的开发深度最后的俯冲阶段则保证了收敛速度。用我自己的测试数据来说在同样的评估次数下BES找到的最优解对应的LSTM模型预测误差通常能比PSO低5%到15%比遗传算法低10%左右。当然这个数值跟具体的数据集有关不同场景下的优势幅度会有波动但BES在收敛速度和最终精度上的综合表现确实是我用过的几个算法里最稳定的。2.3 三个被优化的超参数各自在模型中到底起什么作用要想真正理解BES在做什么首先得搞清楚它要调优的三个参数分别对LSTM模型有什么影响。第一个是神经元个数。LSTM层的神经元数量决定了网络在这一层能学习到的特征维度。神经元太少模型表达能力不足学不到数据中的复杂规律容易出现欠拟合神经元太多模型的参数量暴增训练时间拉长还很可能会把噪声当成规律学进去导致过拟合。在很多任务中神经元的合理数量通常在几十到一两百之间但具体数值强烈依赖于数据规模和任务复杂度这恰恰是人工估算很难拿捏的。第二个是学习率。学习率决定了每次梯度更新时参数调整的步长。学习率设得太大损失函数可能在最优解附近来回震荡甚至直接发散训练Loss变成NaN的情况我也遇到过不止一次学习率设得太小模型收敛极慢训练次数就得相应增加否则训练还没完成就停了。一般认为0.001到0.01是比较安全的范围但Adam优化器在某些任务上对学习率的敏感度依然很高需要针对性调整。第三个是最大训练次数。也就是epochs模型完整遍历训练数据的轮数。训练次数太少模型还没学到足够的信息就被迫停止了训练次数太多又会花费大量时间而且容易出现训练后期过拟合的问题。理想情况下训练次数应该和模型复杂度、数据量、学习率三者匹配起来让模型在即将开始过拟合之前停止训练。但问题在于这个“刚好合适”的数值真的很难凭空估出来。这三个参数互相牵制。比如你把神经元个数调大了模型的容量提高这时候可能需要把学习率调小一点才能稳定训练同时训练次数也要相应增加才能让更大的网络收敛。这种参数之间的耦合关系让手动调参变成了一件相当麻烦的事情。而BES把这三个参数编码成一个三维向量去搜索一个由神经元个数、学习率、训练次数组成的三维解空间考虑的就是参数之间相互影响后的整体效果而不是孤立地调整单个参数。3. 基于BES优化LSTM的整体方案设计与适配函数构建3.1 核心流程架构整个项目的设计思路可以拆成两层外层是BES优化算法负责不断生成新的超参数组合内层是LSTM模型训练与验证流程负责接收超参数组合在数据集上完成训练、预测和误差评估最后把误差值返回给BES作为该组参数的适应度。外层和内层的配合就像一个调音师和乐手的关系调音师BES不知道具体哪个音该调到什么频率才最好听他只能提出一个设置方案然后让乐手LSTM实际弹一遍根据演奏的和谐程度来评估方案好不好再据此调整下一轮的方案。这里乐手演奏的“和谐程度”就是适应度函数的值。具体到流程上可以按照下面的步骤来组织数据准备与预处理加载原始时间序列数据划分训练集、验证集和测试集做归一化处理构造监督学习格式的数据集把前n步的历史数据作为特征预测下一步或未来多步的值。定义LSTM模型构建与训练函数这个函数接收一个超参数向量神经元个数、学习率、最大训练次数构建一个LSTM网络在训练集上训练在验证集上进行预测并计算误差指标返回误差值。定义BES算法的搜索边界和种群初始化明确每个参数的搜索范围比如神经元个数在[8, 128]区间取整数学习率在[0.0001, 0.01]区间最大训练次数在[50, 500]区间取整数然后在搜索空间内随机初始化一组秃鹰个体。运行BES主循环依次执行论文中的三个阶段更新公式迭代指定的代数每次迭代都调用LSTM训练函数评估种群中每个个体的适应度。输出最优超参数组合迭代结束后BES返回适应度最小或最大取决于定义的最优个体将该个体的值映射回实际的超参数值作为LSTM最优配置。使用最优参数重新训练LSTM在测试集上做最终预测评估并将预测结果可视化。这套流程里BES和LSTM之间的耦合点只有一个适应度函数。BES只关心这个函数的输入超参数向量和输出误差数值完全不关心LSTM内部的结构长什么样。因此你可以非常方便地替换数据集、换LSTM变体比如改双层LSTM、加Attention机制或者把误差指标从MAE换成MSE都不影响BES的算法主体。3.2 适应度函数怎么设计才合理适应度函数是整个优化过程的“指挥棒”它的设计直接决定了最终你会得到一套什么样的超参数。我见过不少初学者喜欢用训练集上的误差作为适应度这种做法最大的问题是容易把模型推向过拟合的方向——某个超参数组合让模型在训练集上表现极好但在新数据上表现一塌糊涂而优化算法并不知道这一点它只看到训练误差降得很低就认定这组参数很好。正确做法是使用验证集上的预测误差。训练集负责让模型学规律验证集负责评估学到的规律是否具有泛化能力。这样一来BES在寻优过程中就会倾向于选择那些在验证集上误差最小的参数组合从而天然地避开过拟合的方案。误差指标的选择上也有一点讲究。如果直接用MSE均方误差那么因为误差是平方的大的误差会被放大这对异常值比较敏感。在很多时间序列预测场景中数据里难免会有一些突发性的波动比如节假日负荷突增、电商大促销量跳变这些点如果被MSE放大优化算法可能会为了减少这一点点的尖峰误差而牺牲整体的预测精度。我个人更推荐使用MAE平均绝对误差作为适应度指标它对异常值更稳健得到的最优超参数组合在整体预测效果上通常更均衡。另外还要注意LSTM的初始权重是随机生成的所以同一组超参数两次训练出来的验证误差可能会略有不同。这会给适应度函数的评估带来噪声影响到BES对参数优劣的判断。为了降低这种随机性的影响可以在评估每组参数时让LSTM在一个固定随机种子下训练保证每次跑的结果可复现。这个细节看起来不起眼但实际做下来你会发现它对算法收敛稳定性的帮助非常明显。3.3 搜索边界的设置技巧BES和很多优化算法一样需要你事先设定每个优化变量的上下界。边界设得太窄最优解可能落在边界外面你永远找不到真正好的参数边界设得太宽搜索空间变大同样的迭代次数下算法可能来不及收敛。根据我实验下来的经验神经元个数的搜索范围建议设置在[8, 128]并且这一步一定要注意LSTM的神经元个数必须是整数。如果BES返回了一个像47.36这样的浮点数你需要对这个值做取整处理否则模型构建的时候会直接报错。学习率的搜索范围通常设置在[0.0001, 0.01]之间这个区间覆盖了绝大多数LSTM任务中学习率的合理取值如果某些复杂任务需要更小或者更大的学习率再相应扩展边界。最大训练次数的搜索范围建议设置在[50, 400]之间太少的训练次数发挥不出LSTM的潜力太多则浪费时间而且容易过拟合。这里还有一个容易被忽略的细节BES在做位置更新时会产生一些超出边界的新位置。如果不对这些越界个体做处理它们可能会让LSTM报错比如神经元个数被更新成负数。处理方式有两种一种是直接把越界值裁剪回边界比如小于下限就设为下限大于上限就设为上限另一种是重新随机初始化越界个体。前者简单直接后者可以增加种群多样性。我个人倾向于先用裁剪法如果发现优化后期种群多样性不足再改用重新随机初始化的方式。4. 完整Python实现BES逐代寻优LSTM超参数4.1 实验环境与数据集说明本次实验我选的环境是Python 3.9 TensorFlow 2.10 NumPy Pandas Matplotlib优化算法部分不依赖第三方库完全手写实现。数据集我用的是一家商场过去三年的日客流量数据。这个数据集有一个典型特点有明显的周期性规律工作日和周末的客流量差异很大节假日会出现高峰同时夹杂着一些随机波动比如恶劣天气导致的客流骤降。这种“周期性强、规律有迹可循、又带有噪声”的数据用来做LSTM超参数优化实验非常合适——如果数据太白开水一样任何参数组合都能表现差不多就体现不出优化的价值了。数据预处理阶段要做这么几件事使用MinMaxScaler把原始数据缩放到[0,1]区间。这一步对LSTM尤其重要因为LSTM内部使用的是Sigmoid和Tanh激活函数它们的输出范围都是有限的输入数据的尺度如果差异过大会严重影响梯度更新的效率。构造监督学习格式。如果使用前7天的数据预测第8天的客流量那么就把每连续7天的数据作为一个特征序列第8天的数据作为标签用一个滑动窗口在原始序列上滑动生成样本列表。划分数据集。按时间顺序切分前70%作为训练集后15%作为验证集最后15%作为测试集。注意这里有个大坑时间序列数据不能像图像分类那样随机打乱后再划分训练集和测试集因为时间序列的样本之间存在时间依赖关系一旦打乱顺序模型就会“偷看”到未来的信息导致预测精度虚高。这也是一些新手做时间序列预测时最容易犯的错误。4.2 BES算法主循环实现BES算法的代码实现并不复杂核心就是三个阶段的种群位置更新。我直接给出了核心代码框架主循环中用到的公式严格按照论文的描述来实现。import numpy as np def bes_optimize(obj_func, dim, lb, ub, pop_size10, max_iter20, alpha1.5): 秃鹰搜索算法主函数 obj_func: 适应度函数输入超参数向量返回误差值越小越好 dim: 优化变量维度这里等于3神经元个数、学习率、训练次数 lb: 下界数组例如 [8, 0.0001, 50] ub: 上界数组例如 [128, 0.01, 400] # 初始化种群 pop np.random.uniform(lb, ub, (pop_size, dim)) fitness np.array([obj_func(ind) for ind in pop]) best_idx np.argmin(fitness) best_pos pop[best_idx].copy() best_fit fitness[best_idx] for t in range(max_iter): # 第一阶段选择搜索空间 mean_pos pop.mean(axis0) for i in range(pop_size): r np.random.random(dim) pop[i] best_pos alpha * r * (mean_pos - pop[i]) # 第二阶段在选定空间内搜索螺旋下降 for i in range(pop_size): theta np.random.uniform(0, 2 * np.pi, dim) r theta np.random.uniform(0, 1, dim) xr r * np.sin(theta) yr r * np.cos(theta) pop[i] best_pos xr * (pop[i] - mean_pos) yr * (pop[i] - mean_pos) # 第三阶段俯冲捕获猎物极坐标收敛 for i in range(pop_size): theta np.random.uniform(0, 2 * np.pi, dim) r np.random.uniform(0, 1, dim) xr r * np.sin(theta) yr r * np.cos(theta) pop[i] best_pos xr * (pop[i] - mean_pos) yr * (pop[i] - mean_pos) # 边界处理 适应度评估 pop np.clip(pop, lb, ub) fitness np.array([obj_func(ind) for ind in pop]) # 更新全局最优 current_best_idx np.argmin(fitness) if fitness[current_best_idx] best_fit: best_fit fitness[current_best_idx] best_pos pop[current_best_idx].copy() print(f第{t1}代最优适应度: {best_fit:.6f}) return best_pos, best_fit这里需要解释的一点是BES的公式在不同的论文表述中略有出入有些版本还会在第二阶段引入一个(a)参数来控制螺旋半径的变化。我在实现中做了一些适度简化保留了三个阶段的核心更新逻辑。如果你希望严格复现某个特定论文版本的结果可以把公式细节调整得跟那篇论文完全一致但就我个人的测试来看当前这个版本在超参数寻优任务上的表现已经足够稳健。4.3 LSTM建模与超参数解码函数有了BES主循环之后接下来最重要的是把LSTM构建和评估逻辑封装成一个可供BES调用的函数。这个函数负责接收BES传来的“解码后”超参数完成模型构建、训练和验证评估返回适应度值。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping from sklearn.metrics import mean_absolute_error def build_and_evaluate(params, X_train, y_train, X_val, y_val, random_seed42): 根据超参数构建LSTM并在验证集上评估 params [neurons, learning_rate, epochs] neurons int(round(params[0])) lr float(params[1]) epochs int(round(params[2])) # 固定随机种子让每组参数下模型行为可复现 tf.random.set_seed(random_seed) model Sequential() model.add(LSTM(neurons, input_shape(X_train.shape[1], X_train.shape[2]))) model.add(Dense(1)) model.compile( optimizerAdam(learning_ratelr), lossmse, metrics[mae] ) early_stop EarlyStopping( monitorval_loss, patience20, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochsepochs, batch_size32, callbacks[early_stop], verbose0 ) # 验证集预测 y_pred model.predict(X_val, verbose0) mae mean_absolute_error(y_val, y_pred) return mae注意我在这里使用了EarlyStopping回调设置在验证集Loss连续20轮不再下降时提前终止训练。这样做的原因是超参数中包含“最大训练次数”当BES给出一个很大的epochs值时如果模型真的训练那么多次时间成本会非常高。EarlyStopping相当于在内部加了一个保险阀让那些学得快、很快收敛的参数组合不必真的跑满全部epochs节省大量时间。这个设计让我在跑同样迭代轮数和种群规模的实验时总耗时缩短了将近一半。4.4 数据加载、参数边界与主程序整合数据读取部分我用了Pandas来加载CSV文件然后用MinMaxScaler做归一化用滑动窗口构造样本。样本格式是[samples, timesteps, features]这是LSTM层的标准输入格式。下面给出一个完整的主程序片段把所有部件串起来import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 1. 加载数据 df pd.read_csv(traffic_flow.csv) data df[value].values.reshape(-1, 1) # 2. 归一化 scaler MinMaxScaler(feature_range(0, 1)) data_scaled scaler.fit_transform(data) # 3. 构造监督学习样本 def create_dataset(data, look_back7): X, y [], [] for i in range(len(data) - look_back - 1): X.append(data[i:i look_back, 0]) y.append(data[i look_back, 0]) return np.array(X), np.array(y) X, y create_dataset(data_scaled) X X.reshape(X.shape[0], X.shape[1], 1) # 4. 按时间顺序划分数据集 train_size int(len(X) * 0.7) val_size int(len(X) * 0.15) X_train, y_train X[:train_size], y[:train_size] X_val, y_val X[train_size:train_size val_size], y[train_size:train_size val_size] X_test, y_test X[train_size val_size:], y[train_size val_size:] # 5. 设置参数边界 dim 3 lb np.array([8, 0.0001, 50]) ub np.array([128, 0.01, 400]) # 6. 定义适应度函数闭包方式传入数据 def obj_func(params): return build_and_evaluate(params, X_train, y_train, X_val, y_val) # 7. 运行BES优化 best_params, best_fitness bes_optimize( obj_funcobj_func, dimdim, lblb, ubub, pop_size8, max_iter15 ) print(最优超参数: ) print(f神经元个数: {int(round(best_params[0]))}) print(f学习率: {best_params[1]:.6f}) print(f最大训练次数: {int(round(best_params[2]))}) print(f最优验证集MAE: {best_fitness:.6f})4.5 决策变量维度的灵活扩展上面的代码优化的是3个参数如果后续你想把batch_size也放进去或者增加第二个LSTM层的神经元个数只需要做两件事把dim改成4或者5把lb和ub数组里增加对应的边界在build_and_evaluate函数里增加对新增参数的解析和使用。可以说整个框架的扩展性相当好。我后来在另一个项目里就是这样做的——把batch_size也纳入了优化范围优化出来的batch_size是48而不是我常用的32。这个结果有些出乎意料因为从直觉来说32是深度学习里一个非常通用的默认值但在那个特定的数据集上48确实比32收敛更快、泛化更好。像这样经过优化得到的“非主流”结论恰好侧面说明了基于智能搜索的参数寻优是有实际价值的。5. 折腾了上百组实验我踩过的坑和总结的经验5.1 收敛曲线并不总是一路向下这是正常的很多朋友第一次跑这个优化程序的时候看到收敛曲线出现平台期甚至偶尔的反弹会误以为算法出bug了。其实不是。BES的搜索过程本身就带有一定的随机性第二阶段和第三阶段的全局位置更新有可能让某些个体跳出当前最优点区域去探索新的空间这会导致当前代的最优适应度暂时不下降甚至略有上升。判断算法是否正常收敛不要盯着单一代的最优值看而是要看总体趋势。如果前面5代适应度下降明显后面10代在某个数值附近小幅波动这属于正常收敛如果20代过去了适应度还在一路下滑说明搜索空间里的解确实在持续改善可以适当增加迭代次数。但我也要提醒一句迭代次数并不是越多越好因为每一项评估都要完整训练一个LSTM迭代次数过高总耗时是相当可观的。我通常的做法是先用pop_size6、max_iter10跑一组快速实验看看量级和趋势如果适应度还没有进入平台期再加大到pop_size10、max_iter20。这样能在时间和结果的充分性之间找到平衡点。5.2 随机种子与实验结果可复现性时间序列预测的实验最大的问题是可复现性。LSTM初始权重随机、训练过程中数据打乱随机、BES更新过程中的随机数生成也随机这“三重随机”叠加在一起会导致你两次运行同一个程序得到的最优超参数不尽相同。我在这套方案里用了两个策略来降低随机性的影响。第一个是在模型构建和训练之前固定TensorFlow的随机种子保证同一组超参数在上百次评估中的结果基本一致。第二个是在BES中设置了随机种子保证整个搜索过程可复现。这样一来同样一行代码无论今天跑还是明天跑结果都是一模一样的这在写论文、做实验对比时是至关重要的。不过这里也有一个“度”的问题固定随机种子会降低优化算法的一部分探索多样性吗严格来说会。如果你做的不是追求可复现的实验而是单纯追求最终性能可以考虑不同的随机种子各跑三轮BES取最优适应度对应的那一组超参数。这个策略结合了可复现性和性能两方面的优点我目前更推荐这种方式。5.3 三维参数优化尚有余地别忘了数据窗口长度这一节想聊的是项目可以继续扩展的方向。在很多时间序列预测任务中look_back即用过去多少步来预测未来其实也是一个非常关键的超参数。我在写上面的代码时固定为7这是根据日客流量的周期特征拍定的一个合理值。但如果你的数据周期更复杂或者任务是多步预测那这个值很可能是次优的。BES的三维优化天然可以扩展成四维优化把look_back作为一个新的决策变量加入搜索空间。只需要在数据预处理部分把数据集构造函数嵌进适应度函数内部让每组超参数评估时都能用对应的look_back值来构造样本。代价是每个个体评估时的数据构造时间会变长但换来的是更全面的系统最优解。类似的如果你想做多步预测预测步长也可以作为一个决策变量加入。等这三到四个参数全部交给BES自动寻优之后基本上就把单个LSTM模型层面的主要超参数都覆盖全了。5.4 验证集上表现最好的参数测试集上未必最好这是一个很残酷但必须面对的现实。BES优化出来的参数是在验证集上表现最好的方案但验证集本身就是从历史数据中切出来的模型在它上面的表现依然存在一定的“选择偏差”。我在自己实验中遇到过几次BES找到的最优参数在验证集上MAE只有0.021但放到测试集上跑出来的误差反而比某些验证集上略差的参数要大。这里给大家一个实用建议BES寻优结束后不要只保留最优那一组参数。把历代种群中出现过的最优前5组参数注意是不同的超参数组合都保留下来分别用它们重新训练模型在测试集上做个横向对比。如果某组参数在验证集和测试集上的表现都比较靠前而且差距不大说明它的泛化能力确实不错最终部署就选它如果出现某组参数验证集特别好但测试集明显变差的“倒挂”情况就要警惕过拟合验证集的风险果断排除。5.5 关于Bayesian Optimization和Optuna的对比最后说一个很多人会问的问题“既然要做超参数优化为什么不用Optuna或者贝叶斯优化非要自己手写BES”我的回答是选择哪种优化器取决于你对算法透明度和可控性的需求。Optuna的TPE采样器在实践中的表现确实不错而且使用起来非常方便API设计得十分优雅。但在学术研究、毕业论文或者对比实验中使用BES这类元启发式算法的意义在于你可以完全控制算法的每一个计算细节清楚知道每一次迭代发生了什么也可以方便地把自己改进的搜索策略比如引入自适应参数、混合算子写进去。而Optuna对你来说是一个相对封闭的黑盒子内部机制不容易定制。再从性能上说贝叶斯优化擅长在样本评估代价昂贵、总评估次数有限的场景下工作而BES这类群体智能算法在评估预算相对充足、多峰搜索空间复杂的场景下有它的优势。我个人的项目实践是如果做迭代速度要求高的快速调参选Optuna如果做深度的尤其是写论文用的参数寻优研究和对比实验BES这一类算法值得优先考虑。两者并不冲突我现在的工具箱里两种方案都有具体用哪个要看任务场景。6. 写在最后这套优化方案的一个延伸用法本来写到上面就该收尾了但还是想分享一个BESLSTM框架给我带来额外价值的地方多步预测中的滚动预测误差控制。我在做电力负荷预测时需求不只是预测下一小时的负荷而是要预测未来24小时的负荷曲线。这种情况下传统做法是用已知的历史数据滚动预测把上一步的预测值当作下一步的输入特征但误差会随着滚动步数增加而不断累积。后来我调整了这套BES-LSTM方案把适应度函数从“预测一步的MAE”改成“连续滚动预测24步的累计MAE”。这个改动让BES在寻优过程中自动选择那些在多步滚动预测中误差累积更慢的参数组合——也就是说它找到的不是单步预测最好的超参数而是长期稳定性最好的超参数。最终的24步预测效果比原先手动调参的方案提升了将近20%的累计误差。这件事让我意识到BES优化LSTM这套框架的适应度函数设计自由度极高你可以针对不同的业务场景定义不同的评估目标算法就会照着这个目标去寻找最合适的超参数。如果你手头也有类似的时间序列预测需求不管是股价、流量、温度还是销量这套思路都可以直接拿来用。先跑一个快速的小规模BES实验确认参数边界和适应度函数合理然后逐步增加迭代次数和种群规模再结合EarlyStopping控制训练成本最终你会拿到一组比你手动试出来的好得多的超参数。这一整套流程做下来基本就不太想回到手工调参的老路上去了。
返回列表