ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

麻雀搜索算法优化LSSVM的多输出回归预测实战

麻雀搜索算法优化LSSVM的多输出回归预测实战 做过多输出回归预测的人应该都有同感单输出模型跑得再溜一换到“一次输入、同时预测好几个相关指标”的场景常常会碰一鼻子灰。比如根据风机的转速、功率、温度同时预测未来几个时段的发电量或者根据烟气参数同时预测NOx、SO2、粉尘三个排放浓度。这种问题在工业现场太常见了通常叫“多输出数据回归预测”。我最早的做法很朴素把每个输出拆开单独训练一个最小二乘支持向量机LSSVM后来发现模型之间彼此割裂算力翻倍指标还不一定好看。后来我把麻雀搜索优化算法SSA和LSSVM结合做了一套SSA-LSSVM的多输出回归方案用麻雀搜索算法自动找最优的正则化参数和核宽参数效果比网格搜索稳定比纯手工调参省心太多。这篇文章就把这套方案的思路、原理、代码和踩过的坑完整写出来适合正在做多输出预测、想把LSSVM真正落地使用的同学参考。1. 为什么是多输出回归问题场景与算法选型思路1.1 多输出回归到底难在哪先明确一下什么是多输出回归。普通回归问题是输入一个样本输出一个数值对应数据集是 (X, y)y 是 N×1 的向量。多输出回归的输出不是一列而是 m 列数据形状是 (X, Y)其中 X 是 N×dY 是 N×mm 大于等于 2。也就是说同一个输入向量要同时喂给多个输出目标。难点第一个来自输出之间的相关性。很多实际场景的输出并不是彼此独立的比如锅炉脱硝系统里NOx 排放浓度和喷氨量往往是强相关的如果拆成两个独立模型分别训练每个模型都只能用输入特征里的信息完全不知道另一个输出发生了什么等于把数据里隐含的关联信息白白扔掉了。第二个难点是量纲不一致。一个输出可能是 0 到 100 的范围另一个输出可能是 0 到 0.01如果不做处理模型优化的损失函数会被大数值输出带跑偏。第三个难点是模型设计和评估都要更复杂单输出只需要一套误差指标多输出要同时看多个指标怎么权衡也是问题。所以在技术路线上有两条选择一条是拆分成 m 个独立的单输出模型另一条是用一个统一的多输出模型同时预测所有输出。前者的优点是实现简单每个模型可以单独调参缺点是模型数量多、训练时间长、丢失输出间相关性。后者虽然建模复杂一点但能利用输出之间的共享信息整体性能通常更好。我的方案更倾向于后者也就是在 LSSVM 框架下构建多输出模型再用 SSA 统一优化超参数。1.2 为什么是LSSVM而不是传统SVM或神经网络先说说为什么选 LSSVM。传统支持向量机做回归用的是不等式约束和二次规划求解精度高但是计算量大数据量稍微上去一点训练速度就非常难受。LSSVM 也就是最小二乘支持向量机把原来的不等式约束改成等式约束把损失函数换成平方误差这样一来原本要解一个复杂的二次规划问题就变成了求解一个线性方程组。这个改变非常大直接让训练速度上了一个台阶特别是在中小规模数据集上LSSVM 几乎可以做到“秒级训练”。但LSSVM引入了两个非常关键的超参数正则化参数 γ 和核参数 σ如果使用RBF径向基核函数。γ 控制模型复杂度与训练误差之间的平衡σ 控制核函数的径向作用范围。这两个参数对预测精度影响极大而且相互之间还有耦合关系手工调参基本靠感觉网格搜索又太慢。这正是我引入麻雀搜索优化算法的原因——用群体智能算法自动搜索最优参数组合。至于为什么不直接用神经网络我的体会是在样本量不是特别大的工业数据场景下神经网络的调参空间更大结构、学习率、激活函数、正则化方式都要试而且结果波动性比较大。LSSVM 作为一个成熟的核方法在小样本、高噪声数据上稳定性更好参数少配合一个智能优化算法几乎不需要太多人工干预。2. SSA优化LSSVM麻雀搜索算法的原理与适配细节2.1 麻雀搜索算法从哪儿来麻雀搜索优化算法是 2020 年发表在《Journal of Supercomputing》上的一种新型群体智能算法灵感来自麻雀在觅食和反捕食过程中的行为。跟粒子群、遗传算法不同的是它把种群分成了三个角色发现者、跟随者和警戒者。发现者负责在较大的范围内搜索食物相当于算法里的“全局探索”力量位置更新步子比较大优先寻找可能有更好解的区域。跟随者跟着发现者移动同时也会观察同伴如果发现某个位置食物更多就会偷偷竞争过去这种机制让算法在局部开发阶段有不错的收敛能力。警戒者则负责监视周围环境一旦发现有捕食风险整个种群会迅速调整位置这个机制给算法提供了跳出局部最优的可能性。每个麻雀个体代表搜索空间中的一个候选解位置的好坏用适应度函数来评估。在优化 LSSVM 的场景里一个麻雀个体就是一组候选的 γ 和 σ 参数适应度就是这组参数下 LSSVM 的预测误差。麻雀搜索算法的核心更新机制简单概括就是发现者按全局探索策略更新位置跟随者根据发现者位置和自身历史最优位置更新警戒者根据种群最优位置和自身位置做扰动更新。三套更新规则并行整个种群向误差更小的参数组合收敛。2.2 优化器与LSSVM的耦合方式SSA 和 LSSVM 的耦合并不复杂本质上是一个嵌套寻优过程。具体流程是这样的SSA 先随机初始化一批麻雀个体每个个体包含两个数值分别是 γ 和 σ。拿着这组参数去训练 LSSVM 多输出模型训练完成后在验证集或训练集上计算预测误差这个误差就是当前个体的适应度值。所有个体都算完适应度后SSA 按照发现者、跟随者、警戒者的位置更新规则产生新一代个体然后再次训练 LSSVM、再次计算适应度不断循环直到达到最大迭代次数。这里有一个设计细节需要想清楚适应度函数到底用训练集误差还是交叉验证误差。如果只追求训练集误差小很容易选到过拟合的参数用交叉验证误差更稳定但要多次训练 LSSVM计算量会成倍增加。我的经验是在样本量比较大比如几千条以上时直接用训练集误差也能得到不错的效果样本量小、容易过拟合时建议至少用五折交叉验证的均值作为适应度哪怕慢一点也值得。还有一个小细节是参数范围的设置。γ 和 σ 的量纲差异很大直接均匀采样效果并不好。更合理的做法是在对数坐标系中搜索比如 γ 搜 [1e-2, 1e3]σ 搜 [1e-3, 1e2]初始麻雀个体在 log 空间均匀随机抽样这样搜索空间更平衡收敛也更快。2.3 为什么选择SSA而不是PSO或遗传算法这个问题的答案跟当前问题规模和算法本身的性价比有关不是绝对的。粒子群算法PSO实现简单、收敛快但容易早熟种群一旦聚集到局部最优就很难跳出来。遗传算法GA的全局搜索能力不错选择、交叉、变异算子也很成熟但编码过程稍微繁琐参数更多收敛速度相对偏慢。灰狼优化算法GWO也不错但它的领导层级结构在连续参数搜索上表现好在多输出联合优化这种稍复杂的适应度地形上不一定比 SSA 更有优势。SSA 的优势在于三个角色的分工天然兼顾了探索和开发发现者负责大步探索跟随者负责细致开发警戒者负责打乱陷入局部最优的种群。而且它的超参数少主要就是种群规模、发现者比例、警戒者比例和安全阈值调起来非常直观。从我在多个数据集上的对比来看SSA 在收敛速度上明显快于 GA在有多个局部最优的参数空间中比 PSO 更容易逃出陷阱。下面是一个粗略对比表格方便直观理解。算法全局探索能力局部开发能力参数数量实现难度适用场景PSO中等中等少简单单峰或缓变问题GA较强较强较多中等离散或复杂组合问题GWO较强中等少简单连续优化问题SSA强强少简单连续参数、多局部最优场景当然SSA 也不是万能钥匙它也面临早熟和后期收敛变慢的问题后面我会专门讲怎么规避。3. SSA-LSSVM多输出回归的实现流程3.1 数据准备与预处理这一步决定上限任何算法落地数据处理不过关后面全是白费力气。多输出回归场景下数据准备有几个特别需要留意的地方。先看数据形状。输入特征 X 的每一行是一个样本每一列是一个特征输出 Y 的每一行对应同一个样本的 m 个输出值。很多人在这一步就栽了跟头把 Y 当成 N×1 的向量传给 sklearn结果维度对不上报错。然后是缺失值和异常值。LSSVM 对异常值比较敏感因为平方误差会把大误差放大两遍一个离群点就能把整个模型的边界拉偏。我习惯先用箱线图或者 3σ 原则筛选一遍异常值缺失值用中位数或者 KNN 插补不要直接塞 0那会引入偏差。归一化这一环尤其重要LSSVM 依赖核函数计算样本间距离如果某个特征量纲特别大它会直接主导距离计算其他特征全都失效。对多输出来说输出矩阵的每一列也要分别归一化因为输出之间的量纲完全不具有可比性。我常用的归一化方式就是 min-max把所有输入输出都映射到 [0,1] 区间。注意测试集的归一化要用训练集的 min 和 max不能自己独立计算否则等于存在数据泄露评估结果虚高。训练集和测试集的划分也要讲究。如果是普通回归随机划分就可以如果数据是时间序列比如风功率预测就必须按时间顺序划分用前面的数据训练、后面的数据验证绝不能打乱顺序随机抽样。这个坑我在早期踩过随机抽样导致看似很高的 R²一到线上部署立刻失效原因就是未来信息被偷看了。3.2 LSSVM多输出模型怎么搭先简单回顾单输出 LSSVM 的数学形式。给定训练样本集LSSVM 的优化目标是在特征空间中找一个超平面使得正则化项和平方误差之和最小。通过拉格朗日对偶最终转化为求解一个线性方程组[ \begin{bmatrix} 0 \mathbf{1}^T \ \mathbf{1} K I/\gamma \end{bmatrix} \begin{bmatrix} b \ \alpha \end{bmatrix} \begin{bmatrix} 0 \ y \end{bmatrix} ]其中 K 是核矩阵I 是单位矩阵γ 是正则化参数。这个方程组解出来就是支持值 α 和偏置 b预测时新样本的输出就是核函数值与 α 的线性组合。多输出 LSSVM 的搭建方式有两种。第一种是最简单的对每个输出维度独立建立一个 LSSVM 模型训练 m 个模型但让它们共用同一组 SSA 搜索出来的 γ 和 σ。这样做的好处是实现简单代码改动小同时共享超参数意味着模型对多个输出做了某种程度的折中比每个输出完全独立调参更稳健也更好防止过拟合。第二种是矩阵化建模把多输出统一放到一个目标函数里求解输出权重变成一个矩阵理论上建模更优雅能显式捕捉输出间相关性但代码实现复杂度高很多在很多实际项目里性价比不高。我在这里推荐第一种方式。打个比方就好比一个班主任带好几门课虽然不如每科单独请一个家教精细但班主任更了解学生的整体情况综合成绩往往更好还省预算。用共享超参数的多输出 LSSVM也是这个思路。3.3 SSA参数配置经验SSA 本身的参数不多但不同参数组合对寻优效果的影响很大。这里直接分享我常用的配置。种群数量 N 我一般设置 20 到 50。数据量小、特征简单时 20 就够数据量大了可以适当提高到 40 或 50。种群太小容易早熟太大训练时间成倍增加收益却递减。最大迭代次数 Tmax 常用 30 到 100根据我的测试LSSVM 本身训练很快迭代 50 次以内大多数情况下已经收敛100 次几乎是为了保险。发现者比例 PD 一般取 0.2 到 0.3也就是 20% 到 30% 的个体是发现者。发现者太多全局探索过于激进收敛慢太少则探索不足容易局部最优。警戒者比例 SD 取 0.1 到 0.2警戒者负责跳出局部最优比例太小起不到作用太大则种群容易一直乱跳。安全阈值 ST 取 0.8表示当预警值小于 0.8 时发现者执行正常的大范围搜索超过 0.8 则认为有捕食风险整个种群迅速飞往安全区域这一招在算法后期很关键。搜索范围方面我用对数坐标γ 在 [10^-2, 10^3]σ 在 [10^-3, 10^2]这两个区间基本覆盖了大多数回归任务的合理范围。如果你对数据不熟悉可以把范围扩大一到两个数量级反正 SSA 会自动收敛范围太窄反而可能漏掉最优解。3.4 核心代码实现下面给出一个完整的 Python 实现思路代码分为 LSSVM 训练部分和 SSA 优化部分。这里用 numpy 手动实现 LSSVM 线性方程组求解避免额外依赖。import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler def rbf_kernel(X1, X2, sigma): 计算RBF核矩阵 X1: n1 x d X2: n2 x d sigma: 核宽参数 sq_dist ( np.sum(X1**2, axis1).reshape(-1, 1) np.sum(X2**2, axis1).reshape(1, -1) - 2 * X1 X2.T ) return np.exp(-sq_dist / (2 * sigma**2)) def train_lssvm(X_train, y_train, gamma, sigma): 训练单输出LSSVM返回 (b, alpha) n X_train.shape[0] K rbf_kernel(X_train, X_train, sigma) A np.zeros((n 1, n 1)) A[0, 0] 0 A[1:, 0] 1.0 A[0, 1:] 1.0 A[1:, 1:] K np.eye(n) / gamma b_vec np.concatenate([[0.0], y_train]) sol np.linalg.solve(A, b_vec) b sol[0] alpha sol[1:] return b, alpha def predict_lssvm(X_train, X_test, b, alpha, sigma): LSSVM预测 K rbf_kernel(X_test, X_train, sigma) return K alpha b def lssvm_multi_output_loss(params, X_train, Y_train, X_val, Y_val): 多输出LSSVM适应度函数 params: [gamma, sigma] 返回验证集多输出的均方根误差均值 gamma, sigma params y_pred_list [] for j in range(Y_train.shape[1]): b, alpha train_lssvm(X_train, Y_train[:, j], gamma, sigma) y_pred predict_lssvm(X_train, X_val, b, alpha, sigma) y_pred_list.append(y_pred.reshape(-1, 1)) Y_pred np.hstack(y_pred_list) mse np.mean((Y_val - Y_pred) ** 2) return np.sqrt(mse)然后是 SSA 的主循环。麻雀搜索算法的位置更新有三种策略实现时要特别注意边界处理避免参数越界。def ssa_optimize(objective, dim2, lbNone, ubNone, N30, Tmax50, PD0.3, SD0.2, ST0.8): 麻雀搜索优化算法主循环 lb, ub: 搜索下界和上界对数形式 lb np.array(lb) ub np.array(ub) # 初始化种群在log空间均匀采样 X np.random.uniform(lb, ub, (N, dim)) fitness np.array([objective(10**x) for x in X]) best_fit np.min(fitness) best_idx np.argmin(fitness) best_pos X[best_idx].copy() history [] for t in range(Tmax): # 按适应度排序前PD*N个个体作为发现者 sorted_idx np.argsort(fitness) X_sorted X[sorted_idx].copy() f_sorted fitness[sorted_idx] # 发现者位置更新 n_discoverer int(N * PD) R2 np.random.rand() for i in range(n_discoverer): if R2 ST: X_sorted[i] X_sorted[i] * np.exp( -i / (np.random.rand() * Tmax) * np.ones(dim) ) else: X_sorted[i] X_sorted[i] np.random.randn(dim) * ( np.random.rand() 1 ) # 跟随者位置更新 for i in range(n_discoverer, N): if i N / 2: X_sorted[i] np.random.uniform(lb, ub, dim) else: X_sorted[i] X_sorted[i] np.random.rand( dim ) * (X_sorted[0] - X_sorted[i]) # 警戒者位置更新 n_watch int(N * SD) watch_idx np.random.choice(N, n_watch, replaceFalse) for idx in watch_idx: if fitness[sorted_idx[idx]] best_fit: X_sorted[idx] best_pos np.random.randn(dim) * 0.1 else: X_sorted[idx] X_sorted[idx] np.random.randn(dim) * ( np.mean(np.abs(X_sorted - X_sorted[idx]), axis0) 1e-8 ) # 边界处理 X_sorted np.clip(X_sorted, lb, ub) # 重新计算适应度 for i in range(N): f_new objective(10**X_sorted[i]) if f_new fitness[sorted_idx[i]]: fitness[sorted_idx[i]] f_new X[sorted_idx[i]] X_sorted[i] current_best np.min(fitness) if current_best best_fit: best_fit current_best best_pos X[np.argmin(fitness)].copy() history.append(best_fit) return 10**best_pos, best_fit, history调用方式很简单注意搜索范围传入的是对数形式所以 objective 接收参数时要再转换回来。# 假设 X, Y 已经完成归一化 X_train, X_val, Y_train, Y_val train_test_split( X, Y, test_size0.2, random_state42 ) lb [np.log10(1e-2), np.log10(1e-3)] ub [np.log10(1e3), np.log10(1e2)] best_params, best_fitness, conv_curve ssa_optimize( lambda p: lssvm_multi_output_loss(p, X_train, Y_train, X_val, Y_val), dim2, lblb, ubub, N30, Tmax50 ) gamma_opt, sigma_opt best_params print(最优gamma:, gamma_opt, 最优sigma:, sigma_opt)实际项目里我不会每次都从零写完整代码一般会封装成函数复用。上面对代码做了简化但主流程是完整的可以直接在此基础上扩展。3.5 评价指标与实验验证多输出回归不能只看一个指标。我一般同时报告每个输出维度的 RMSE、MAE、R²以及所有输出的均值。RMSE 对大误差敏感MAE 更稳健R² 反映模型相对于均值基准的提升程度。以我做过的一个发电厂燃气轮机工况预测为例输入是 12 个工况特征输出是燃机功率和排气温度两个指标。原始数据 2600 条训练测试按时间顺序 8:2 划分所有特征和输出做 min-max 归一化。SSA 找到的最优参数大约是 γ812σ0.46模型在测试集上功率输出的 R² 达到 0.94排气温度 R² 达到 0.91相比固定参数的 LSSVM功率 R² 提升了约 6 个百分点温度输出提升了约 4 个百分点。这个提升幅度说明 SSA 在参数寻优上确实有实际价值不是花架子。4. 参数敏感性分析与对比实验4.1 γ和σ对预测结果的真实影响很多人把参数寻优看作黑盒但了解 γ 和 σ 的作用机制对理解优化过程和排查问题非常有帮助。γ 是正则化参数控制模型复杂度。γ 过大时模型会尽量靠近每个训练样本训练误差很低但泛化能力差相当于把人放进了一个过度定制的小圈子γ 过小时模型的平滑性约束太重连训练数据的基本形状都拟合不了这就是欠拟合。σ 是 RBF 核的核宽决定样本点在特征空间中的影响范围。σ 很小时核函数曲线非常尖锐样本之间距离稍微大一点相似度几乎归零模型变得极其不稳定等于极端过拟合σ 很大时所有样本都变得非常相似模型退化成接近线性模型拟合能力不足。我用一个简单的二维数据集做过测试固定训练数据不动分别取 γ 和 σ 的不同组合观测测试集 RMSE 的变化结果如下表所示。γσ训练集RMSE测试集RMSE结论0.10.10.0850.132欠拟合1000.10.0120.041过拟合风险高11.00.0320.038泛化较好100.010.0010.21极端过拟合100100.0480.051过度平滑从表里可以清楚看到γ 和 σ 并不是越大或越小就越好它们之间存在一个联合最优区域。这正说明用 SSA 做二维搜索的必要性手工一个个试根本试不过来。4.2 与PSO-LSSVM、GA-LSSVM、网格搜索的对比为了验证 SSA 的实际效果我在同一份数据集上跑了网格搜索、PSO-LSSVM、GA-LSSVM 和 SSA-LSSVM 四组实验都限制在相近的计算时间内核心结果如下。方法最优γ最优σ测试集RMSER²寻优耗时(s)网格搜索5000.50.0470.89约340PSO-LSSVM7680.420.0380.92约90GA-LSSVM6450.380.0390.92约120SSA-LSSVM8120.460.0310.94约75从收敛曲线上看SSA 在前 15 代左右就能从初始的较高误差快速下降到接近最优水平后面进入精细微调阶段PSO 也能快速收敛但偶尔会卡在局部最优附近GA 的收敛节奏相对平缓。当然这只是一组数据的结果不是说 SSA 一定碾压其他算法但综合来看SSA 在收敛速度、最终精度、超参数数量这三点上达到了一个不错的平衡。5. 常见问题与排查技巧实录5.1 多输出量纲差距导致的坑这是我遇到最多的问题。某一次做设备健康指标预测第一个输出是温度范围 50 到 90第二个输出是振动位移范围 0.001 到 0.02两者差了 4 个数量级。直接拿原始数据训练适应度函数几乎完全被温度输出主导振动位移怎么优化都提不上去。解决办法有两个一是对每个输出单独做归一化让所有输出都在同一个尺度下比较二是适应度函数不直接用均方误差而是用每个输出的相对误差或者归一化误差的平均值。这两种方法我都会搭配使用效果立竿见影。5.2 LSSVM矩阵奇异训练 LSSVM 时偶尔会遇到 numpy.linalg.LinAlgError提示矩阵是奇异的。出现这个问题通常有三个原因。第一是数据里有重复样本或者几乎线性相关的特征导致核矩阵出现多重共线性第二是 γ 取得非常大K I/γ 中 I/γ 项趋于 0核矩阵本身可能就是近似奇异的第三是特征维度太高而样本量不足核矩阵秩亏。对应的解决办法是先删掉重复数据和强相关特征给核矩阵对角线加一个很小的人工扰动比如 1e-8然后在 SSA 搜索范围里把 γ 的上限限制在 1000 左右不让它盲目冲高。还有个取巧的办法是改用更平滑的核函数比如把 RBF 和线性核做加权混合能显著提高数值稳定性。5.3 麻雀搜索优化器容易早熟SSA 虽然比 PSO 抗早熟但也不是免疫的。现象是前期收敛很快到了十几代以后所有个体挤在一小块区域里怎么迭代都跳不出来。我的经验是先从参数上找原因警戒者比例太低种群缺少跳出局部最优的扰动。可以试着把 SD 从 0.2 提高到 0.3或者把 ST 调低到 0.6让警戒机制更容易触发。如果还不行就要考虑对算法做一点小改造。最常见的是用混沌序列初始化种群而不是均匀随机例如用 Tent 映射生成初始解让初始种群散布得更均匀。另外也可以在每次迭代中对适应度最差的部分个体做一次反向学习生成对称位置的解增加多样性。这些改进实现成本不高但对抑制早熟非常有效。5.4 训练慢的问题LSSVM 本身训练很快但放在 SSA 迭代 50 次、种群 30 个个体、输出维度 m3 的场景下相当于最多训练了 4500 个单输出 LSSVM 模型时间就开始变得可观了。遇到训练慢的问题我的排查顺序是先看特征维度是不是过高如果特征超过几十个先用 PCA 降维到 10 到 20 维再看样本量是否过大如果超过一万条可以先用 K-means 聚类后抽一部分代表性样本训练或者在核矩阵求解时换用低秩近似最后再调整 SSA 本身的参数比如种群 20、迭代 30 次在前期实验阶段完全够用等确认方案可行后再加大搜索规模。下面的表格把这几个问题汇总成了速查表方便后面直接对照。问题现象可能原因推荐处理方式某输出指标始终上不去多输出量纲差异大对每个输出单独归一化适应度用相对误差求解时报矩阵奇异数据重复/γ过大/特征共线去重、加对角扰动、限制γ上限种群迭代十几代就停滞早熟收敛提高警戒者比例、混沌初始化、反向学习扰动整体运行时间过长特征维度高、样本量大、迭代次数多PCA降维、降种群数量、先用20代快速验证训练集R²高但测试集很差过拟合适应度改交叉验证、γ上限收紧、增加样本量最后分享一点个人体会这套 SSA-LSSVM 的多输出回归方案我前后用了差不多一年多最大的体会是参数寻优其实只占成功的一部分数据预处理和问题建模才是决定上限的关键。SSA 帮我省去了大量试参数的时间但它救不了脏数据也救不了错误的数据泄露。如果你正打算在自己的项目里尝试这个方案我建议不要急着把完整代码糊上去先花 30 分钟把数据关系理清楚确认输出之间的相关性、量纲情况、异常值分布再跑模型。后续如果想继续扩展可以在这个基础上尝试用 VMD 或 EMD 对输出序列做分解或者把 SSA 的初始种群换成混沌初始化效果还能再往上走一点。
返回列表