光伏电站的出力曲线做过新能源数据分析的人应该都不陌生每天从日出开始爬坡午间达到峰值傍晚回落归零。表面看起来都是“一座山”但你要是把一整年的曲线叠在一起看会发现形态千差万别——晴天是干净利落的单峰多云天是锯齿状的起伏阴雨天是又矮又平的“小土丘”。这篇内容想聊的就是怎么用MATLAB把这些曲线自动分类核心方法就是用K-means聚类。把每条日曲线当成一个样本让算法自己找出“晴天类”“多云类”“阴雨类”并且能直接落地的完整流程。对做光伏功率预测、电站运行分析、调度策略研究的同学来说这套东西几乎是必备工具。我尽量把预处理、特征工程、K值选择、代码实现和踩坑经历都讲透。1. 光伏曲线聚类到底在解决什么问题1.1 聚类结果能用在哪些地方先说个实际场景。你在做光伏功率预测最简单粗暴的做法是用历史数据训练一个模型然后不管明天什么天气往里一扔就出预测值。结果往往是晴天预测得不错一到多云天误差大得离谱阴雨天又出现系统性偏低。原因其实不复杂——光伏出力曲线在不同天气下的形态差异太大了。晴天的曲线基本是平滑单峰多云的曲线频繁波动阴雨天的曲线整体低平。用同一个模型去描述这三种完全不同的规律本身就是为难它。如果在建模之前先做一次聚类把历史曲线分成几个典型模式然后针对每个模式单独建预测模型效果会明显改善。这是聚类在光伏领域最常见的应用也是我最初做这个项目的原因。聚类结果还有几个很实用的方向调度与并网识别出极端天气日连续阴雨、剧烈波动提前安排储能充放电策略或备用容量。电站体检同一区域、同一时段的电站曲线如果某个电站经常被分到和自己历史模式不同的类别大概率是设备故障、遮挡或者数据采集问题。典型日选取做容量规划、仿真计算时不需要几千天的数据每个聚类簇抽一条中心曲线就能代表该区域的主要出力模式。1.2 为什么选K-means而不是其他聚类方法既然要聚类为什么一定是K-means这个选择我是综合考量过的。光伏日曲线是高维数值型数据。一条按5分钟间隔采样的日曲线有200多个点K-means处理这种数据非常自然——直接计算欧氏距离迭代更新簇中心速度很快几百上千条曲线几秒钟就能出结果。MATLAB里kmeans函数封装得相当成熟自带Replicates、MaxIter等参数写起来几行代码就完事。对比其他方法DBSCAN能处理不规则簇形但对高维数据距离度量敏感参数调节很麻烦层次聚类结果直观但计算复杂度偏高。至少在光伏曲线这个场景K-means的性价比是最高的。当然K-means也有明显的限制——它倾向于发现球形簇对噪声敏感而且K值需要自己定。这些问题不是无解的我们可以在预处理阶段把曲线归一化、剔除异常值用肘部法则和轮廓系数来确定K再配合kmeans初始化策略避免陷入局部最优。后面我会逐个展开。2. 拿到数据别着急跑算法预处理与特征工程2.1 原始数据里的那些坑很多新手拿到数据直接把全部曲线丢给kmeans函数结果跑出来乱七八糟。问题往往不在算法而在数据本身。光伏出力数据常见的污染来源采样间隔不一致有的电站5分钟一个点有的15分钟有的甚至前半年5分钟、后半年改成了1分钟。必须统一重采样到同一时间轴。夜间零值光伏夜间出力为0这些点对聚类没贡献反而拉低距离计算的有效性。建议只截取日出到日落时段比如夏季的06:00到19:00。缺失和负值逆变器故障、通信中断会导致整段缺失电流传感器偏移可能产生微小的负功率读数。负值直接置0或剔除缺失较多的天数直接从样本里删掉。异常跳变水平轴上偶尔出现突然冲到满发又在几分钟内回落的“毛刺”大概率是数据误码需要用滑动窗口做平滑或设置功率变化率阈值。我的处理习惯是这样的先用30天数据画一个堆叠图肉眼扫一遍整体形态再看单条曲线是否有明显异常。堆叠图在MATLAB里就是循环plot而已但这一步能帮你建立对数据的直觉后面调参会省很大力气。2.2 归一化怎么做才公平归一化是光伏曲线聚类里最容易被低估的一步。我见过不少项目聚类出来的簇中心曲线形状几乎一样只是高低不同——这就是归一化没做好的典型表现。同一个光伏电站夏天晴天的峰值功率可能是10MW冬天晴天可能只有6MW。如果我们不做归一化K-means会倾向于把相同量级的曲线聚在一起从而把“夏季晴天”和“冬季晴天”拆成两个簇而多云天因为峰值低、整体数值偏低可能被错误地和阴雨天分到一组。解决方法是按天归一化每条曲线都除以当天的峰值或者除以装机容量。归一化之后我们比较的是“形状”而不是“大小”。这样晴天无论冬夏曲线都是单峰形态自然聚到一起阴雨天无论峰值多低只要形状相似都会进同一类。有一点要注意如果做跨电站聚类最好用装机容量做标幺值而不是按天最大值归一化。因为不同电站的装机容量不同按最大值归一化之后数值都在0到1等于丢掉了电站规模信息但很多分析场景需要保留这个差异。2.3 特征选择直接用全序列还是提炼特征K-means可以直接跑原始曲线一条157维的向量就是一个样本。这种方法叫“全序列聚类”优点是信息损失少缺点是维度高、计算慢、容易引入噪声。我在实际项目中一般两种都试但更推荐先用全序列跑一版再根据业务需要做特征聚类。全序列的结果可以作为基准特征聚类的优势是解释性强、抗噪能力强。常用的特征维度包括峰值大小一天的最高出力反映辐照度和天气状况。峰值出现时间正常情况下在正午前后若偏移明显可能是云遮挡。日均出力或总发电量积分面积反映全天整体资源水平。波动次数出力曲线在一天内增减剧烈变化的次数多云天这个数值会显著偏高。平均爬坡速率单位时间内出力的变化幅度对储能调度很有参考价值。特征不是越多越好。特征之间相关性太强等于重复计入权重我建议先用corrplot看一下特征相关系数保留相互独立的主成分。如果仓库里有PCA函数也可以用PCA降到3到5维再做K-means效果往往也不错。3. K值怎么定、距离怎么算3.1 肘部法则和轮廓系数结合判断K-means最尴尬的问题就是“你到底想要几类”。这个问题没有绝对标准但有两种工具组合使用基本能给出合理的参考区间。第一种是肘部法则。计算不同K值下的总类内距离SSE随着K增大SSE必然下降但下降的速度会越来越慢。画一条K-SSE曲线找那个“拐点”——拐点之前下降很快之后趋于平缓这个拐点对应的K就是比较合理的值。在MATLAB里可以用silhouette或自己写循环跑kmeans把不同K的SSE存下来画图。第二种是轮廓系数Silhouette Coefficient。取值范围在-1到1之间越接近1说明样本和自己的簇越紧密、和其他簇越疏远。MATLAB自带的silhouette函数可以直接返回每个点的轮廓值取平均就得到整体轮廓系数。实际操作中我的习惯是先跑K从2到10记录SSE和平均轮廓系数画在一张图里看趋势。选K时如果肘部法则指向4轮廓系数指向3我会倾向于取较小的值。光伏曲线的业务解释能力比统计学上的“最优K”更重要分5个簇却解释不了第4和第5类的区别这个聚类对业务就是失败的。3.2 欧氏距离快但DTW更懂曲线K-means默认使用欧氏距离计算效率高实现简单。但欧氏距离是点对点逐个比较这意味着它要求两条曲线在时间上严格对齐。光伏曲线有一个本质特征每天时序上的相位并不固定。同样是晴天一片云可能上午10点飘过来另一天下午2点飘过来导致曲线的波动位置完全不同。用欧氏距离比较这两条“波形相同但位置不同”的曲线距离会被拉得很大算法会认为它们不是同一类。动态时间规整DTW可以解决这种相位偏移问题。它允许曲线在时间轴上“扭曲”后找相似性非常适合光伏这类具有形变特征的曲线。我在小数据集上测试过DTW聚类的轮廓系数比欧氏平均高10%左右。问题在于DTW的计算复杂度是O(n²)几百个点一天还扛得住几千条曲线循环跑K-means就非常慢了。工程上的折中方案是先用欧氏距离跑一遍如果轮廓系数不理想再把距离度量换成FastDTW近似算法或者先降采样再计算。只追求精度而忽视算力在真实项目中是走不远的。3.3 初始化方式直接影响稳定性K-means对初始质心非常敏感。初始质心选得不好很可能收敛到局部最优解每次跑出来的聚类结果都不一样。这会让结果很难复现写论文、做报告都不好交代。解决办法有两个层面。一是用kmeans初始化。kmeans的核心逻辑是让初始质心之间尽量分散从而显著降低收敛到局部最优的概率。MATLAB的kmeans函数默认使用的就是这种策略所以不用自己手写。二是多次重复运行。设置Replicates参数为10或者20算法会从不同初始点出发跑多轮最后返回所有轮次中SSE最小的结果。虽然计算时间增加了但换来的是稳定的结果。还有一个小技巧设置随机种子。在调用kmeans之前执行rng(42)可以固定随机过程让每次运行结果完全一致。写论文的时候这招特别有用审稿人复现你的结果不会发现差异。4. MATLAB完整实现与代码详解4.1 整体框架设计整个项目我分成了三个模块数据准备、K值评估、聚类与可视化。这样拆的好处是调试方便改参数不用从头跑也方便后续接入真实数据。数据准备加载光伏日曲线数据重采样、剔除异常、按天归一化输出一个N天 x M点的二维矩阵。K值评估用肘部法则和轮廓系数绘制曲线结合业务确定K。聚类与可视化执行K-means聚类绘制簇中心曲线、各簇样本叠影图和PCA降维散点图。4.2 模拟数据准备脚本为了让你能直接跑通整个流程我用MATLAB生成了三类模拟光伏曲线晴天、多云、阴雨。每类数据在形状、峰值、波动程度上都有差异算法分起来比较清晰。%% 生成模拟光伏日曲线数据 clear; clc; rng(42); % 固定随机种子保证结果可复现 % 时间轴06:00 到 19:005分钟间隔 t 6:5/60:19; n length(t); % 157个采样点 % 基准曲线钟形正午达到峰值 base exp(-((t-12.5).^2) / 8); base base / max(base); % 每类样本数量晴天80条多云60条阴雨45条 NperClass [80, 60, 45]; X []; trueLabels []; for c 1:3 for i 1:NperClass(c) curve base; switch c case 1 % 晴天平滑单峰 轻微噪声 curve base .* (0.85 0.15 * rand(1, n)); case 2 % 多云叠加随机波动 ripplePeriod randi([8, 15]); ripplePhase 2 * pi * rand; ripple 0.45 * sin((1:n) / ripplePeriod * 2 * pi ripplePhase) .* base; curve base ripple; curve max(curve, 0); case 3 % 阴雨整体低矮 curve 0.35 * base .* (0.6 0.4 * rand(1, n)); end X [X; curve]; trueLabels [trueLabels; c]; end end fprintf(数据规模%d 天每天 %d 个采样点\n, size(X, 1), n);4.3 确定K值这段代码会循环K从2到10计算每种情况下的总SSE和平均轮廓系数并且画出趋势图。%% 通过肘部法则和轮廓系数确定K值 Krange 2:10; SSE zeros(length(Krange), 1); avgSil zeros(length(Krange), 1); for i 1:length(Krange) k Krange(i); [~, C, sumd] kmeans(X, k, Replicates, 10, MaxIter, 500); SSE(i) sum(sumd); % 轮廓系数 [~, s] pdist2(C, X, euclidean, Smallest, 1); % 这里用MATLAB自带silhouette函数更直接 sidx kmeans(X, k, Replicates, 10, MaxIter, 500); s silhouette(X, sidx); avgSil(i) mean(s); end figure; subplot(1, 2, 1); plot(Krange, SSE, o-, LineWidth, 1.5); xlabel(K); ylabel(SSE); title(肘部法则); subplot(1, 2, 2); plot(Krange, avgSil, s-, LineWidth, 1.5); xlabel(K); ylabel(平均轮廓系数); title(轮廓系数);代码里有个小冗余说明一下前面跑了一次kmeans取SSE后面又跑了一次取轮廓系数。实际可以合并成一次调用同时返回idx和centers。贴这段主要是为了体现思路正式写的时候建议优化一下。4.4 执行聚类并可视化选定K3后正式执行聚类输出簇中心曲线图和各簇样本叠影图。%% 执行K-means聚类K3 K 3; [idx, centers] kmeans(X, K, Replicates, 20, MaxIter, 500, Display, final); % 按簇内样本数排序方便后续解释 [~, order] sort(histcounts(idx, 1:K1), descend); centers centers(order, :); idx arrayfun((x) find(order x), idx); %% 可视化簇中心曲线 figure(Color, w, Position, [100, 100, 1000, 500]); for k 1:K subplot(1, K, k); hold on; % 该簇所有样本的叠影 samples X(idx k, :); for j 1:size(samples, 1) plot(t, samples(j, :), Color, [0.7, 0.7, 0.7]); end % 簇中心曲线 plot(t, centers(k, :), r-, LineWidth, 2.5); xlabel(时刻); ylabel(归一化出力); title(sprintf(簇%d样本数%d, k, sum(idx k))); xlim([6, 19]); ylim([0, 1.2]); grid on; hold off; end运行这段代码后你应该会看到三个明显的簇一个中心曲线高且光滑对应晴天一个形状相似但带有明显锯齿对应多云一个整体低平对应阴雨。样本叠影图能直观反映每个簇内部的形态一致性。4.5 PCA降维散点图高维曲线没法直接画散点图但可以先用PCA降到前两个主成分再按簇标签着色观察聚类在低维空间的分布情况。%% PCA降维可视化 [coeff, score] pca(X); figure(Color, w, Position, [100, 100, 800, 600]); gscatter(score(:, 1), score(:, 2), idx, rgb, o, 8); xlabel(PC1); ylabel(PC2); title(K-means聚类结果的PCA投影); legend({簇1, 簇2, 簇3}); grid on;如果聚类效果好你会看到三个点群在低维空间里彼此分离边界相对清晰。如果散点图上有明显重叠说明这组聚类结果可能不够稳定需要重新考虑归一化方式、特征选择或K值。4.6 快速检验聚类效果的指标除了可视化我还会顺手输出一些量化指标方便写入报告。比如每个簇的样本数占比、簇内平均距离、Davies-Bouldin指数等。轮廓系数前面已经算过这里补充一个簇内紧密度统计。%% 聚类效果统计 fprintf(\n 聚类结果统计 \n); for k 1:K samples X(idx k, :); center centers(k, :); dists sqrt(sum((samples - center).^2, 2)); fprintf(簇%d样本数%d平均欧氏距离%.4f\n, ... k, size(samples, 1), mean(dists)); end这段统计的价值在于如果你发现某个簇样本数极少比如只有一两条同时平均距离又特别大那这个簇很可能是异常值包K值可能选大了。5. 聚类结果的判读与实际应用5.1 怎么给每个簇起名字聚类算法只负责分组不负责解释。簇分出来之后我们得根据簇中心的形态和业务知识给每个簇赋予一个可理解的名字。以模拟数据为例结果很典型簇编号簇中心特征天气类型典型应用簇1峰形高尖曲线平滑样本数最多晴天/少云预测精度最高可简化调度策略簇2峰形较完整锯齿波动明显多云/阵雨预测难度大需要额外气象数据修正簇3整体低平峰值很低阴雨/连续阴天出力低储能需提前充电备用命名不是随便贴标签而是要和气象记录做对照验证。如果手头有当天的气象数据辐照度、云量、降水可以抽样核对看聚类结果和气象记录是否吻合。吻合度高说明聚类有效吻合度低要先检查数据预处理。5.2 聚类结果怎么反哺业务拿到了几个簇之后接下来的应用就有方向感了。功率预测方面最直接的做法是“分簇建模”。用天气数值预报数据判断明天大概属于哪个簇或者计算明天曲线与各簇中心的距离然后用对应的预测模型做预测。我看到的很多案例里分簇预测比统一预测的RMSE能降低5%到10%在极端天气日提升幅度更大。调度方面簇中心曲线就是典型出力场景。比如簇3阴雨代表低出力调度系统可以提前规划储能充电策略而不是当天临时应对。簇2多云波动大就需要更频繁的AGC调节和爬坡备用。运维方面把某个电站历史曲线逐日聚类后如果某天同类曲线较少可以调出原始数据排查。遇到过的情况是数据采集器故障导致曲线形态异常被聚类算法单独分了出来反而成了故障预警信号。6. 常见问题与排查技巧实录6.1 问题速查表下面这张表是这些年在光伏曲线聚类上踩过的坑按出现频率排序对照排查基本能解决大部分问题。问题现象可能原因解决办法每次运行聚类结果不一样初始质心随机导致局部最优设置rng固定种子增大Replicates某个簇只有一个样本K选得过大或该样本是异常值减小K值或者先剔除异常日曲线簇中心曲线形状相似只是高低不同没有按天归一化量级主导了聚类按天最大值或装机容量归一化多云曲线的波动特征被吞掉特征不够敏感或全序列维度太高增加波动次数、爬坡率等特征连续阴雨天被分到多个簇K值偏大导致同类型被拆散参考肘部法则适当减小K聚类结果和天气记录不符数据时间轴对齐出问题检查时区和采样间隔统一重采样计算速度太慢数据量大且用了全序列特征降维到5维以内或降低采样分辨率6.2 几个容易忽略的细节关于归一化的细节再啰嗦一句。不要用全局Min-Max归一化比如把所有天的曲线除以整个数据集的最大值。这种全局归一化会保留“晴天比阴雨天数值大”的信息但K-means会优先根据量级分组而不是形状。光伏聚类的核心是挑形状模式按天归一化才能把重点放在形态上。关于聚类的“可解释性”。算法的评价指标再漂亮如果业务方看不懂每个簇代表什么项目就很难推进。我每次交付聚类的产出都会配一张簇中心曲线图和一张典型样本图用天气场景来解释每个簇的含义。用业务语言翻译算法结果是这类项目落地的关键步骤。关于DTW的使用时机。如果样本量不大比如几百条在K-means里换成DTW距离矩阵完全可行。MATLAB里可以先算一个DTW距离矩阵再配合层次聚类或者谱聚类使用。实测在1000条以内数据量上这种做法的精度提升还挺明显。数据量超过2000条就不建议了耗时指数级上升收益反而不明显。6.3 一个我强烈推荐的小工具最后分享一个自己一直在用的习惯聚类跑完别急着走再做一步“簇中心与真实样本的对比图”。把每个簇的中心曲线和簇内离中心最近、最远的三条样本曲线叠在一起。这张图能快速暴露聚类里的边界问题——如果最远的样本看起来已经非常不像该簇了说明这个簇太松散了。这一步不需要写多复杂的代码就是在前面的叠影图基础上多画几条突出颜色的曲线。但是它对结果判断的帮助非常大比任何聚类评价指标都直观。我在给电网客户汇报时也经常直接贴这张图对方看完基本不需要更多解释就能理解聚类结果。写在最后的一些体会这套K-means光伏曲线聚类流程我在真实电站数据上跑过很多遍。最大的感受是聚类这门课在学校里学的是算法推导到了工程里难的是数据预处理和结果解释。同样的代码换个数据集可能结果就完全不一样关键还是你对自己手里数据的理解有多深。如果你手头有真实的光伏数据我建议拿到数据先做一个全年的日曲线堆叠图肉眼看完再跑算法。你会发现K-means给你的答案往往和你第一眼的直觉是吻合的——这个算法最擅长的就是把你能感觉到但说不清的模式用一条簇中心曲线清晰地表达出来。
