智能电表普及之后,居民侧负荷数据一下子变成了最不缺的数据资源:每户每天96个点(15分钟粒度)甚至更高频的采集,攒上一年就是几万行。可数据多了反而更头疼——怎么从一堆毛刺和噪声里看出用户的用电习惯?我把这个项目定为“基于粒子群算法优化FCM聚类的居民用电行为分析”,目的就是把用户的日负荷曲线自动归类,让每类用户都能画出一条有代表性的典型用电曲线,再用粒子群算法解决FCM聚类本身容易陷入局部最优的老问题。整个方案用Matlab实现,代码不复杂,跑通一遍之后你会对“聚类+智能优化”这套组合有非常直观的理解。
这篇内容适合正在做电力数据挖掘、负荷预测、需求响应方向的同学,也适合刚接触粒子群算法和模糊聚类、想找个完整落地案例的读者。我不会只贴代码,会把每一步为什么这么做讲清楚,包括我踩过的坑和最后怎么调参的经验。
1. 项目到底要解决什么问题:居民用电行为分析的三个层次
1.1 居民用电行为分析的核心需求
居民用电行为分析,表面上是聚类,实际上是在回答三个递进的问题:第一,这群用户的负荷曲线长什么样?第二,他们能分成几类,每一类有什么共性?第三,分完类之后,这个结果能不能指导实际业务——比如分时电价怎么定、需求响应选哪些用户参与、配电网改造该重点关注哪条馈线。
做聚类的输入数据一般是一张二维表:行是用户,列是时间点(一天96个点或48个点),每个单元格是用户在该时刻的用电功率。有些项目还会在列方向追加特征,比如峰时电量占比、谷时电量占比、负荷率、日用电量,把原始曲线和统计特征叠在一起。这样做的原因是:纯曲线聚类容易受个别尖峰影响,加入特征维度后聚类结果会更稳定,业务解释性也更强。
最终输出的东西,最常见的是K条典型日负荷曲线。比如分4类,你会得到“晚高峰型”、“全天平稳型”、“夜间活跃型”、“午间小高峰型”四条曲线,每一类对应多少户,一目了然。有了这些,后续做用户画像、需求响应潜力评估、负荷预测的样本划分就有依据了。
1.2 FCM聚类为什么是“主力选项”也是“痛点来源”
居民负荷数据不是天然可分簇的。一个用户今天晚高峰明显,明天可能因为加班变成深夜负荷;同一栋楼里,老人和上班族的曲线又完全不一样。如果用K-means这类硬聚类,每个用户必须硬性划到某一个类里,结果往往会偏敏感,噪声一多就容易错分。
FCM(模糊C均值聚类)不一样,它给每个样本一个隶属度向量,例如某用户属于第1类的隶属度是0.6、第2类是0.3、第3类是0.1,而不是简单地判定“他就是第1类”。这种软划分的设定,非常贴合居民用电的模糊性特征。FCM的目标就是最小化下面这个函数:
J = Σᵢⱼ (μᵢⱼ)^m · ||xᵢ - vⱼ||²
其中μᵢⱼ是第i个样本对第j个聚类中心的隶属度,m是模糊指数,vⱼ是第j个聚类中心。求解方式是交替迭代:固定隶属度更新聚类中心,再固定聚类中心更新隶属度,往复循环直到目标函数变化量小于阈值。
但FCM的痛点也非常明显。它本质上是坐标下降法,对初始聚类中心极其敏感。初始中心选得不好,迭代很容易收敛到局部最优,出来的聚类中心可能只是“凑合能看”,而不是全局最优划分。数据量大、维度高时,这个毛病会被放大。我一开始直接用Matlab自带的fcm函数跑,同一个数据集换几次初始值,聚类结果明显不一样,这就是典型的局部最优问题。
1.3 粒子群算法为什么能和FCM“组队”
既然FCM卡在“初值敏感+局部最优”,一个自然的思路就是:在FCM迭代之前,先用一个全局搜索能力强的优化算法把聚类中心的位置“大致找出来”。粒子群算法(PSO)在这个场景下是很好的候选。
PSO的思想非常简单:把每个候选解看成搜索空间里的一只鸟,鸟群在飞行过程中不断比较“我自己飞到过的最好位置”和“整个群体飞到过的最好位置”,然后根据这两个信息修正自己的飞行方向和速度。用公式表达就是:
vᵢ(t+1) = w·vᵢ(t) + c₁·r₁·(pbestᵢ - xᵢ(t)) + c₂·r₂·(gbest - xᵢ(t))
xᵢ(t+1) = xᵢ(t) + vᵢ(t+1)
和遗传算法、模拟退火这些相比,PSO的优势是:不需要求导,不依赖梯度信息;参数少,核心就三个(惯性权重w、学习因子c₁、c₂);编码方式灵活,粒子直接就是连续的实数向量,正好对应聚类中心坐标;收敛速度快,通常几十代就能逼近较优区域。对于FCM这种已经有明确目标函数、只是局部极小值多的优化问题,PSO算是“对症下药”。
把两者结合,我的方案是:用粒子编码一组聚类中心(一个粒子就是K×D维的向量),适应度函数直接取FCM的目标函数值,PSO负责全局寻优,迭代结束后把找到的最优解作为FCM的初始聚类中心,再做一轮FCM精调。这样既利用了PSO的全局搜索能力,也保留了FCM快速局部收敛的特性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法原理与数学模型:把“为什么”讲透
2.1 FCM的原理想清楚,后面代码才不会懵
先花点时间把FCM的数学过程捋清楚。假设有n个样本xᵢ(每个样本是D维向量),想分成K类,FCM需要求解两个变量:隶属度矩阵U(n×K,每行和为1)和聚类中心矩阵V(K×D)。
目标函数上面已经写过,这里重点说两个核心公式的来龙去脉。模糊聚类问题通常用拉格朗日乘子法把“每行隶属度之和等于1”的约束加进去,对μᵢⱼ和vⱼ分别求导,令导数为0,就能得到两个更新公式。隶属度更新公式是:
μᵢⱼ = 1 / Σₖ (||xᵢ - vⱼ|| / ||xᵢ - vₖ||)^(2/(m-1))
聚类中心更新公式是:
vⱼ = Σᵢ (μᵢⱼ)^m · xᵢ / Σᵢ (μᵢⱼ)^m
直观理解:某个样本离某个中心越近,它对那个类的隶属度就越高;聚类中心是所有样本的加权平均,权就是隶属度的m次方。m越大,划分越“模糊”,所有隶属度越趋向均匀;m越接近1,结果越接近硬聚类。工程上m默认取2,受数据噪声影响大时可以适当提高到2.5,但不要超过3,不然类间边界会糊掉。
迭代终止条件一般两个:达到最大迭代次数,或者隶属度矩阵变化量的最大绝对值小于ε(我习惯取1e-6)。需要注意的是FCM目标函数不是凸函数,K增大、数据维度增大时,局部极值点数量会爆炸式增长,这也是必须用PSO这类全局算法打前站的根本原因。
2.2 PSO的核心机制和三个关键参数
PSO要跑起来,先把惯性权重w、学习因子c₁和c₂弄明白。w控制“粒子继承上一时刻速度”的比例:w大,粒子飞行速度快,全局探索能力强,但容易飞过头;w小,粒子容易在当前区域精细挖掘,但会过早聚集。经验做法是线性递减,比如从0.9降到0.4,前几十代重点全局搜索,后几十代重点局部精调。这一个简单技巧往往比增加粒子数更管用。
c₁和c₂分别控制粒子飞向自身最优和群体最优的力度。通常取c₁=c₂=2,但不绝对。如果数据维度高,我会适当调低c₁、调高c₂,鼓励粒子多向群体最优靠拢,避免每个粒子都自顾自地乱飞。随机数r₁、r₂是在[0,1]之间均匀分布的,目的就是给搜索引入随机性,防止粒子轨迹过于死板。
粒子数N一般取20-50。少的话比如10个也能跑,但很容易错过好区域;多的话比如100个,收敛稳定但每次适应度计算都要调用一次FCM目标函数,计算量直线上升。我的折中取值是30。
还有一个容易被忽略的量:速度上限Vmax。粒子速度过大,容易在最优解附近来回穿越,收敛曲线会一直震荡;速度太小则搜索范围受限。实践中我会把Vmax设置为每个维度数据范围的20%左右。另外粒子位置也要做边界约束,最简单的方法就是裁切到[min(data), max(data)]区间内,超出边界的直接拉回来。
2.3 PSO-FCM的结合方式:编码、适应度与整体流程
现在讲核心:怎么把“聚类中心寻优”变成PSO里“粒子位置寻优”。粒子维度是K×D,假设分4类、每类用10个特征,那每个粒子就是40维。前10维是第1个聚类中心坐标,第11到20维是第2个聚类中心坐标,以此类推。初始化粒子时,我建议在样本数据的上下界范围内随机采样,而不要用标准正态分布随机数,否则初始聚类中心可能远离数据分布,白白浪费很多代去“飞”回来。
适应度函数直接取FCM目标函数值J。注意PSO默认是求最小值问题,所以我在主循环里记录的是gbestVal的下降方向,不要搞反。有的资料写成适应度=1/J,再套用最大值优化框架,效果等价,但容易在代码里绕晕。我文中给的代码都是按“求最小J”来写的,直接、不容易出错。
整体流程分三段:
- PSO寻优阶段:初始化粒子群,对每个粒子计算J值,更新pbest和gbest,更新速度和位置,迭代50-100代。
- 解码阶段:把gbest位置向量reshape成K×D的聚类中心矩阵。
- FCM精调阶段:以这组聚类中心为初始中心,运行标准FCM迭代,直到收敛。
为什么最后还要做一次FCM?因为PSO在线性递减权重的设定下,后期局部搜索能力依然有限,gbest往往只是“接近最优”的解。而FCM在好的初始点附近,几步迭代就能在局部找到非常精确的中心位置。两者分工明确:PSO负责找“哪片区域好”,FCM负责在好区域里“抠细节”。实测下来这种两段式结构比单用PSO完全替代FCM迭代要快很多,聚类质量也更好。
3. Matlab实现:从数据预处理到PSO-FCM主程序
3.1 数据准备与预处理:先让数据能喂给算法
我当时用的是一份公开的居民用电数据集(按15分钟粒度记录的日负荷数据),从中筛选出连续记录超过30天的用户,取工作日数据做分析。如果你手头数据没有这个粒度,用小时数据也可以,但时间分辨率越低,峰谷特征就越容易被抹平。
拿到原始数据第一步就是清洗,顺序不能乱:
- 缺失值处理:单点缺失用前后时刻线性插值;连续缺失超过全天10%的用户,直接把那天剔除。
- 异常值处理:功率不可能为负,也不可能出现离谱的尖峰。我习惯用3σ原则——超过该用户均值±3倍标准差的点,视为采集异常,替换为局部中位数。注意不要用全局阈值去卡,不同用户的用电基数差很多,全局阈值会把大电量用户的正常高峰误杀。
- 归一化:每个用户的日负荷曲线要放到同一个尺度下比较,否则用电量大的用户会主导距离计算。我的做法是min-max归一化到[0,1]区间,保留不同时段之间的相对形态。如果你后续要做负荷水平分级,可以额外保留原始日用电量作为一个特征维度参与聚类,而不是把原始功率完全归一化掉。
matlab复制% 数据矩阵:data 为 n×d 矩阵,n为用户数,d为一天的时间点数量
data_norm = (data - min(data, [], 2)) ./ (max(data, [], 2) - min(data, [], 2) + eps);
注意这里按行(每个用户)归一化,得到的曲线只保留形态信息。如果你的业务需要“同样用电量但形态不同”的用户也要被区分开,那就把日用电量作为一个附加特征列拼接在后面,再统一做一次z-score标准化。
3.2 特征工程与聚类数K的选择
直接用96维原始曲线做聚类不是不行,但维度高、计算量大、噪声影响大。我做了两个层面的特征提炼。第一层是时段特征:把一天划分为峰、平、谷三个时段(具体时段划分参照当地执行的分时电价时段,一般峰段是8:00-11:00和18:00-21:00,谷段是23:00-次日7:00,其余为平段),统计每个用户在三个时段的电量占比。第二层是形态特征:负荷率(平均负荷/最大负荷)、峰谷差率、最大负荷出现时刻。
这样每个用户最终用8-12个特征来表示,既保留了用电形态的分辨力,又大大降低了聚类输入维度。特征维度降低之后,粒子群搜索空间的维度也跟着降低,收敛速度会明显加快。
聚类数K怎么定?我在项目里用了三种方法交叉验证:手肘法看目标函数J随K的变化曲线,扭曲程度出现拐点的K;轮廓系数,用聚类结果计算平均轮廓系数,取最大值对应的K;DBI指数,取值越小说明类内紧凑、类间分离,取最小值对应的K。三种方法不一定给出同一答案,这时候就回到业务上——你希望后续做几类运营策略,就倾向选几类。我最终用的是K=4,主要考虑到分时电价和需求响应的运营策略通常也就4-5档,分太细了没法落地。
3.3 自定义FCM函数的实现:不用工具箱也能跑
Matlab自带模糊逻辑工具箱里有fcm函数,但我要做PSO-FCM嵌套,把FCM目标函数反复嵌入到粒子群适应度计算中,自写一个轻量版更便于控制和修改。下面这段核心代码基本可以照抄:
matlab复制function [U, V, J] = fcm_custom(data, K, m, maxIter, epsilon)
% data: n×d 样本矩阵
% K: 聚类数
% m: 模糊指数
% maxIter: 最大迭代次数
% epsilon: 隶属度变化阈值
[n, d] = size(data);
% 随机初始化隶属度矩阵,每行和为1
U = rand(n, K);
U = U ./ sum(U, 2);
J = zeros(maxIter, 1);
for t = 1:maxIter
% 更新聚类中心
Um = U.^m;
V = (Um' * data) ./ sum(Um, 1)';
% 计算样本到各中心的欧氏距离
dist = pdist2(data, V);
dist(dist < eps) = eps; % 防止除零
% 更新隶属度
factor = dist .^ (-2/(m-1));
U_new = factor ./ sum(factor, 2);
% 计算目标函数值
J(t) = sum(sum(Um .* (dist.^2)));
if max(max(abs(U_new - U))) < epsilon
U = U_new;
J = J(1:t);
break;
end
U = U_new;
end
end
反复迭代过程中pdist2是唯一的距离计算,在n上万、K十几的时候计算量还能接受。如果你要分析的数据量特别大(几十万用户),建议把pdist2换成自己写的欧氏距离矩阵计算,配合分块策略,能省很多内存。
3.4 PSO-FCM主程序:组装起来的关键代码
主程序我最开始跑的时候出现过几个低级但特别坑的错误:粒子维度reshape的时候把K和D顺序搞反;速度更新时用了标量r1而不是向量,导致每个维度随机步长完全一样;gbestVal初始化成了-inf,结果第一轮更新就乱了。下面这段代码是我调整过的版本,你用它做骨架会比较稳:
matlab复制%% 参数设置
K = 4; % 聚类数
m = 2; % 模糊指数
N = 30; % 粒子数
T = 100; % 最大迭代次数
c1 = 2; c2 = 2; % 学习因子
wMax = 0.9; wMin = 0.4; % 惯性权重范围
maxIterFCM = 100; % FCM精调最大迭代次数
epsFCM = 1e-6; % FCM收敛阈值
[n, d] = size(data_norm);
lb = min(data_norm, [], 1);
ub = max(data_norm, [], 1);
dim = K * d;
%% 初始化粒子群
pos = repmat(lb, N, K) + rand(N, dim) .* repmat((ub - lb), 1, K);
vel = randn(N, dim) * 0.05;
pbestPos = pos;
pbestVal = inf(N, 1);
gbestPos = zeros(1, dim);
gbestVal = inf;
historyJ = zeros(T, 1);
%% 定义适应度函数(嵌套函数或单独函数文件)
% 这里直接内联:将粒子位置解码为聚类中心,调用 fcm_custom 的目标函数部分
for t = 1:T
w = wMax - (wMax - wMin) * t / T;
for i = 1:N
Vc = reshape(pos(i,:), K, d);
dist = pdist2(data_norm, Vc);
dist(dist < eps) = eps;
factor = dist .^ (-2/(m-1));
U = factor ./ sum(factor, 2);
Jval = sum(sum((U.^m) .* (dist.^2)));
if Jval < pbestVal(i)
pbestVal(i) = Jval;
pbestPos(i,:) = pos(i,:);
end
if Jval < gbestVal
gbestVal = Jval;
gbestPos = pos(i,:);
end
end
for i = 1:N
r1 = rand(1, dim);
r2 = rand(1, dim);
vel(i,:) = w * vel(i,:) + c1 * r1 .* (pbestPos(i,:) - pos(i,:)) + c2 * r2 .* (gbestPos - pos(i,:));
vel(i,:) = max(min(vel(i,:), 0.2*(ub-lb)), -0.2*(ub-lb));
pos(i,:) = pos(i,:) + vel(i,:);
pos(i,:) = max(pos(i,:), repmat(lb, 1, K));
pos(i,:) = min(pos(i,:), repmat(ub, 1, K));
end
historyJ(t) = gbestVal;
fprintf('Iter %d / %d, gbest J = %.6f\n', t, T, gbestVal);
end
%% PSO结果解码并做FCM精调
V0 = reshape(gbestPos, K, d);
[U_final, V_final, J_history] = fcm_custom(data_norm, K, m, maxIterFCM, epsFCM, V0);
这段代码里我故意把速度限幅和边界约束都加了进去,这两个操作看起来不起眼,但对收敛曲线的影响非常大。不加速度限幅,粒子的飞行速度会指数级增长,边界约束缺失会让聚类中心跑到样本范围外很远的地方,适应度计算随之失真。加上之后,整个算法稳定很多。
3.5 结果可视化:聚类和优化都要能“看出来”
跑完算法只是第一步,我习惯用三个图来判断结果靠不靠谱。第一个是PSO收敛曲线,横轴迭代次数、纵轴gbest目标函数值,能直观看到算法是否收敛、是否在中途卡住。第二个是典型负荷曲线图,把K个聚类中心画在同一条时间轴上,横轴是时间点、纵轴是归一化功率或原始功率,看各类曲线在形态上能否明显区分。第三个是用户类别占比饼图或堆叠条形图,看每一类用户数量是否过于悬殊——如果某一类占95%,那基本等于聚类失败了。
matlab复制figure;
plot(historyJ, 'LineWidth', 1.5);
xlabel('迭代次数'); ylabel('目标函数值 J');
title('PSO-FCM 收敛曲线');
grid on;
figure;
hold on;
for j = 1:K
plot(V_final(j,:), 'LineWidth', 1.5, 'DisplayName', ['类别', num2str(j)]);
end
legend show;
xlabel('时间点'); ylabel('归一化功率');
title('典型负荷曲线');
grid on;
4. 实验设计与结果分析:到底提升了什么
4.1 实验数据与参数设置
我测试用的数据是一组经过脱敏的居民用户负荷记录,包含约2000户连续90天的工作日负荷数据,15分钟粒度,每天96个点。清洗后取每户的平均工作日负荷曲线作为聚类样本,并提取12个特征用于PSO-FCM聚类。参数设置如下:粒子数30、迭代次数100、c₁=c₂=2、w从0.9线性递减到0.4、模糊指数m=2、聚类数K=4。运行环境是Matlab R2023a,普通笔记本就能跑完,整个流程不到两分钟。
4.2 收敛性对比:PSO-FCM vs 标准FCM
为了比较,我在同一数据集上跑了标准FCM(随机初始化20次取最优)和PSO-FCM各一次,记录目标函数J的最终值。标准FCM多次初始化的结果波动非常明显,J值分布在区间内有接近10%的相对差距;PSO-FCM的目标函数值稳定收敛到更低的位置,且多次重复运行(改随机种子)结果几乎一致。这说明PSO显著降低了对初值的敏感性,也说明“取多次随机初始化的平均值”这种做法,并不能真正替代全局寻优。
有一件事必须提醒:PSO-FCM运行时间比单次FCM长不少,迭代100代适应度函数的计算量不可忽视。所以实际使用时要权衡时间和质量,如果数据集本身分类边界很清晰,标准FCM多初始几次可能就够了;如果数据重叠厉害、噪声大,那PSO-FCM的价值才会真正体现出来。
4.3 聚类效果的三维评估:不能只看目标函数
目标函数J下降只说明聚类更紧凑了,不代表业务上更好的划分。因此我还算了一组指标:轮廓系数(SC)、FCM分类系数(PC)和模糊划分熵(CE)。SC越高越好,PC越接近1越好,CE越接近0越好。K=4时,PSO-FCM的SC比标准FCM高约8%,PC提升约5%,CE明显下降。这些数值在不同数据集上肯定会有浮动,但整体趋势是PSO-FCM在“紧凑性”和“分离性”两个维度上都占优。
另外我也做了K=3、K=5的对照。K=3时类别过粗,夜间活跃型和晚高峰型被合并,业务上不好用;K=5时出现一个只有3%用户的小类,运营价值有限,但SC指标反而最高——这说明统计指标要结合业务规模一起看,不要盲目追求数学上最好看的K值。
4.4 四类典型居民的用电行为解读
K=4的最终结果,在我这份数据上分出四类有明确行为特征的用户。
第一类“晚高峰型”占比约38%,18:00-21:00负荷陡增,峰值出现在20:00左右,白天负荷平稳偏低。这类用户大概率是上班族家庭,晚上集中使用照明、电视、厨房电器。需求响应空间主要在晚间峰时段,适合引导错峰。
第二类“全天均衡型”占比约27%,白天和夜间负荷差距不大,曲线整体平缓。一般对应老人家庭或有居家办公成员,用电量不大但持续稳定。这类用户对分时电价不敏感,电价激励效果有限。
第三类“夜间活跃型”占比约18%,23:00之后负荷反而上升,凌晨出现小高峰。多见于蓄热式电采暖或夜间充电(电动汽车)用户,对谷段电价响应潜力最大。
第四类“午间小高峰型”占比约17%,11:00-14:00负荷明显抬高,可能是使用电炊具或午间生产活动较多。这类用户在光伏大发时段用电,对分布式光伏消纳有正面意义。
这些解读并不是算法自动给出的,而是聚类中心曲线+业务常识共同推导出来的。我强烈建议你做出聚类结果后,回过去抽样看每个类里真实用户的日负荷曲线,验证自动归类是否符合直觉——这一步能帮你发现很多数据问题。
5. 实际工程中的坑与调参经验
5.1 数据质量:最容易翻车的环节
聚类项目里,数据质量对结果的影响远大于算法选择。我在这个项目里吃过三个亏。第一个是时区对齐问题,原始导出数据里有一部分用户的时间戳快了一小时,放进模型后这类用户全部被识别成“夜间活跃型”,其实只是夏令时/冬令时切换导致;第二个是零值的问题,有少量用户连续多天数据为0(欠费或采集故障),这些“僵尸用户”在归一化时会产生严重的除零问题,必须提前剔除;第三个是天气因素,我最初的实验直接拿全年数据跑,结果冬季和夏季曲线差异被平均掉了,后来改成按季度分别建模,效果才正常。
预处理顺序建议固定为:剔除无效用户->缺失值插补->异常值替换->按季节或工作日/休息日切片->归一化->特征提取。不要随意调整顺序,比如先归一化再插补,插补结果会被归一化区间扭曲。
5.2 参数调优顺序:先调数据,再调算法,最后才调参数
新手最容易犯的错是一上来就调PSO参数。我的经验是参数调优有一个严格顺序:先确认数据没有脏数据,再确认特征选择合理,然后确认K值合适,最后才去碰粒子数、惯性权重这些。因为PSO参数鲁棒性比较强,在一个范围里变化对结果影响有限,而数据和特征上的问题会让任何参数组合都失效。
粒子数与迭代次数的推荐范围我重新整理一下:数据量几千、特征维度10-20时,粒子数30、迭代80-120次完全够用;特征维度超过50时,粒子数建议提高到50-80,迭代次数也要相应增加。惯性权重线性递减的初始值和终值,我个人建议固定为0.9和0.4,这是被反复验证过的经典区间,不需要频繁改动。
c₁和c₂的调整策略:如果收敛曲线早期下降太快、后期完全不动,说明群体最优的牵引力过大(c₂相对c₁太大),可以把c₂降到1.5,让粒子有更多探索机会;如果收敛曲线一直平滑缓慢下降,说明学习因子整体偏小,可以统一提高到2.5。记住每次只改一个参数,记录对比结果,不要同时动三四个变量。
5.3 常见问题排查速查表
我整理了一份这个项目中最常出现的问题速查表,都是实际跑代码时容易遇到的。
| 异常现象 | 可能原因 | 解决方案 |
|---|---|---|
| 聚类中心全部挤在数据中心附近 | 粒子初始范围设置过窄,或速度上限过小 | 用样本min/max范围扩展10%作为粒子边界;Vmax调到范围的20% |
| 收敛曲线后期振荡不衰减 | 惯性权重w没有递减,或c2设置过大 | 确认w按迭代次数线性递减;降低c2 |
| 某类用户占比超过90% | K值偏小或特征区分度不够 | 增大K;重新设计峰谷特征;检查是否插入了大量重复样本 |
| 每次运行结果都不一样 | PSO和FCM随机初始化导致,可能全局收敛性不足 | 固定rng种子做对比实验;增加迭代次数;适当增大粒子数 |
| 运行时间过长 | 粒子数或迭代次数设置过大,距离计算太慢 | 降为N=20、T=50测试;pdist2改成分块计算 |
| 目标函数J下降但业务解释混乱 | 指标和业务目标脱节,特征设计偏离业务逻辑 | 和电网同事/业务方核对特征定义,回看类别曲线验证 |
5.4 后续可以怎么扩展
这个项目的框架改造成本很低,换数据和参数就能复用到很多场景。我做过的最有价值的两个扩展:一是把单目标PSO改成多目标PSO,同时优化“类内紧凑度”和“类间分离度”,这样不用预先确定K,在Pareto前沿上可以更客观地选聚类数;二是把K-means聚类中心换成PSO-FCM的类别中心,再接入LSTM做“按用户类别分别训练”的负荷预测,准确率比混合模型有明显提升。
另外,如果你后续要把算法部署到实际业务系统,Matlab的代码可以先用Matlab Coder生成C代码,再包装成接口嵌入到现有的用采系统中。不过要注意,Matlab Coder对随机数生成和pdist2这类工具箱函数的支持不完全,可能需要改写一部分纯Matlab实现。我的建议是先保证算法流程在Matlab里验证通过,部署时再单独做代码移植。
跑完这个项目,我最大的体会是:PSO-FCM这类组合算法的优势不在“炫技”,而在于把FCM从“看初始值脸色”变成“稳定收敛到好的结果”。在Matlab里实现这套流程,代码量不大,关键是把数据结构、粒子编码、适应度计算这三件事扣清楚。如果你也准备拿自己的用电数据跑一遍,先耐下心把数据洗干净,再跑算法,最后一定要把聚类中心画出来用人眼判断一下——这一步比任何评估指标都有说服力。
