ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于粒子群优化模糊C均值聚类的居民用电负荷分析

基于粒子群优化模糊C均值聚类的居民用电负荷分析 拿到这个课题的时候我第一反应是“终于有人把这两个算法凑在一起了”。做居民用电行为分析的同行应该都有体会负荷数据本身没有标签想给用户分群只能靠聚类。但直接上FCM结果经常不稳定跑十次十个样原因就出在FCM对初始聚类中心太敏感一不留神就掉进局部最优。粒子群优化FCM的思路本质上就是拿PSO的全局搜索能力去帮FCM找个好起点让结果既稳定又合理。这篇内容适合正在做电力数据挖掘课题的学生、刚接触负荷聚类的数据分析师以及想在Matlab里快速落地一套可复现聚类流程的工程人员。我会把“为什么这么做”“参数怎么定”“代码怎么搭”“踩过哪些坑”都讲透尽量让你看完能直接复现而不是只记住几个名词。1. 为什么居民用电聚类要选FCM但又不直接用FCM1.1 居民用电行为分析到底在解决什么问题居民用电数据本质上是一条条时间序列最常见的是按15分钟或1小时采集的日负荷曲线一天下来就是96点或24点。分析的核心目标是把大量用户“归类”比如谁是上班族、谁是“家里蹲”、谁家有电动汽车夜间充电习惯然后针对不同群体做需求响应、分时电价设计或个性化用能建议。这事听起来简单做起来有一层隐性门槛用户的行为不是非黑即白的。一个用户可能工作日晚间用电高峰周末白天也高另一个用户可能全天平稳但夏天开空调时晚高峰异常突出。如果用硬聚类一刀切很容易把“既是又不是”的用户强行塞进某个类别失真比较严重。FCM这种软聚类正好适合这种场景因为它给每个样本输出的是“属于各类的隶属度”而不是一个硬标签天然能表达用户的模糊属性。1.2 FCM的聚类思想与它在负荷数据上的天然优势FCM全称是模糊C均值聚类它在K-Means的基础上引入了一个模糊指数m让每个样本点不再只属于一个簇而是用隶属度表示属于每个簇的概率。目标函数长这样[ J_m \sum_{i1}^{N}\sum_{j1}^{C} u_{ij}^m \cdot |x_i - c_j|^2 ]其中u_ij是样本i对聚类中心j的隶属度m是模糊指数一般取2x_i是样本向量c_j是第j个聚类中心。算法通过迭代更新隶属度矩阵和聚类中心直到目标函数收敛或达到最大迭代次数。我把FCM和K-Means的区别打个比方K-Means像“分座位每个人必须坐一把椅子”FCM像“一个观众可以同时给三场演出打分最后按综合偏好分组”。在居民用电场景里一个用户既像“上班族型”又带“夜猫子型”的特征FCM能把这个模糊性保留下来后续做需求响应时可以根据隶属度加权而不是粗暴处理。1.3 FCM最让人头疼的三个问题初始化敏感、局部最优、噪声干扰FCM的痛点实际跑过的人都知道。第一初始化敏感。FCM初始聚类中心如果选得不好迭代很容易收敛到一个局部最优解。比如你随机初始化到某个密集区域可能把两个真正应该分开的类合并成一个再跑一次又可能拆成三个。同一个数据集结果方差极大。第二局部最优。FCM本质上是坐标下降法每一步固定隶属度更新中心、固定中心更新隶属度这种交替优化的方式天然容易陷进局部极值尤其当数据分布复杂、类别边界模糊时。第三噪声和异常值敏感。FCM的隶属度计算基于欧氏距离如果一个用户某天负荷数据出现异常尖峰会直接拉偏聚类中心。虽然模糊机制比K-Means平滑一点但不代表它免疫。所以做居民用电聚类时我从不建议直接拿原始FCM跑一次就下结论至少要用多个初始化去对比。而粒子群优化FCM正是从“全局搜索”的角度去治FCM的“初始化病”。2. 粒子群优化FCM的思路拆解与参数设计2.1 粒子群算法的启发与核心机制粒子群优化PSO的思路来自鸟群觅食行为。每只鸟粒子在搜索空间里飞行它既记得自己到达过的最好位置个体最优pbest又能感知群体里所有鸟找到的最好位置全局最优gbest飞行速度由这三部分共同决定。速度更新公式[ v_i^{t1} w \cdot v_i^t c_1 \cdot r_1 \cdot (pbest_i - x_i^t) c_2 \cdot r_2 \cdot (gbest - x_i^t) ]位置更新[ x_i^{t1} x_i^t v_i^{t1} ]w是惯性权重控制粒子延续上一时刻速度的程度c1和c2是学习因子r1和r2是[0,1]之间的随机数。把PSO用到FCM上核心思路是用粒子群搜索全局最优的初始聚类中心再把最优解传给FCM去迭代。这样FCM不再是“自己瞎猜一个起点”而是从一个经过全局搜索的较好位置开始收敛路径稳定很多。2.2 粒子编码、适应度函数与整体流程在PSO-FCM里一个粒子表示一组聚类中心。假设我们要分C类每个样本特征维度是D那么一个粒子的编码长度就是C*D。比如分4类、每类用24维日负荷曲线表示那一个粒子就是96维的向量每24维对应一个聚类中心。适应度函数可以直接用FCM的目标函数J_m适应度值越小说明当前的聚类中心组合下类内紧凑度越高、隶属度分配越合理。计算适应度时每拿到一组粒子位置就把它解析成C个聚类中心然后算一遍FCM的目标函数值把这个值作为该粒子的适应度。整体流程是初始化随机生成N个粒子每个粒子编码一组聚类中心初始化速度矩阵。对每个粒子解码出聚类中心计算FCM目标函数值作为适应度。更新每个粒子的pbest和有史以来全局最优gbest。按PSO速度、位置更新公式更新所有粒子。重复2~4步直到达到最大迭代次数或全局最优值连续多轮变化小于阈值。把最终gbest解码为聚类中心作为FCM的初始聚类中心。运行标准FCM迭代得到最终的隶属度矩阵和聚类结果。这个流程的好处显而易见PSO负责“大范围找好位置”FCM负责“局部的精细打磨”。二者分工明确既不浪费PSO的全局探索能力也不牺牲FCM在局部收敛上的效率。2.3 关键参数怎么定种群、迭代、惯性权重与学习因子参数这个东西论文里常常一句“经过多次实验确定”就带过了真正自己调的时候才知道水多深。我按自己实操经验给一组落地取值不做保证但可以直接起步。种群规模N一般取20到50。太小了全局搜索能力不够太大了计算量成倍增长Matlab跑起来尤其明显。我做居民负荷数据样本量几千到几万N取30就够用。最大迭代次数取50到100。PSO迭代太多收益递减因为后期粒子基本收敛继续迭代只是在局部附近抖。可以先设100观察适应度曲线什么时候平坦下来再相应缩短。惯性权重w建议从0.9线性递减到0.4。开头w大粒子飞得快全局探索能力强后期w小粒子精细搜索能力强。这种线性递减策略不需要额外复杂机制实测效果稳定。学习因子c1和c2通常取2.0或者让c1从2.5递减到0.5、c2从0.5递增到2.5。后者在复杂数据上更容易跳出局部最优但也不是必须。模糊指数m取2这是FCM的标准默认值绝大多数文献也这么用。我踩过的坑是一开始把种群和迭代数都调得很大结果跑一次要十几分钟而且结果并没有比参数适中的版本好多少。后来才明白PSO-FCM这种“粗搜精调”的组合核心价值是稳定性和收敛质量不是暴力计算。3. 居民用电数据预处理与特征构造3.1 数据清洗缺失值、异常值和设备误差怎么处理真实用电数据远没有公开数据集那么干净。我拿到台区用户数据时缺失、零值、突刺、连续重复值到处都是。数据清洗这一步做得不好后面聚类再花哨也是“垃圾进垃圾出”。缺失值处理我一般看缺失比例。单个用户缺失比例低于5%用该用户同时刻前后几天的均值填充缺失超过20%直接剔除因为强行填充会引入虚假规律。请注意填充一定要按用户维度做不能用全体用户的均值去填否则会把个体行为特征抹掉。异常值处理我常用3σ准则和IQR结合。先计算每个用户日电量序列的均值和标准差把超过均值三倍标准差的点标记出来再用前后邻域的中位数替换。有些用户家里可能有光伏、电动汽车负荷曲线本身波动大不能用一刀切的阈值这类用户在特征构造阶段再单独考虑。还有个容易忽略的点智能电表长时间离线或通信故障可能出现连续多天数据完全相同的状况。这种数据有“规律”但是假的必须剔除。检查方法是算用户日负荷曲线的方差方差接近0直接标记。3.2 特征工程从96点日负荷曲线到聚类输入特征居民用电行为聚类可以直接用日负荷曲线作为特征但我不建议把96个原始点全部丢进聚类模型。原因有两个一是维度太高FCM对高维数据的距离计算压力大二是原始曲线里包含大量冗余信息真正区分用户行为的往往是几个统计特征。我一般会构造以下几类特征日总用电量一天用电的总体水平。峰时段电量占比晚间高峰时段比如19:00-22:00电量占全天比例。谷时段电量占比凌晨低谷时段电量占比。负荷率平均负荷与最大负荷的比值反映用电平稳程度。峰谷差日最大负荷与最小负荷的差值标准化后使用。夜间用电量占比23:00-次日6:00的电量占比用于识别“夜猫子”用户。工作日与周末用电差异比如工作日平均负荷与周末平均负荷的比值。这些特征加在一起维度在10个以内既保留了用户行为的关键信息又大幅降低了计算开销。聚类结果的可解释性也更强——你可以直接说“第三类用户峰谷差大、夜间占比高疑似有电动汽车充电行为”而不用去解释96维聚类中心每一点的含义。如果你还是想保留曲线形状信息可以做降维比如PCA取前5~10个主成分再和统计特征拼接。不过我自己实践下来纯统计特征已经够用加PCA收益不大。3.3 为什么要做归一化以及归一化的时机归一化这一步特别重要。居民用电数据里日总用电量可能是几十千瓦时而峰谷比只有0到1之间的数。如果不归一化FCM计算欧氏距离时会天然放大数值大的特征峰谷比这种区分度高的特征反而被淹没。我用的归一化方法是最大最小值归一化把每个特征缩放到[0,1][ x \frac{x - x_{min}}{x_{max} - x_{min}} ]注意三个细节归一化要按特征列做不是按用户行做。每个特征独立算最小值和最大值。如果后续要做在线预测要保存训练时的x_min和x_max用同一组参数去缩放新数据不要重新计算。如果某个特征方差非常小比如所有用户的负荷率都在0.2到0.3之间归一化后区分度依然不大可以考虑直接去掉避免引入噪声。我见过有人把归一化时机搞错的先做了聚类再想起来归一化只好重新跑。其实应该在特征构造完成后、进入PSO-FCM之前就归一化好因为PSO的粒子初始化是在特征空间里进行的尺度不一致会直接影响初始搜索范围。4. Matlab实现流程与核心代码解读4.1 代码整体结构与主函数框架Matlab是这项工作最顺手的工具矩阵运算方便、画图省事。我做PSO-FCM聚类时代码分三个文件主脚本、PSO优化函数、FCM目标函数计算函数。如果数据量大了还可以再拆一个数据预处理脚本。主脚本的逻辑简单清晰读取样本特征矩阵data大小是N行×D列。设置聚类数C、模糊指数m、PSO参数种群数、迭代数、w、c1、c2。调用PSO优化函数得到最优聚类中心。用最优聚类中心作为初始中心调用Matlab自带的fcm函数或自己写的FCM迭代函数。输出隶属度矩阵、聚类标签画聚类中心曲线。这里有一个小设计要提前想清楚PSO优化出来的最优粒子编码最终要解码成C×D的聚类中心矩阵传给FCM时注意reshape的方向。我最开始就是这里reshape反了导致聚类结果乱成一锅粥。4.2 PSO-FCM核心逻辑的Matlab实现先看主脚本我给出一个可以直接跑的简化版本%% 主脚本 clear; clc; close all; % 假设 data 是 N x D 的特征矩阵且已完成归一化 load(resident_data.mat); % data, 最后一列或单独存标签用于验证 X data(:, 1:end-1); % 特征 N size(X, 1); C 4; % 聚类数根据轮廓系数或业务确定 m 2; % 模糊指数 % PSO参数 nPop 30; % 种群规模 maxIter 80; % 最大迭代次数 w_start 0.9; % 惯性权重初始值 w_end 0.4; % 惯性权重结束值 c1 2.0; % 个体学习因子 c2 2.0; % 全局学习因子 % 粒子维度 聚类数 x 特征维度 dim C * size(X, 2); % 初始化粒子位置和速度 lb min(X(:)); % 下界归一化后一般是0 ub max(X(:)); % 上界归一化后一般是1 positions rand(nPop, dim) * (ub - lb) lb; velocities zeros(nPop, dim); % 计算初始适应度 fitness zeros(nPop, 1); for i 1:nPop centers reshape(positions(i, :), C, size(X, 2)); fitness(i) fcmObjective(X, centers, m); end % 初始化个体最优和全局最优 pbest_pos positions; pbest_fit fitness; [gbest_fit, gbest_idx] min(fitness); gbest_pos positions(gbest_idx, :); % 记录适应度变化 best_record zeros(maxIter, 1); %% PSO迭代 for iter 1:maxIter w w_start - (w_start - w_end) * iter / maxIter; for i 1:nPop r1 rand(1, dim); r2 rand(1, dim); velocities(i, :) w * velocities(i, :) ... c1 * r1 .* (pbest_pos(i, :) - positions(i, :)) ... c2 * r2 .* (gbest_pos - positions(i, :)); positions(i, :) positions(i, :) velocities(i, :); % 边界处理越界则拉回边界 positions(i, :) max(positions(i, :), lb); positions(i, :) min(positions(i, :), ub); centers reshape(positions(i, :), C, size(X, 2)); fitness(i) fcmObjective(X, centers, m); if fitness(i) pbest_fit(i) pbest_fit(i) fitness(i); pbest_pos(i, :) positions(i, :); end end [best_fit_current, best_idx_current] min(pbest_fit); if best_fit_current gbest_fit gbest_fit best_fit_current; gbest_pos pbest_pos(best_idx_current, :); end best_record(iter) gbest_fit; end % 把全局最优粒子解码为FCM初始聚类中心 init_centers reshape(gbest_pos, C, size(X, 2)); % 用FCM做最终聚类 options [m, 100, 1e-6, 0]; % [模糊指数, 最大迭代数, 最小改进量, 是否显示] [centers_final, U, obj_final] fcm(X, init_centers, options); [~, label] max(U, [], 2);fcmObjective函数实现FCM目标函数计算这一步是PSO和FCM之间的桥梁function J fcmObjective(X, centers, m) % X: N x D % centers: C x D N size(X, 1); C size(centers, 1); % 计算样本到每个聚类中心的欧氏距离矩阵 D zeros(N, C); for j 1:C diff X - repmat(centers(j, :), N, 1); D(:, j) sum(diff.^2, 2); end % 计算隶属度矩阵防止除零 D(D eps) eps; invD D .^ (-1/(m-1)); sumInvD sum(invD, 2); U invD ./ repmat(sumInvD, 1, C); % 目标函数值 J sum(sum((U .^ m) .* D)); end这段代码最需要注意的就是D矩阵的防除零处理。如果某个样本刚好和某个聚类中心重合D为0直接除零会得到NaN整个PSO适应度就崩了。加一个eps下界是个简单有效的托底手段。4.3 聚类结果评估与可视化聚类中心曲线、轮廓系数聚类跑完后先用轮廓系数评估聚类效果。轮廓系数取值范围在-1到1之间越接近1说明类内紧凑、类间分离越好。0.25以上说明聚类有一定结构0.5以上说明结构明显。Matlab里用silhouette函数直接算figure; silhouette(X, label); title(Silhouette Plot);只看轮廓系数还不够做居民用电行为分析一定要把每个类别的聚类中心曲线画出来。每一类取中心曲线横轴是时间点纵轴是归一化负荷值一眼就能看出这个类别的用户什么时候用电多、什么时候用电少。比如四类用户可能分别呈现晚高峰单峰型、早晚双峰型、全天空调平稳型、夜间充电型。4.4 完整运行流程与结果解读跑完聚类后我会按以下顺序检查结果看PSO适应度收敛曲线确认gbest是在下降后趋于稳定而不是从一开始就震荡。看轮廓系数确认聚类结构是否清晰。看每类的样本占比如果某一类占比超过70%说明聚类数可能选多了或者特征没有区分度。看聚类中心曲线结合业务判断每一类用户的行为画像是否合理。举例来说我做过一个实际台区的数据聚类数选4结果四类中心的特征分别是第一类晚高峰集中用电峰谷差大负荷率低典型上班族。第二类早晚两个用电高峰白天也有不低的基线负荷可能家里有老人小孩。第三类全天负荷平稳峰谷差小可能是长期居家或小型家庭作坊。第四类夜间用电占比高白天负荷很低疑似电动汽车用户。这类结果写进报告里比单纯说“我们聚类效果好”有说服力得多业务人员也听得懂。5. 实操中常见问题与排查技巧5.1 聚类结果出现空簇空簇是FCM里很常见的现象——某类在迭代过程中没有样本归入聚类中心变成NaN或者悬浮在无数据区域。出现空簇的原因一是初始聚类中心距离真实数据分布太远二是PSO在搜索时粒子的某些位置对应了无效聚类中心。排查方法在fcmObjective里加一个检查如果D矩阵某列全为NaN或者中心附近没有样本落簇直接给一个大的惩罚值引导PSO避开这种无效区域。具体做法是计算每个聚类中心周围最近的K个样本的距离和如果距离和超过阈值目标函数值设置成一个极大的数。另一个思路是限制粒子位置的取值范围不要让它跑出数据边界太远。我常用上面代码里的边界处理把位置限制在数据最小值和最大值之间这样聚类中心至少落在数据范围内空簇概率大幅下降。5.2 多次运行结果差异过大如果两次运行PSO-FCM聚类结果差异很大原因大概率是PSO收敛到了不同的局部最优。此时优先检查粒子群是否早熟——所有粒子聚集在一起失去了探索能力。一个有效的处理手段是增加w的起始值让粒子前期飞得更猛。我试过把w_start从0.9提到1.0结合线性递减到0.4效果明显。另外检查随机种子固定随机种子可以保证结果可复现但对“改善结果”本身没有帮助。如果业务上需要稳定结论建议多次运行取目标函数最小的那一次。5.3 轮廓系数的合理范围与K值选择轮廓系数低通常不是算法的问题而是K值选得不对。这个问题在居民用电数据里尤其明显因为不同用户的行为差异不是均匀分布的有些用户天然介于两类之间。我的做法是对K取2到8每个K跑3次PSO-FCM记录平均轮廓系数画出K值-轮廓系数曲线。选择曲线拐点处的K或者选择业务上最合理的K。比如曲线在K4和K5之间变化不大那就选4因为从业务角度更容易解释。不要无脑追求最高轮廓系数有些K连轮廓系数高但类别边界不清晰实际业务价值不大。5.4 运行速度慢的优化技巧PSO加FCM的双层迭代计算量确实比单跑FCM大。样本量几千时还好如果上了几万甚至十几万条记录速度会直线下降。我常用的提速手段有三个一是用矩阵化运算替代循环。上面fcmObjective代码里距离计算用了repmat加循环优化方向是把所有中心一次算出来用三维矩阵或者pdist2函数直接算全部距离。Matlab的pdist2在欧氏距离下速度非常可观。二是提前抽样。PSO阶段用30%的样本计算适应度找到最优初始中心后再全量数据跑FCM。因为PSO只需要比较相对好坏样本量足够代表分布就行。我实测下来抽样对最终聚类中心的影响很小速度却快了不止一倍。三是降低PSO迭代中FCM的迭代次数。PSO阶段只是“粗选”不需要把每个候选中心都FCM迭代到完全收敛只要把目标函数算个大概就能比较优劣。可以在PSO内部用一个简化版目标函数只算一步隶属度更新后的值。5.5 PSO收敛但FCM目标函数不降有时PSO收敛得很好适应度曲线很漂亮但把最优中心传给FCM后目标函数值反而比PSO阶段还高。这通常是因为PSO阶段用的目标函数和FCM阶段用的标准不一致。排查方法检查两边用的模糊指数m是否一致距离计算方式是否一致。我在初版代码里踩过这个坑——PSO内部用欧氏距离的平方FCM内部用fcm函数默认的欧氏距离范数两边的量纲不同结果自然对不上。统一距离定义后问题就消失了。6. 这套方法还能怎么扩展做完PSO-FCM之后我发现这套组合思路的价值其实不止于居民用电聚类。任何“对初始值敏感容易局部最优”的聚类或分类算法理论上都能用类似思路去优化。比如用PSO优化K-Means的初始中心、用PSO优化DBSCAN的epsilon和MinPts参数逻辑完全同构。在居民用电分析这个具体场景里后面有几个值得继续做的方向把时间维度拉长。用一周或一个月的负荷数据做时序聚类识别用户长期行为模式的变化。把聚类结果作为上游特征输入预测模型。先分群再对每个群单独做短期负荷预测精度通常比全局模型高。结合外部因素。把天气、节假日、电价信息加入特征用户分群的结果会更精细也更容易解释。做这个课题的过程中我最大的体会是不要迷信算法组合的复杂度真正决定结果上限的是你对数据的理解。PSO-FCM的价值不是“用了两个智能算法”听起来厉害而是它解决了FCM在实际负荷数据上稳定性差的问题。如果数据清洗和特征工程做得扎实哪怕用标准FCM都能得到不错的结果PSO优化是在这个基础上再推一把让结果更加可靠、可复现。最后分享一个实操细节跑完聚类一定要把随机种子保存下来。PSO-FCM不是完全确定性的算法保存种子可以让你在任何时候复现出同一份结果这在写论文、汇报或者给业务方演示时特别重要。别问我怎么知道的我被“换台电脑结果变了”这种问题坑过不止一次。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进