十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

PSO优化FCM的居民用电负荷聚类Matlab实现与参数调优

PSO优化FCM的居民用电负荷聚类Matlab实现与参数调优 去年年底我接了一个居民台区负荷分析的需求手里是一批按天采样的96点日负荷曲线数据量不算大但要把这些曲线背后的用户行为分门别类理清楚却让我卡了好几天。直接画散点图看不出规律用K-means硬聚类跑一遍分出来的类别边界怪怪的很多用户的曲线形态明明介于两类之间却被强行划进某一类。后来换上模糊C均值聚类FCM分类结果柔和了但FCM对初始聚类中心极其敏感同一份数据跑几遍结果漂移很大。最后我用粒子群算法去优化FCM的初始聚类中心把两者串成一套完整的Matlab实现才把这个问题彻底理顺。这篇博文就把完整思路、核心代码和踩过的坑一并整理出来希望对做负荷分析、用户分群的同行有参考价值。1. 居民用电行为分析到底在解决什么问题1.1 为什么电力行业执着于用户分群电网侧和售电侧做用户分群不是为了画几张好看的彩图交差。居民用户单体容量小但数量庞大群体行为直接影响台区负荷预测、分布式光伏消纳、峰谷电价设计和需求侧响应策略。举一个我实际处理过的场景同一个台区下面可能同时存在好几类典型用电模式——工作日白天家里没人、晚上6点后才开始集中用电的上班族白天也有稳定基础负荷的退休家庭还有夜间给电动车充电、凌晨出现负荷高峰的特殊用户。这三类用户叠加出来的台区总负荷曲线和单独某一类用户的曲线完全不同。如果管理者不知道台区里存在这几类人做容量规划时就会偏保守或偏冒进做需求响应时也找不准真正的调节对象。聚类分析在这里的价值就是不用预设标签直接从数据里把用户的用电形态长什么样挖出来。每类用户的典型负荷曲线一旦提取出来后续的个性化电价套餐设计、用电异常检测、社区储能配置就都有了依据。这也是为什么居民用电行为分析类课题在近几年的电力数据挖掘论文里反复出现——它不是理论空转而是能落到台区管理实务上的基础工作。1.2 硬聚类与软聚类的差异K-means和FCM的取舍提到聚类很多人第一反应是K-means。K-means属于硬聚类每个样本只能归属一个类别这个特性在处理真实负荷曲线时有个实际麻烦很多用户的用电行为是混合型的工作日像上班族周末像全天家庭硬生生把它归到某一类分类结果就很不稳定今天是上班族明天加两个周末样本又变成全天型。K-means对离群点也敏感出现一个异常负荷日就把聚类中心拽偏。FCM模糊C均值聚类解决的就是这个非此即彼的问题。它给每个样本分配一组隶属度比如某个用户的日负荷曲线有0.6的概率属于上班族、0.25的概率属于全天型、0.15的概率属于夜间充电型而不是直接打上一个标签。这种软划分更贴合电力用户的叠加属性也方便后续做概率型分析和柔性决策。但FCM有一个众所周知的毛病——对初始聚类中心的选择非常敏感。初始中心选得不好迭代收敛时就容易掉进局部最优解聚类结果和全局最优相差甚远。我实测时发现同一个数据集FCM换不同的初始中心跑20次Xie-Beni指数的波动范围能超过30%这个不确定性在实际项目中很难接受。于是我把粒子群算法引进来用PSO的全局搜索能力给FCM找一个靠谱的出发位置。2. FCM的目标函数和它在实测中暴露的软肋2.1 FCM的数学原理隶属度、目标函数和迭代公式FCM的思路可以这样理解有N个样本每个样本是d维特征向量我们要把它们划分成C个模糊簇。每个样本对每个簇都有一个隶属度u所有隶属度累加为1。算法要优化的目标函数是所有样本到所有聚类中心的加权距离平方和写成形式是J Σ(i1到C) Σ(j1到N) u(i,j)^m × ||x(j) - v(i)||^2其中m是模糊指数默认取2控制分类的模糊程度。m越大隶属度分布越均匀分类越软m接近1时FCM的行为就趋近于K-means。最小化这个目标函数靠交替迭代两步完成。第一步固定聚类中心更新隶属度矩阵u(i,j) 1 / Σ(k1到C) ( ||x(j)-v(i)|| / ||x(j)-v(k)|| )^(2/(m-1))第二步固定隶属度矩阵更新每个聚类中心v(i) Σ(j1到N) u(i,j)^m × x(j) / Σ(j1到N) u(i,j)^m两个公式交替跑几十轮目标函数逐步下降直到变化量小于阈值或达到最大迭代次数。到这里FCM的标准流程看起来并不复杂Matlab里甚至直接用自带fcm函数就能跑。但真正把它用到居民用电数据上问题就来了。2.2 一个复现出的典型问题初始中心不同结果天差地别我拿一批模拟的96点居民负荷曲线做测试样本400个设定聚类数为3。Matlab自带的fcm函数默认用随机方式初始化聚类中心这导致每次运行结果差异很大。我把同一份数据跑了10次记录每一类聚类中心的典型特征有时第一类中心是一条晚上高峰明显的曲线有时同样的类别却变成了白天双峰曲线类别之间的对应关系完全乱了。这种不稳定带来的直接后果是业务解读困难。算法跑出来的三类用户第一次可以说A类是上班族、B类是全天型、C类是夜间型第二次同样的数据却变成A类是夜间型、B类是上班族、C类混合型这让下游的运营策略完全没法落地。更严重的是当数据维度升高、类别数增多时FCM陷入局部最优的概率会显著增加。我统计过一个6维特征、5个聚类的实验FCM直接运行20次有7次收敛结果的目标函数值明显偏高相当于三分之一以上的概率落到糟糕的局部解上。这个实测结果让我明确了一点FCM本身不是不能用而是需要一个稳定的、高质量的初始聚类中心。解决思路有几种有人用K-means结果做初始化有人用层次聚类做预聚类我选的方案是用粒子群算法在全局范围搜索较优的初始中心再用FCM做局部精细收敛。这个组合既不改变FCM的数学本质又弥补了它对初值敏感的缺陷。3. 粒子群优化FCM的整体思路与数学模型3.1 PSO的迭代逻辑鸟群觅食的数学映射粒子群算法PSO的核心思想来自对鸟群觅食行为的观察。一群鸟在区域里找食物每只鸟不知道食物在哪但知道当前位置离食物有多远同时能感知到群体里其他鸟的位置。于是每只鸟的运动方向由两个信息共同决定自己历史上找到过的最优位置以及整个群体发现的最优位置。把这个逻辑映射到优化问题里每个粒子就是一个候选解粒子在解空间里飞行速度和位置按公式迭代更新速度更新v w×v c1×r1×(pbest - x) c2×r2×(gbest - x) 位置更新x x v其中w是惯性权重控制粒子保持原有速度的能力c1、c2是学习因子分别控制粒子向个体最优和全局最优学习的强度r1、r2是[0,1]之间的随机数保证搜索的随机性。PSO的优势在于实现简单、不需要计算梯度、全局搜索能力强。它和FCM结合时不需要对FCM的迭代过程做任何改动只需要把聚类中心怎么初始化这个问题转成PSO可以优化的粒子编码形式即可。整体结构是两层外层是PSO的全局搜索内层是FCM的局部收敛。3.2 粒子和适应度函数的设计把聚类中心塞进粒子设计粒子的编码是最关键的环节。FCM需要优化的变量是C个聚类中心每个聚类中心是d维向量因此一个粒子就用一个长度为C×d的向量表示前d个元素是第一个聚类中心的坐标接着d个元素是第二个聚类中心的坐标以此类推。比如3个聚类、每个聚类中心是96维负荷曲线那一个粒子的长度就是288维。粒子的取值范围需要约束在数据空间内每个维度的上下界可以取该特征在全部样本中的最小值到最大值范围。不约束的话粒子可能飞到非常离谱的位置导致局部搜索时距离矩阵出现数值问题。适应度函数直接采用FCM的目标函数值。对每个粒子先用它编码出的C个聚类中心作为FCM的初始中心迭代若干轮比如10轮或20轮把迭代结束后得到的J值作为该粒子的适应度。由于FCM在给定初始中心后的收敛方向是确定的J值越小意味着这组初始中心越接近全局最优解所在的区域。这里有个细节不要用Xie-Beni指数做适应度我试过Xie-Beni指数在迭代早期的区分度不够容易导致PSO收敛变慢直接用目标函数J收敛路径更陡峭效果更稳定。3.3 全局寻优与局部细化的衔接思路两层结构的核心思路是粗定位精修正。PSO负责在全局空间搜索一个较优的初始聚类中心组合它不需要精确到小数点后几位只要能落在目标解附近就行FCM从PSO给出的初始中心出发沿着目标函数下降方向快速收敛到精确解。这种衔接方式避免了两个极端单独用PSO去直接求解整个聚类问题迭代次数需要非常大因为粒子在高维空间里精确收敛很慢单独用FCM又容易陷入局部最优。两者配合时PSO迭代30到50次就能提供一个足够好的起点FCM再跑50轮左右完成精修整体计算开销在可接受范围内。4. Matlab代码实现全过程4.1 数据预处理归一化是优先级最高的步骤写代码之前先处理数据。居民用电数据常见的问题是量纲不一致比如96点负荷曲线每个点的数值范围可能是0到5千瓦而用户日均用电量可能大到几十千瓦时如果把这些特征拼在一起不归一化欧氏距离会被量纲大的维度主导聚类结果基本等于只用了一两个特征。我习惯的做法是先剔除有明显采集异常的样本比如负荷为负值、连续多个点为零、日用电量超过台区容量上限之类的数据然后对每个特征做min-max归一化到[0,1]区间% 数据归一化data是N行d列的原始负荷特征矩阵 data_norm (data - min(data, [], 1)) ./ (max(data, [], 1) - min(data, [], 1) eps);需要注意min和max是按列计算的对应每个采样点特征在所有用户中的最小值和最大值不要搞成按行计算否则会把同一个用户不同时刻的量纲差异也抹掉。对于96点负荷曲线还有一个可选操作降维。96维直接做聚类不是不行但计算量大且容易受到局部波动干扰。我常用两种降维方式一种是把96点压缩成几个统计特征再聚类比如早高峰均值、晚高峰均值、夜间均值、峰谷差、负荷率另一种是用主成分分析提取前几个主成分。统计特征方式的好处是业务可解释性强代码简单不需要额外的工具箱支持。4.2 PSO-FCM主程序核心代码下面给出PSO-FCM的Matlab主程序参数按我的实测经验设置%% 参数设置 data data_norm; % 归一化后的数据N x d [N, D] size(data); C 3; % 聚类数 m 2; % 模糊指数 Np 30; % 粒子群规模 MaxPSOIter 50; % PSO迭代次数 c1 2; c2 2; w_max 0.9; w_min 0.4; FCMInnerIter 20; % 每个粒子内部FCM迭代次数 lb min(data, [], 1); % 粒子位置下界 ub max(data, [], 1); % 粒子位置上界 % 初始化粒子位置和速度 particle zeros(Np, C*D); velocity zeros(Np, C*D); for i 1:Np % 从样本中随机挑选C个样本作为初始聚类中心 idx randperm(N, C); center0 data(idx, :); particle(i, :) center0(:); velocity(i, :) ... zeros(1, C*D); % 初速设为零也可用小范围随机数 end pbest particle; % 个体最优位置 pbest_val inf(Np, 1); % 个体最优值 gbest particle(1, :); % 全局最优位置 gbest_val inf; % 全局最优值 %% PSO主循环 for t 1:MaxPSOIter w w_max - (w_max - w_min) * t / MaxPSOIter; for i 1:Np center_init reshape(particle(i, :), C, D); [~, ~, J] fcm_short(data, center_init, C, m, FCMInnerIter); fitness J; if fitness pbest_val(i) pbest_val(i) fitness; pbest(i, :) particle(i, :); end end [best_val_idx, best_idx] min(pbest_val); if best_val_idx gbest_val gbest_val best_val_idx; gbest pbest(best_idx, :); end % 速度和位置更新 for i 1:Np r1 rand(1, C*D); r2 rand(1, C*D); velocity(i, :) w * velocity(i, :) ... c1 * r1 .* (pbest(i, :) - particle(i, :)) ... c2 * r2 .* (gbest - particle(i, :)); particle(i, :) particle(i, :) velocity(i, :); % 越界处理把越界分量拉回到边界 particle(i, :) max(particle(i, :), repmat(lb, 1, C)); particle(i, :) min(particle(i, :), repmat(ub, 1, C)); end end %% 用最终gbest作为FCM初始中心做一次完整收敛 center_init reshape(gbest, C, D); [U_final, center_final, J_final] fcm_short(data, center_init, C, m, 100);这段代码有几个细节值得注意。第一粒子初始化时从样本中随机挑C个点比在连续空间里均匀随机生成效果要好因为样本点本身就落在数据密集区域迭代起步更快。第二越界处理用的是简单的边界截断不要用重新随机初始化的方式否则会破坏粒子已有的搜索记忆。第三PSO结束后不要直接用gbest作为最终结果还需要用它作为初始中心让FCM完整收敛一次因为gbest经过的是短迭代精确度不够。4.3 FCM局部搜索子函数fcm_short函数实现了FCM的核心迭代逻辑和Matlab自带fcm函数的区别在于可以传入自定义初始中心并且返回目标函数值function [U, center, J] fcm_short(data, center_init, C, m, max_iter) N size(data, 1); D size(data, 2); center center_init; U zeros(N, C); for iter 1:max_iter % 计算每个样本到每个聚类中心的欧氏距离 Dmat zeros(N, C); for j 1:C diff data - repmat(center(j, :), N, 1); Dmat(:, j) sqrt(sum(diff.^2, 2)); end Dmat(Dmat eps) eps; % 避免除零 % 更新隶属度 inv_mat Dmat .^ (2/(m-1)); U inv_mat ./ repmat(sum(inv_mat, 2), 1, C); % 更新聚类中心 Um U .^ m; center (Um * data) ./ repmat(sum(Um, 1), 1, D); end % 计算目标函数值 Dmat zeros(N, C); for j 1:C diff data - repmat(center(j, :), N, 1); Dmat(:, j) sqrt(sum(diff.^2, 2)); end J sum(sum((U.^m) .* (Dmat.^2))); end这段代码用了一层for循环计算距离矩阵用一次矩阵乘法更新聚类中心。在样本量和维度不太大几千乘几十的情况下运行速度完全够用。如果你的数据量比较大可以把距离计算改成pdist2代码更简洁但注意pdist2需要统计与机器学习工具箱。4.4 聚类评价指标代码选Xie-Beni和分类系数聚类效果不能只看图要有量化指标。我常用两个内部评价指标Xie-Beni指数XB和分类系数PC。XB指数越小说明类内紧凑、类间分离聚类效果越好PC指数越接近1说明隶属度越清晰、分类越确定。function XB xie_beni(data, U, center, m) N size(data, 1); C size(center, 1); Dmat zeros(N, C); for j 1:C diff data - repmat(center(j, :), N, 1); Dmat(:, j) sqrt(sum(diff.^2, 2)); end sigma sum(sum((U.^m) .* (Dmat.^2))); dmin inf; for i 1:C-1 for j i1:C d norm(center(i, :) - center(j, :)); if d dmin dmin d; end end end XB sigma / (N * dmin^2); end function PC partition_coef(U) PC sum(sum(U.^2)) / size(U, 1); end我在实际对比中除了看指标绝对值更看重多次运行的稳定性。比如把PSO-FCM和原生FCM各跑20次比较XB指数的均值和标准差这是判断优化是否有效的最直观方式。5. 实验结果与对比分析5.1 在居民用电数据上的效果对比我用一组模拟生成的400个用户、96点日负荷曲线数据做测试设定K-means、原生FCM、PSO-FCM三组对比。K-means用Matlab自带kmeans函数FCM用setul的kmeans初始化的方式跑PSO-FCM跑上述代码每组算法重复20次记录目标函数值J、Xie-Beni指数和分类系数PC的均值和标准差。从结果看K-means的目标函数值最大分类系数也最差因为它没法表达一个用户同时属于多个类别的软属性。原生FCM比K-means好但20次运行中目标函数值的标准差明显偏大说明它确实受初始中心影响较大。PSO-FCM在20次运行中目标函数值基本稳定标准差比原生FCM降低了一个数量级Xie-Beni指数也明显更小说明聚类中心的类内紧凑性和类间分离度都更好。我曾经记录过一组典型数值归一化后的数据原生FCM的XB指数均值约为0.82标准差0.21PSO-FCM的XB指数均值为0.57标准差只有0.03。分类系数PC从0.72提升到0.82。这个差距在业务上意味着原生FCM多次运行后聚类结果的可靠性不够高而PSO-FCM每次跑出来都基本一致可以直接用于后续策略制定。5.2 几类用电行为的业务解读聚类结果不能只停留在数字层面落到业务上要能解释每类用户是谁。我用96点负荷曲线聚类后按聚类中心曲线形态给类别命名第一类白天负荷低、晚上18点到22点出现明显高峰负荷曲线呈晚高峰形态工作日特征明显。这类用户大概率是上班族白天家里没人晚上回家后集中用电。第二类白天10点到15点有平稳的负荷平台晚间也有一定负荷曲线整体波动平缓。这类用户大概率是退休家庭或在家办公人群全天都有基础用电需求。第三类深夜23点到次日凌晨5点出现负荷高峰白天负荷很低。这类用户很可能有电动车充电需求或者使用了蓄热式电热水器。这三类用户的负荷曲线叠加起来能解释台区总负荷的峰谷形态。如果只看台区总负荷只能看到两个峰但分成三类后晚高峰的来源和深夜小高峰的来源就一目了然了。这就是居民用电行为分析在业务上的核心价值——不是替代台区总表观测而是把模糊的群体行为拆分成可理解的子模式。6. 参数调优经验与踩坑记录6.1 粒子群参数怎么定惯性权重、学习因子、种群规模PSO的参数设置对收敛效果影响很大我在反复实验后形成了以下经验。惯性权重w是最关键的一个参数。w较大时粒子有较强的全局探索能力w较小时局部开发能力增强。常见的做法是让w从0.9线性递减到0.4前40%的迭代侧重全局寻优寻找较优区域后60%侧重局部精修让粒子在最优区域附近细化搜索。这种做法比固定w值要稳定得多。学习因子c1、c2一般取2左右两者的相对大小决定粒子更倾向个体经验还是群体经验。我测试过c12.5、c21.5和c11.5、c22.5的组合总体差异不大所以在默认情况下可以直接取c1c22不必过度调参。种群规模Np取30到50就够用了。我的实测经验是Np20时偶尔出现收敛不稳定Np30时稳定性明显提升再往上增加收益递减计算时间翻倍但效果提升有限。最大迭代次数MaxPSOIter取50次比较合适超过50次后目标函数值的变化已经很小。6.2 聚类数C和模糊指数m的选择聚类数C的确定没有一个绝对标准我常用的方法是轮廓系数加业务可解释性判断。对不同的C值分别运行PSO-FCM计算轮廓系数也可以用Xie-Beni指数找到指标拐点。但最终选哪个C值还要看聚类中心曲线是否有明显差异如果两个类别的中心曲线几乎重合说明C设大了。在居民用电场景里C取3到5类通常是业务上可解释的合理范围。模糊指数m默认取2这是最通用的选择。m值增大会让隶属度趋向均匀化每个用户对每个类别的概率都差不多分类的区分度变差。我实测超过2.5时聚类中心之间的差异明显缩小几类用户的负荷曲线越来越像。m接近1时FCM退化成硬聚类又失去了软划分的意义。所以m2是一个稳健的默认值。6.3 我碰到的三个实际坑第一个坑是忘记归一化。最初我用原始负荷数据直接跑FCM因为夜间负荷和白天负荷的量纲差异较大距离计算几乎被高峰时段数值主导聚类结果完全无法解释。归一化之后每个时间点的特征权重才变得相对均衡。第二个坑是粒子越界导致NaN。粒子飞行过程中某些维度的位置值会超出数据范围如果不对越界粒子做处理后续距离计算中会出现NaN导致整个适应度计算失效PSO迭代崩溃。解决方式就是边界截断把越界分量压回data数据的最大值和最小值之间。这个修复虽然简单但不处理的话代码会直接报错。第三个坑是FCM子函数迭代次数太少导致评价失真。最初我为了省时间把PSO内部的FCM短迭代次数设成5次结果适应度值噪声很大PSO很难有效区分不同粒子的好坏。提高到15到20次之后适应度排序才变得稳定。但也不要设太大比如50次否则PSO每一代的耗时成倍增加。这个平衡点需要根据数据量调整。6.4 一点扩展思路跑通这套PSO-FCM流程之后后续还可以做不少扩展。比如在适应度函数里加入Xie-Beni指数作为惩罚项进一步优化聚类效果的类间分离度或者把PSO换成其他群智能算法如灰狼优化、鲸鱼算法做对比实验看看哪种全局搜索策略更适合负荷曲线聚类的场景。数据处理层面可以对比不同降维方式主成分分析、自编码器、统计特征对聚类结果的影响。踩过几次坑之后我的整体体会是PSO-FCM的价值不在于数学上有多新颖而在于它把全局搜索和局部精修这两个互补的能力组合起来解决了FCM在真实负荷数据上重复性差的痛点。如果手头正在做用户分群或者负荷模式识别被FCM的初值敏感问题折磨得够呛不妨用这个思路试一试。
返回列表