做居民用电行为分析,聚类几乎是绕不开的工具。最开始我用的是标准FCM(模糊C均值)聚类,跑出来的效果也还行,但有一个问题非常折磨人:同样的数据,只要随机初始值变一下,聚类中心就会漂移,甚至类别数量看起来不变,但某个类里的用户构成完全变了一副面孔。折腾了几次之后,我把粒子群算法(PSO)塞进了FCM的寻优过程里,做成PSO优化FCM的混合聚类流程,这个问题才算真正解决。
这篇文章就围绕这个方案讲透:为什么FCM在居民用电场景下这么“脆”,粒子群在里面到底优化了什么,Matlab落地时每一步该怎么写、参数怎么调,以及我从实验中踩出来的一堆坑。适合正在做负荷分析、电力客户画像、需求侧响应研究的同学,也适合对聚类算法调优和群智能优化算法感兴趣的人。
1. 为什么居民用电行为聚类要先换成FCM,又为什么要引入粒子群
1.1 居民负荷数据的特点:高维、波动大、类边界模糊
居民用户日负荷曲线通常以15分钟、30分钟或1小时为采样间隔,一天下来就是96点、48点或24点的高维向量。这个维数看起来不算极端,但它有几个非常难受的特征。
第一是波动性强。和工业负荷那种相对平稳、规律性强的曲线不同,居民用户受上下班、做饭、看电视、空调启停等随机因素影响,曲线毛刺多、峰谷随时可能偏移。今天18点的负荷高峰,明天可能变成19点半,后天又可能因为下雨而整体下移。这种波动让“类内距离”天然就大。
第二是噪声多。偶发的电器同时启动、短暂的电压异常、采集终端的通信故障,都会在曲线里留下尖刺。如果直接用原始曲线做距离计算,这些尖刺会主导聚类结果。
第三是类边界模糊。所谓“上班族”用户和“居家型”用户,没有一条能画出来的清晰分界线。很多上班族周末在家待一整天,曲线就跟居家型很像;也有不少家庭白天老人孩子在家,负荷曲线介于两者之间。这种重叠性意味着硬划分逻辑很难完全适用。
1.2 FCM的价值:软划分更贴合“一个人可能有多种用电习惯”
K-means的思路是每个样本硬生生地归到某一个类,这在物理上是“排他”的。但居民用电恰恰不排他——一个用户完全可能同时具备“白天基础负荷高”和“夜间低谷期有明显用电”两种特征,他只是在不同时段、不同场景下表现出不同的用电偏好。
FCM(Fuzzy C-Means)引入了一个隶属度矩阵 U,每个样本对每个聚类中心都有一个 0 到 1 之间的隶属度,全部隶属度之和为1。这个设计在物理意义上非常契合居民负荷:它允许一个用户 0.6 属于“上班族型”、0.3 属于“傍晚高峰型”、0.1 属于“全天平稳型”。后面做需求侧响应或精准营销时,这套概率化的归属信息比硬标签值钱得多。
1.3 FCM的软肋:目标函数非凸、初始值敏感、容易局部最优
FCM 的目标函数长这样:
[
J = \sum_{i=1}^{N} \sum_{j=1}^{C} u_{ij}^{m} \cdot |x_i - v_j|^2
]
其中 N 是样本数,C 是聚类数,m 是模糊指数,u 是隶属度,v 是聚类中心。隶属度更新公式和聚类中心更新公式都是通过拉格朗日乘子法导出的。问题在于,这个目标函数是一个非凸函数,存在大量局部极小值点。FCM 本质上是靠交替迭代(先固定中心算隶属度,再固定隶属度更新中心)去逼近一个解,初始点落在哪个“山谷”里,最终就很可能停在哪个局部极小值附近。
居民用电数据类边界模糊、重叠严重,直接放大了这个问题。同一个数据集,初始化矩阵稍微变一点,最后可能收敛到完全不同的聚类中心组合。这种不稳定在业务上很致命:昨天跑出来的用户分类,今天加了几条新数据重新聚类,类别含义就对不上了。
解决思路就是全局寻优。粒子群算法不依赖梯度信息,通过一群粒子在解空间里进行社会协作搜索,对非凸问题的全局搜索能力比传统交替迭代强得多。于是把粒子群作为外层优化器、FCM 交替迭代作为内层局部精化,就成了一个非常自然的组合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 粒子群与FCM结合的两种通用方式:初始中心引导与迭代内嵌
在具体写代码之前,得先想清楚粒子群在FCM里到底扮演什么角色。网上能看到很多“PSO-FCM”代码,实现思路大致分两类,效果差异很大。
2.1 思路A:PSO只负责搜索较好的初始聚类中心
这种方案最容易理解:先用粒子群去搜一组聚类中心,搜索完把最优粒子代表的中心作为FCM的初始聚类中心,然后交给标准FCM继续交替迭代到收敛。
粒子的位置直接编码成 C 个聚类中心的拼接向量,维度是 C×D(D 是样本特征维数)。适应度函数可以先用“每个样本到最近聚类中心的距离和”这类简单指标,也可以直接用FCM目标函数。粒子群迭代结束后,FCM 用这个较优起点做局部精化,通常几个迭代步就能收敛。
这种方式的优点是稳、快,FCM部分的迭代次数显著减少,而且基本保留了FCM在局部收敛上的精度。缺点是粒子群只优化了初始位置,没有在迭代过程中持续介入,遇到某些极端非凸情况,仍然可能从较优起点坠入不太好的局部区域,只是概率比随机初始化低很多。
2.2 思路B:粒子位置直接表示聚类中心,每个粒子内部完整执行一次FCM迭代
更“激进”的做法是把FCM的迭代过程放进粒子群的每一次适应度评估里。每个粒子代表一组聚类中心,粒子群每更新一次位置,就对每个粒子执行一次完整的FCM局部搜索,然后把搜索后得到的目标函数值作为该粒子的适应度。
这样一来,粒子群负责在整个解空间里做宏观搜索,每个粒子自身又带着FCM的局部精细搜索能力。理论上,全局寻优能力和局部收敛精度同时被强化了。
缺点是计算量成倍增加。N 个样本、C 个聚类、S 个粒子、G 次迭代,每一轮都要跑 S 次FCM内层循环,整体计算量大约是标准FCM的 S×G 倍。我在自己机器上跑 2000 个用户、96 点曲线、4 类聚类,迭代 30 代、粒子数 20,一次实验跑下来要等好几分钟。如果数据量到几万用户,不优化代码根本扛不住。
2.3 两种方案的对比与选型建议
表格对比:
| 对比维度 | 思路A:PSO优化初始中心 | 思路B:PSO内嵌FCM迭代 |
|---|---|---|
| 计算量 | 较低,接近标准FCM | 很高,约为标准FCM的S×G倍 |
| 全局搜索能力 | 中等偏上 | 强 |
| 实现复杂度 | 简单,易调试 | 较高,参数联动多 |
| 稳定性 | 比随机初始化好很多 | 最稳,但也会受PSO早熟影响 |
| 适合场景 | 样本量数千级、需要快速出结果 | 样本量中等、对聚类稳定性要求极高 |
我的建议是:如果样本量在数千级别,优先用思路B;如果样本量过万,或者需要反复调参试跑,先用思路A把整体流程跑通,再根据结果决定是否升级到思路B。很多论文里写的“PSO优化FCM”其实都是思路A,因为实验效果已经足够说明问题了——标准FCM陷入局部最优本来就是大概率事件,粒子群只要把起点拉到一个合理区域,改善就已经很明显。
3. 数据预处理与特征构造:算法跑得准不准的隐性前提
3.1 原始负荷数据形态与常见清洗
居民用户用电行为分析常见的原始数据是一张宽表,每一行是一个用户,每一列是一个采样时刻,值为该时刻的用电功率;也有的是长表,按用户ID、日期、时刻、功率四列存储。无论哪种格式,第一步都是把数据整理成“用户×时段”的矩阵,才能丢给聚类算法。
数据清洗里最容易踩的坑是“负值”和“跳变”。居民智能电表偶尔会出现负功率,常见原因是电流互感器接线问题或表计暂态异常,按行业惯例,短时负值可以置零或按前后均值修正。跳变则表现为某几个连续采样点功率骤增又骤降,比如从500W突然跳到8500W再跌回300W,这种极大概率是表计异常而非真实用电,我用的是滑动窗口内的中值滤波,窗口宽度一般为3到5个点,既能滤掉尖刺,又不会把真实峰谷削平。
空置用户要特别注意。有些房子长期没人住,功率整日接近零;有些用户在数据采集期内刚搬走,后半个月全是零。这类用户如果直接丢进聚类,会形成非常明显的“零值簇”,把整体聚类结果带偏。建议先用日用电量的阈值做一次初筛,比如连续30天日用电量低于0.5度电的用户,直接标记为空置用户单独处理,不参与聚类。
3.2 重新构造特征的必要性
直接拿96点曲线做聚类不是不行,但效果往往不如先做特征提取。原因很简单:96维数据里,真正区分用电行为的关键维度其实很少,大部分维度是冗余信息,而且高维空间里距离度量会趋于平均化,信噪比下降。
我在项目里常用的特征组合分成了三类:
- 负荷水平类:日平均负荷、日最大负荷、日最小负荷、日用电量。反映用户整体用电规模。
- 峰谷特性类:峰时段用电占比、谷时段用电占比、峰谷差、最大负荷出现时刻。反映用户在时间维度上的用电偏好。
- 稳定性类:负荷曲线标准差、变异系数、夜间用电比例(22点到次日6点的用电量占比)。反映用电行为的规律程度。
选取特征时要兼顾两点:一是对业务可解释,比如“夜间用电比例高”可以直接联想到蓄热式热水器或电动汽车充电;二是特征之间不要高度共线,比如“日平均负荷”和“日用电量”本质上是一个东西,保留一个即可。
这类特征化处理之后,数据维度降到10维以内,聚类结果比直接用96点曲线稳定得多,而且聚类中心画出来之后,业务部门一眼就能看懂,不用解释什么叫“欧氏距离”。
3.3 归一化处理:量纲统一是距离计算的前提
FCM用的是欧氏距离,特征量纲直接影响距离大小。如果“日用电量”数量级在几十度电,而“夜间用电比例”是0到1之间的小数,距离计算会被用电量主导,夜间比例形同虚设。
常用的归一化有两种:Min-Max归一化和Z-score标准化。Min-Max会把所有特征压到0到1之间,缺点是如果某个用户的负荷存在极端尖峰,会把整体尺度拉偏。Z-score对离群值更稳健,可以容忍一定程度的异常点干扰。
我给居民用电数据做Z-score用了下面这段Matlab代码:
matlab复制% 原始特征矩阵 featMat: N行 × D列
% 计算每列的均值和标准差
mu = mean(featMat, 1);
sigma = std(featMat, 0, 1);
% 防止分母为0,标准差过小的列直接保留原值
sigma(sigma < 1e-10) = 1;
% 标准化
featNorm = (featMat - mu) ./ sigma;
这里有个小细节:居民负荷里可能存在“某用户所有特征全是0”的极端样本,标准化之后仍然全是0,它们到任何聚类中心的距离都很小,会形成一个“准零值簇”。这类用户应该在清洗阶段就剔除,而不是等到聚类结束再解释为什么多了一个奇怪的类。
4. Matlab实现框架与核心代码解读
4.1 整体算法流程
我的Matlab实现按以下流程组织:
text复制1. 读取并清洗原始负荷数据
2. 提取特征矩阵
3. 归一化
4. 初始化粒子群各项参数
5. 进入粒子群迭代循环
a. 更新惯性权重
b. 更新粒子速度与位置
c. 对每个粒子执行FCM内层迭代,计算目标函数值
d. 更新个体最优和全局最优
6. 输出全局最优聚类中心
7. 用最优中心初始化FCM,做最终收敛
8. 计算轮廓系数、DBI等评价指标
9. 可视化聚类中心曲线与隶属度矩阵
主函数我一般写成这样,方便切换参数:
matlab复制%% 主参数设置
c = 4; % 聚类数
m = 2; % 模糊指数
popSize = 20; % 粒子数
maxGen = 30; % 粒子群最大迭代次数
wMax = 0.9; % 惯性权重上限
wMin = 0.4; % 惯性权重下限
c1 = 1.5; % 个体学习因子
c2 = 1.5; % 社会学习因子
rng(42); % 固定随机种子
%% 读取归一化后的特征矩阵 featNorm (N行 × D列)
%% 调用PSO-FCM主函数
[bestCenter, bestVal, U] = psoFcm(featNorm, c, m, popSize, maxGen, ...
wMax, wMin, c1, c2);
4.2 FCM核心更新公式的Matlab实现
FCM 内层迭代的核心就两个公式:
隶属度更新:
[
u_{ij} = \frac{1}{\sum_{k=1}^{C} \left( \frac{|x_i - v_j|}{|x_i - v_k|} \right)^{2/(m-1)}}
]
聚类中心更新:
[
v_j = \frac{\sum_{i=1}^{N} u_{ij}^{m} \cdot x_i}{\sum_{i=1}^{N} u_{ij}^{m}}
]
在Matlab里,这段代码我用矩阵运算直接实现,不写双层循环:
matlab复制function [U, V, J] = fcmStep(X, V, m)
% X: N×D 样本矩阵
% V: C×D 当前聚类中心矩阵
% 计算所有样本到所有中心的距离矩阵 N×C
N = size(X, 1);
C = size(V, 1);
distMat = zeros(N, C);
for j = 1:C
diff = X - repmat(V(j, :), N, 1);
distMat(:, j) = sum(diff.^2, 2);
end
% 防止除零,给距离矩阵加一个极小量
distMat = max(distMat, 1e-12);
% 隶属度更新
invDist = distMat .^ (-1/(m-1));
U = invDist ./ sum(invDist, 2);
% 聚类中心更新
Um = U .^ m;
V = (Um' * X) ./ sum(Um, 1)';
% 目标函数值
J = sum(sum(Um .* distMat));
end
这段代码每次传入当前聚类中心,返回新的隶属度矩阵和聚类中心。有个细节值得注意:distMat 需要做一次下限截断,否则当样本点正好落在聚类中心上时,距离为0会导致隶属度更新公式分母爆炸。
4.3 PSO主循环及其与FCM的衔接
粒子群部分的核心是速度与位置更新公式:
matlab复制v = w * v + c1 * rand(size(pos)) .* (pbest - pos) ...
+ c2 * rand(size(pos)) .* (gbest - pos);
pos = pos + v;
其中 w 是惯性权重,采用线性递减策略,从 wMax 递减到 wMin。这个递减设计很关键:迭代前期需要较大的 w 去探索全局空间,后期需要较小的 w 去精细搜索,如果全程用同一个 w,要么前期搜索不足、要么后期震荡不收敛。
粒子位置向量的维度是 c×D,也就是把 C 个聚类中心拉成一维向量。每个粒子的适应度评估函数如下:
matlab复制function fitness = evaluateParticle(posVec, X, m, c, D)
V = reshape(posVec, c, D);
[~, ~, J] = fcmIterToConverge(X, V, m);
fitness = J;
end
fcmIterToConverge 的作用是以 V 为初始聚类中心,执行若干步FCM交替迭代(比如10到20步),返回最终目标函数值。这里有个重要的工程决定:粒子群每次评估并不需要让FCM完全收敛,只需要做有限步迭代,得到一个能反映当前中心质量的目标函数近似值即可。等粒子群结束之后,再把全局最优粒子的位置作为FCM的初始中心,做完整的迭代收敛。这样既利用了粒子群的全局搜索优势,又避免了“粒子群内嵌FCM”带来的天量计算。
4.4 结果输出与可视化
聚类结果出来后,一般画两张图就够用。一张是各类用户的聚类中心负荷曲线图,也就是每个类在所有时段上的平均负荷走势,这张图可以直接和业务方讨论“这类用户是白天用电多还是晚上用电多”。第二张是隶属度热力图,横轴是用户,纵轴是类别,颜色深浅代表隶属度大小,能直观看出哪些用户属于“归属明确”,哪些属于“骑墙用户”。
matlab复制figure;
plot(t, centerCurves', 'LineWidth', 1.5);
xlabel('时间(h)');
ylabel('负荷(kW)');
legend(arrayfun(@(x) sprintf('类别%d', x), 1:c, 'UniformOutput', false));
grid on;
5. 实验对比与典型用电模式解读
5.1 PSO-FCM和标准FCM效果对比
我在同一个数据集上做了对比:约1500户居民的96点日负荷曲线,取夏季连续30天的平均值,聚成4类。标准FCM用随机初始化,每组参数跑20次;PSO-FCM同样跑20次。
结果非常直观:
| 指标 | 标准FCM(20次最好) | 标准FCM(20次平均) | PSO-FCM(20次平均) |
|---|---|---|---|
| FCM目标函数值 | 2.135e4 | 2.418e4 | 2.102e4 |
| 轮廓系数 | 0.532 | 0.481 | 0.564 |
| DBI | 1.286 | 1.519 | 1.207 |
目标函数值衡量的是聚类紧致度,越小说明类内样本越紧凑。标准FCM 20次运行的结果波动非常大,最好值和平均值之间差了13%左右,这说明FCM非常依赖初始点的运气。而PSO-FCM由于有粒子群的全局搜索,平均值甚至优于标准FCM的最好值,稳定性提升明显。
5.2 四类典型用电行为模式
从聚类中心曲线来看,四类用户的行为特征非常清晰:
- 第1类用户:白天外出为主,负荷曲线在深夜和凌晨接近零,早晨7点到9点出现小高峰,中午低谷,傍晚18点到21点出现最大负荷。典型对应上班族家庭,空调和炊具集中在早晚使用。
- 第2类用户:全天负荷相对平稳,白天有小幅波动,夜间没有完全归零。典型对应白天有老人或学龄前儿童在家的家庭,基础负荷高,但波动幅度不大。
- 第3类用户:夜间用电占比明显偏高,负荷曲线在22点后不降反升,凌晨1点到4点仍然维持较高水平。这类用户很可能有蓄热式电热水器或电动汽车充电桩,是需求侧响应的重点潜力客户。
- 第4类用户:整体负荷水平偏低,峰值很小,曲线的峰谷差异也不明显。对应独居年轻人或低频居住用户,用电随机性较强。
这里有一个业务上很有价值的发现:第3类用户的识别如果只靠总用电量排序,很容易被漏掉,因为这类用户的日用电量不一定最高,但夜间用电比例非常突出。特征工程设计对这类细分有着直接影响。
5.3 隶属度矩阵的附加价值
FCM输出的不只是每个用户的最终标签,还有隶属度向量。对大多数用户,某个类别的隶属度会超过0.8,归属很明确。但总有3%到5%的用户,最大隶属度只有0.4到0.5,也就是说他同时具有两三类用户的行为特征。
这类“骑墙用户”在业务上其实是最有价值的群体。比如用夜间充电特征选需求响应候选用户,隶属度恰好0.4到0.6的用户可能只是偶尔充电,但稍微给点电价激励,就可能转化为高频充电用户。基于隶属度的用户分群,比硬标签更有指导意义。
6. 调参经验与踩坑记录:让PSO-FCM在真实数据上稳定可复现
6.1 固定随机种子与多轮择优
Matlab里 rng(42) 这种固定随机种子的操作一定要养成习惯,否则换了随机种子结果就变。但要注意,固定随机种子之后,单次实验结果只能说“可复现”,并不代表它避开了所有局部最优。保险做法是先固定种子跑通流程,再换几个种子分别运行,取目标函数值最优的一组作为最终结果。
我在代码里加了一个外层循环:
matlab复制bestOverallVal = inf;
bestOverallCenter = [];
for trial = 1:5
rng(trial * 100);
[center, val, ~] = psoFcm(featNorm, c, m, popSize, maxGen, ...);
if val < bestOverallVal
bestOverallVal = val;
bestOverallCenter = center;
end
end
5次重复实验在计算量上多出4倍,但换来的稳定性非常值得。
6.2 粒子群参数调节心得
粒子群参数里最影响结果的是惯性权重 w 的取值区间。我一般用 0.9 到 0.4 线性递减,效果比较稳定。如果 w 下限设到 0.2,后期粒子速度过慢,容易早熟;如果下限保留 0.6,后期过度搜索又会导致中心点震荡,目标函数不降反升。
学习因子 c1 和 c2 都取 1.5 是比较公认的折中方案。c1 过大,每个粒子只顾自己历史最优位置,群体协作弱;c2 过大,所有粒子过早被拉到同一个全局最优位置,多样性迅速下降,同样陷入局部最优。粒子数 20 到 30 在我的实验里足够,再往上提升计算量明显,但目标函数改善非常有限。
最大迭代次数建议先设一个偏大的值(50代),观察目标函数的收敛曲线:如果30代之后曲线已经平了,说明之前的设置足够;如果50代还在下降,就需要增大 popSize 或 maxGen。收敛曲线还能暴露一个常见问题——如果下降曲线出现明显的“分段”现象,前几代大幅下降、之后突然停滞,那很可能是粒子群早熟,需要调整 w 的递减速度或增大粒子数。
6.3 FCM模糊指数m和聚类数c的敏感性
模糊指数 m 是一个常被忽略但影响极大的参数。m=2 是最常见的默认值,此时隶属度分布相对平滑,用户更容易出现“骑墙”归属。把 m 降到 1.3,隶属度会趋于锐利,每个用户几乎被强制拉到单类,聚类行为接近K-means;把 m 升到 2.5 以上,所有隶属度趋于均匀,聚类中心会向整体均值靠拢,类别差异被抹平。
我给居民负荷做聚类时,m=2 在大多数情况下效果最稳定,但如果业务方明确要求“每个用户只给一个标签”,可以考虑把 m 调到 1.5 左右,比直接用K-means柔和,又保留足够的区分度。
聚类数 c 的确定没有银弹,我用的是 DBI 和轮廓系数的组合。把 c 从2到8各跑一遍,画 DBI 曲线,取曲线拐点或局部最小点。需要注意:用电数据量大的时候,DBI 往往随着 c 增大而单调下降,这时候要在“指标最优”和“业务可解释”之间做取舍。3到5类通常是居民用电分析的合适区间,类别太多,业务部门记不住,也无法制定差异化策略。
6.4 数据层面的坑:零值用户、全天空调负荷和夏季冬季混用
开头已经提过零值用户会形成“零值簇”,这里再补两个实际场景。
第一个是“季节性用电差异”。同一批用户,夏季因为空调负荷和冬季因为电采暖,聚类结果可能完全不同。如果不做日期范围筛选,直接把一年数据混在一起聚类,聚类中心曲线会出现非常奇怪的“双峰”,业务上也没法解释。我的做法是先做季节切片,比如只取6月到8月的负荷数据做夏季用电行为分析,避免季节混用。
第二个是“用户容量等级差异”。居民用户里既有基础容量5kW的老旧小区用户,也有容量12kW以上的新建小区用户。容量等级不同,负荷绝对数值差异很大,即使用Z-score归一化,高容量用户极端值仍然会主导距离计算。如果数据里有容量字段,建议先按容量分段再聚类,或者把特征全部换成“负荷率”(实际负荷/容量),效果会好很多。
6.5 Matlab性能细节:矩阵化能救命的例子
最后说一个纯工程的问题。我最早写FCM内层迭代时,用了一个两层循环逐样本逐类目更新隶属度和中心,2000用户、96点、4类、30个粒子、30代迭代,跑一次要7分多钟。改成矩阵运算之后,直接压到1分半以内。
具体来说,距离矩阵尽量一次性算出来,用 repmat 或隐式扩展都可以;隶属度更新不要逐元素算,而是先算距离幂次,再做行归一化。聚类中心更新本质是加权平均,Um' * X 一行就能搞定。这段代码在4.2节已经给出,核心就是三个矩阵操作:距离计算、隶属度归一化、加权求和。
如果数据量再大一个数量级,Matlab 里还能用并行计算工具箱,把粒子群里的适应度评估做成 parfor。粒子群算法天生适合并行——每个粒子的适应度评估相互独立,并行之后计算时间基本可以除以CPU核数。
做这个项目最大的收获,倒不是那个优化了多少的目标函数数值,而是理解了“算法组合”的真正意义。粒子群和FCM单独拿出来都是成熟很多年的算法,但把它们放到居民用电这个具体场景里,就必须认真考虑数据形态、业务解释、计算成本这些论文里不会写的东西。如果让我再重做一遍,我会先花更多时间在特征工程和业务洞察上,PSO-FCM只是一个更可靠的工具,真正有价值的是聚类结果能不能帮助理解用户、指导业务决策。这个思路,无论对于想复现这篇研究的人,还是正在做类似电力数据挖掘项目的朋友,都应该是最值得带走的东西。
