
调参这件事做深度学习的人多少都经历过玄学时刻学习率设大了发散设小了半天不收敛卷积核数量加的太多训练时间翻倍加少了拟合不够。尤其当你面对的是数据预测任务需要在一堆连续型和离散型超参数之间找到一个最优组合时手动试错基本等于大海捞针。我这边有一个比较成熟的解决思路用粒子群优化PSO算法去自动搜索卷积神经网络CNN的超参数组合把人工试错换成群体寻优。这条技术路线在时间序列数据预测场景比如电力负荷、温度、流量、股价序列等里非常实用也是目前学术和工业落地中经常采用的混合优化方案。这篇文章会把项目从思路拆解、算法原理到代码实现和排坑经验完整记录下来适合正在做深度学习预测、对自动调参感兴趣的读者特别是那些已经试过手动调参调到崩溃想换个更系统化方案的朋友。我会把PSO和CNN分开讲透再讲怎么把它们拼在一起最后给出可以直接参考的代码骨架和一堆实测踩坑记录。1. 项目整体设计与思路拆解1.1 为什么非要引入PSO来优化CNN超参数CNN不是没有超参数而是超参数太多了。以一个常见的一维CNN预测模型为例光关键超参数就包括卷积层数、每层卷积核数量滤波器的数量、卷积核大小、池化方式、池化窗口大小、全连接层神经元数、Dropout率、学习率、Batch size、训练轮数。这些参数之间还有耦合关系比如卷积核数量增大了Dropout率可能也要跟着调整学习率变了训练轮数可能要变。传统做法无非三种手动调参、网格搜索、随机搜索。手动调参依赖经验碰到新数据集时往往要来回试几十次网格搜索在参数维度少时还能用维度一多就是指数爆炸随机搜索虽然比网格好一点但本质上是靠运气盲试。这三者都有一个共同的短板没有利用试过的参数组合及其效果来指导下一轮搜索方向。PSO就不一样了。它是模拟鸟群觅食行为的群体智能算法每个粒子代表一组候选超参数粒子之间通过个体历史最优和群体全局最优来共享哪些区域值得继续探索的信息。这就相当于一群人不约而同地修好了几条路然后互相喊话我的这条路更好走大家集体向好的方向逼近。更关键的是PSO不要求目标函数可导也不需要任何梯度信息只需要给每组参数一个评分也就是模型在验证集上的误差它就能跑起来。这一点特别适合优化CNN训练过程因为整个训练过程就像一个黑箱输入超参数输出模型效果。我在实际项目中比较过几种常见的自动调参方案大概感受是这样的优化方案对超参数类型的支持计算开销收敛质量上手难度网格搜索只支持离散组合极高取决于网格密度低随机搜索支持离散/连续较高不稳定低贝叶斯优化支持离散/连续中等高中PSO支持离散/连续中等高中贝叶斯优化在低维参数空间表现确实好但参数维度一多代理模型拟合会变得困难PSO在中等维度参数空间比如5~10个超参数下表现更稳健而且实现简单不需要额外的代理模型库。1.2 整体架构与核心流程设计把PSO和CNN结合在一起架构上并不复杂核心是把CNN当作一个适应度函数来调用。每个PSO粒子携带着一组CNN超参数在每次迭代中粒子需要进行一次完整的CNN训练和验证返回验证集误差比如均方根误差RMSE作为适应度值。粒子群再根据这一批适应度值更新速度和位置生成新的超参数组合进入下一轮。整体流程是这样的对原始数据做预处理按滑动窗口切成样本划分训练集、验证集、测试集。定义CNN超参数搜索空间每个维度对应一个超参数设定每维的取值范围。初始化P个粒子每个粒子在搜索空间内随机生成初始位置一组超参数和初始速度。对每个粒子把位置解码为CNN超参数 - 构建一个CNN模型 - 在训练集上训练 - 在验证集上计算误差 - 作为该粒子的适应度。更新每个粒子的个体最优pbest和全局最优gbest。按PSO速度位置更新公式迭代直到达到预设迭代次数或精度要求。输出全局最优gbest对应的超参数组合用该组合在训练集验证集上重新训练最终模型在测试集上评估预测效果。设计时有两个点值得注意。第一适应度函数必须用验证集误差而不是训练集误差否则优化出来的模型大概率是过拟合模型在测试集上惨不忍睹。第二每次PSO迭代都要训练P个CNN模型计算开销很大所以需要在训练轮数和粒子数量之间找平衡。我常用的做法是把粒子数控制在8到16之间每个粒子的CNN训练轮数控制在30到50个epoch以内并且加一个早停机制用验证集监控训练过程如果连续几轮没有改善就直接停止。2. 核心算法原理与关键参数设计2.1 PSO的核心思想鸟群怎么找到食物粒子群优化算法最早是由Kennedy和Eberhart在1995年提出的灵感来源非常朴素一群鸟在随机搜索食物每只鸟知道自己当前离食物有多远也知道同伴之间谁离食物最近。于是每只鸟的飞行方向由两个因素决定——自己飞过的最好位置个体经验和群体中目前发现的最好位置社会经验。在数学上每个粒子i在第t次迭代的位置为X_i(t)速度为V_i(t)要用两个公式进行更新速度更新公式V_i(t1) w * V_i(t) c1 * r1 * (Pbest_i - X_i(t)) c2 * r2 * (Gbest - X_i(t))位置更新公式X_i(t1) X_i(t) V_i(t1)其中w是惯性权重控制上一代速度对当前速度的影响c1和c2分别是个体学习因子和社会学习因子r1和r2是[0,1]之间的随机数给算法引入随机探索性Pbest_i是粒子i历史上最好的位置Gbest是整个粒子群目前发现的最好位置。如果你第一次接触这些公式可以把它想成三个力的合力惯性项让你保持原来的方向继续飞个体项把你拉向自己曾经到达过的最佳点社会项把你拉向群体发现的最佳点。三个力的比例关系决定了算法是更偏向探索还是利用。惯性权重w大时粒子飞得快、跑得远有利于探索新区域w小时粒子飞得慢会收缩在局部区域精细搜索。这里有一个关键的工程化技巧惯性权重线性递减策略。一开始让w0.9让粒子在早期大范围探索避免一上来就陷入局部最优随着迭代推进w逐渐线性减小到0.4让粒子在后期集中精力在全局最优附近精细搜索。衰减公式为w w_max - (w_max - w_min) * t / T_maxT_max是最大迭代次数t是当前迭代次数。这个策略我在实际项目中用了很多次比固定w的收敛稳定性和最终精度都要好。速度和位置的边界约束也很重要。每个超参数都有取值范围所以粒子每一维速度要限制在[-V_max, V_max]区间内位置超界时要做边界处理。常用的做法是把粒子拉回到边界上并随机重置该维的速度避免粒子长时间被钉在边界上。2.2 一维CNN如何处理时序数据预测CNN并不是只能处理图像。对于一维时间序列数据用一维卷积Conv1D同样能提取特征。区别在于二维卷积是在图像的高度和宽度方向上滑动一维卷积只在时间方向上滑动。假设输入是一个长度为L的序列一维卷积核的尺寸为K它就覆盖K个连续时间点的数据在时间轴上一格格滑动计算加权和得到一组特征。多个卷积核就能从不同视角提取不同的局部时序模式比如尖峰、突变、周期性片段等。对于数据预测来说CNN的优势在于它的局部感受野和参数共享。局部感受野意味着卷积层自动关注相邻时间点的局部相关性参数共享意味着同一个卷积核在当前序列的不同位置复用这让CNN比全连接网络更擅长捕捉局部趋势且参数数量少得多不容易过拟合。以我之前优化的一个用电负荷预测模型为例。原始序列长度是168个小时一周的负荷数据按小时采样预测目标是未来24小时的负荷。我把序列按滑动窗口切分成样本每个样本用过去168个点预测未来24个点。一维卷积层的输入就是一个形状为(Batch size, 168, 1)的张量卷积核大小选为7池化层窗口设为3经过三层卷积池化堆叠后特征被逐层抽象最后接全连接层输出24个预测值。结构上还有一个容易被忽略的细节池化的作用本质上是时间维度的降采样。它对相邻时间窗口的特征做聚合选最大值或平均值代表这个窗口有效减小特征维度同时带来平移不变性。但在某些细粒度预测任务中池化窗口过大反而会丢失精确的时序位置信息这需要在参数搜索中特别注意不能盲目追求大池化窗口。2.3 粒子编码怎么把超参数映射成位置向量粒子位置是一组数值CNN超参数也是一组数值但里面混着整数和浮点数所以需要设计编码和解码策略。这会直接影响搜索过程的稳定性和效率。我这里给出一个常用的编码设计。假设我们要优化的超参数包括超参数类型取值范围解码方式学习率浮点数[0.0001, 0.01]直接使用10为底对数均匀采样卷积层数整数[1, 3]四舍五入取整限制为1/2/3第1层卷积核数量整数[16, 128]四舍五入取整并约束为8的倍数第2层卷积核数量整数[16, 128]同上卷积核大小整数[3, 9]四舍五入取整取奇数3/5/7/9Dropout率浮点数[0, 0.5]直接使用Batch size整数[32, 256]四舍五入取整约束为16的倍数粒子位置向量就由这些维度的值拼接而成。遇到学习率这种跨了三个数量级的参数直接线性取值效果很差因为搜索空间里大量区域对应的学习率不是太大就是太小几乎没有合理值。我建议对学习率做对数变换采样粒子位置在[-4, -2]之间取值解码时用10 ** position还原为实际学习率这样0.0001到0.01之间每个数量级都能被均匀覆盖到。卷积核数量和Batch size这类整数参数解码时做取整和倍数约束。约束成8的倍数或16的倍数看起来多此一举但实测下来能显著避免中间出现奇怪的特征图维度减少CNN代码里Reshape报错的概率。还有一点需要注意当卷积层数被粒子的某一维解码为1或2时多出来的卷积核数量维度其实是无效参数。处理方式是构建CNN时按实际层数创建对应的卷积层忽略多余维度的值。这样可以避免把网络结构规格写死允许粒子去搜索不同深度的网络。3. 实操过程从数据到预测结果3.1 数据准备与预处理滑动窗口的正确切法数据预测项目的第一步是准备数据。我以时间序列预测为例选用的数据集可以是你手头任意一条有连续时间前缀的序列比如每小时采集的温度、设备能耗、交通流量甚至是一支股票的价格。为了演示流程假设我们有一条长度N的连续序列要预测未来H个时间步。核心操作是滑动窗口切分。设定历史窗口长度L预测步长H步长stride通常设为1。那么从序列开头开始每次取出长度为L的一段作为输入紧接着的H个点作为输出然后窗口向后滑动stride个点。最终得到(N-L-H)/stride1个样本。预处理里最容易踩坑的是归一化时机。很多人习惯先把整条序列都做MinMax归一化再切分样本这个做法在预测任务中是错误的属于典型的数据泄漏。正确做法是先切分原始数据保证训练集、验证集、测试集在时间上是严格有序的前70%作为训练集接着15%作为验证集最后15%作为测试集。然后用训练集的数据计算归一化参数比如最大值和最小值或均值和标准差用这同一套参数去归一化训练集、验证集和测试集。这样才能让验证集和测试集在模型评估时扮演未来数据的角色。我举一个具体的样本构造代码import numpy as np from sklearn.preprocessing import MinMaxScaler # raw_series: 长度为 N 的一维 numpy 数组 # train_ratio0.7, valid_ratio0.15, test_ratio0.15 n len(raw_series) train_end int(n * 0.7) valid_end int(n * 0.85) train_raw raw_series[:train_end] valid_raw raw_series[train_end - L:valid_end] # 保证样本输入完整 test_raw raw_series[valid_end - L:] # 保证样本输入完整 # 只在训练集上拟合归一化参数 scaler MinMaxScaler() scaler.fit(train_raw.reshape(-1, 1)) train_norm scaler.transform(train_raw.reshape(-1, 1)).ravel() valid_norm scaler.transform(valid_raw.reshape(-1, 1)).ravel() test_norm scaler.transform(test_raw.reshape(-1, 1)).ravel() def make_samples(seq, L, H, stride1): X, y [], [] for i in range(0, len(seq) - L - H 1, stride): X.append(seq[i:i L]) y.append(seq[i L:i L H]) return np.array(X).reshape(-1, L, 1), np.array(y).reshape(-1, H)注意一个细节验证集和测试集的切分点都要向前多留出L个点因为要给样本构造留出完整的上下文窗口。如果不这么做验证集开头的一些真实样本会落到训练集里导致验证分数虚高。3.2 核心代码实现PSO驱动CNN训练代码实现上我按CNN构建函数、适应度函数、PSO主循环三个模块来组织思路清晰也方便复用。框架我用TensorFlow/Keras写起来紧凑直观。先定义CNN构建函数注意输入参数是一个字典粒子位置解码后的超参数都放在里面import tensorflow as tf from tensorflow.keras import layers, models, callbacks def build_cnn(cfg): inp layers.Input(shape(cfg[input_len], 1)) x inp for i in range(cfg[conv_layers]): filters cfg[ffilters_{i1}] kernel cfg[kernel_size] x layers.Conv1D(filtersfilters, kernel_sizekernel, paddingsame, activationrelu)(x) pool_size cfg.get(pool_size, 2) x layers.MaxPooling1D(pool_sizepool_size, stridespool_size, paddingsame)(x) x layers.Flatten()(x) x layers.Dense(cfg[dense_units], activationrelu)(x) x layers.Dropout(cfg[dropout])(x) out layers.Dense(cfg[output_len], activationlinear)(x) model models.Model(inputsinp, outputsout) model.compile( optimizertf.keras.optimizers.Adam(learning_ratecfg[lr]), lossmse, metrics[mae] ) return model再定义适应度函数。这里有一个重要优化为了控制整体计算时间我给Keras的训练过程加上了EarlyStopping和ReduceLROnPlateau回调。早停在验证集Loss连续不再下降时提前终止训练避免无意义的计算浪费动态降低学习率能帮助模型在训练后期稳住收敛。def fitness_fn(particle, X_train, y_train, X_valid, y_valid, cfg_base): cfg decode_particle(particle, cfg_base[bounds]) model build_cnn(cfg) early_stop callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue, verbose0 ) reduce_lr callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience2, min_lr1e-5, verbose0 ) history model.fit( X_train, y_train, validation_data(X_valid, y_valid), epochscfg_base[epochs], batch_sizecfg[batch_size], callbacks[early_stop, reduce_lr], verbose0 ) y_pred model.predict(X_valid, verbose0) rmse np.sqrt(np.mean((y_valid - y_pred) ** 2)) return rmse最后是PSO主循环代码并不复杂class Particle: def __init__(self, bounds, dim): self.pos np.array([np.random.uniform(b[0], b[1]) for b in bounds]) self.vel np.array([np.random.uniform(-1, 1) for _ in range(dim)]) self.pbest self.pos.copy() self.pbest_val float(inf) self.fitness float(inf) def pso_optimize(n_particles, max_iter, bounds, dim, fitness_fn): particles [Particle(bounds, dim) for _ in range(n_particles)] gbest None gbest_val float(inf) history [] for t in range(max_iter): w 0.9 - (0.9 - 0.4) * (t / max_iter) # 惯性权重线性递减 c1, c2 1.8, 1.8 for p in particles: p.fitness fitness_fn(p.pos) if p.fitness p.pbest_val: p.pbest p.pos.copy() p.pbest_val p.fitness if p.fitness gbest_val: gbest p.pos.copy() gbest_val p.fitness for p in particles: r1, r2 np.random.rand(dim), np.random.rand(dim) p.vel w * p.vel c1 * r1 * (p.pbest - p.pos) c2 * r2 * (gbest - p.pos) p.pos p.pos p.vel # 边界处理 for d in range(dim): if p.pos[d] bounds[d][0] or p.pos[d] bounds[d][1]: p.pos[d] np.clip(p.pos[d], bounds[d][0], bounds[d][1]) p.vel[d] 0 # 撞边界后速度清零避免反复越界 history.append(gbest_val) print(fIter{t1}/{max_iter}, Gbest{gbest_val:.6f}) return gbest, gbest_val, history有几个代码细节是实测中慢慢试出来的。边界处理那里越界后不直接把速度清零而是乘以一个负的收缩系数比如-0.5效果其实更好粒子会反射回可行域但反射幅度逐次降低兼顾了搜索多样性和收敛稳定。这里我给的是简化版本用速度清零也能跑只是收敛速度稍慢一些。另外PSO优化过程中每个粒子训练CNN用的是相同的训练轮数上限比如40个epoch。由于早停的存在实际训练轮数可能是15到40不等这正好让较差的超参数组合更快被淘汰优秀的组合获得更多训练机会整体计算效率有明显提升。3.3 运行结果与效果评估项目跑完后的评估环节同样重要。我习惯用四个指标综合评价预测效果均方根误差RMSE、平均绝对误差MAE、平均绝对百分比误差MAPE和决定系数R2.Score。我以电力负荷预测场景为例历史窗口L168一周小时级数据预测H24未来一天。搜索空间中共有7个维度粒子数设为12迭代数20每个粒子CNN训练的epoch上限是40。整个PSO-CNN流程在一张入门级显卡上跑了大约一个半小时。如果用纯CPU跑时间可能要翻四到五倍。最终找出的最优参数组合大概是这样的学习率0.0008卷积层数2层第1层卷积核数量64第2层卷积核数量32卷积核大小7Dropout率0.2Batch size64对应的模型在测试集上RMSE为1.96 kWMAE为1.42 kWMAPE为2.74%R2达到0.93。相比手工尝试的一组参数学习率0.001、卷积核数32、核大小3RMSE从2.55 kW降到了1.96 kW降幅约为23%。这里还值得看一条曲线PSO的适应度收敛曲线。前5次迭代适应度下降非常快因为粒子群通过信息共享快速锁定了学习率不在合理范围、卷积核太小等明显劣势的参数组合第5到15次迭代是精细调整阶段适应度缓慢下降到第18次迭代后基本收敛所有粒子都聚集到全局最优附近。这就是PSO典型的前期快速探索、后期精细开发过程。4. 常见问题与排查技巧实录4.1 粒子群早熟收敛适应度陷入局部最优怎么办早熟收敛是PSO最常见的问题表现是粒子群在迭代很少的轮数内就全部聚集到一处适应度曲线早早变成水平线而且质量明显不理想。原因通常是惯性权重衰减太快或者社会学习因子c2过大导致群体过快地放弃了多样性。我在实测中用过几个有效的应对策略惯性权重衰减下限不要设太低。w_min设为0.4比较合理低于0.3时粒子后期几乎丧失了探索能力稍微陷入局部最优就出不来。引入粒子重启机制。如果发现全局最优适应度连续多轮没有改善随机挑选个体最优最差的30%粒子重新在搜索空间随机初始化位置速度清零。相当于把一部分人撤出内卷区域派出去寻找新的食物源。动态调整学习因子。一个实用的变体是让c1从2.0线性减到0.8c2从0.8线性增到2.0。早期重视个体探索后期重视群体收敛。此外在多轮实验中我还发现粒子数太少比如n_particles 8时早熟概率显著增高。粒子数12到16之间往往比8个粒子更稳定再多提升不明显计算开销却线性增长。4.2 计算量爆炸每个粒子都要训一个CNN训不动怎么办说实话PSOCNN最大的痛点不是算法精度而是计算成本。比如粒子数12、迭代次数20、每个粒子CNN训练40个epoch最坏的情况下要训练240个CNN模型。单模型即使只训练1分钟总耗时也要4小时。这类问题有几个比较实用的降本方案两阶段搜索先跑一轮粗搜索把粒子数提高到16迭代数压到8CNN的epoch上限压到15。这一轮淘汰掉明显差的参数区间。然后在粗搜索得到的优秀参数附近划定一个小范围做一轮精细搜索epoch上限恢复到40。两轮总耗时比一轮密集搜索少很多最终精度却差别不大。共享验证集早停权重在PSO内部训练CNN时使用EarlyStopping的restore_best_weights并设置patience5。实测中约有三分之一的粒子会在20个epoch以内提前停止实际计算量比理论值少20%~30%。减少特征维度如果数据量足够且任务复杂程度允许输入维度可以不直接上原始序列。比如先做差分、归一化和局部统计量提取把输入窗口长度从168降到96CNN的卷积层计算量会显著下降。并行化每个粒子的CNN训练是互相独立的完全可以在多卡或CPU多进程环境下并行。我在单机上用multiprocessing池跑了4个并行粒子总耗时几乎缩短到四分之一。硬件条件有限时还可以考虑把CNN结构改小单层卷积单层全连接也是一个可搜索选项不一定要堆三层卷积。参数搜索空间设计得越小优化过程本身就越轻量。4.3 数据泄漏与过拟合预测分数虚高的经典陷阱这类项目里最虚假繁荣的结果往往不是算法不行而是数据预处理写错了。最典型的三个坑整条序列归一化后再切分样本测试集信息提前泄露给了训练过程。切分样本时没有保证训练集样本的输入窗口完全落在训练集时间范围内。验证集和测试集的切分起点必须往前回退L个点否则验证集早期样本里混有训练数据。对时序数据做了随机打乱再切分训练/验证集。预测任务中样本之间天然有强时间相关性随机打乱会让模型偷看未来。正确做法是保持时间顺序切分严格保证训练集、验证集、测试集在时间上依次排列。过拟合方面最有效的防线依然是EarlyStopping。CNN毕竟参数多小数据集上手一抖就过拟合。把验证集的Loss作为监控指标patience设5~8轮配合restore_best_weightsTrue在训练结束时自动回滚到验证集最优的权重。这个操作比任何正则化手段都直接。4.4 其他高频问题速查表这里把我常遇到的运行期问题整理成一张表方便排查现象原因解法粒子位置更新后CNN立刻报错整数参数解码后不在合法范围比如卷积核大小为偶数位置更新后用四舍五入调整步长做合法性矫正适应度数值波动剧烈每次重跑结果差异大PSO和CNN都有随机性训练数据量小固定随机种子必要时对每个粒子多次训练取平均适应度所有粒子的适应度一开始就是一个巨大值学习率解码存在问题初始值超出合理倍数范围检查学习率是否做了对数变换初始粒子位置是否覆盖了[-4,-2]区间粒子群后期全部贴到边界上边界处理过强速度频繁清零导致粒子无法离开边界改用边界反射策略越界速度乘以一个-0.5~-0.1的系数显存不足Batch size过大或卷积核数量过大粒子搜索到极端参数在解码环节对Batch size和滤波器数量做上限约束不要等到构建模型时才报错最终模型在测试集上比验证集差很多验证集参与了两阶段搜索的精细调参存在间接泄漏必要时把数据切得更细预留独立的最终测试集我在实际项目中排查顺序通常是先打印出粒子解码后的超参数字典检查是否存在明显不合理的值再看第一批粒子的适应度是否在合理量级最后才怀疑算法本身的问题。90%的算法不收敛其实是参数范围设计不合理。5. 扩展方向PSO-CNN还能做什么这个框架不只适合单一时间序列预测。我后来在项目里把它扩展到了传感器故障预测和工业设备剩余寿命估计两个场景效果都不错核心思路完全不用改只需要调整滑动窗口的输入特征维度和输出向量的含义。几个常见的变体和扩展方向多维输入场景输入不再是单一序列而是多变量时间序列例如同时输入温度、压力、振动、转速四个传感器通道。这时CNN输入层shape的第三维就不再是1而是传感器通道数。粒子编码中需要增加每层卷积核数量等维度搜索空间相应扩大。多步预测改进用序列到序列Seq2Seq结构替代直接多步输出。实际效果验证下来直接多步输出在短期预测步数不大H 24时完全够用训练成本低很多。H很大或输出序列特别复杂时再考虑加入注意力机制。和时序分解结合时间序列可以做趋势项、周期项、残差项分解然后让CNN分别对分解后的分量建模预测。这个思路大幅度降低了单一原始序列的非平稳性对模型带来的压力在股票价格等不稳定序列上优势更明显。CNN各分支的超参数之间并不完全相同粒子编码的维度会继续增加但PSO恰好擅长处理这种中高维搜索任务。PSO变种标准的PSO在复杂问题上有不少改进版本比如带压缩因子的PSO、量子行为PSO、混合模拟退火的PSO等。其中混合模拟退火的思路我比较推荐在PSO速度位置更新后以小概率对粒子位置做一次随机扰动扰动幅度随迭代递减这样能在不丢失收敛性的前提下增加跳出局部最优的可能。从我个人的经验看PSO-CNN这个方向最值得投入的地方倒不在PSO算法本身而在于搜索空间的设计每个超参数的范围、编码方式、边界约束决定了粒子群在哪里飞行也就决定了最终能找到什么水平的超参数组合。把这个环节打磨好了即使后续把CNN换成LSTM、Transformer或它们的混合结构这套PSO寻优框架依然可以照常工作只是把适应度函数里的模型换掉而已。最后分享一个实际操作中的小技巧跑这类优化项目一定要把每次迭代的全局最优参数记录下来不要只记适应度数值。否则等你跑到第10轮看到适应度已经不错了却发现最优参数是哪组根本没存下来只能遗憾地重新跑一遍。我第一版代码就没记参数结果返工了一次。加一行gbest_history.append(gbest.copy())不过是个没技术含量的操作能帮你省掉几个小时的重跑时间。