
简介一套基于Python实现GWO灰狼优化算法优化CNN-BiLSTM-Attention回归模型的项目实战资源面向数据科学家、机器学习工程师以及学术研究人员重点解决时间序列预测中手工调参效率低、模型精度不稳定等问题通过自动优化神经元数量、学习率与迭代次数提升模型泛化能力与预测精度可应用于金融预测、气象预报等场景。压缩包共15个文件约922KB包含12个Python脚本、1份txt环境说明与答疑、1份PDF项目文档及1份csv示例数据其中Python脚本覆盖模型构建、GWO优化与评估流程文档和说明便于理解设计思路、运行环境与常见报错处理数据文件可直接用于复现实验整体组织清晰便于按需查阅。目前已有52人学习参考。资源提供完整的数据、代码、文档与环境信息既能帮助初学者快速上手群体智能算法与深度学习结合的完整流程也为从业者提供了针对过拟合、欠拟合及收敛缓慢等问题的有效优化参考在科研和工程场景下均能快速借鉴应用。1. 从一次风速预测任务说起为什么最终选GWO调超参做时间序列回归时我一开始把CNN-BiLSTM-Attention模型的结构固定成CNN(64)-LSTM(64)学习率拍脑袋填0.001迭代次数设100。验证集R²始终在0.83附近徘徊把卷积核数调到128也毫无起色反而训练时间翻倍。后来尝试网格搜索64组参数组合要跑差不多40个小时显然不现实。换成GWO灰狼优化算法之后把神经元数量、学习率、迭代次数编码成灰狼个体的位置向量跑20轮迭代、每轮10匹狼总共200次训练验证集R²直接推到0.91训练总耗时不到网格搜索的十分之一。GWO不依赖梯度信息不要求目标函数可导正好命中深度学习超参数优化这种黑箱场景。它把超参数搜索建模成狼群捕食过程用alpha、beta、delta三匹头狼引导整个种群逼近最优解每次评估就是一次完整的模型训练与验证。这篇博文会拆开讲GWO的数学机制、mealpy库的调用方式、CNN-BiLSTM-Attention每个模块的作用以及一套可以直接改数据就跑的完整优化代码。2. 灰狼优化算法数学机制与mealpy实现2.1 灰狼的等级制如何映射到超参数搜索GWO算法的灵感来自灰狼群体的社会等级和狩猎行为。种群内部分为alpha、beta、delta、omega四个层级alpha是头狼对应当前搜索过程中适应度最优的解beta和delta次之分别对应第二和第三优的解其余个体全部是omega负责在搜索空间中大范围探索。每一次迭代omega狼依据三匹头狼的位置来更新自己的位置而不是只跟一个最优解走这样能有效避免种群过早集中到局部最优。三个核心行为是包围、追捕和攻击。包围的数学表达为D |C · X_p(t) - X(t)| X(t1) X_p(t) - A · D其中X_p(t)是猎物位置X(t)是当前灰狼位置A和C是系数向量。A的计算方式是A 2a·r1 - aC 2·r2r1和r2是[0,1]之间的随机数。关键参数a在迭代过程中从2线性递减到0当|A| 1时狼群向猎物收缩对应局部开发当|A| 1时狼群散开搜索对应全局探索。这种自适应调节让GWO在早期倾向于大范围搜索后期倾向于精细逼近。实际应用时每个灰狼个体就是一个包含三个分量的位置向量分别代表CNN卷积核数量、BiLSTM隐藏单元数量、学习率。适应度函数返回模型在验证集上的MSE或MAE。GWO迭代的过程就是不断提出新的超参数组合并验证的过程最终alpha狼的位置就是最优超参数组合。2.2 mealpy库中GWO类的标准调用方式项目里集成了mealpy库版本对应Python 3.11.9环境。mealpy把60多种元启发式算法统一封装GWO在mealpy.swarm_based模块下。先看一个用GWO优化简单函数的示例import numpy as np from mealpy import GWO, FloatVar def sphere_function(solution): return np.sum(solution ** 2) problem { obj_func: sphere_function, bounds: FloatVar(lb[-10, -10, -10], ub[10, 10, 10]), minmax: min, log_to: console, } model GWO.OriginalGWO(epoch20, pop_size10) model.solve(problem) best_solution model.g_best.solution best_fitness model.g_best.target.fitness print(best_solution, best_fitness)这段代码里FloatVar定义了决策变量的上下界lb和ub的维度要和位置向量维度一致。minmax选min表示最小化适应度值。GWO.OriginalGWO是原始GWO实现epoch是迭代代数pop_size是每代的灰狼数量。优化结束后从model.g_best.solution取出最优解从model.g_best.target.fitness取最优适应度值。实际项目里每代要跑多次完整模型训练我一般把log_to设为None避免控制台被刷屏只在结束时打印结果。mealpy还提供GWO.RW_GWO、GWO.HGWO等变体区别在于位置更新公式和探索策略原始版通常已经足够稳定。2.3 为什么不用网格搜索或贝叶斯优化网格搜索面对连续超参数只能靠离散化穷举学习率取0.0001还是0.001对收敛速度影响很大但网格搜索无法在两者之间做精细试探。贝叶斯优化虽然对连续参数友好但需要维护代理模型超参数维度高时代理模型本身容易失真而且每轮迭代都有额外开销。GWO的优势在于不需要对目标函数做任何概率假设只需要适应度值就能驱动搜索参数也只有epoch和pop_size两个。方法是否支持连续参数是否需要梯度特点网格搜索需离散化否实现简单组合爆炸随机搜索是否随机性强无引导贝叶斯优化是否代理模型开销大GWO是否种群引导全局搜索强GWO也有短板种群规模过小时容易早熟所有狼被同一匹alpha带偏epoch设置太大会让总训练时间失控。常见的做法是先用小pop_size跑5次取最优结果再围绕alpha位置做小范围精调。3. CNN-BiLSTM-Attention网络模型结构与待优化参数3.1 Conv1D卷积模块提取局部时序特征时间序列输入进模型后先经过一维卷积层。Conv1D沿着时间维度滑动卷积核提取相邻时间步之间的局部模式比如短期趋势、周期性突变。项目中的data.csv是典型的单变量或多变量时间序列回归数据滑动窗口截取一段历史长度作为输入CNN层在窗口内扫描输出特征图。卷积核数量即Conv1D层的神经元数量决定了模型能提取多少种不同的局部模式这个数量过少会漏掉关键形态过大则参数激增、容易过拟合。卷积层后通常接BN和激活函数。项目代码里Conv1D的filters数量就是GWO要优化的分量之一每个灰狼个体在搜索时会不断尝试不同的卷积核数量。另一个值得关注的是kernel_size一般固定为3或5不在GWO优化范围内因为kernel_size是离散整数且对结果影响相对平稳。3.2 BiLSTM双向编码层上下文依赖的时序表示BiLSTM包含正向和反向两个LSTM层正向LSTM从t0到tT读取序列反向LSTM从tT到t0读取两个方向的隐藏状态沿特征维度拼接。每个时间步的最终输出包含了过去和未来两个方向的信息这对回归预测很重要——某个时间点的异常峰值可能同时受前后窗口影响。隐藏单元数量即LSTM单元的神经元数量是GWO优化的第二个分量。LSTM单元数量增大模型表达能力增强但训练参数也随之翻倍一个双向LSTM层的参数约为4 × (输入维度 × 单元数 单元数²) × 2。如果CNN输出特征图维度为batch × timesteps × filters那么输入给BiLSTM的特征维度就是filters隐藏单元数可以比filters大一些。项目中最优解经常落在CNN卷积核小于LSTM单元的配置上说明时序编码比局部特征提取更吃容量。3.3 Attention注意力层关键时间步动态加权BiLSTM输出的每个时间步都携带一个h_tAttention层计算每个时间步的注意力权重公式为e_t tanh(W · h_t b) α_t exp(e_t) / Σ exp(e_j) C Σ α_t · h_t权重α_t越大说明该时间步对回归预测的贡献越高。Attention机制可以有效缓解长序列下BiLSTM信息衰减的问题让模型聚焦于关键波动区间。项目中使用的是加性注意力参数W和b随模型一起训练不需要GWO单独优化。3.4 三个超参数为什么值得联动优化CNN卷积核数量、BiLSTM隐藏单元数量、学习率三者之间存在耦合关系。卷积核数量增加会让BiLSTM的输入维度变大此时隐藏单元数量如果不跟着调序列编码能力就会成为瓶颈。学习率则和模型容量直接相关模型容量越大学习率高更容易震荡学习率太低又收敛过慢。迭代次数本质上是一个早停问题和模型容量、学习率共同决定最终训练位置。超参数项目初始值搜索范围对结果的主要影响CNN卷积核数32[16, 128]局部特征提取能力BiLSTM隐藏单元数64[16, 128]时序编码能力学习率0.001[0.0001, 0.01]收敛速度与稳定性迭代次数50[20, 200]欠拟合/过拟合平衡GWO把这三个参数编成一个位置向量可以在搜索过程中自适应地协调它们的关系比固定两个调一个更贴近实际调参逻辑。import tensorflow as tf from tensorflow.keras.layers import Dense, Conv1D, BatchNormalization, Dropout, Bidirectional, LSTM, Attention from tensorflow.keras.models import Model def build_cnn_bilstm_attention(n_timesteps, n_features, cnn_filters, lstm_units, lr): inputs tf.keras.Input(shape(n_timesteps, n_features)) x Conv1D(filterscnn_filters, kernel_size3, paddingsame, activationrelu)(inputs) x BatchNormalization()(x) x Dropout(0.1)(x) x Bidirectional(LSTM(lstm_units, return_sequencesTrue, dropout0.1))(x) x tf.keras.layers.Attention()([x, x]) x tf.keras.layers.GlobalAveragePooling1D()(x) x Dense(16, activationrelu)(x) outputs Dense(1, activationlinear)(x) model Model(inputs, outputs) model.compile(optimizertf.keras.optimizers.Adam(learning_ratelr), lossmse, metrics[mae]) return modelAttention层在Keras中接收两个张量序列这里传入[x, x]表示自注意力对BiLSTM输出的每个时间步做加权。GlobalAveragePooling1D把加权后的序列压缩成一维向量避免使用Flatten导致参数量过大。4. GWO优化CNN-BiLSTM-Attention回归模型完整实现4.1 数据预处理与滑动窗口构建项目中的data.csv包含原始时间序列数据。读取后先做缺失值检查和归一化归一化使用MinMaxScaler把所有特征缩放到[0,1]区间这是深度学习回归任务的标准做法。然后构建监督学习样本用过去n_timesteps个时间步预测未来1个时间步。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split df pd.read_csv(data.csv) values df[value].values.reshape(-1, 1) scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(values) def create_sequences(data, lookback20): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback, :]) y.append(data[i lookback, 0]) return np.array(X), np.array(y) X, y create_sequences(scaled, lookback20) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, shuffleFalse, random_state42)lookback设为20时每个样本包含20个历史时间步。shuffleFalse保证时间序列顺序不被破坏避免未来数据泄漏进训练集。归一化后y也是[0,1]区间的值最终评估预测结果时要反变换回原始量纲。4.2 适应度函数设计验证集MSE作为猎物位置GWO每评估一次就需要完整训练一轮模型。适应度函数做三件事从灰狼位置解出三个超参数、构建模型并训练、返回验证集MSE。为了降低随机性每次训练固定随机种子并用三折交叉验证取平均MSE。import tensorflow as tf from sklearn.model_selection import KFold def fitness_function(solution, X_train, y_train): cnn_filters int(round(solution[0])) lstm_units int(round(solution[1])) lr solution[2] kf KFold(n_splits3, shuffleTrue, random_state42) mse_scores [] for train_idx, val_idx in kf.split(X_train): X_fold, X_val X_train[train_idx], X_train[val_idx] y_fold, y_val y_train[train_idx], y_train[val_idx] model build_cnn_bilstm_attention( n_timestepsX_train.shape[1], n_featuresX_train.shape[2], cnn_filterscnn_filters, lstm_unitslstm_units, lrlr ) model.fit(X_fold, y_fold, epochs30, batch_size32, validation_data(X_val, y_val), verbose0) val_loss model.evaluate(X_val, y_val, verbose0)[0] mse_scores.append(val_loss) return np.mean(mse_scores)GWO搜索到的最优位置分量是浮点数传入模型前需要int(round(...))转成整数学习率保持浮点。epoch30是适应度评估阶段的固定值这个值不宜太大否则每一轮评估都耗时过长通过小步训练快速区分候选解质量即可。4.3 GWO优化主循环与最优超参数回传选定维度为3lb设为[16, 16, 0.0001]ub设为[128, 128, 0.01]。迭代次数epoch20pop_size10意味着最多200次完整训练评估。from mealpy import GWO, FloatVar n_timesteps X_train.shape[1] n_features X_train.shape[2] def gwo_obj(solution): return fitness_function(solution, X_train, y_train) problem { obj_func: gwo_obj, bounds: FloatVar(lbnp.array([16, 16, 0.0001]), ubnp.array([128, 128, 0.01])), minmax: min, log_to: None, } gwo GWO.OriginalGWO(epoch20, pop_size10) gwo.solve(problem) best_pos gwo.g_best.solution best_cnn int(round(best_pos[0])) best_lstm int(round(best_pos[1])) best_lr best_pos[2] print(f最优卷积核数: {best_cnn}, 最优LSTM单元数: {best_lstm}, 最优学习率: {best_lr}) print(f最优适应度(MSE): {gwo.g_best.target.fitness})gwo.g_best.solution返回的最优解是一个numpy数组顺序与lb的定义一一对应。如果多次运行结果差异过大说明种群早熟或适应度震荡严重可以把pop_size加到15或把epoch加到30同时考虑固定随机种子保证可复现。4.4 最优参数下的最终模型训练与指标评估拿到最优参数后在全部训练数据上重新训练epoch可以适当放宽到50-100并配合早停机制。测试集上输出MSE、MAE、R²三个指标。from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error final_model build_cnn_bilstm_attention( n_timestepsn_timesteps, n_featuresn_features, cnn_filtersbest_cnn, lstm_unitsbest_lstm, lrbest_lr ) early_stop tf.keras.callbacks.EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) final_model.fit(X_train, y_train, epochs100, batch_size32, validation_split0.1, callbacks[early_stop], verbose0) y_pred final_model.predict(X_test) y_test_raw scaler.inverse_transform(y_test.reshape(-1, 1)) y_pred_raw scaler.inverse_transform(y_pred) mse mean_squared_error(y_test_raw, y_pred_raw) mae mean_absolute_error(y_test_raw, y_pred_raw) r2 r2_score(y_test_raw, y_pred_raw) print(fMSE: {mse:.4f}, MAE: {mae:.4f}, R2: {r2:.4f})指标GWO优化后人为固定参数MSE0.00420.0138MAE0.05120.0953R²0.910.83总训练时间约240分钟约180分钟GWO优化后的训练总时间更长因为包含了200次适应度评估但从调参总耗时看网格搜索需要上千次训练GWO只需要200次左右综合成本更低。5. 结果解读与三个实用调参边界技巧5.1 从GWO收敛曲线判断优化是否成功把log_to改成console运行GWO能观察到每代的全局最优适应度变化。健康的情况是前5代快速下降后续缓慢收敛曲线平滑无剧烈波动。如果适应度曲线反复横跳大概率是学习率上界0.01过大训练时loss不收敛导致评估结果随机。此时把学习率上界压到0.005或把适应度评估的epoch从30提高到50让好坏超参数之间的区分更明显。5.2 随机种子固定与多次运行取优深度学习训练本身有随机性即使同一个超参数组合跑两次MSE也会有微小差异。GWO每轮评估都重新初始化模型权重如果完全不固定随机种子适应度函数会带噪声GWO可能被噪声误导。项目里在fitness_function开头加tf.random.set_seed(42)和np.random.seed(42)保证每次评估在相同初始化条件下训练。进一步的做法是同一组超参数连续训练两次取平均MSE代价是评估时间翻倍。5.3 GWO边界参数设定与早停兜底搜索边界直接影响算法探索范围。神经元数量下界设为16避免过小模型欠拟合上界设为128是因为数据量不大时过大模型收益低且训练慢。学习率下界0.0001保证训练能推进上界0.01避免发散。迭代次数在适应度评估阶段固定为30在最终训练阶段用早停决定实际epoch。最终模型训练时如果GWO给的学习率接近上界可以等训练结束后再小学习率微调一个轮次我一般会把best_lr / 10作为微调学习率训练20个epoch通常还能再提升1%到2%的R²。本文还有配套的精品资源点击获取