ARTICLE DETAIL

资讯详情

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

粒子群算法优化FCM聚类:居民用电行为分析的Matlab实现与调参指南

粒子群算法优化FCM聚类:居民用电行为分析的Matlab实现与调参指南 拿到一份居民用电负荷数据集第一件要回答的问题往往不是“用什么高级算法”而是“这些用户到底能分成几类人”。白天家里常有人和白天上班、晚上才回来的用户负荷曲线天然就是两种形态再加上周末规律、季节差异靠人工打标签累死人。我最近用粒子群算法PSO优化FCM聚类框架对某小区500户居民一年多的用电数据做行为分析把从特征工程、算法改进到Matlab代码落地的整套流程跑通了。这篇东西不是论文复现是实际调试中沉淀下来的可抄作业方案需要的直接拿走改数据就能用。我先把结论放前面纯用标准FCM同一份数据跑10次可能得到三四种差别明显的聚类结果而给FCM套上PSO做全局寻优之后10次结果基本稳定目标函数值也更小。这不是什么玄学而是FCM本身的迭代机制决定的。下面我会从原理、特征准备、Matlab实现到结果对比逐步拆开讲。1. 先搞清楚一件事FCM聚类在用电行为分析里的地位和痛点1.1 居民用电行为分析到底在分析什么居民用电行为分析简单说就是基于用户的负荷曲线把“行为模式相似”的用户归到同一类进而支撑需求侧响应、分时电价设计、台区负荷预测这些下游业务。举个例子某地区要做峰谷电价你不能把所有用户一刀切得先知道哪些用户对电价敏感、哪些用户根本不在乎聚类结果就是这些业务决策的输入。常见的分析维度包括日平均负荷、最大负荷、负荷率、峰谷差、夜间用电占比、不同时段电量占比、工作日与周末负荷差异等。把这些维度抽成特征向量之后再用聚类算法分组比直接对24小时负荷曲线做聚类要稳健。原因很简单原始曲线维度高、噪声大直接聚类很容易被个别异常点带偏而特征向量做了信息压缩也方便解释结果。1.2 FCM为什么比K-means更适合刻画用电模式K-means是硬聚类每个样本只能属于一个簇。但居民用电行为本质上带有很强的模糊性一个用户可能工作日是上班族模式周末又切换成居家模式另一个用户可能介于“白天在家”和“白天偶尔外出”之间。硬聚类一刀切会把这种过渡状态强行归到某一类聚类边界处容易失真。FCM模糊C均值聚类的核心是引入隶属度矩阵U每个样本对每个簇都有一个0到1之间的隶属度而且每行之和等于1。划分不再是“非此即彼”而是“以多大程度属于”。FCM迭代优化目标函数J Σ(i1..N) Σ(j1..C) u_ij^m · ||x_i - v_j||²其中m是模糊指数一般取2。U和V交替迭代直到目标函数变化小于阈值。这套机制用在用电行为上能保留“过渡型用户”的信息聚类结果的边界也更贴合真实情况。所以在电力用户分群这类问题上FCM比K-means用得更多不是我个人的偏好是这类数据本身的性质决定的。1.3 FCM的两个硬伤初始中心敏感和局部最优用过FCM的人应该都有这种经历同一个数据集、同一个参数就换个随机初始中心结果变化很大。原因在于FCM的目标函数对聚类中心V来说是非凸的迭代更新本质上是交替最小化从不同的起点出发最终会停在不同的局部极小点。我实测过一个500户、5维特征的数据标准FCM用不同随机种子跑10次有两三次的聚类中心和其余几次完全对不上对应的目标函数J值有时能差出30%以上。这个不稳定在业务上是非常致命的你给营销部门出的用户分群表今天跑和明天跑可能不一样后面所有基于分群的分析都会受影响。要解决这个问题常规思路有三个多起点随机重启、用遗传算法或用粒子群算法做全局优化。多起点随机重启最笨但最省事问题是不知道要跑多少次才能拿到好结果遗传算法编码和调参比PSO略重粒子群算法结构简单、参数少、收敛快而且是连续优化算法和FCM的连续变量天然匹配。所以我把PSO作为优化工具去解决FCM的初始中心敏感问题这一步是整个方案的核心动机。2. 粒子群算法凭什么能优化FCM原理与编码设计2.1 PSO的核心思想个体极值与全局极值的协作粒子群算法Particle Swarm Optimization是Kennedy和Eberhart在1995年提出的群体智能算法灵感来自鸟群觅食。每个粒子就是搜索空间里的一个候选解粒子在飞行过程中会记住自己历史最优位置pBest同时整个群体共享全局最优位置gBest然后按这两个信息调整自己的速度v_i(t1) w·v_i(t) c1·r1·(pBest_i - x_i(t)) c2·r2·(gBest - x_i(t))x_i(t1) x_i(t) v_i(t1)其中w是惯性权重控制“沿用之前速度”的程度c1、c2是学习因子r1、r2是[0,1]均匀随机数。理解起来可以这样类比粒子既想飞回自己发现过的好位置又想追随群体发现的好位置同时还要兼顾原来的飞行惯性三者合力决定了下一步往哪走。PSO最大的好处是不要求目标函数可导只要求你能算出每个候选解的适应度值。FCM的目标函数J虽然可以用解析梯度迭代但梯度法容易卡在局部极值而PSO是种群式搜索多个粒子分布在搜索空间不同区域天生就不容易被单一局部极值困住。2.2 把FCM的优化问题包装成PSO能解的粒子现在的问题是怎么把“求一组好的聚类中心”这个任务映射成PSO里的粒子。标准做法是让每个粒子直接编码一套聚类中心矩阵。假设聚类数C、特征维度D那么粒子的维度就是C×D。粒子位置x就是一个一维向量前D个元素是第1个聚类中心的各维坐标后面D个元素是第2个中心依此类推。解码时用reshape把x还原成C行D列的矩阵V。初始粒子群不是用纯随机数生成的而是从训练样本里随机抽C个样本作为某个粒子的初始聚类中心。这样每个粒子一开始就已经是“一套合理解”在搜索空间里有实际意义比纯随机初始化的收敛速度快很多这个细节对实验效果影响很明显。速度也要初始化一般设为向量范围的10%到20%即可。粒子位置的边界用数据特征的最小值和最大值适当外扩防止粒子飞出有意义的区域。这一步编码做好后面PSO才能稳定迭代。2.3 适应度函数的选择不是随便用个目标函数就行PSO里粒子的适应度函数就是FCM的目标函数J。每次迭代时解码出V计算出隶属度矩阵U然后算JJ越小表示聚类越紧凑、划分越清晰。这一步看起来直接但有几点要提醒第一计算U时分母可能会出现距离为0的情况也就是某个样本恰好和聚类中心重合。实际中我会给距离矩阵加一个极小值比如1e-10避免除零否则算出来的隶属度全是NaN整个程序直接崩掉。第二有些论文会在适应度里叠加“惩罚项”或者直接用XB指标、划分系数这类有效性指标作为适应度目的是让算法在迭代时既降低J又自动规避过拟合。但如果只做单一目标寻优直接用J最干净最后用有效性指标去做横向评价就好。我建议别一上来就搞多目标容易把调参难度拉高。第三如果想让PSO只负责找初始中心那适应度计算可以只执行一次FCM迭代就返回J不必等FCM完全收敛。实际上我测试过PSO内部每一代都做完整FCM迭代计算量太大而且效果并不会更好。正确做法是PSO在适应度函数里只算一次U和J等PSO收敛后把最优中心交给标准FCM精调几步。这种“PSO粗寻优、FCM精调和”的混合策略速度和最终质量都更好。3. 用电数据怎么准备特征构造、清洗与归一化3.1 从原始负荷曲线到行为特征向量原始数据一般长这样每一行是一个用户每一列是某个时间点的负荷读数可能是15分钟或者1小时一个点。如果直接对24点曲线做FCM一是特征维度高二是曲线之间的相似度容易受整体量级影响比如一户人家用电量大但不代表行为模式不同。所以我习惯先把原始负荷曲线转成一组有业务含义的特征。下面是我在居民用电行为分析里常用的特征清单日平均负荷全天用电的整体水平。日最大负荷与最小负荷用电峰值与谷值。日负荷率平均负荷除以最大负荷反映负荷平稳程度。峰谷差率最大负荷-最小负荷/最大负荷反映负荷波动幅度。夜间用电占比22点到次日6点用电量占总用电量的比例。峰段电量占比按当地峰谷时段规则统计峰段电量占总电量比例。负荷标准差反映各时刻负荷偏离均值的程度。这些特征组合起来基本能把“白天在家型”“上班族晚间型”“全天均衡型”“夜间活跃型”这几类典型居民用户区分开来。特征不是越多越好我建议先按业务理解挑6到8个后面用聚类结果的可解释性判断要不要增删。3.2 特征选择哪些指标最能区分不同用电行为这个环节容易被跳过但对结果影响很大。我实际筛选特征时主要用两个抓手第一是看特征之间的相关性。比如“日最大负荷”和“日平均负荷”往往高度相关两个都放进去相当于给某个维度加了双倍权重归一化之后还容易掩盖其他特征的作用。遇到相关度超过0.8的特征我会保留业务解释更清晰的那一个。第二是看聚类结果的轮廓系数。先用全部特征跑一遍PSO-FCM然后逐个删掉某个特征再跑看轮廓系数是升还是降。轮廓系数升高说明这个特征在拖后腿删掉降低说明它对区分类别有帮助留着。这个做法虽然要多次跑聚类但作为一次性的特征筛选完全值得。3.3 归一化处理的细节与陷阱聚类算法对特征的尺度非常敏感FCM里的距离计算会给量级大的特征更大权重。比如用“日最大负荷”数值是几千瓦“夜间用电占比”是0到1的小数不归一化时聚类几乎只由日最大负荷决定。我用的归一化方式是min-max标准化到[0,1]x_norm (x - min(x)) / (max(x) - min(x))这里有个细节min和max是从样本上算出来的。数据里如果存在异常尖峰比如某天功率表误记录了一个极大值min-max后大多数样本都会被压到很窄的区间。遇到这种情况我会先剔除明显异常样本再做归一化也可以改用分位数缩放比如把2%到98%分位数映射到[0,1]对噪声更稳。另外聚类完之后如果要拿聚类中心做业务解读记得把中心反归一化回原始量纲否则你只能说“类别1的平均负荷特征是0.78”业务人员根本没法理解。这一步很多人会漏掉属于典型的“算法跑通了、业务用不上”。4. PSO-FCM聚类的Matlab实现从主函数到子函数逐段拆解4.1 主程序框架与参数初始化我用的Matlab版本是R2021a不需要额外工具箱除了pdist2需要Statistics and Machine Learning Toolbox之外其余都是基础函数。整个程序分成三块主脚本、PSO适应度函数、标准FCM精调函数。先看主脚本的骨架%% PSO-FCM 主程序 clear; clc; close all; rng(2024); % 载入特征数据矩阵大小为 N x D % 这里用模拟数据跑通流程真实使用时请替换为 load(user_feature.mat) N 500; D 5; data [randn(N,1)*1.24.5, randn(N,1)*0.40.5, randn(N,1)*0.250.6, ... randn(N,1)*0.30.45, randn(N,1)*0.20.35]; data max(data, 0); % 归一化到[0,1] data_norm (data - min(data)) ./ (max(data) - min(data)); % PSO 参数 C 3; % 聚类数 m 2; % 模糊指数 popsize 30; % 粒子数 maxgen 80; % PSO迭代次数 wmax 0.9; wmin 0.4; c1 1.5; c2 1.5; vmax_ratio 0.2; [N, D] size(data_norm); nvars C * D; lb repmat(min(data_norm), 1, C); ub repmat(max(data_norm), 1, C);这里要插一句上方的data只是模拟数据。真实使用时直接把外部读进来的特征矩阵做一遍归一化然后替换成data_norm就行。D的取值以实际特征数为准。4.2 PSO迭代核心位置更新与速度更新初始化粒子时每个粒子的位置都是从样本中随机抽C个样本拼成的向量这样初始解比纯随机数更有意义pop zeros(popsize, nvars); vel zeros(popsize, nvars); for p 1:popsize idx randperm(N, C); pop(p,:) reshape(data_norm(idx,:), 1, nvars); vel(p,:) (ub - lb) .* rand(1, nvars) * vmax_ratio * 2 - (ub - lb) * vmax_ratio; end pbest pop; pbest_fit inf(popsize, 1); gbest_fit inf; for gen 1:maxgen for p 1:popsize fit fitness_fcm(pop(p,:), data_norm, C, m); if fit pbest_fit(p) pbest_fit(p) fit; pbest(p,:) pop(p,:); end if fit gbest_fit gbest_fit fit; gbest pop(p,:); end end w wmax - (wmax - wmin) * gen / maxgen; for p 1:popsize vel(p,:) w * vel(p,:) c1 * rand(1,nvars) .* (pbest(p,:) - pop(p,:)) ... c2 * rand(1,nvars) .* (gbest - pop(p,:)); vel(p,:) max(min(vel(p,:), (ub-lb)*vmax_ratio), -(ub-lb)*vmax_ratio); pop(p,:) pop(p,:) vel(p,:); pop(p,:) min(max(pop(p,:), lb), ub); end if mod(gen, 10) 0 fprintf(第%d代目标函数J %.4f\n, gen, gbest_fit); end end速度限幅我用的是(max-min)*0.2防止粒子一步跨到远离搜索空间的位置。惯性权重w从0.9线性降到0.4初期多探索、后期多收敛这是PSO调参里的常见套路。4.3 FCM子程序如何嵌入PSO循环这里是整个方案的关键。适应度函数做了一个精简版粒子位置解码为V然后按FCM公式算隶属度U再算J不执行完整FCM迭代。代码如下function J fitness_fcm(x, data, C, m) D size(data, 2); V reshape(x, C, D); dist2 pdist2(data, V).^2; % N x C dist2(dist2 1e-10) 1e-10; % 防止除零 % 隶属度矩阵 U invdist dist2 .^ (-1/(m-1)); U invdist ./ sum(invdist, 2); J sum(sum((U.^m) .* dist2)); endPSO跑完之后把gbest作为初始聚类中心交给标准FCM再精调几步这一步是为了把PSO找到的“大致区域”收敛到更精确的局部最优。标准FCM迭代就是反复更新U和Vfunction [U, V, Jhist] fcm_refine(data, V0, C, m, maxiter) V V0; D size(data, 2); N size(data, 1); Jhist zeros(maxiter, 1); for t 1:maxiter dist2 pdist2(data, V).^2; dist2(dist2 1e-10) 1e-10; invdist dist2 .^ (-1/(m-1)); U invdist ./ sum(invdist, 2); % 更新聚类中心 U_m U.^m; V (U_m * data) ./ sum(U_m, 1); Jhist(t) sum(sum(U_m .* dist2)); end end关于pdist2如果你没有Statistics和Machine Learning Toolbox可以用两层循环自己算距离矩阵只是会慢一些。我这里为了代码简洁用了pdist2实际项目里我会封装一个dist_matrix函数方便在不同机器上跑。4.4 聚类效果评估与结果可视化代码聚类完成后最终结果保存在隶属度矩阵U和聚类中心V里。硬划分结果用每个用户的最大隶属度类别% 主脚本后半部分 [U, V, ~] fcm_refine(data_norm, reshape(gbest, C, D), C, m, 30); [~, label] max(U, [], 2); % 反归一化聚类中心便于业务解读 V_orig V .* (max(data) - min(data)) min(data);可视化方面我会画两张图。第一张是PSO的收敛曲线看目标函数J随迭代的变化第二张是聚类后的典型特征对比柱状图或者雷达图让业务人员直观看到每一类的行为画像。雷达图的Matlab代码不复杂但要注意每个特征先归一化到相同尺度再画否则量级大的特征会撑满整个图量级小的特征根本看不出差异。5. 实验对比PSO-FCM、标准FCM、K-means的结果分析5.1 聚类有效性指标怎么解读聚类结果好不好不能光靠肉眼。我把通用的几个指标拉出来对比目标函数JFCM内部指标J越小说明簇内越紧凑但过度聚类时J会持续变小所以不能单独用。划分系数PC越接近1越好反映隶属度划分的清晰程度但数值本身随着聚类数C增大而增大只能同C下横向比。XB指标Xie-Beni指标分子是J分母是任意两个聚类中心之间的最小距离的平方XB越小越好可以用于不同C之间的比较。轮廓系数SC在[-1,1]之间越大越好计算时用的是硬划分标签所以K-means也能算。这些指标各有偏向我通常看XB和轮廓系数为主要依据再结合业务可解释性做判断。我建议不要机械地选“指标最优”的聚类数因为聚类数C6可能指标很好但分出来的类别业务看不懂那就没有价值。5.2 同一份用电数据上三类算法的表现为了说明PSO-FCM的改进效果我构造了三类存在部分重叠的模拟用电数据三类样本数分别是180、170、150特征5维。三类分别模拟上班族夜间用电占比高、峰谷差率大、全天居家型负荷率高、波动小、白天在家的混合型数据有一定重叠度更贴近真实情况。分别用标准FCM随机初始中心跑一次、K-means和PSO-FCM跑记录一组典型结果如下模拟数据实测值会有波动但趋势一致算法目标函数J按FCM公式计算XB指标轮廓系数10次运行中心偏差标准FCM387.20.2760.313较大部分簇中心偏移超0.1K-means386.50.3010.295中等硬划分损失模糊信息PSO-FCM352.80.1310.458较小中心偏差在0.02以内从趋势看PSO-FCM在三个指标上都优于前两者最重要的提升是稳定性10次运行结果基本一致。标准FCM因为随机初始化的关系有时候能跑到和PSO-FCM接近的J值但概率不高。我在电力数据上做过几十次重复实验PSO-FCM几乎每次都能拿到接近最优的J而标准FCM有接近一半次数会陷在明显更差的局部极值里。5.3 结果图谱怎么画从聚类中心到典型用电曲线聚类中心反归一化之后可以还原每类的典型特征。比如我得到三个聚类中心第一类的日平均负荷高、夜间用电占比低对应“全天活跃型”第二类峰谷差率极大、夜间用电占比高对应“上班族晚间型”第三类负荷率接近0.8、波动小对应“全天均衡型”。把这些画像交给业务部门时我会把每个类别的用户数量、占比、典型特征均值、用电量区间都整理成一张表。聚类只是手段分群后能落地到业务才是有价值的。比如“上班族晚间型”这类用户对晚峰时段电价更敏感就可以作为分时电价策略的主要目标群体。6. 实操避坑清单与调参建议6.1 粒子数量与迭代次数怎么定粒子数量过少全局搜索能力不足过多计算量大但收益递减。我的经验是样本量几百到几千时粒子数30到50就够不用堆到100以上。迭代次数80到120代基本就能收敛跑完之后看收敛曲线就能判断如果曲线在后半段已经平了说明没必要继续加迭代。需要特别提醒的是PSO-FCM的计算量是PSO迭代次数、粒子数、样本量和特征维度的乘积。500个样本、5维特征、30个粒子、80代在普通笔记本上大概几秒到十几秒完全可以接受。但如果是5000个样本、24维特征计算量会涨很多这时可以考虑对数据先降维或者对粒子做并行评估。6.2 惯性权重与学习因子的调整经验惯性权重w决定粒子的“探索”和“开发”平衡。w大粒子飞得远全局搜索能力强w小粒子在局部精细搜索。我采用线性递减w从0.9降到0.4前期快速覆盖搜索空间后期慢慢收敛。如果发现收敛曲线一直在震荡可以把wmin调高到0.5如果发现收敛太早就停在了一个较差的局部解可以把wmax提高到1.0。学习因子c1和c2一般取值1.5到2.0两个取相等即可。如果c2大于c1群体容易过早被全局最优吸引种群多样性降低反过来c1大于c2粒子过于“自我”收敛速度慢。实际调试时我大多数情况下直接设c1c21.5效果已经很稳。6.3 一个被很多人忽略的问题模糊指数m的取值模糊指数m控制隶属度的模糊程度。m1时FCM退化为硬聚类m越大隶属度越趋向均匀分布聚类边界越模糊。绝大多数文献默认m2这个取值在中庸场景下没问题但不是万能的。我的建议是m在1.5到2.5之间做一个小扫描每次跑PSO-FCM后算XB指标和轮廓系数对比哪个m下两个指标都更优。我遇到过一类数据m2时两类用户分得比较模糊改成m1.6之后边界变得清晰轮廓系数从0.32升到0.41。但也别把m降到1.3以下那会丢失FCM本身“软划分”的优势。6.4 算法稳定性验证与多次运行结果无论参数调得多好我都建议至少跑10次重复实验记录每次的目标函数最终值和聚类中心偏差。如果10次的结果差异很小说明这个模型在这个数据上是稳定的。这一步在业务环境里特别重要因为下游系统可能每天都要重跑分群任务不稳定就等于给业务埋雷。另外还要警惕聚类数C随意取的问题。我通常的做法是遍历C从2到8跑一遍PSO-FCM画XB指标随C变化的曲线看拐点或者极小值点。但在业务上我还会要求每个聚类都能“讲出一个合理的故事”指标只是参考不能为了追求指标好看把一个可解释的类别拆成两个说不清的子类。最后再说一个实际操作里的体会。我一开始把PSO迭代次数设到200、粒子数设到50总希望“全局搜索更充分”结果跑一次要半分钟而且结果并没有比30个粒子、80代更好。后来我把PSO的定位调整为“找一个好的初始区域剩下的让FCM去精修”计算量瞬间降下来稳定性反而更高。做算法优化有时候不是堆资源而是想清楚每个模块到底该负责哪一段。这套PSO-FCM流程我已经在多个电力和工业数据上验证过凡是碰到FCM初始敏感、结果反复跳的问题先试试这个框架基本都能把“玄学”变成“稳定复现”。
返回列表