1. 为什么用PSO去调KNN的光伏功率预测模型
1.1 光伏功率预测的问题背景
光伏发电功率预测这件事,说白了就是给电站、电网、储能系统一个“接下来一段时间能发多少电”的靠谱估计。光伏出力本身受辐照度、温度、云层、风速、湿度等因素影响,波动性极强,而且不同季节、不同天气类型下的出力曲线差异很大。电网调度、电力现货交易、储能充放电策略,都需要依赖准确的功率预测结果来提前做决策,所以这个方向一直是新能源领域的研究热点和工程难点。
对于做工程和比赛的人来说,光伏功率预测本质上是一个回归问题:输入历史功率数据和气象特征,输出未来时刻的功率值。但它的难点在于,光伏数据的非线性和非平稳性很强,尤其是多云天气下出力在分钟级尺度上剧烈波动。传统的物理模型方法(基于辐照度换算、组件效率建模)虽然原理清晰,但对气象数据质量要求高,建模过程复杂,在不同场景下的泛化能力有限。而纯数据驱动的机器学习方法,如支持向量回归、随机森林、K近邻(KNN)、神经网络等,因为不需要建立复杂的物理方程,在实测数据充足的情况下往往能取得更好的预测效果。
KNN在机器学习里算法逻辑最简单,不需要训练过程,原理就是“物以类聚”——找历史数据里和当前时刻最相似的若干条样本,用它们的功率值来预测当前功率。这个思路对光伏这种强非平稳序列来说很直观,因为相似的气象条件大概率对应相似的出力水平。但它有个致命的问题:k值选多少、距离怎么算、要不要给不同特征加权重,这些超参数对预测精度影响极大,而人工试凑又特别费时间,且往往找不到全局最优。
1.2 K近邻回归的短板在哪里
KNN的分类版本大家都很熟,但光伏预测里用的是KNN回归。它的核心流程可以概括为三步:第一步,计算新样本与训练集中所有样本的距离;第二步,选出距离最近的k个样本;第三步,对这k个样本的功率值求平均,作为预测结果(或者按距离加权平均)。
听起来很简单,但实际用的时候会遇到几个很现实的问题。
第一个问题是k值的选择。k太小,模型只参考了极少数样本,很容易被个别极端天气样本带偏,预测结果方差大,属于过拟合;k太大,模型参考了大量相关度低的样本,把不同天气状态下的出力混在一起平均,预测结果过于平滑,跟随性变差,属于欠拟合。光伏出力突变时尤其明显——设小的k能抓住变化,但会带来很多毛刺;设大的k虽然平滑,却会延迟响应,预测曲线看起来“钝钝的”。
第二个问题是特征的量纲和权重。光伏预测的输入特征通常包括历史功率、辐照度、温度、湿度、风速等,这些特征的单位不同(比如辐照度可能是几百W/m²,温度可能是几十摄氏度),如果不做归一化,距离计算会被大数值特征主导,相当于模型自动忽略了量纲小的特征。而即便做了归一化,不同特征对功率预测的重要程度也不同——辐照度明显比风速重要得多,但KNN的欧氏距离把所有特征等权对待,这就限制了模型的精度上限。
第三个问题是距离度量本身。常规用的是欧氏距离,但光伏数据在高维空间里的结构并不规则,有时候曼哈顿距离、切比雪夫距离或者带特征权重的马氏距离效果更好。问题是,这些选择都属于模型超参数,靠人工一个个试不仅效率低,而且很难找到组合上的最优解。
1.3 PSO + KNN的组合逻辑
粒子群优化(PSO)算法的灵感来源是鸟群觅食行为。每个粒子代表一个候选解,在解空间里带着速度飞行,通过不断比较“自己找到过的最好位置”和“整个群体找到过的最好位置”来调整飞行方向和步长,从而在迭代中逼近全局最优解。
把PSO和KNN结合的逻辑其实很直白:既然KNN的预测效果严重依赖超参数(比如k值、距离度量类型、各特征的权重系数),而这些超参数之间又是相互影响的(改了距离度量,最优的k值可能就变了),那就把这些超参数编码成粒子的位置向量,让PSO自动在参数空间中搜索最优组合,以“预测误差最小”作为适应度评价标准,迭代若干代后得到一组全局较优的超参数方案。
这个组合方案的优点有三点:
一是实现成本低。KNN本身不需要训练,PSO的迭代只在参数空间里进行,整套逻辑在MATLAB里用一两百行就能实现,不需要深度学习框架,也不需要GPU。
二是可解释性强。PSO优化出的k值、权重、距离度量都是肉眼可见的参数,你可以直接检查模型到底倾向于用几个邻居、看重哪些特征,比黑箱式的神经网络更容易被工程人员接受。
三是精度提升效果明显。在光伏功率预测这种中等规模数据集上,PSO-KNN通常能比默认参数下的普通KNN提升好几个百分点的预测精度,尤其在中短期预测(未来1小时到6小时)场景下,效果和随机森林、支持向量机不相上下,但代码量小得多。
所以这篇文章就顺着这个思路,把“PSO优化KNN做光伏功率预测”的整套实现细节拆开讲清楚,包括算法原理、代码结构、运行结果和常见问题的排查,让拿到代码的人不仅能跑通,还能真正理解每一项设置背后的意图。
2. 算法细节与关键参数,先搞清楚再跑代码
2.1 KNN回归的三个核心参数
KNN回归虽然原理简单,但要用好,有三个参数必须先弄清楚。
第一个是k值,也就是选取的近邻样本数量。在回归任务中,k个邻居的功率值会被平均或加权平均来作为预测结果。这个值的取值范围通常从1到30左右,取决于训练集大小和数据分布。在PSO优化时,k值需要离散化,因为KNN不支持非整数的邻居数,这一点和连续参数的处理不太一样,建议用round(x)取整后再送入适应度函数。
第二个是距离度量方式。最常用的是欧氏距离,即 d(x, y) = sqrt(sum((x_i - y_i)^2))。 如果数据经过归一化,欧氏距离在高维空间里能基本反映样本之间的相似度。但如果特征之间存在较强的相关性(比如辐照度和历史功率高度相关),可以考虑马氏距离来消除相关性影响,不过这需要计算协方差矩阵的逆,对样本量不大的情况容易不稳定。在PSO编码里,可以给距离度量类型一个整数参数,比如1代表欧氏距离、2代表曼哈顿距离、3代表切比雪夫距离,交给PSO去选择。
第三个是特征权重向量。每个特征乘一个权重系数w_i,再把加权后的特征送入距离计算: d(x, y) = sqrt(sum(w_i^2 * (x_i - y_i)^2))。 权重越大表示该特征对“样本相似度”的贡献越大。这是提升KNN精度最有效的手段之一,因为光伏预测中辐照度的权重如果等于湿度,那预测结果基本上就被不相关的特征拉偏了。特征权重是连续变量,适合用PSO优化,取值范围一般设在0到1或0到2之间。
这里有一个非常重要的实操经验:特征权重一定要和归一化配套使用。先对每个特征列做min-max归一化到[0,1],再做加权距离计算,否则权重本身没有可比意义。还有一种做法是归一化之后再用PCA降维,但那样会丢失物理可解释性,光伏场景下不建议优先用PCA。
2.2 PSO的粒子编码与迭代公式
PSO的粒子位置就是一组待优化的参数。在PSO-KNN模型里,我采用的是如下编码方式:粒子位置x = [k, d, w1, w2, ..., wm],其中k是KNN邻居数(离散整数,取值范围1~30),d是距离度量类型(整数1~3),w1到wm是m个输入特征的权重系数(连续变量,范围0~1或0~2)。
粒子速度v的结构和位置完全一致,也是同样维度的向量。初始速度通常设置为在位置范围内随机初始化,或者设为0,让粒子在迭代过程中逐渐探索。
速度和位置的更新公式是:
v(t+1) = w * v(t) + c1 * r1 * (pbest - x(t)) + c2 * r2 * (gbest - x(t)) x(t+1) = x(t) + v(t+1)
其中:
- w是惯性权重,控制粒子保持原有速度的能力。常用的做法是让w在迭代过程中从0.9线性递减到0.4,这样前期粒子搜索范围大、探索充分,后期收敛稳定、精细搜索。
- c1和c2是学习因子,c1越大粒子越倾向于向自身历史最优位置靠拢(认知部分),c2越大粒子越倾向于向群体最优位置靠拢(社会部分)。常见设置是c1 = c2 = 2,也有用c1 = 1.5、c2 = 1.5的,差别不大。
- r1和r2是[0,1]之间的均匀随机数,每个维度独立生成,用于增加搜索的随机性。
- pbest是粒子i的历史最优位置,gbest是整个种群的历史最优位置。
每次迭代更新完位置后,需要检查边界。我用的方法是:位置越界的维度直接拉回边界值,同时把该维度的速度取反并乘以衰减系数(比如0.5),避免粒子频繁冲出边界。对于k和d这样的整数变量,在迭代之后用round()取整。
还有一个细节:种群规模和迭代次数不要盲目取大。光伏功率预测数据集通常几千到几万条样本,每评估一次适应度就要跑一遍KNN的交叉验证,计算量不小。种群规模设20~30,迭代次数设30~60就已经能收敛得不错。我试过把种群设到100、迭代设到200,效果提升极其有限,但运行时间直接从几分钟飙升到半小时以上,完全不划算。
2.3 适应度函数怎么设计
适应度函数是整个PSO-KNN模型里最关键的环节,因为它决定了粒子“好坏”的评价标准。光伏功率预测中常见的评价指标有平均绝对误差(MAE)、均方根误差(RMSE)、平均绝对百分比误差(MAPE)、决定系数R²。PSO的目标函数选择,直接影响优化方向。
我推荐用交叉验证下的MAE作为适应度值,原因有两个:第一,MAE对异常值的敏感度比RMSE低,而光伏数据中偶尔会有传感器异常或通信丢包导致的离群点,用RMSE会被这些离群点主导优化方向,导致模型把太多“精力”花在拟合异常值上;第二,MAE的数值量级和功率的单位一致,解释起来更直观。
但注意,只用验证集或者只用训练集的误差来算适应度都不靠谱。只算训练集误差,会导致选出的超参数在训练集上效果好看,但泛化能力差,本质上就是过拟合;只算验证集误差,则验证集本身就参与了参数选择,后续再拿测试集评估时效果会被虚高。最稳妥的做法是在PSO迭代过程中用交叉验证:把训练集划分成5折,每折轮流作为验证集,其余4折作为训练集训练KNN并预测,计算5折平均MAE作为该粒子的适应度值。
具体步骤如下:
- 将训练样本随机打乱后均分为5份。
- 对每个粒子,将位置解码成k、d、w。
- 循环5次:每次取其中1份作为验证集,其余4份作为参考集,用KNN回归预测验证集的功率值,计算MAE。
- 5次MAE求平均,作为该粒子的适应度。
这里有一个工程细节:交叉验证过程中要保证KNN每次都能从参考集里找到足够多的近邻样本,所以数据量太少(比如少于200条)的时候5折交叉验证的参考集会变得更少,KNN的效果会受到明显影响。这种情况下可以把折数减到3,或者干脆用留出法。光伏实测数据一般不少,一般不会遇到这个问题。
适应度值越小说明粒子位置越优,所以PSO的目标是最小化适应度。在编写MATLAB代码时,每个粒子的适应度单独计算,算完保存到数组里,然后更新pbest和gbest。需要提示的一点是:每次迭代结束后,如果gbest对应的适应度值连续多代不再下降,可以考虑对当前最优粒子做一次局部精细搜索(比如在其邻域内再用小步长试探几个点),这比让所有粒子继续漫无目的地飞效率更高。
3. MATLAB代码结构拆解与一键运行指南
3.1 代码整体架构和数据格式说明
收到这套代码之后,第一件事是把整个文件夹的结构看清楚。我建议把代码文件按以下方式组织:
main_pso_knn.m主程序,一键运行的入口data_process.m数据读取和归一化模块pso_optimize.mPSO优化核心函数knn_predict.mKNN回归预测函数fitness_func.m适应度计算函数plot_result.m结果可视化模块
主程序里不要写一大坨不可拆分的代码,一是维护困难,二是想换数据集或者换参数的时候会很痛苦。虽然标题说“一键运行”,但对于实际工程使用来说,还是建议把功能模块分开。
数据格式方面,这份代码默认使用Excel表格输入,逻辑如下:每一行是一条时间记录,第一列是时间戳(可以不用,代码里默认忽略),中间几列是气象和功率特征,最后一列是待预测的功率值。特征列建议包含历史功率(比如前1小时、前30分钟的功率)、辐照度、组件温度、环境温度、湿度、风速等,具体特征数量和顺序需要和代码里的列索引对应。
运行之前的三个预处理步骤很关键,不要跳过:
- 去掉明显异常的数据行,比如功率为负、辐照度超过物理上限、时间戳重复的行。
- 检查是否存在连续缺失值,如果有,用前后均值插值填充,或者直接删除整行。
- 对特征列和功率列分别做min-max归一化。功率列的归一化很重要,因为后面的KNN距离计算是在特征空间里做的,但适应度计算用的误差指标必须回到真实的功率量纲(kW或MW)下才有意义,所以预测完成后要反归一化回原量纲再算误差。
3.2 PSO主循环和KNN回归的核心片段
PSO主循环的MATLAB代码逻辑可以用下面这一段来说明。注意这里只列核心部分,完整的行列注释在代码里会更详细。
% PSO参数设置 pop_size = 25; % 粒子数 max_iter = 50; % 最大迭代次数 dim = 2 + num_features; % 粒子维度:k + 距离类型 + 特征权重 w_max = 0.9; % 惯性权重上限 w_min = 0.4; % 惯性权重下限 c1 = 2.0; % 个体学习因子 c2 = 2.0; % 群体学习因子 % 粒子位置和速度初始化 pos = zeros(pop_size, dim); vel = zeros(pop_size, dim); for i = 1:pop_size pos(i,1) = randi([1, 30]); % k值随机初始化 pos(i,2) = randi([1, 3]); % 距离度量类型随机初始化 pos(i,3:end) = rand(1, num_features) * 2; % 特征权重0~2随机初始化 end % 计算初始适应度 for i = 1:pop_size fitness(i) = fitness_func(pos(i,:), train_x, train_y); end pbest = pos; % 个体历史最优位置初始化为当前 pbest_fitness = fitness; [gbest_fitness, idx] = min(fitness); gbest = pos(idx,:); % 主迭代 for iter = 1:max_iter w = w_max - (w_max - w_min) * iter / max_iter; % 惯性权重递减 for i = 1:pop_size r1 = rand(1, dim); r2 = rand(1, dim); vel(i,:) = w * vel(i,:) + c1 * r1 .* (pbest(i,:) - pos(i,:)) ... + c2 * r2 .* (gbest - pos(i,:)); pos(i,:) = pos(i,:) + vel(i,:); % 边界处理与整数参数还原 pos(i,1) = min(max(round(pos(i,1)), 1), 30); pos(i,2) = min(max(round(pos(i,2)), 1), 3); pos(i,3:end) = min(max(pos(i,3:end), 0), 2); % 重新计算适应度并更新个体最优 fitness(i) = fitness_func(pos(i,:), train_x, train_y); if fitness(i) < pbest_fitness(i) pbest(i,:) = pos(i,:); pbest_fitness(i) = fitness(i); end % 更新全局最优 [min_f, min_idx] = min(pbest_fitness); if min_f < gbest_fitness gbest_fitness = min_f; gbest = pbest(min_idx,:); end end % 记录收敛过程 convergence(iter) = gbest_fitness; endKNN回归函数的核心并不复杂。MATLAB自带的knnsearch函数可以返回近邻索引,然后对索引对应的标签值求加权平均即可:
function pred = knn_predict(x_train, y_train, x_test, k, dist_type, weight) % 先对训练和测试特征做加权处理 train_w = x_train .* weight; test_w = x_test .* weight; % 计算距离矩阵 if dist_type == 1 D = pdist2(test_w, train_w, 'euclidean'); elseif dist_type == 2 D = pdist2(test_w, train_w, 'cityblock'); else D = pdist2(test_w, train_w, 'chebychev'); end % 找最近的k个样本索引 [~, neighbor_idx] = sort(D, 2); neighbor_idx = neighbor_idx(:, 1:k); % 用近邻对应标签的平均值作为预测值 pred = mean(y_train(neighbor_idx), 2); end网上很多版本用的是自带的fitcknn函数,但那个是基于分类器的,回归问题要用fitcknn还需要额外改标签类型,不太方便。我更推荐直接手写这段近邻回归逻辑,代码可控性好,也方便后续改成加权KNN或者kd-tree加速。
3.3 运行步骤与结果图的含义
代码拿到手以后,建议按下面几个步骤执行:
第一步,打开main_pso_knn.m,修改数据文件名和路径,确认Excel里特征列和标签列的索引与代码一致。第二步,运行主程序。在默认参数下,PSO迭代50次、种群规模25,处理1500条样本左右的光伏数据,运行时间大约在3到8分钟之间,取决于电脑CPU性能。第三步,程序结束后会生成三张图:适应度收敛曲线、测试集预测值与真实值对比曲线、误差分布直方图。
第一张图——适应度收敛曲线,横轴是迭代次数,纵轴是适应度值(MAE)。如果曲线前期快速下降,后期趋于平缓,说明PSO收敛正常。如果曲线到迭代结束还在明显下降,说明迭代次数不够,需要把max_iter调大。
第二张图——预测值与真实值对比曲线,是你最需要关注的图。横轴是测试集样本点序号,纵轴是功率值。真实值是一条曲线,预测值是另一条曲线,两条曲线越贴合说明预测效果越好。特别注意预测曲线在功率陡升陡降段的跟随情况,如果预测比真实延迟了若干个时间点,说明特征里缺少影响功率变化趋势的关键变量,或者k值偏大导致预测过于平滑。
第三张图——误差分布直方图,横轴是预测误差,纵轴是频次。正常情况下误差分布应该接近零附近的对称钟形分布。如果误差直方图明显偏向正方向(预测普遍偏低),可以考虑是否需要在特征里加入“时间趋势项”或“天气类型编码”。
我拿到这套代码时第一件做的事情就是看预测对比曲线在多云天气片段的表现。因为光伏功率预测真正的难点不在晴天——晴天的功率曲线接近钟形,任何模型都能预测得很好——而在多云天气下出力快速频繁波动的时候。KNN的核心优势就是能通过“找相似历史天气”来捕捉这种波动模式,所以如果你在测试集里刻意选取一些阴晴交替的日子来评估,会更能看出PSO优化后的KNN到底有几斤几两。
4. 实测过程中的效果分析与避坑记录
4.1 结果怎么看:误差指标与预测曲线
模型跑完以后,不要只看一张预测对比图就下结论。我习惯用四个指标来综合判断:MAE、RMSE、MAPE和R²。
MAE反映整体平均偏差,RMSE对大幅偏差更敏感(因为误差被平方了),MAPE反映相对误差水平(尤其适合评估不同量级时间段的预测效果),R²反映模型对功率变化的解释能力。四者应该结合起来看:如果一个模型的MAE很低但RMSE很高,说明大部分样本预测得很好,但某些极端时段出现了较大的偏差,这时候要去检查是不是k值设置得太小,导致个别异常样本被过分参考;如果MAE和RMSE都不高,但MAPE很高,通常说明预测误差集中在功率值很小的时段——比如夜间功率趋近于0,很小的绝对误差也会导致相对误差百分比巨大,这种情况其实可以接受,不用过度纠结。
预测对比曲线方面要留意训练集和测试集的效果差异。如果训练集上R²接近99%、误差几乎为零,但测试集上误差明显增大,说明模型过拟合了,这时候优先降低特征权重最大值或者适当增大k值。如果训练集和测试集的误差差距不大但都偏高,说明特征本身的预测能力不足,需要补充新的气象特征,而不是继续调KNN的超参数。
我在自己的模拟项目X中跑出来的典型结果大致是:MAE约为装机容量的6%~8%,RMSE约为装机容量的9%~12%,R²在0.92到0.95之间。具体数字受数据质量影响很大,不一定代表你手上的数据也能达到同等水平,但如果R²低于0.85,大概率不是代码问题,而是输入特征和功率之间的关联太弱,或者数据预处理环节没有清理好异常值。
4.2 实际踩过的坑和排查方法
我把实际运行中比较容易踩的坑整理成了一张速查表,供参考:
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
程序报错Undefined function 'pdist2' | MATLAB版本过旧,缺少统计工具箱 | 升级到较新版本,或确认已安装Statistics and Machine Learning Toolbox |
| PSO全程不收敛,适应度值几乎不下降 | 粒子数太少或迭代次数不够 | 调大pop_size到30以上,max_iter到80以上,看收敛曲线是否开始下降 |
| 测试集预测值全部趋同,曲线像一条直线 | k值被优化到接近30的上限,导致过度平滑 | 缩小k的搜索范围到1~15,或者改用加权KNN |
| 预测曲线在突变处严重延迟 | 特征权重被PSO优化得过小 | 检查输出权重,看哪个特征权重接近0,考虑去掉该特征 |
| 每次运行得到的最优k值和误差都不一样 | PSO随机初始化导致结果有波动 | 固定随机种子(用rng(2024))确保实验可复现 |
| 归一化后反变换回来的预测功率出现负值 | min-max反归一化公式中的min和max与实际不符 | 检查mapminmax保存的ps结构体是否使用了正确的功率列 |
| 交叉验证速度极慢,适应度计算耗时太大 | 每次适应度都重新对全训练集算距离矩阵 | 如果样本量超过5000条,对训练集先做随机抽样(如每类特征工况下抽样)再跑CV |
其中,固定随机种子这件事值得多说一句。PSO算法本身带有随机性,粒子的初始位置、速度更新时的随机因子都会影响最终搜索结果。如果你不固定随机种子,那么同样的数据、同样的代码,每次运行得到的最优k值和测试集误差可能都有细微差别。这在论文实验或工程报告里是非常忌讳的。解决办法很简单,在主程序开头加一行rng(2024),或者任意一个固定整数,确保每次运行的随机序列一致,结果可复现。
4.3 关于随机性、数据划分和复现的补充
数据划分也是影响结果可信度的关键因素。做光伏功率预测时,千万不要随机打乱全部样本后再划分训练集和测试集,因为光伏数据是强时间序列,相邻时间点的功率高度相关。如果随机划分,训练集和测试集里会出现大量“将来预测过去”的数据泄露,测试集误差会虚低,工程上完全没有参考价值。
正确的做法是按时间先后划分:比如前80%的时间段作为训练集,后20%作为测试集。这样可以模拟“用历史数据训练模型,预测未来功率”的真实应用场景。如果你在代码里看到的是随机划分方式,建议立刻改成按时间划分。这个改动对最终结果的影响非常大,我之前就踩过这个坑——随机划分时R²能到0.97,改成按时间划分以后降到0.91,虽然数字变“难看”了一些,但这才符合实际工程的场景。
另外,如果用交叉验证评估模型,也要注意交叉验证折间的划分不能打乱时间顺序,而应该使用“扩展窗口”或“滑动窗口”的策略。比如第一折用前20%作为训练集预测接下来10%,第二折用前30%作为训练集预测再接下来的10%,以此类推。PSO迭代过程中为了计算速度,可以先用随机打乱的交叉验证粗筛超参数,但最终评估时一定要用时间顺序划分的数据来汇报结果,否则论文和工程报告都站不住脚。
5. 后续还能怎样扩展这套思路
5.1 二进制PSO做特征选择
上一部分提到特征权重优化,这已经是一种软特征选择,因为权重趋于0的特征对距离计算的贡献微乎其微。但如果想更进一步,可以用二进制PSO(BPSO)做硬特征选择。BPSO的粒子位置不再是连续的权重值,而是0和1构成的向量,1表示保留该特征,0表示剔除该特征。
这种做法的好处是最终模型只保留对预测贡献最大的少数几个特征,降低数据采集成本,也提升模型的稳健性。比如原始数据有10个特征,BPSO可能选出其中5个,KNN只用这5个特征计算距离,效果不仅不比全部特征差,反而可能因为剔除了噪声特征而更好。
在MATLAB里实现BPSO只需要改动粒子的编码和更新方式:速度更新公式不变,但位置更新使用的是sigmoid函数变换。具体就是计算S(v) = 1 / (1 + exp(-v)),然后以概率S(v)决定该维度取1,否则取0。值得注意的是,BPSO的适应度函数里建议在误差指标基础上加一个小的惩罚项,比如误差加上“保留特征数 / 总特征数”乘以一个权重系数,这样才不会出现把所有特征都选上才最优的情况。
5.2 加权KNN与距离度量细化
普通KNN回归对选出的k个近邻的功率值求的是算术平均,但这里面有个隐含问题:距离最近的样本和排名第k的样本,在相似度上是有差异的,理应给更近的样本更大的话语权。加权KNN的思路正好解决这个问题,最常见的做法是用距离倒数的平方作为权重:
w_i = 1 / (d_i^2 + eps) pred = sum(w_i * y_i) / sum(w_i)
这样距离越近的样本对预测结果影响越大,距离远的样本影响小,预测曲线在功率突变处的跟随性会明显改善。在PSO的编码里,可以在原有参数基础上加一个“是否使用加权”的整数开关,或者把距离权重公式中的指数也作为连续参数一并优化。
另外,距离度量上除了欧氏距离、曼哈顿距离、切比雪夫距离之外,还能考虑动态时间规整(DTW)距离。DTW对时间序列的形状相似度更加敏感,尤其适合光伏这种相邻时段出力曲线形态相关的数据。不过DTW的计算复杂度比欧氏距离高不少,如果训练集样本量在几千条以上,每次适应度计算都会比较慢,需要权衡是否引入。
5.3 混合模型的思路
如果单纯用PSO-KNN跑出来的误差还不能满足要求,可以考虑和信号分解方法组合,比如先对光伏功率序列做变分模态分解(VMD)或完全自适应噪声集合经验模态分解(CEEMDAN),把原始功率序列拆分成若干个不同频率的分量,然后再对每个分量分别建立PSO-KNN模型,最后把各分量的预测结果叠加得到总功率预测值。
这个混合方案的核心逻辑是:光伏功率序列可以看成是趋势项加多个波动项的叠加,分解之后每个分量的平稳性更好,KNN在平稳序列上寻找相似近邻显然更加可靠。不过这类方法的计算量会增加好几倍,单层PSO-KNN变成了多层的融合模型,调参和维护的复杂度也随之上升。
如果是对比精度优先的学术实验,混合模型通常比单一模型高2到4个百分点的精度。如果是工程实施,我更建议先把单模型的特征工程和异常数据处理做好,因为很多项目里编码不当、数据泄漏导致的问题远比算法不够先进造成的影响大。
在跑这个项目的过程里,我个人最深刻的体会是:这套代码能不能产生好结果,最关键的并不是PSO那几十行更新公式,而是你给KNN喂进去什么样的特征、用什么样的方式划分数据、选择什么样的适应度函数。同样的超参数组合,放晴天数据上能跑出很高的R²,放到复杂天气场景下就可能被打回原形。所以不管你后面是想把模型做得更精细,还是直接用到实际数据上,都建议先花时间把数据清理和特征分析做好。PSO-KNN只是帮你把KNN这个简单模型发挥到极限的工具,而真正决定预测上限的,始终是你对数据本身的理解。