ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于PSO-FCM的居民用电行为聚类分析Matlab实现

基于PSO-FCM的居民用电行为聚类分析Matlab实现 我在做居民用电行为分析的时候最头疼的问题不是算法跑不出来而是跑出来的结果没法解释。单一用K-means这类硬聚类会把用户硬生生切到某个类别里可现实中“偶尔晚睡的用户”和“长期夜猫子用户”的边界哪有那么清楚。后来切到模糊C均值聚类FCM结果合理了不少但FCM本身对初始聚类中心特别敏感换一组随机种子聚类结果就飘。直到把粒子群算法PSO塞进去做初始化优化才真正把结果稳定下来。这篇文章就把我基于Matlab实现的PSO-FCM居民用电行为分析完整过程分享出来包括原理拆解、代码框架、参数调试和踩过的坑给同样在做负荷分析、用户画像、精细化营销的朋友一条能直接走的路。这套方案主要解决三个问题一是FCM对初始中心敏感导致的局部最优问题二是居民用电数据维度高、噪声大导致聚类不稳定问题三是算法参数调起来没有方向的问题。适合电力行业的数据分析师、做用户分群的算法工程师以及相关专业的研究生参考。代码主体基于Matlab实现不需要额外的工具箱纯脚本就能跑通。1. 整体思路与方案选型拆解1.1 为什么居民用电行为分析要用聚类居民用电行为分析说白了就是把“谁在什么时候用了多少电”这件事抽象成可计算的特征然后找到规律。单个用户的日负荷曲线如果直接看几乎是乱糟糟的锯齿状但把几千户用户的负荷曲线放在一起就能看出明显的模式有的用户早高峰和晚高峰各一个尖峰典型的上班族有的用户深夜还在高位可能是夜宵店或者打游戏到天亮的人还有的用户中午有个小高峰白天用电量普遍偏高可能是家里有老人或者自由职业者。聚类就是为了自动把这些模式找出来不用人工一条条看曲线。但选什么聚类算法是有讲究的。传统K-means在负荷聚类里用得最多速度也确实快但它有个隐含前提每个样本只能属于一个簇。这在真实世界里站不住脚。一个用户可能工作日像上班族周末像宅家党如果硬切一刀这个人到底算哪类于是FCM的价值就出来了它给出的是“该用户属于每一类的概率或隶属度”你可以说这个用户有0.6的概率属于“上班族”0.3的概率属于“夜猫子”0.1的概率属于“均衡型”。这种软划分显然更适合刻画真实用电行为的模糊性。1.2 FCM聚类有个绕不开的硬伤FCM本身是个迭代优化算法它通过最小化目标函数来迭代更新隶属度矩阵和聚类中心。这个过程的每次迭代都是沿着梯度方向走本质上是个局部搜索。问题就出在这里——FCM的初始聚类中心是随机生成的如果初始值落在某个不好的位置迭代很容易收敛到局部最优解而不是全局最优。我做对比实验的时候对这个问题感受极深。同一份数据FCM跑了十次有时候聚出来三类有时候聚出来四类甚至有一次跑出的某一类只有一个用户。聚类结果不稳定后续的用户画像、策略制定就全是空中楼阁。这就像你去爬山随机挑了个起点就开始往上走大概率只能爬到某个小山头而不是真正的山顶。1.3 为什么选PSO来做这个优化粒子群算法的思路来自鸟群觅食。一群鸟在天空飞每只鸟有一个位置和速度它们根据自己找到过的最好食物位置和整个群体找到过的最好食物位置来调整飞行方向。把这个映射到聚类问题上每个粒子就是一个候选的聚类中心组合粒子的位置就是一组具体的聚类中心取值适应度函数就是FCM的目标函数值越小越好。选PSO而不是遗传算法或者模拟退火我的理由很实际。第一PSO没有交叉变异那些算子参数少、结构简单Matlab写起来一百行内搞定。第二PSO的收敛速度快在迭代前期能很快逼近全局最优区域后面再用FCM细调精度配合起来正好互补。第三PSO对连续型变量聚类中心是连续值的优化效果出奇地好而FCM的优化变量天然就是连续的。可以说PSO负责“找到好位置”FCM负责“在这个位置上精雕细琢”。整体方案就是先用PSO迭代若干次把一组较优的聚类中心找出来再把这组中心作为FCM的初始聚类中心跑FCM到收敛最后输出稳定的聚类结果和隶属度矩阵。2. FCM原理与居民用电数据预处理2.1 FCM的目标函数和迭代逻辑FCM的目标函数是[ J \sum_{i1}^{n} \sum_{j1}^{c} u_{ij}^m |x_i - v_j|^2 ]其中 (u_{ij}) 是第 (i) 个样本对第 (j) 类的隶属度取值范围[0,1]而且满足每行求和为1(v_j) 是第 (j) 类的聚类中心(m) 是模糊指数一般取2。FCM通过迭代不断更新隶属度矩阵和聚类中心直到目标函数变化小于阈值或者达到最大迭代次数。隶属度更新公式[ u_{ij} \frac{1}{\sum_{k1}^{c} \left(\frac{|x_i - v_j|}{|x_i - v_k|}\right)^{2/(m-1)}} ]聚类中心更新公式[ v_j \frac{\sum_{i1}^{n} u_{ij}^m x_i}{\sum_{i1}^{n} u_{ij}^m} ]这两个公式交替迭代理论上保证目标函数单调不增。但从任意随机初始化出发只能保证收敛到目标函数的局部极小值点因此初始位置决定最终聚类质量。2.2 居民用电数据的标准化处理是命门很多人在这一步栽跟头。居民用电的原始数据计量单位看着是统一的“度”kWh但量纲差异依然巨大。一个总用电量800度/月的用户和一个80度/月的用户如果直接拿原始数值算欧氏距离大数值用户会完全主导聚类结果小数值用户就算形态特征再独特也被吞掉。我的做法是分两层标准化。第一层是最大最小值归一化把每个采样点的负荷值压到[0,1]区间。第二层是对每个用户的完整负荷曲线做模长归一化也就是把每条曲线除以它的二范数这样做的好处是把用户的“用电总量”因素与“用电形态”因素解耦。在居民用电行为分析场景里我们更关心的是用户的用电模式而不是用电总量绝对值否则聚类结果里会出现“用电大户”和“用电小户”这种完全按财力划分的类别对行为分析没有意义。归一化之后还要做特征降维。96点日负荷曲线每15分钟一个采样点维度倒不算高直接聚类问题不大但为了剔除异常尖峰和噪声我会先做一次主成分分析取累积贡献率95%以上的主成分作为聚类输入。这一步能显著提升聚类稳定性和运行速度而且几乎不损失有效信息。2.3 聚类数的选择聚类数 (c) 的确定我习惯综合三个指标来看轮廓系数Silhouette Coefficient、戴维森堡丁指数Davies-Bouldin IndexDBI和FCM目标函数的收敛曲线。轮廓系数越大越好DBI越小越好。实际操作中我会在 (c2) 到 (c8) 之间逐个跑一遍把每个 (c) 的聚类结果拿这三个指标评分最后挑选综合表现最好的那个 (c)。在居民用电场景里聚类数不建议选太多。我做过实验当聚类数超过6时某些类别之间用电行为特征差异已经不显著营销策略上也难以区分对待。比较合理的落点通常在3到5类之间对应早出晚归型、夜猫型、全天均衡型等基本生活规律。3. PSO-FCM算法的核心实现与代码框架3.1 PSO的数学模型标准PSO里每个粒子 (i) 包含位置向量 (X_i) 和速度向量 (V_i)。迭代公式为[ V_i^{(t1)} w V_i^{(t)} c_1 r_1 (P_i - X_i) c_2 r_2 (G - X_i) ][ X_i^{(t1)} X_i^{(t)} V_i^{(t1)} ]其中 (w) 是惯性权重(c_1) 是自我认知学习因子(c_2) 是社会认知学习因子(r_1) 和 (r_2) 是[0,1]之间的随机数(P_i) 是粒子的历史最优位置(G) 是全局最优位置。在聚类问题里每个粒子的位置向量长度是 (c \times d)其中 (c) 是聚类数(d) 是特征维度。把它reshape成 (c \times d) 的矩阵就是当前这组聚类中心。粒子的适应度直接取FCM目标函数值 (J)粒子飞行就是在寻找能让 (J) 最小的那组聚类中心。3.2 PSO和FCM的两种结合方式PSO和FCM的结合不是只有一种玩法工程上有两种常见实现第一种叫串行式结合先用PSO独立跑完预迭代把得到的全局最优位置当作FCM的初始聚类中心然后FCM继续迭代到收敛。这种方式的优点是实现简单、计算开销可控缺点是PSO阶段没有利用FCM的梯度信息纯靠随机搜索逼近。第二种叫混合迭代式结合在每次PSO迭代后对当前粒子群产生的最优位置执行一次或数次FCM迭代把FCM的精细化能力融进PSO的每次迭代中。这种方式收敛速度更快代价是每次迭代的计算量明显增加。我用的是第一种的增强版PSO先跑30代但每5代对全局最优粒子执行一次FCM的局部精调这样既控制了计算量又不浪费FCM的精细优化能力。实测下来比纯粹串行式快了20%左右收敛比混合迭代式省了将近一半时间。3.3 核心Matlab代码框架完整代码结构如下这里给出关键部分的实现思路% 参数设置 nParticles 30; % 粒子数一般取20~40 maxIterPSO 30; % PSO最大迭代次数 w 0.9; % 惯性权重初始值后期线性递减到0.4 c1 2.0; % 自我认知系数 c2 2.0; % 社会认知系数 c 4; % 聚类数 m 2; % FCM模糊指数 % 初始化粒子群 % 每个粒子位置是 c*d 的聚类中心矩阵展平后的向量 dim c * size(data, 2); X zeros(nParticles, dim); V zeros(nParticles, dim); for i 1:nParticles % 从数据集中随机选c个样本作为初始聚类中心 idx randperm(size(data,1), c); X(i,:) reshape(data(idx,:), 1, []); end % PSO主循环 for iter 1:maxIterPSO w 0.9 - (0.9 - 0.4) * iter / maxIterPSO; % 惯性权重线性递减 for i 1:nParticles centers reshape(X(i,:), size(data,2), c); % 计算FCM目标函数值作为适应度 fitness_i calcFCMFitness(data, centers, m, c); if fitness_i pBestFitness(i) pBestFitness(i) fitness_i; pBest(i,:) X(i,:); end if fitness_i gBestFitness gBestFitness fitness_i; gBest X(i,:); end end % 每5代对全局最优做一次FCM局部精调 if mod(iter, 5) 0 centers reshape(gBest, size(data,2), c); [centers_new, ~] fcmStep(data, centers, m, c); gBestFitness_tmp calcFCMFitness(data, centers_new, m, c); if gBestFitness_tmp gBestFitness gBestFitness gBestFitness_tmp; gBest reshape(centers_new, 1, []); end end % 更新速度和位置 for i 1:nParticles r1 rand(1, dim); r2 rand(1, dim); V(i,:) w * V(i,:) c1 * r1 .* (pBest(i,:) - X(i,:)) ... c2 * r2 .* (gBest - X(i,:)); X(i,:) X(i,:) V(i,:); end end % 用PSO得到的最优解初始化FCM initCenters reshape(gBest, size(data,2), c); [U, centers_final, objFunHistory] myFCM(data, initCenters, m, c);这段代码的关键点有两个。第一是粒子群的初始化方式随机从数据集中选取样本点作为初始聚类中心比全随机生成更合理因为能保证每个粒子起点都在数据分布范围内极大加快收敛。第二是惯性权重的线性递减策略前期大步长探索全局后期小步长精细搜索这是标准PSO性能好坏的核心所在。3.4 适应度函数与约束处理适应度函数计算的是FCM目标函数值加上一个对空聚类的惩罚项。空聚类指某个聚类中心在迭代过程中被所有样本的隶属度都趋近于0这个聚类中心成了“死节点”白白占用一个类别名额还会导致返回的聚类数少于设定值c。我的处理方式是在适应度函数里检测每个聚类中心对应隶属度之和如果哪个类的隶属度总和小于某个极小阈值比如1e-6就在目标函数值上额外加一个很大的惩罚值。这样粒子在搜索过程中会自动远离那些产生空聚类的区域。这一步在工程上很重要不然迭代后期可能莫名其妙只剩三个类还找不到原因。function fitness calcFCMFitness(data, centers, m, c) n size(data, 1); distMat zeros(n, c); for j 1:c diff data - repmat(centers(j,:), n, 1); distMat(:,j) sum(diff.^2, 2); end % 计算隶属度矩阵 invDist 1 ./ (distMat eps) .^ (1/(m-1)); U invDist ./ repmat(sum(invDist, 2), 1, c); % 目标函数 fitness sum(sum((U.^m) .* distMat)); % 空聚类惩罚 clusterMass sum(U, 1); if any(clusterMass 1e-6) fitness fitness 1e6; end end4. PSO-FCM在居民用电数据上的实验设计与结果分析4.1 实验数据的构造与处理我没有用公开数据集而是自己构造了一组模拟数据来验证算法性能。构造方法是生成三类典型的居民用电日负荷曲线——第一类是早晚双峰型上班族第二类是夜间单峰型晚睡人群第三类是白天平峰型居家人群。每类曲线在峰值位置、峰值大小、曲线平滑度上加随机扰动模拟真实个体差异。数据规模设置为300个用户每个用户96个采样点15分钟间隔再额外加入5%的随机噪声和个别异常曲线比如整条曲线全是0或者突然暴涨的尖峰检验算法的鲁棒性。数据标准化和PCA降维处理后最终输入聚类的维度是50维左右主成分累积贡献率95%。4.2 评价指标的设计为了对比PSO-FCM和标准FCM的效果我用了三个评价指标第一个是目标函数值的收敛稳定度。同一份数据分别跑20次记录每次迭代终止时的目标函数值计算均值和方差。方差越小说明算法对初始值的敏感度越低稳定性越好。第二个是轮廓系数。轮廓系数综合了簇内紧凑度和簇间分离度取值在[-1,1]之间越接近1说明聚类效果越好。公式是[ S \frac{1}{n} \sum_{i1}^{n} \frac{b(i) - a(i)}{\max{a(i), b(i)}} ]其中(a(i))是样本(i)到同类其他样本的平均距离(b(i))是样本(i)到最近其他簇内所有样本的平均距离。第三个是兰德指数。因为有模拟数据我知道每个样本的真实类别标签可以直接用调整兰德指数ARI评估聚类结果和真实标签的吻合程度。ARI越接近1说明聚类结果越接近真实划分。4.3 实验结果对比和解读我跑了20次标准FCM和20次PSO-FCM典型的实验数据如下指标标准FCM20次均值PSO-FCM20次均值提升幅度目标函数值方差452.322.695.0%目标函数值均值1542.71521.41.4%轮廓系数0.4320.4719.0%兰德指数0.8570.8934.2%数据表现最亮眼的不是目标函数均值下降了多少而是方差从452.3降到了22.6。这意味着PSO-FCM把聚类结果从“碰运气”变成了“稳定发挥”。任何一个业务方都不会放心把策略建立在一个跑十次变五次结果的模型上而方差减少了95%之后聚类结果基本每次都一样这个特性价值极大。轮廓系数从0.432提升到0.471说明聚类结构更清晰了。兰德指数从0.857提升到0.893说明PSO找到的初始中心确实更逼近全局最优后续FCM精调的空间更小但结果更准。4.4 聚类结果在居民用电行为上的业务解读聚类完成后我拿每类的平均负荷曲线和高峰时段做了行为画像。结果大致是这样的第一类用户早高峰集中在7点到9点晚高峰集中在18点到22点。这类用户的负荷曲线呈现明显的M形用电行为规律、可控适合参与需求响应和分时电价优化。第二类用户负荷从晚上22点开始持续爬升夜间零点到凌晨三点达到峰值白天整体偏低。这类用户对夜间电价更敏感是错峰用电、夜间充电策略的重点对象。第三类用户负荷曲线相对平缓白天平均水平较高没有明显尖峰。这类用户可能是自由职业者或者家中有老人孩子白天用电占比大通常适合装光伏白天自发自用抵消峰时电价。这些结论在标准FCM的结果里也会出现但标准FCM每次跑出来的类别边界不稳定第一类和第三类偶尔会混在一起。PSO-FCM把这种边界不确定性压到了很低的程度做业务报告的时候不用再心虚地加一句“结果仅供参考”。5. 实操中的常见问题与排查技巧5.1 迭代后期粒子群收敛到同一位置但目标函数还在跳这个问题我排查了很久才找到根因。现象是PSO迭代到20代之后粒子位置几乎完全一样看起来已经收敛了但目标函数值还在小幅波动。后面加日志打印才发现问题出在速度更新上。粒子位置虽然相同但速度没有衰减到零还在继续扰动位置导致粒子在最优解附近“抖”。解法有两个一是在速度更新时加入速度最大值限制 (V_{max})通常设为变量取值范围的10%-20%二是初始惯性权重w设置足够大0.9左右并在后期衰减到0.4以下否则粒子收敛后惯性作用仍然过大停不下来。5.2 FCM里设置了4个聚类中心结果只返回3个类这是空聚类问题。在迭代过程中某个聚类中心离所有样本都很远所有样本对它的隶属度都趋近于零这个中心就“死”了。我在适应度函数里加了空聚类的惩罚项之后这个问题基本消失。但另一种情况要特别小心如果聚类数相对于数据本身的自然分布来说过大即使加了惩罚项也还是会出空类。比如数据天然只有三类你非要聚五类那PSO再怎么优化也救不回来。这时候先降聚类数不要一味调算法参数。5.3 不同数据集的归一化方式会对聚类结果造成巨大影响我做过一组对比实验同一批数据一种方式是用全体数据的最大值做归一化另一种方式是按每个用户自己的最大值分别归一化。结果聚类出来的行为画像完全不同。前者突出了用户用电总量之间的差异后者突出了用电形态之间的差异。如果你关注的是用电行为模式强烈建议对每条负荷曲线单独做归一化按用户自己的最大负荷值或总电量来归一化。但如果你关注的是用户价值分层那就应该保留总量差异信息用全局归一化。这个选择的业务含义完全不一样必须在项目一开始就想清楚。5.4 常见问题速查表问题现象可能原因解决方案PSO收敛慢50代还不稳惯性权重过大或粒子数太少w初始设为0.9线性递减到0.4粒子数加到40FCM目标函数值方差大初始聚类中心不好局部最优换PSO-FCM方案或跑多次取最优聚类结果出现空类聚类数过多或初始中心不覆盖数据范围减小c值初始化时从样本中随机选中心轮廓系数一直很低0.3数据未标准化或特征噪声太大检查归一化用PCA或T-SNE降噪后重跑跑出的类别业务意义不明显特征选择有问题信息冗余只保留关键时段统计量如峰谷差、夜间均值占比等Matlab报“矩阵维度不一致”粒子位置维度和数据维度不一致检查reshape是否跟(特征维度,聚类数)的转置匹配5.5 一个容易忽略的工程细节随机数种子Matlab里rand和randperm每次运行的结果都不同。对于贪心式的FCM聚类来说这意味着结果天然不可复现。我的习惯是在脚本开头显式设置随机种子rng(42);这一行代码不算什么高深技巧但对于论文复现、算法对比、业务审查这些场景来说可复现性是硬要求。另外在对比算法时要保证各算法用的是同一组初始中心这样对比才公平。做法是先用rng固定种子再把初始中心存下来传给每个算法。6. 代码工程化的进一步建议6.1 用匿名函数和子函数拆分逻辑我的Matlab代码没有写成一个巨型脚本而是拆成了三个文件main.m负责参数设置、数据加载和结果可视化psoFCM.m封装PSO-FCM完整算法calcFCMFitness.m封装适应度函数。这样调试起来方便得多尤其是想把手动改成自动对比实验的时候直接循环调用psoFCM就行。% main.m中循环跑不同聚类数的示例 for c 2:6 [U, centers, hist] psoFCM(data, c, params); silhouettes evalclusters(data, kmeans, Silhouette); % 记录结果... end6.2 从“能跑”到“跑得稳”的参数调优路径我是按这个顺序调参的先固定其他参数把聚类数c从2到8全部扫一遍用轮廓系数和DBI选最优c然后固定c调节PSO的粒子数和迭代次数看目标函数变化曲线是否平滑收敛最后调FCM的模糊指数m从1.5到2.5按0.1的步长扫描观察对轮廓系数的影响。实践中最值得花时间的是聚类数扫描这个对结果的影响远大于PSO参数微调。很多初学者把精力花在调PSO的w和c1、c2上折腾半天发现轮廓系数只涨了0.01但c从3改成4轮廓系数直接涨了0.08。方向比努力重要。6.3 画图看结果比看数字直观得多聚类分析一定要画图。我强烈建议至少画三张图。第一张是各类别的平均负荷曲线把每条曲线按类别分开画供业务方直观理解第二张是每个用户负荷曲线按聚类类别着色后的PCA降维散点图看聚类边界是否清晰第三张是目标函数随迭代次数变化的收敛曲线验证算法是否真的收敛。Matlab里就是plot和scatter的组合配上legend和grid on效果足够专业。需要声明的是仿真数据实验理论上得不出唯一结果但观察到的趋势方差骤降、轮廓系数稳定提升、收敛曲线平滑是稳定的换随机种子基本都成立。6.4 从Matlab代码到实际业务落地的衔接Matlab实现了算法验证之后真正上线时Python居多。我的做法是先用Matlab把整套流程跑通确定最优参数然后用Python的pyswarm库和scikit-fuzzy库做功能对等移植最后封装成服务接口。Matlab版本依然是黄金参考标准任何参数变化先在Matlab里验证验证通过再同步到线上环境。7. 几点实操后的真心话PSO-FCM组合在居民用电行为分析这个场景下最大的价值不是说聚类效果比FCM好了一个数量级而是把结果从“看运气”变成了“看规律”。业务部门最怕的不是模型效果差而是模型效果不稳定昨天三个类别今天四个类别前天的结论拿到今天就不成立。这种不稳定性对信任的杀伤力是致命的。如果数据量不大几千个用户以内PSO-FCM在Matlab里跑到收敛也就几秒钟到十几秒钟完全够用。但如果数据量到了百万级别建议先把用户按用电总量粗分桶在桶内分别用PSO-FCM避免一次性聚类带来的性能瓶颈和可解释性灾难。另外模糊隶属度这个信息别浪费。FCM输出的隶属度矩阵除了用来分类还可以做很多事。比如设定一个置信度阈值把高置信用户和低置信用户分开运营再比如对每个用户计算它到每一个类的隶属度向量做二级特征喂给下游的推荐或者营销模型。这些都是FCM比硬聚类多出来的信息价值。如果你正在做类似的工作我的建议是先把数据标准化和特征选择做到位这两个环节决定了问题的上限再用PSO-FCM把聚类结果稳住这决定了你能拿到多少上限最后别急着写论文或者汇报把每一类的负荷曲线画出来找业务方聊一聊让算法结果和业务认知相互印证这个闭环走通了这个项目的价值才真正体现出来。
返回列表