☰
PSO-FCM:粒子群优化模糊聚类实现居民用电行为分群
2026/10/22 15:58:30 网站建设 项目流程

居民用电行为分析这几年在需求响应、有序用电和台区精细化管理里被反复提起,但真正落地的难点从来不是“有没有数据”,而是“怎么从海量曲线里分出有意义的几类人”。很多人一上来就用KMeans,分完才发现聚类结果根本解释不了生活规律。后来我换成模糊C均值聚类,效果好了不少,但它又特别吃初始聚类中心。于是就有了这个方案:用粒子群算法先帮FCM找到一组好中心,再做模糊聚类,跑完的每一类都能对应到一种实实在在的用电习惯。下面把这套思路的完整设计、Matlab实现和排坑经验都理一遍。

1. 为什么把粒子群算法和模糊聚类放到一块儿用

1.1 居民用电行为为什么需要聚类

居民负荷曲线不是一条标准曲线,而是一堆“只有整体相似、细节各不同”的序列。同一个小区里,有人早九晚五上班,家里白天几乎空载;有人在家办公,中午和下午也有稳定用电;还有老人家庭,晚上八点前后集中开电视、做饭、开空调。这些差异如果只算总用电量,根本看不出来,但把一天24小时或48个采样点铺开,就能看出明显的形状差异。

聚类就是干这个的:把一天内的负荷曲线看成高维空间里的点,距离近的归到同一类。类内曲线形状接近,类间差异足够大。有了这种分类,供电公司才能针对不同人群制定不同的电价套餐、需求响应策略和台区改造方案。比如识别出“夜间用电型”,就能鼓励他们参与夜间谷时充电,避开晚高峰。

这里有一个容易忽略的点:居民用电曲线和工业负荷不一样,它随机性强、波动大、受天气和节假日影响明显。单纯用总用电量排序再分档,很可能把“开着空调睡一天”和“白天上班晚上回家”的两类用户混在一起。所以真正好用的算法必须能识别曲线的形态特征,而不仅仅是数值高低。

1.2 FCM 的模糊逻辑和七寸所在

模糊C均值聚类,也就是FCM,和KMeans最大的区别在于:它不强迫每个样本只属于某一个类,而是给每个样本算出一组属于各个类的隶属度。隶属度取值在0到1之间,加起来等于1。这样做的好处是符合居民用电的真实情况——一个用户可能既像上班族、又有一点居家办公的影子,完全硬切成某一类反而失真。

FCM的目标函数长这样:

[ J=\sum_{i=1}^{C}\sum_{k=1}^{N}u_{ik}^m |x_k-v_i|^2 ]

其中 (u_{ik}) 是第 (k) 个样本对第 (i) 个聚类中心的隶属度,(m) 是模糊指数,一般取2。算法的思路是先随机初始化一组聚类中心 (v_i),然后反复执行两步:根据当前中心更新隶属度,再根据隶属度重新计算中心,直到目标函数变化量小于阈值。

听上去挺优雅,但实际操作中最头疼的是初始化。FCM的目标函数非凸,初始聚类中心选得不好,迭代很容易收敛到局部极小值。比如数据里本身有三个明显的形态,你若随机把两个中心初始化到同一堆样本附近,另一个中心孤零零落在远处,最后结果就是两类几乎重合、第三类把边角料全吸收,得到的行为标签完全没法解释。

1.3 PSO 为什么适合来“收拾”FCM

粒子群算法是一种群体智能优化算法,它不依赖梯度信息,所以对非凸目标函数有很强的适应能力。把FCM的目标函数当作粒子群优化的适应度函数,每个粒子就代表一组候选聚类中心。粒子群在搜索空间里来回飞,靠个体最优和群体最优互相牵引,最后收敛到一组目标函数值更小的聚类中心。再用这组中心去启动FCM,局部收敛的坑就被绕开了很大一部分。

说白了,就是把“随机初始化中心”这种碰运气的事情,换成“用粒子群先搜索一遍好位置”。这样做虽然会增加计算量,但聚类稳定性大大提升。我实测下来,对几百户、48维的负荷曲线,粒子群几十代收敛完全够用,计算时间也就是几秒到十几秒的事,完全在可接受范围内。

2. 方案设计:目标函数、编码方式与参数选择

2.1 PSO-FCM 的整体流程

整个方案可以拆成几个模块:数据预处理、粒子群搜索初始中心、FCM迭代、结果评估和可视化。流程图用文字描述是这样的:

  1. 读入某小区居民用户一周或一月的负荷数据,按户整理成二维矩阵,每行是一个用户,每列是一个时间点。
  2. 数据清洗,包括缺失值插补、异常值剔除、按日类型拆分。
  3. 归一化,消除用电量绝对水平差异。
  4. 确定聚类数C。
  5. 初始化粒子群,每个粒子是一个C乘维度的中心矩阵拉直向量。
  6. 计算每个粒子的适应度值,这里直接调用一次FCM内部迭代后返回的目标函数值。
  7. 更新个体最优和群体最优,更新粒子速度和位置。
  8. 达到迭代次数后,把群体最优解还原成C个聚类中心。
  9. 以这组中心作为FCM的初始中心,继续迭代到收敛。
  10. 输出隶属度矩阵和最终聚类中心,绘制典型负荷曲线图,给每类用户贴行为标签。

这里的关键点是:粒子群不是在替代FCM,而是在给FCM找一个更好的起点。这样既保留了FCM对模糊归属的精细刻画能力,又利用PSO规避了初始化敏感问题。

2.2 编码与适应度函数怎么定

PSO里每个粒子必须对应一个解。在这个问题里,解就是C个聚类中心,每个中心是一个d维向量。以48点负荷曲线为例,C等于4,那么一个粒子的位置就是 (4 \times 48 = 192) 维向量。这个维度并不算高,粒子群完全扛得住。

适应度函数的定义是重中之重。网上很多代码直接把FCM的目标函数J当作适应度,但直接用会有问题:J是越小越好,而粒子群标准形式是朝适应度越大方向搜索。所以通常要在J前面取倒数或者取负号。我习惯写成:

[ fitness = \frac{1}{J + \epsilon} ]

加上一个小的 (\epsilon) 防止除零。也见过有人直接把目标函数写成FCM的迭代公式、跑完固定次数返回J值,粒子群内部按最小化处理,效果同样可以。关键是要保持粒子群代码和目标函数之间的逻辑一致,别弄混方向。

适应度计算里还有一个小坑:每次算适应度时要不要把FCM迭代到完全收敛?我建议不要。因为粒子群本身要算几十次甚至几百次适应度,每次内部再迭代一两百次FCM,计算量就太大了。实际操作中,我每个粒子只执行FCM内部的20到50次迭代,取当前目标函数值作为适应度。粒子群搜索完成后,再用最优解做一次完整FCM到收敛。这样既省时间,又不影响最终精度。

2.3 参数选择的血泪经验

粒子群的参数没有绝对标准,但有一些被反复验证有效的经验范围。下面这个表格是我跑居民负荷数据时常用的配置,照着设基本不会出大问题。

参数常用取值说明
种群规模20~30粒子上限和下限边界,搜索速度要稳定
迭代次数50~100维度越高越要加大,超过300对大多数数据集是浪费
加速度系数 c11.5~2.0个体学习因子,太大容易过度探索
加速度系数 c21.5~2.0群体学习因子,太大容易过早聚拢
惯性权重 w0.9 线性递减到 0.4前期全局搜索,后期局部精修
速度上限位置范围的20%左右防止粒子飞出样本空间太远
模糊指数 m2.0居民用电场景下默认值

特别提醒一句:惯性权重线性递减是我用得最顺手的方式。w大的时候粒子飞得远,不容易扎堆;w小的时候粒子在局部慢慢磨,精度更高。如果你发现聚类结果每次都差不多但就是不够好,先别急着调c1和c2,把w的递减策略改一改往往更有效。

聚类数C的确定也要提前做。我一般会把肘部法则和轮廓系数结合起来:先让C从2跑到8,记录每个C下的目标函数和轮廓系数,看曲线拐点。能解释比纯数学更优先。C等于3到5在居民用电场景里最常见,少于3太粗,多于5又会让标签碎片化,反而不利于后续策略制定。

3. Matlab 代码实现与关键段解读

3.1 数据准备和归一化

代码的第一步是把数据读进来。假设我们有一个CSV文件,每一行是一个用户,每一列是当天按半小时采样的48个负荷点。读取后要做三件事:去空值、去零值、归一化。

data = readmatrix('residential_load.csv'); data(isnan(data)) = 0; % 去除全天用电量接近于零的无效用户 valid_idx = sum(data, 2) > 0.01; data = data(valid_idx, :); % 按行做最大最小值归一化,保留曲线形态 data_norm = data; for i = 1:size(data, 1) row_min = min(data(i, :)); row_max = max(data(i, :)); if row_max - row_min > 0 data_norm(i, :) = (data(i, :) - row_min) / (row_max - row_min); end end

为什么要按行归一化而不是整体归一化?因为居民用电的绝对电量差异太大了,有的用户一天用2度电,有的用15度。如果直接用原始数值聚类,最终分类基本被用电量水平主导,曲线形态反而退居其次。归一化之后,所有曲线都变成0到1之间的形状曲线,聚类结果才真正反映“用电行为”。

这里也要留个心眼:如果分析目标就是区分“大电量用户”和“小电量用户”,那就不要做这种归一化,改用加绝对值或者使用未归一化数据。方法服务于业务目标,别为了套算法而失去原本想解决的问题。

3.2 粒子群主循环代码

粒子群部分我建议封装成一个函数,输入样本数据、聚类数、最大迭代次数,输出最优聚类中心。核心循环如下:

function [best_center, best_fitness] = pso_fcm(data, cluster_num, max_iter) [n, dim] = size(data); particle_num = 25; w = 0.9; w_end = 0.4; c1 = 1.5; c2 = 1.5; % 初始化粒子位置:从样本中随机抽 cluster_num 个点作为一组中心 positions = zeros(particle_num, cluster_num * dim); velocities = zeros(particle_num, cluster_num * dim); for i = 1:particle_num idx = randperm(n, cluster_num); centers = data(idx, :); positions(i, :) = centers(:)'; end pbest_pos = positions; pbest_fit = inf(particle_num, 1); gbest_fit = inf; gbest_pos = zeros(1, cluster_num * dim); for t = 1:max_iter for i = 1:particle_num centers = reshape(positions(i, :), cluster_num, dim); fit = fcm_objective(data, centers); if fit < pbest_fit(i) pbest_fit(i) = fit; pbest_pos(i, :) = positions(i, :); end if fit < gbest_fit gbest_fit = fit; gbest_pos = positions(i, :); end end w_current = w - (w - w_end) * t / max_iter; for i = 1:particle_num velocities(i, :) = w_current * velocities(i, :) ... + c1 * rand(1, cluster_num * dim) .* (pbest_pos(i, :) - positions(i, :)) ... + c2 * rand(1, cluster_num * dim) .* (gbest_pos - positions(i, :)); positions(i, :) = positions(i, :) + velocities(i, :); % 位置越界修正,回到样本点范围内 positions(i, positions(i, :) < 0) = 0; positions(i, positions(i, :) > 1) = 1; end end best_center = reshape(gbest_pos, cluster_num, dim); best_fitness = gbest_fit; end

这里要注意,粒子初始化时我用的是从样本点里随机抽几个点,而不是在全体范围内随机撒点。这样做的好处是初始中心一定落在数据的实际分布范围内,不会出现某个中心从一开始就飞到空荡荡的角落,导致后期怎么拉都拉不回来。如果你在数据上反复跑但聚类结果总是不稳定,先检查这一步。

速度越界也很重要。我在位置修正里直接做了0到1的裁剪,因为前面归一化过,数据的每个维度都落在0到1区间。归一化之后做约束不仅简单,而且有效防止粒子跑到样本空间外太远,产生无意义的中心。

3.3 FCM 目标函数与最终聚类

粒子群里面每次都要评估适应度,所以我单独写了一个fcm_objective函数。它只做有限次数的FCM迭代,返回目标函数值,不参与最终聚类。

function J = fcm_objective(data, centers, max_inner_iter) if nargin < 3 max_inner_iter = 20; end [n, ~] = size(data); c_num = size(centers, 1); m = 2; epsilon = 1e-6; U = zeros(n, c_num); for k = 1:max_inner_iter % 更新隶属度 for i = 1:n dist = sum((data(i, :) - centers).^2, 2); dist = max(dist, epsilon); U(i, :) = (1 ./ dist).^(1 / (m - 1)); U(i, :) = U(i, :) / sum(U(i, :)); end % 更新聚类中心 for j = 1:c_num centers(j, :) = sum((U(:, j).^m) .* data, 1) / sum(U(:, j).^m); end end % 计算目标函数 J = 0; for i = 1:n for j = 1:c_num J = J + U(i, j)^m * sum((data(i, :) - centers(j, :)).^2); end end end

到了最后一步,就是粒子群输出的最优中心接上完整FCM,跑足够多的迭代直到收敛,得到最终的隶属度矩阵和聚类中心。

cluster_num = 4; max_iter = 60; [init_center, ~] = pso_fcm(data_norm, cluster_num, max_iter); options = [cluster_num, 2, 1e-5, 200]; [center_final, U_final] = fcm(data_norm, cluster_num, options, init_center); [~, label] = max(U_final, [], 2);

Matlab自带fcm函数的第四个输入参数正好可以传入初始聚类中心,这一步非常关键。很多人不知道这个用法,只能自己从头写全部的FCM迭代。其实直接用自带的fcm做后半段,稳定又高效,只要把粒子群搜索出来的中心传进去就行。

3.4 结果可视化

聚类做完之后,不能只盯着标签看,一定要把每类的典型曲线画出来。这里我用的是每类所有曲线中隶属度大于0.6的那些用户,逐点取中位数或均值,得到一条“典型日负荷曲线”。

colors = lines(cluster_num); figure; hold on; for j = 1:cluster_num idx_j = find(U_final(:, j) > 0.6); if isempty(idx_j) continue; end curve = mean(data_norm(idx_j, :), 1); plot(curve, 'DisplayName', ['类' num2str(j) '(用户数' num2str(length(idx_j)) ')'], ... 'Color', colors(j, :), 'LineWidth', 2); end xlabel('采样点(半小时)'); ylabel('归一化负荷'); legend show; grid on; hold off;

可视化时我习惯把用户数也放进图例里。比如“类1(用户数126)”,这样一眼就能看出分类是否均匀。如果出现某一类只有三五户,另一类占了七八成,那基本可以判断C选大了,或者归一化方式把太多曲线都压到了一样的形态里。

另外,不要只看均值线,最好把每类的箱线图也画一下。均值线可能掩盖类内的巨大波动。我在实际项目中多次遇到均值曲线很漂亮、但类内标准差非常大、用户之间曲线形态差异明显的情况。这时候说明算法虽然收敛了,但业务上并没有真正把行为分开,需要重新检查特征选择。

4. 实际实验分析:行为标签能说明什么

4.1 一个典型的聚类场景

我拿某小区300户居民一个月的工作日负荷数据做过一次完整实验。数据是半小时一个点,一天48维。我先剔除了用电量几乎为零的空置户和只有几天数据的非完整用户,剩下来283户。归一化之后,把样本输入PSO-FCM,聚类数设为4,粒子群迭代60代。

跑出来的四类曲线特征非常清楚。第一类在早上七点到九点出现一个小高峰,晚上六点到九点出现大高峰,白天有一段低平期,典型特征就是“早出晚归上班族”。第二类全天都有一定负荷,午间和下午略高,夜间也不完全归零,更像“居家办公型”。第三类集中在晚间七点到十一点用电,白天负荷很低,是“夜间活跃型”。第四类曲线非常平,波动不大,用电行为规律性很强,可能是“全天基础用电型”,也可能是家里有常年待机的设备群体。

这个结果最大的价值在于:它不是一个刻板的工作日模板,而是完全从数据里长出来的规律。你完全不需要事先知道谁家是上班族,聚类完成后再结合户内人口问卷去验证,重合度非常高。

4.2 和传统 FCM 的直接对比

同一个数据集,我用传统随机初始化FCM跑了十次,每次的聚类结果都不太一样。有时候第三类和第四类边界漂移,有时候第二类缺了一半用户。粒子群优化后的FCM连续跑十次,分出来的用户数比例基本稳定,类中心曲线也几乎重合。

指标传统FCMPSO-FCM
十次运行中心点误差波动大,最大偏差超过15%稳定,最大偏差5%以内
目标函数终值分布多次陷入局部极值十次结果基本相同
典型曲线可解释性偶尔出现混叠形态清晰,标签直观
运行耗时极短,毫秒级增加数秒,可接受

这组对比并不是说FCM不能用,而是提醒你:在要做行为画像这种需要结果稳定、可用于后续营销策略的场景里,随机初始化产生的“不确定性”是不可接受的。分析报告交给业务方之后,如果每次重跑结果都不一样,对方就再也不敢用了。

4.3 行为画像怎么解释成业务语言

聚类算法只帮你分组,不帮你解释。最终还是要由人来给每个类起名。我的方法是把用户数和峰值时段对应起来,形成一张标签表:

类编号典型特征建议的行为标签推荐应用方向
类1早晚双峰上班族推广分时电价,提醒谷时充电
类2全天平稳家庭长时用电检查待机损耗,推荐节能设备
类3夜间集中夜间活跃型可参与夜间需求响应
类4午间偏高白天在家型配合光伏中午发电时段用电

这种表一出来,整个分析就不再是抽象的数字了。业务部门拿到表以后,可以直接对号入座。这也是为什么我坚持要把每一类的用户数、峰值时段、工作日夜间的差异都写进结果里。技术分析最终价值体现在业务行动的落地能力上。

5. 常见问题与排查实录

5.1 聚类结果完全乱套,类与类之间看不出区别

这种情况十有八九出在特征或者归一化上面。先检查是不是所有曲线都长得差不多,比如夏天高温天家家户户都开空调,整条曲线全是“早高晚高中间高”,此时聚类无论怎么优化都很难劈开。解决办法是把特征换掉:不要用24小时曲线,改用高峰负荷率、谷段占比、夜间电量占比、负荷波动率等统计特征和原曲线拼在一起。或者把周末和工作日分开建模,周末的用电形态差异会明显得多。

还有一个高发原因是没有剔除节假日。节假日里上班族的曲线变成了全天平稳,和居家办公型完全重合。如果数据集没有做日类型拆分,聚类结果就会一团糟。

5.2 粒子群收敛太慢,迭代几十代都没动静

先看种群规模和维度是不是不匹配。48维的情况下,种群25够了。如果用了96维甚至更高维,种群要适当加到40到50。同时检查速度上限是否设置得太小,速度上限太小粒子飞不动,搜索效率特别低。我通常把速度上限设为位置范围的20%,再配合线性递减的惯性权重,实测收敛速度明显加快。

另外,如果适应度函数内部每次迭代次数设得过大,粒子群每一轮的耗时也会拉满。把内部迭代限制在20到30次,粒子群本身多跑几轮,总时间反而更少。

5.3 粒子群过早聚集,所有粒子都挤到一个区域

过早收敛的典型特征是粒子群迭代没几次,群体最优位置就固定不动了。这时候先检查c2是不是太大,c2大大会让所有粒子被群体最优牢牢吸住,失去了探索能力。可以适当把c2降到1.2到1.5,同时让c1维持1.5以上,保证个体探索的权重。

还有个容易忽略的点:粒子速度初始化全零本身会加剧早熟。可以在初始化时给速度加上一个小的随机量,让粒子一开始就有差异化的运动方向。这个小改动在不少数据集上都起到了立竿见影的作用。

5.4 计算过程中出现 NaN 或者分母为零

这是我在调试时遇到最多的数值问题,根因往往是某个聚类中心离某个样本点完全重合,导致距离为零。在fcm_objective里我习惯把距离用一个很小的正数包住,比如eps或1e-6。另外,数据里如果存在整行全部相等的异常用户,归一化后就会出现0除以0,所以清洗阶段一定要把方差过小的行剔除或者做平滑。

如果NaN出现在粒子群位置更新后,大概率是速度更新时积攒了过大的数值,乘以随机数后越界。每次迭代结束时检查位置是否超出0到1范围,越界的拉回来,速度也可以同时减半处理,避免下一轮继续膨胀。

5.5 轮廓系数很高,但业务标签解释不通

这种冲突我见过太多次了。轮廓系数是纯数学指标,它只在乎簇内紧、簇间远,根本不在乎分出来的类有没有业务含义。有时候轮廓系数最高的C等于6,但其中有三个类的曲线形状几乎一样,只是用电量高低差了一截。因为归一化如果没做好,用电量高低就会形成额外维度,算法自然就按这个维度劈开了。

这时要回头审视:归一化真的消除了幅值影响吗?要不要在聚类前做一次标准化?要不要改距离度量?如果欧氏距离下的结果不好解释,可以试试把曲线换成日用电模式的余弦相似度,或者用动态时间规整距离来替代距离计算。后者对负荷曲线的相位偏移更鲁棒,适合那些用电习惯一样但作息时间差半个小时的用户。

6. 实操总结与扩展想法

6.1 什么样的项目不适合用 PSO-FCM

不是所有场景都值得上粒子群。如果你的数据集特别小、只有几十户,传统FCM随机跑几次挑一个最优解就够用了,完全没必要增加计算量。如果你的业务场景只关心“高电量”和“低电量”两档,简单阈值法可能更直接,聚类反而把问题复杂化。PSO-FCM适合的是数据量中等以上、需要稳定可解释的类别标签、且传统聚类法结果反复不定的场景。

另外要提醒一个数据量问题:聚类算法对样本量的要求主要体现在聚类数和维度上。几百户、48维跑起来完全没压力。但如果数据扩大到几万户、维度又到几百维,粒子群每代适应度计算就会变得很重。这时候要考虑分布式计算或者先用PCA降维,而不是单纯增加迭代次数。

6.2 后续可以怎么扩展

这套基础框架的扩展空间很大。一是把日类型加进来,分别对工作日和休息日建模,给每个用户算出“工作日标签+周末标签”的组合,比如“工作日上班族、周末居家型”,这种复合标签在营销里更精准。二是引入时序衰减机制,用电行为是会变化的,最近三个月的数据权重应当高于半年前的数据。三是把天气变量融合进特征里,温度突变日期的负荷形态和正常日期完全不同,先做气象敏感负荷拆分,再对残差部分聚类,能得到更稳定的行为底数。

还有一个值得尝试的方向是自动确定聚类数。目前C基本靠肘部法则和人工解释确定,但如果分析频率变成每月一次,每次都去人工看图就太累了。可以考虑用粒子群同时优化聚类数和中心点,适应度函数里加入对过度聚类的惩罚项,让算法自动在可解释性和类内紧凑性之间取平衡。

最后,如果想把这份工作从研究变成落地工具,最好把整套流程封装成一个自动脚本:输入原始抄表数据,输出带标签的用户清单和可视化图表。我在实际项目里最后就是这么用的,分析一次几十户到上千户都只用改文件路径,效率高很多,出错的概率也小很多。

我个人在实际项目中最大的体会是:算法选型重要,但对数据的理解更重要。PSO-FCM不是万灵药,它解决的只是FCM初始化不稳这一个问题。如果你连数据里的节假日效应、缺失值分布、用户组成结构都没摸清楚,再好的优化算法也救不了结果。先把数据进行分型、清洗、可视化,再决定要不要上粒子群,才是省时省力的路子。下次如果你也遇到聚类结果飘忽不定、业务方反复追问“这个类到底是什么人”,不妨把粒子群优化FCM这套方案拿出来试一轮。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询