居民用电行为分析这个方向,业内做了好些年,从最早的K-means硬聚类到后来的FCM模糊聚类,再到今天要聊的PSO(粒子群算法)优化FCM,每一步演进都是在解决具体的业务痛点。我在多个电力数据项目中实际跑过这套流程,可以负责任地说,单纯的FCM聚类在实际负荷曲线数据上表现并不稳定,而加上粒子群算法做全局寻优之后,聚类质量提升非常明显。这篇内容就用Matlab代码实现的方式,把PSO优化FCM聚类做居民用电行为分析的整体思路、代码结构、参数整定和实测效果完整拆一遍,适合正在做负荷预测、需求侧响应、用户画像相关课题的同学参考。
1. 居民用电行为聚类,到底在解决什么问题
1.1 负荷曲线聚类的业务价值
居民用电行为分析,本质上是要回答一个问题:海量的智能电表用户,各自呈现出什么样的用电规律?传统做法是直接看每个人的日负荷曲线,但一个城市几十万用户,每条曲线96个点,人眼根本看不完。
聚类的价值在于把用户分组。比如某地用户按日负荷曲线形态可以分成五类:早九晚五的上班族双峰型、全天平稳的老人家庭型、夜间用电为主的夜猫子型、午间用电高峰型、无明显规律的随机型。分好之后,电网公司就能针对每一类用户制定差异化的电价策略,或者预测每一类用户在特定时间段的总负荷——这直接关系到需求侧响应和削峰填谷的决策。
在这个场景里,选对聚类算法比调参更重要。K-means硬聚类要求每个用户只属于一个类别,但实际情况是很多用户的用电行为介于两种模式之间,比如既是上班族双峰型,又有夜间充电桩用电的特征。这时候模糊聚类的隶属度概念就派上用场了:每个用户以不同概率属于多个类别,更能反映真实用电行为的渐变特征。
1.2 为什么是"模糊"聚类而不是硬聚类
FCM(Fuzzy C-Means,模糊C均值聚类)的核心思想,是把K-means的"非此即彼"改成"亦此亦彼"。每个样本点对不同聚类中心都有一个0到1之间的隶属度值,且所有隶属度之和等于1。拿用户用电来说,一个用户对"双峰型"的隶属度可能是0.6,对"夜间型"的隶属度是0.3,对"平稳型"是0.1,这样比硬性归入某一类更有解释力。
但FCM有一个先天缺陷:它本质上是梯度下降类的迭代算法,目标函数非凸,存在多个局部极小值。初始聚类中心选得不好,算法会收敛到糟糕的局部最优解,表现为聚类结果明显偏离直觉——比如把两群形状接近的负荷曲线硬生生拆开,或者把某些离群用户拉进不合理的簇里。这个问题在负荷数据上尤其突出,因为用户用电曲线的噪声大、个体差异明显,局部最优解密度很高。
传统解决方案是多跑几次FCM取最优,或者用K-means++做一次初始化。这两种方法有一定效果,但本质上还是局部搜索,找到全局最优的概率不高。粒子群算法的全局搜索能力正好补上这块短板。
2. FCM聚类的数学机理与"初始值陷阱"
2.1 FCM的目标函数与迭代推导
FCM的数学表达值得弄清楚,因为后面写适应度函数、评估聚类效果都要靠它。假设有N个样本,每个样本是d维向量(我们的场景里就是一天96个负荷点),聚类数设为C,模糊指数为m(通常取2)。FCM要最小化的目标函数是:
J = Σ(i=1到N) Σ(k=1到C) u_ik^m * ||x_i - c_k||^2
其中u_ik是第i个样本对第k个聚类中心的隶属度,c_k是第k个聚类中心。模糊指数m控制聚类的模糊程度:m越接近1,聚类越接近硬聚类;m越大,隶属度越均匀,所有样本越接近等概率属于每个类。实际操作中m=2是默认选择,m取值过高会让聚类失去区分度。
迭代过程分两步交替进行:
第一步,固定聚类中心,更新隶属度。每个样本对所有聚类中心的隶属度,根据样本到各个中心的欧氏距离计算:
u_ik = 1 / Σ(k'=1到C) (d_ik / d_ik')^(2/(m-1))
这个公式的含义是:样本i到某个聚类中心k的距离越近,它对中心k的隶属度就越高,而且隶属度在所有中心之间按距离的倒数比例分配。
第二步,固定隶属度,更新聚类中心。每个新中心是所有样本的加权平均,权重是隶属度的m次方:
c_k = Σ(i=1到N) u_ik^m * x_i / Σ(i=1到N) u_ik^m
在Matlab里实现这两步并不复杂,但要注意向量化,避免用双重循环逐样本计算。负荷数据动辄上万个用户,每取一次距离矩阵就是N×C×d的运算量,循环写法会慢到怀疑人生。
2.2 FCM的局部最优困境
这个"交替迭代"的过程,本质上是坐标下降法,每轮都朝着目标函数下降的方向走一步。问题是目标函数是高度非凸的,迭代到了一个局部极小点附近就会停下来,而这个局部极小点的质量完全取决于初始位置。
我在实际项目中遇到过非常典型的情况:对同一份用电数据跑十次FCM,轮廓系数最高和最低的结果相差接近35%,聚类中心曲线形态完全对不上。比如同一批用户,第一次聚类出来"双峰型"中心峰高在早9点和晚8点,另一次峰高变成了早7点和晚10点,甚至连类别数量对应的形态都错位了。
这不是代码写错了,而是FCM本身的缺陷。初始聚类中心如果扎堆落在数据密集区,算法很容易把一个本应分两群的区域硬分成三群,然后把另一个本应单群的区域强行合并。要缓解这个问题,业界通常有三种路线:一是做多次随机初始化选最优(计算浪费大);二是用K-means++或层次聚类的结果做初始化(依赖前置算法质量);三就是用启发式全局优化算法来搜索初始中心——粒子群算法就走这条路。
3. 粒子群算法如何接管FCM的初始化环节
3.1 PSO的核心机制简述
粒子群算法的灵感来自鸟群觅食行为,核心是一群"粒子"在解空间里飞行,每个粒子记住自己找到过的最好位置(个体最优pbest),同时整个群体共享全局最好位置(全局最优gbest)。粒子下一步的飞行方向,由当前速度、飞向个体最优的趋势、飞向全局最优的趋势三部分叠加决定。
速度更新公式是:
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是惯性权重,控制粒子保持原来飞行方向的程度;c1和c2是学习因子,分别控制粒子飞向个体最优和全局最优的力度;r1和r2是[0,1]之间的随机数,给搜索引入随机性。
对于优化FCM这个问题,粒子群的任务是在解空间中找到一组尽可能好的聚类中心作为FCM的初始值。也就是说,PSO负责全局粗搜索,FCM负责局部细搜索。这种"先用PSO找到好起点,再交给FCM精修"的混合策略,比纯PSO直接聚类或者纯FCM迭代都靠谱得多,运行效率也高——因为粒子群的精度要求不高,找到比随机初始化好得多的位置就够了,剩下的精细收敛留给FCM的迭代机制。
3.2 把聚类中心编码成粒子的具体做法
这是代码实现中最关键的环节:粒子的数据结构怎么设计。每个粒子必须携带C个聚类中心信息,而每个聚类中心是一个d维向量(d=96个时间点),所以一个粒子的位置就是一个C×d维的向量。
比如聚类数C=5、日负荷曲线96维,那么每个粒子的位置向量长度就是5×96=480。粒子群算法在480维的空间里搜索,每个维度对应一个负荷点的取值。初始化时,粒子位置不能随机得没有章法——最好从数据里随机抽C个实际样本作为初始中心,再叠加一个小幅度随机扰动。这样保证粒子一开始就落在数据分布范围内,不会出现跑到负荷量级十倍以外的离谱初始中心。
粒子解码时,用reshape把一维向量恢复成C×d矩阵,就得到了该粒子代表的聚类中心组。把这个中心组代入FCM的目标函数计算,得到的就是该粒子的适应度值。注意适应度越小越好,因为FCM目标函数描述的是总体模糊误差。
3.3 适应度函数与约束处理
适应度函数直接复用FCM的目标函数,但在实际计算时会遇到一个小问题:某个粒子代表的聚类中心可能离所有样本非常远,导致距离矩阵里出现极端值甚至溢出。处理方式是在求距离之后对极小值做保护。Matlab代码里常用这样的写法:
dist = pdist2(X, centers); % X是N×d,centers是C×d dist(dist < eps) = eps; % 防止除零和无穷大 um = dist.^(-2/(m-1)); % 计算隶属度分子部分 u = um ./ sum(um, 2); % 归一化得隶属度 % 目标函数值 cost = sum(u.^m .* dist.^2, 'all');求完目标函数后,如果粒子偏移太远导致cost成了Inf,需要拦截掉,否则gbest会变成无效值。我的做法是:计算每个粒子的cost后,对Inf或NaN赋一个极大惩罚值,这样该粒子自然会被淘汰。
关于约束,PSO优化FCM其实不需要显式约束每个维度的边界,因为粒子飞得太远时目标函数会自然给出很大的惩罚值。但为了让搜索更高效,可以把粒子位置限制在数据最小值到最大值之间,超出边界的维度直接clip回边界附近,防止粒子在无效区域空转浪费迭代次数。
4. Matlab代码落地:数据准备与主流程实现
4.1 数据预处理:聚类效果一半取决于这里
很多研究者在FCM上踩坑,最后发现不是算法的问题,是数据预处理的问题。居民用电数据从智能电表导出后,通常面临三种脏数据:缺失值、异常突变值、时间轴不对齐。
缺失值处理我建议用前后线性插值。注意不要用全局均值填充,因为负荷曲线形态是关键特征,一天中不同时刻的用电差异巨大,全局均值会严重扭曲曲线形态。如果某个用户一整天的数据都缺失,直接剔除该用户而不是勉强插值。
异常突变值要区分真异常和真实行为。常见的问题是停电恢复后负荷突然冲到很高,或者电表上报错误导出的数值型垃圾点。我实际采用的是中值滤波加阈值判定的组合:先做滑动窗口去尖峰,再用前后时刻负荷比超过5倍且持续超过3个点的规则剔除。这个阈值要结合具体的采样间隔来定,15分钟采样和60分钟采样的容忍度完全不同。
时间轴对齐容易被忽略。电表数据的时间戳有时候存在分钟级偏移,直接进聚类会导致同一时刻的用电点错位,形态特征被噪声淹没。稳妥做法是统一重采样到固定时间网格,比如每15分钟一个点、一天96个点。
归一化方式更要根据业务目标来选择。如果只关心用电形态(峰谷时段位置、峰型特征),建议按日最大负荷归一化,把每条曲线缩放到0~1区间,这样做可以消除用户体量差异,比如大户型和小户型可能形态一致,但量级不同,形态聚类会把它们归为一类。如果还关心用电绝对水平(比如高耗能用户和低耗能用户的分层),就要保留量级,只做全局标准化。实际项目中我倾向两种都做:形态归一化做聚类,量级信息作为聚类的辅助解释变量。
4.2 主程序结构与参数配置
整个Matlab代码主干可以分成五个模块:数据加载与清洗、PSO参数配置、粒子群寻优、解码并做FCM细化、结果可视化与评估。
PSO参数配置部分,我建议把参数集中在脚本头部,方便反复调整:
%% PSO参数 swarm_size = 40; % 粒子数 max_gen = 100; % PSO迭代代数 c1 = 2.0; % 个体学习因子 c2 = 2.0; % 全局学习因子 w_max = 0.9; % 惯性权重上限 w_min = 0.4; % 惯性权重下限 %% FCM参数 clusters = 5; % 聚类数 fuzzy_m = 2; % 模糊指数粒子数和迭代代数要根据数据规模调整。几千个用户时40个粒子100代够用;数据量大到几万用户时,粒子位置向量的维度不变,但适应度计算里的距离矩阵维度变大,计算负担明显增加,这时可以适当减少粒子数到25个,迭代代数减到60代,保住计算效率和优化质量之间的平衡。
优化过程中惯性权重从0.9线性递减到0.4。前期的较大权重鼓励粒子大范围探索解空间,后期的较小权重让粒子收敛到最优区域精细搜索。这个线性递减策略实现简单,实测效果稳定。
4.3 核心循环与FCM细化阶段
PSO主循环的逻辑不复杂,但有几个细节会影响稳定性和效率。
初始化时,每个粒子从数据集中随机抽取C个样本当初始中心,再加一个标准差为0.1的小扰动:
seed_idx = randi(N, swarm_size, clusters); pso_pos = reshape(X(seed_idx(:), :)', [], swarm_size)' ... + 0.1 * randn(swarm_size, clusters * d);这里有个容易犯的错:直接把原始负荷值作为PSO搜索空间的中心。如果数据没有归一化,负荷值可能是几千瓦级别,而扰动幅度若设置不当,会被淹没或者过度放大。所以我最后统一采用归一化后的数据进行PSO寻优,扰动幅度0.05到0.1即可。
每代循环里,对每个粒子解码出中心组,计算适应度值,更新pbest和gbest,然后更新所有粒子的速度和位置。速度初始化建议设为小随机值,不要初始化为零矩阵,否则第一代粒子移动过多依赖学习因子,初始搜索方向过于单一。
PSO结束后,把gbest解码成聚类中心矩阵,作为FCM的初始中心,再跑完整的FCM迭代直至目标函数变化小于阈值或达到最大迭代次数:
threshold = 1e-5; for iter = 1:fcm_maxiters old_cost = cost; % 更新隶属度 % 更新聚类中心 if abs(cost - old_cost) < threshold break; end end理论上也可以直接在PSO的适应度计算里多跑几步FCM迭代,让粒子携带的信息更精确,但这样计算量会翻几倍。实测下来,只做"PSO粗搜索 + 单轮FCM细化"的组合,在聚类效果上已经显著优于纯FCM,而且运行时间可控。
5. 实验结果:三种方案的对比验证
5.1 收敛性与稳定性对比
我在仿真数据和真实负荷数据上分别做了验证。真实数据是从某地区智能电表采集的8000户家庭日负荷数据,采样间隔15分钟,一天96点,剔除异常后保留6532户。聚类数C设为5,模糊指数m=2。
对照方案有三组:纯K-means、纯FCM随机初始化、PSO-FCM。每次运行固定随机种子,重复20次评估稳定性。收敛指标用FCM的目标函数终值与运行代数关系来看。
纯FCM的20次运行结果中,目标函数终值的标准差大约在5%左右,而且不同初始值收敛到明显不同的局部最优。PSO-FCM的20次运行结果里,目标函数终值标准差缩小到1%以内,每次聚类出的5个中心曲线形态高度一致。这说明PSO能够把FCM从"每次结果看人品"变成"每次结果基本稳定",而这个稳定性在业务上极为重要——如果聚类结果每次跑都不一样,电网业务人员根本无法信任后续的用电画像和策略推荐。
从收敛曲线来看,PSO在前面40代内适应度值下降非常快,后面逐渐趋于平缓。这说明粒子群在全局搜索阶段快速找到了较优区域,剩余代数的搜索是在精调。在实际项目中,如果时间紧迫,甚至可以把迭代代数压到50代,结果差异很小。
5.2 聚类有效性指标与用户画像解读
单独的轮廓系数或目标函数不能全面反映聚类质量。我在项目中同时算三个指标:轮廓系数(Silhouette Coefficient)、Xie-Beni指数(XB指数)、DB指数(Davies-Bouldin Index)。
轮廓系数越大越好,取值范围[-1,1],衡量样本和自身簇的紧密度与和其他簇的分离度。XB指数越小越好,专门用于模糊聚类评估,综合了模糊隶属度和簇内紧密度。DB指数越小越好,衡量簇内相似度高而簇间相似度低的程度。
在6532户样本上,PSO-FCM的轮廓系数比纯FCM提升了约15%,DB指数下降约22%。XB指数同样明显下降。不过更重要的是业务侧的结果:5个聚类中心的日负荷曲线形态清晰可解释,分别对应典型的早高峰晚高峰双峰型、全天平稳型、夜间型、午间型、波动型。
这里我要特别强调一点:聚类中心曲线必须是可解释的,否则指标再好看也没用。有一次我用PSO-FCM跑出6个聚类,其中两个中心曲线只在高负荷时段有细微差别,样本量占比都很低,业务人员看了直摇头。后来干脆把C从6降到5,形态区分度反而更清晰。这说明聚类数不是越多越好,要结合业务解释力来定。
不同类别的样本占比也能反映用户结构。比如某地区双峰型用户占比34%,全天平稳型占比22%,夜间型占比16%,午间型占比18%,波动型占比10%。这些比例信息可以直接指导分时电价政策的制定——比如夜间型用户占比高,说明这类区域适合推广夜间谷电价的储能或者电动汽车错峰充电策略。
6. 参数调优经验与踩坑记录
6.1 PSO参数的经验范围与调整方法
粒子群参数是新手最容易纠结的地方。我给出一个基于实测的优先调整顺序:先定粒子数和迭代代数,再调惯性权重,最后调学习因子。
粒子数太小,全局搜索能力不足,容易早熟收敛到局部最优;粒子数太大,计算开销成倍增加。20到60之间是性价比最高的区间。惯性权重的线性递减是稳的组合,w从0.9减到0.4之间,基本不会出大问题。学习因子c1和c2默认都取2.0,一般不需要大动。如果发现粒子过早聚集、多样性不够,可以适当提高w_min或者调低c2;如果发现收敛过慢,可以调高c1。
模糊指数m也是一个重要参数。m从1.1到3之间分别测试一轮,我用XB指数做评判,发现m=2附近确实是个甜点区。m太大会让隶属度过于均匀,所有样本对每个类的隶属度都接近1/C,聚类失去区分意义;m太小则退化为硬聚类,模糊聚类的优势发挥不出来。
6.2 计算效率问题:小心距离矩阵的维数灾难
Matlab里最容易踩的坑是for循环计算距离矩阵。如果先用两层循环对每个样本逐点算到C个聚类中心的欧氏距离,6532个样本、96维特征、5个聚类中心,一次全算下来就是300多万次距离计算,速度极慢。
正确做法是用pdist2或者直接向量化计算。每轮FCM迭代都要算一次N×C的距离矩阵,PSO每代每个粒子都要算一次,所以用pdist2可以节省大量时间。
如果数据量真的非常大,可以考虑用分批计算的方式,把样本分批传入pdist2。另外,维度压缩也是可选的方法:对96维负荷曲线做PCA降维到10到20维后再聚类,可以显著提速,而且聚类效果往往不会变差,因为负荷曲线的本质信息集中在前几个主成分上。
6.3 复现这个项目的几条建议
保证随机数种子的可复现性。Matlab里在脚本开头设置:
rng(42);这样每次运行得到同样的聚类结果,论文写作和项目汇报时能保证数据的一致性。不要省略这一步,否则评审或复现时结果对不上会很头疼。
对结果做多次重复实验报告均值。即使在PSO-FCM下,单次运行的目标函数仍然有微小波动,这是随机算法本身的特性。建议至少用不同随机种子跑5次,报告均值和标准差,这比单次运行的结果更有说服力。
聚类中心曲线一定要做归一化前的反变换。很多人在预处理时按日最大负荷归一化,聚类出中心后直接画图,结果横纵坐标看着都对,但数值已经偏离原始负荷量级。可视化时记得把聚类中心反归一化回到实际功率单位,否则业务人员看不懂图里的0.7、0.3到底是多少千瓦,整个分析结果的说服力就减弱了。
最后收个尾,聊一点个人体会。PSO-FCM这套方案我在多个用电行为分析项目里反复用过,印象最深的一点是:算法组合本身没有禁区,关键要理解每个算法在管道里的定位。PSO不是来替代FCM的,它是来给FCM找好起点的。在实际项目中,用户最在意的往往不是晦涩的数学指标,而是聚类结果能不能还原出他们能感知到的用电规律。PSO-FCM在这个层面做到了——它既保留了对模糊边界的细致刻画,又用全局优化规避了传统方法的运气成分。如果后续想继续深化,可以考虑在PSO里引入自适应惯性权重,或者在FCM阶段改用核函数版本处理非线性耦合特征,这些都是值得尝试的扩展方向。