☰
光伏曲线聚类:K-means算法MATLAB实现与参数选择全解析
2026/10/1 22:45:13 网站建设 项目流程

做光伏功率预测这两年,我越来越觉得,气象条件对光伏出力曲线的影响是"一刀切模型"最大的敌人。同样是夏天,晴天和暴雨天的出力曲线从幅值到形态都差了一整个量级,如果拿一个通用模型去拟合所有天气,结果往往是晴天类样本不过拟合、阴雨天类样本欠拟合,两头都不讨好。解决这个问题的实用思路,就是先把历史日功率曲线聚成几类典型的"天气形态",再针对每一类单独建模或分配不一样的模型权值。这个分类工作,我最终落成了一套基于K-means算法的光伏曲线聚类MATLAB实现方案。这篇文章就把这套方案的完整思路、代码细节、参数选择过程和踩坑记录都摊开来讲,希望能给正在做光伏数据分析、功率预测或电站运维分类的朋友省下几个晚上的调试时间。不管你是刚接触聚类的学生,还是已经在跑预测模型的研究生和工程师,这套实现都可以直接改改数据路径就能用。

1. 光伏曲线聚类这件事到底解决什么问题

1.1 从一条曲线到一个集群

光伏发电功率曲线,说白了就是电站一天从早到晚的出功时序。横轴是时间,纵轴是功率或者归一化后的出力系数。由于天气系统的随机性,这条曲线的形态千变万化:晴天是一条接近正弦的光滑拱形,多云天是一条剧烈波动的锯齿状折线,阴天则整段都被压得很低,雨天则可能连拱形都谈不上,完全是噪声主导的乱跳。

所谓光伏曲线聚类,就是由数据自动把历史中每一天的曲线归到少数几个"典型形态"里。这个过程不需要人工预先给每天打天气标签,算法根据曲线之间的距离远近自动划分。我习惯用一个生活里的例子解释这件事:把衣柜里所有的鞋按风格分类,运动鞋、皮鞋、靴子各自堆成一堆,聚类做的就是类似的事情——不过它不看颜色不看品牌,只看"长得像不像"。

这里要强调一个关键思路:聚类不是分类,它不需要先验标签。也就是说,你不需要先知道某天是"晴"还是"多云"来训练模型,算法自己会从数据中找出结构。这在实际工程中太重要了,因为光伏电站历史数据里往往只有功率值,未必有同步的气象观测记录,或者气象数据颗粒度太粗,没法直接对应到每一天的形状上。

1.2 聚类结果能用在哪些地方

把曲线聚类之后,衍生应用非常直接。

第一个大头是短期功率预测。目前主流的做法叫"分类型建模":先把历史日曲线聚成K类,然后对每一类单独训练预测模型(比如BP网络、LSTM或者回归树)。做预测当天先根据气象预报判断当日曲线大概率属于哪一类,再调用对应模型输出功率。实测下来,这种策略比单模型硬扛所有天气要稳得多,精度能提升不少。第二个场景是异常数据识别。聚类完成后,那些落不进任何一个主流簇的曲线、或者轮廓系数极低的点,往往是设备故障、数据采集异常、限电停运导致的坏样本。把这些样本揪出来做标记,对后续所有数据驱动的分析工作都非常有价值。第三类是资源评估和电站选址。聚类统计结果可以告诉你某地区一年里"晴天形态"出现的天数占比,这比单纯看年总辐射量要直观得多,对估算发电量、测算收益都有用。最后,储能配置也能用上:不同类别曲线的峰谷差异和波动剧烈程度是不同的,聚完类后按类统计出力特征,就能更合理地设计储能功率和容量。

2. 为什么用K-means:算法原理与MATLAB选型逻辑

2.1 K-means的核心流程

K-means的核心思想简单到可以用四句话讲完:先在样本空间里随机放K个质心,然后把每个样本划给离它最近的质心,接着重新计算每个簇内所有样本的均值作为新质心,循环往复直到质心不再变化。

用光伏曲线来说,就是把成百上千条日功率曲线看作高维空间里的点,每条曲线都是这个空间里的一个坐标向量。比如每天取12个小时、15分钟一个采样点,那么一天就是48个数值,也就是48维空间里的一个点。K-means的目标就是在这个空间里找到K个中心点,使得所有样本到各自中心的欧氏距离平方和最小。

MATLAB里封装好的kmeans函数,一句代码就能跑起来:

[idx, C] = kmeans(X, K);

其中X是样本矩阵,每行一条日曲线,每列是一个时刻的出力值;K是设置的聚类数量;idx是每个样本所属的类别编号;C是K个聚类中心,每行就是一个"典型日曲线"。

2.2 三个容易忽略的选型细节

为什么偏偏是K-means,而不是别的聚类算法?我在最初也犹豫过要不要用层次聚类或者DBSCAN。实际对比之后,K-means有三个不可替代的优势:第一,计算效率极高,几千条曲线、几百维数据,几分钟之内就能跑完,而层次聚类一旦样本量过万,距离矩阵的内存占用就很头疼了;第二,MATLAB原生支持,kmeans函数参数丰富,配合evalclusters还能做自动K值寻优,开发效率高;第三,聚类中心直接可视化成平均曲线,工程上解释起来非常方便。

当然K-means也有它的硬伤:需要预设K值,对初始质心敏感,而且使用欧氏距离时对曲线形态的"相位偏移"无能为力。如果你遇到的场景里曲线波形相似但是峰值时刻明显错位(比如不同电站因经纬度不同导致日出时间不同),那K-means直接跑的聚类结果可能会混乱。这种情况一般需要先做时间对齐,或者改用DTW距离搭配k-medoids方法。

还有一个容易被新手忽略的点:归一化策略。我在多个项目里对比过,如果直接拿原始功率数据去算欧氏距离,那么幅值大的晴天曲线必然把所有距离主导了,最后聚类出来的就只是"大曲线、中曲线、小曲线",而不是"晴天形态、多云形态、阴天形态"。想要按形态聚类,应该对每条曲线做最大值归一化,把每天的曲线都先除以当天的峰值,这样晴天多云阴天在"绝对大小"上的差异就被抹平了,剩下的就是纯粹的形态差异。

3. 数据预处理:决定聚类成败的第一关

我做过不止一个项目,前期数据清洗仓促,后面聚类结果一团糟,返工成本极高。光伏数据预处理在我看来是整个流程中最不能省的一步,甚至可以说它决定了聚类效果的80%。

3.1 光伏数据的三个"坑"

第一是夜间零值段。光伏电站夜里没有出力,从晚上七八点到第二天凌晨五六点,曲线全是一条直线压在零上。这些零值在欧氏距离计算里会形成一大片无意义的"共性",把所有曲线都朝着"白昼长度"的方向推,而不是朝天气形态推。实际表现就是冬天的短日照曲线跟夏天的长日照曲线被分成两堆,那显然不是我们要的。

第二是日出日落时间随季节漂移。同样的晴天曲线,冬天早上七点才起功率,夏天六点不到就有出力了,如果不做对齐,同类天气的曲线之间的距离反而比不同类天气的曲线还大。

第三是数据缺失和坏值。逆变器通讯中断、传感器故障、限电弃光,都会导致曲线局部缺失或者出现离谱的尖峰毛刺。这些噪声样本如果不处理,聚类中心就会被带偏。

3.2 完整预处理流程

我一般按下面这套流程走,每一步都有明确的目的:

  1. 剔除无效日曲线。检查每天的完整采样点数,如果有效数据不足全天的70%,这一整天的曲线直接不要。

  2. 截取有效日照窗口。根据电站纬度估算全年最早日出和最晚日落时间,取一个保守的公共窗口。我这个项目里取的6:00到18:00,15分钟一个采样点,一共48个点。这一步的目的就是甩掉夜间零值段。

  3. 缺失点插值。窗口内如果有零散的缺失点,用前后时刻线性插值补上。插值只适用于短缺口,超过连续2小时丢失的曲线建议舍弃。

  4. 形态归一化。每条曲线除以当天的最大值,让所有曲线的峰值变成1。如果某天曲线最大值太离谱(比如超过装机容量1.2倍),按坏数据处理。

  5. 再次筛查。归一化后如果曲线出现连续多个零点的"平顶"形态,多半是限电或设备故障,标记出来,聚类时去掉。

这套流程走完,数据基本就干净了。我习惯把最终参与聚类的曲线条数和剔除条数记录下来,写进报告里,方便后面复现时对比。

4. MATLAB代码实现:从数据导入到聚类完成

我用MATLAB 2026b做这套流程,代码量不大,但每一段都值得仔细说说参数的含义。

4.1 数据加载与结构设计

假设数据是Excel表格,每一行是一条日曲线,每一列是一个采样时刻的出力值。用readmatrix直接导入:

% 导入原始数据,假设已按6:00-18:00截取并归一化 rawData = readmatrix('pv_curves_normalized.xlsx'); % 检查维度 fprintf('样本数: %d, 维度: %d\n', size(rawData, 1), size(rawData, 2));

读进来之后我建议先做一次行方向检查,用isnan和isinf函数扫描一遍,MATLAB聚类函数对NaN和Inf是零容忍的,只要数据里有一个NaN它就直接报错。虽然有点啰嗦,但这一步能省下不少排查时间。数据矩阵要求每一行是一个样本,每一列是一个特征维。这跟sklearn的习惯一样,别把行列搞反了。

4.2 kmeans函数参数配置

kmeans函数看起来简单,但参数怎么配差别很大。我最关心的是四个参数:

K = 4; % 聚类数量 repeatTimes = 20; % 重复次数 maxIter = 500; % 最大迭代次数 distance = 'sqeuclidean'; % 距离度量 [idx, C, sumd] = kmeans(rawData, K, ... 'Distance', distance, ... 'Replicates', repeatTimes, ... 'MaxIter', maxIter, ... 'Display', 'final');

Replicates参数是最容易被忽略的。K-means的初始质心是随机放的,一次运行很可能落到局部最优解。设成20的意思是从20组不同的随机初始质心出发,各跑一遍完整的迭代,最后返回组内平方和最小的那一组结果。这个参数基本不增加调试难度,但能显著提高结果稳定性,我建议至少设10次以上,数据量大时可以适当降一点。

MaxIter设成500一般够了,K-means收敛很快,通常不到几十次迭代就稳定了。如果设置太小,算法提前截断,返回的质心可能还没稳定。Display 'final'会在命令行输出每次最优收敛信息,方便看运行状态。

4.3 聚类结果可视化

聚类跑完后,最有用的可视化是把所有曲线按簇分开,叠加画出来,同时画上簇中心。

% 定义时间轴 t = 6:0.25:18; % 6:00 到 18:00,步长15分钟 % 分簇画图 figure; hold on; colors = lines(K); for i = 1:K subplot(2, 2, i); clusterData = rawData(idx == i, :); plot(t, clusterData', 'Color', [colors(i, :) 0.15]); % 半透明曲线 hold on; plot(t, C(i, :), 'Color', colors(i, :), 'LineWidth', 2.5); title(sprintf('簇 %d (样本数: %d)', i, size(clusterData, 1))); xlabel('时刻 (h)'); ylabel('归一化出力'); xlim([6 18]); ylim([0 1.1]); grid on; end

这个图能一眼看出分簇质量:好的聚类是每个子图里曲线形态高度一致,簇中心曲线平滑有代表性;不行的聚类则是子图里曲线形状五花八门,说明K值或者预处理流程有问题。

另外建议顺便统计每个簇的平均峰值、平均波动率、方差特性,这些特征后面写报告时意义很大。比如一个簇的中心曲线峰值稳定在0.9以上且方差极小,基本就可以断定是稳定的晴空日。

5. K值怎么选:肘部法则、轮廓系数与Gap Statistic

K值是K-means唯一需要人为设定的超参数,也往往是争议最大的一环。我最早做聚类时只会用肘部法则,后来发现光看肘部曲线远远不够,得几种方法交叉验证再加业务判断一起上。

5.1 三种方法的具体实现

肘部法则的判断逻辑很直观:随着K增大,簇内样本到质心的距离平方和(SSE)会单调下降,但当K超过数据本身的真实簇数之后,SSE的下降幅度会显著变缓,曲线出现一个"肘部"拐点。MATLAB里手动算SSE很简单:

Klist = 1:8; SSE = zeros(size(Klist)); for i = 1:length(Klist) [~, ~, sumd] = kmeans(rawData, Klist(i), 'Replicates', 10); SSE(i) = sum(sumd); end figure; plot(Klist, SSE, 'o-'); xlabel('K'); ylabel('SSE');

轮廓系数则是从另一个角度衡量——它同时考察样本与自身簇内样本的紧密度,以及样本与最近的其他簇样本的分离度。每个样本的轮廓系数在-1到1之间,越接近1越好。MATLAB的evalclusters函数可以一条命令做这个事:

eva = evalclusters(rawData, 'kmeans', 'Silhouette', 'KList', 1:8); plot(eva); bestK = eva.OptimalK;

Gap Statistic的原理更精细:它拿实际数据的SSE曲线跟一组均匀随机数据的SSE曲线做对比,找实际数据相对随机数据"优势最大"的位置。MATLAB里同样可以用evalclusters,把评价标准换成'gap'即可。这个方法说服力更强,但计算量大一些。

5.2 我实际怎么判断

以我手头这个电站数据为例,做完预处理之后还剩大约340条曲线。用三种方法跑出来的结果分别是:肘部法看起来3到5都说得通,轮廓系数最优是4,Gap Statistic也指向4。三种方法交叉印证,K=4基本是稳的。

但我还想强调一点:统计指标不能替代业务判断。K=4对应的四类曲线分别是晴空平滑型、多云波动型、阴天低平型、降雨杂乱型,每一类都有清晰可解释的天气意义。如果某个K值下有一类曲线内部形态仍然混杂,即使统计指标再好看,我也不建议选那个K值。聚类结果最终是要拿到业务里去解释和使用的,解释不了的结果就是不可用的。

6. 常见问题排查与避坑实录

最后这部分,我把实际操作中遇到过的典型问题整理成一张排查表,这些问题我几乎在每次讲课或者带新人时都会重复一遍,每一行都是真金白银换来的经验。

问题现象可能原因解决方案
每次运行kmeans结果都不一样初始质心随机导致落入不同局部最优增大Replicates,建议10-20次,并固定数据顺序
聚类结果中某条明显异常的曲线独立成一类坏数据占了主导距离预处理阶段严格筛查异常峰值、连续平顶、缺失过多曲线
轮廓系数低且为负值的样本很多曲线存在相位偏移,欧氏距离失效先做时间对齐,或改用DTW距离的k-medoids方案
聚类出来的簇就是"高幅值/低幅值"没有做形态归一化,幅值主导距离每条曲线先除自己最大值再做后续分析
kmeans报错说数据含NaN或Inf导入数据有缺失或无效值调用前用isnan/isinf全面扫描,补值或删行
有一部分样本孤立但数量很少可能是极端天气或数据质量问题数据量不足总样本5%的簇要重点核验原始数据

除了表里的问题,还有一个我特别想单独说说的坑:标准化方式的选择。有人贪图方便直接对整个矩阵做一次zscore标准化,这在很多聚类场景里是合理的,但光伏曲线场景下这种全局标准化会抹掉不同天气之间的幅值层级差异,导致聚类时"晴天"和"阴天"这两个在工程上必须分开的类别被揉在一起。我在预处理章节已经说过,如果目标是按"形态"聚类,就每条曲线除以自身最大值;如果目标是按"出力大小"分层,才考虑是否保留绝对幅值信息。这个选择一定要在跑算法之前想清楚,不要等出结果了再回头改。

还有一个跟可视化相关的经验:聚类完成后不要只盯着簇中心曲线看,一定要叠加画出簇内所有原始曲线。簇中心毕竟是平均后的结果,平滑效应会把很多信息藏掉。只有看到叠加图里每条曲线紧紧地贴着中心,才能确认这个簇是"干净"的。如果叠加图里曲线散得厉害,那不管统计指标多好,都要重新审视数据或换距离度量。

做这套方案最大的体会是:聚类本身只占整个工作量的三成,数据清洗和特征设计花了七成时间,但恰恰是那七成的功夫决定了最后三成的效果上限。一开始我以为K-means就是准备个矩阵调个参数就完事了,真正落地才发现,对光伏曲线这种带有强烈昼夜周期性和季节漂移的数据,预处理阶段每一个细节都直接影响聚类结果的含义。最后再分享一个小技巧:聚类完之后把每类的3条典型曲线截图打印出来贴在工位上。后面做功率预测模型时,拿到新一天的气象预报,看一眼曲线大概形态就能预判它落在哪个簇里,这个"人肉分类先验"在模型冷启动阶段特别管用,也方便你跟非技术背景的同事沟通。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询