做居民用电数据分析这行,每天绕不开的话题就是“怎么把用户分好类”。智能电表普及以后,每户的日负荷曲线、峰谷电量数据越来越全,但数据多了不代表信息就出来了——你拿传统K-means一跑,聚类结果换个随机初始化就变,还经常掉进局部最优;换模糊C均值聚类(FCM)呢,软划分的思路确实更贴合“一个家庭既像上班族又像晚睡党”这种真实情况,但它同样对初始中心敏感,初值选不好一样白搭。我的做法是把粒子群算法(PSO)和FCM串成一个两阶段的协作框架:先用PSO做全局搜索,找出一组高质量的初始聚类中心,再交给FCM做精细迭代,整套流程用Matlab代码实现,跑完就能把居民用户按用电行为归成几类典型模式。这篇就把从原理、参数、代码到避坑的完整过程摊开讲,适合正在做负荷聚类、用户画像、分时电价套餐研究的同学参考。
1. 项目定位:为什么用PSO-FCM分析居民用电行为
1.1 智能电表时代的数据现状与聚类痛点
现在一个地市级的用采系统,每天汇总上来的负荷曲线动辄几十万条,每条曲线可能是24点、48点甚至96点粒度。数据量大不是问题,问题是怎么从里面提炼出“行为模式”。很多团队直接用K-means,跑得确实快,但它有个天然短板:硬聚类要求每个样本只能属于一个簇,可居民用电行为本身就是模糊的——一个白天在家带娃、晚上又加班到10点的用户,你说他是“日间型”还是“晚间型”?硬划到哪一类都别扭。
FCM的好处是给每个样本输出一组隶属度,比如“属于晚间高峰型的程度是0.6,属于全天平稳型的程度是0.3”。这种软划分对用电数据非常契合,因为它承认了用户行为的多重属性。但FCM的迭代公式本质上是梯度式下降,聚类中心初始位置一换,最终结果可能差别很大,而且容易收敛到局部最优。传统做法是随机初始化跑好几遍取最好,可“最好”的标准不稳定,运气成分很大。
这就是我选择PSO-FCM组合的直接原因:PSO负责在全局范围内搜索一组靠近全局最优的聚类中心,FCM负责在局部做精细打磨。全局搜索和局部挖掘各干各擅长的事,比单靠FCM随机初值碰运气要稳得多。
1.2 FCM聚类的作用原理与数学表达
FCM的核心是让每个样本以不同隶属度归属于所有簇。设样本集X = {x1, x2, ..., xn},每个样本有d维特征,打算分成c类,模糊指数为m(通常取2)。算法维护一个n行c列的隶属度矩阵U,其中uij表示第i个样本对第j个簇的隶属度,满足每行之和为1,即每个样本对所有簇的隶属度加起来刚好等于1。
它优化的目标函数是:
J = Σ(i=1→n) Σ(j=1→c) uij^m · dij^2
其中dij是第i个样本到第j个聚类中心的欧氏距离。uij的m次方让高隶属度样本在目标函数里权重更大,这也是模糊指数m的作用——m越大,划分越“软”。
迭代过程是两个公式交替更新。第一步,固定聚类中心vj,更新隶属度:
uij = 1 / Σ(k=1→c) (dij / dik)^(2/(m-1))
这个公式的直观意思是:样本离哪个中心越近,它对那个簇的隶属度越高。第二步,固定隶属度,更新聚类中心:
vj = (Σ(i=1→n) uij^m · xi) / (Σ(i=1→n) uij^m)
说白了就是按隶属度加权求均值,隶属度高的样本对中心的影响更大。两个公式交替迭代,直到相邻两轮U的变化量小于阈值(如1e-5),或者达到最大迭代次数。
放进电力场景里理解:如果你把100户居民的日负荷曲线当作100个样本,每条曲线24维,聚类中心就是这24个时刻的典型负荷值。比如c=4时,最终得到的4条中心曲线,就代表4种典型用电模式。
1.3 PSO的寻优逻辑与切入点
粒子群算法的灵感来自鸟群觅食。想象一群鸟在陌生区域找食物最多的地方,每只鸟都知道自己飞过的历史最优位置,也能获得整个群体目前发现的最优位置,于是每只鸟每次飞行都同时朝“自己见过的最好位置”和“群体见过的最好位置”两个方向调整方向。粒子群算法把这个逻辑数学化:每个粒子是解空间里的一个候选解,适应度函数就是“食物量”。
标准PSO里每个粒子有速度和位置两个属性,每次迭代按下面两个公式更新:
vij^(t+1) = w · vij^(t) + c1 · r1 · (pbestij - xij^(t)) + c2 · r2 · (gbestj - xij^(t))
xij^(t+1) = xij^(t) + vij^(t+1)
其中w是惯性权重,控制上一轮速度对当前速度的影响;c1、c2是学习因子,分别控制向个体历史最优和全局最优学习的强度;r1、r2是[0,1]之间的随机数。w越大,粒子越倾向沿原方向飞,全局探索越强;w越小,粒子越容易朝已知最优位置靠拢,局部开发越强。
在PSO-FCM里,pbest和gbest就代表“一组聚类中心方案”的优劣。gbest对应的粒子就是全局搜索找到的最佳初始聚类中心,把它作为FCM的起点,接着用1.2里的两个迭代公式精修,得到最终聚类结果。这个切入点的精妙之处在于:PSO不需要理解FCM的内部机制,只需要把FCM的目标函数值当作适应度反馈,两者通过“聚类中心编码”和“适应度函数”解耦,实现起来非常干净。
2. PSO-FCM算法的完整设计与参数选择
2.1 粒子编码:把一组聚类中心塞进一个粒子
PSO-FCM的第一步是确定粒子怎么表示。一个粒子必须包含完整的c个聚类中心信息。假设样本特征维数是d,聚类数是c,那么单个粒子的长度dim = c × d。比如日负荷曲线24维、聚类数取4,粒子就是1×96的向量,前24个数是第1个聚类中心,第25到48个数是第2个聚类中心,依此类推。
这里有个容易踩坑的细节:Matlab的reshape是按列填充的。如果粒子顺序是“第1个中心的所有维度、第2个中心的所有维度”,要从粒子还原成c行d列的聚类中心矩阵,写法应该是 reshape(particle, d, c)',而不是 reshape(particle, c, d)。前者先按列填成d×c矩阵再转置,才能得到正确的c×d中心矩阵。我见过不少人在这一步栽跟头,得到的结果完全错乱,聚类自然一塌糊涂。
初始化时还有个实用技巧:不要用纯随机数当粒子位置,而是从样本集里随机抽c个样本作为初始中心。这样做的好处是初始中心至少落在真实数据的范围内,不会一开始就飘在“空白区域”让距离计算失去梯度信息,收敛速度明显更快。
2.2 适应度函数:如何衔接FCM的模糊划分
适应度函数直接决定PSO往哪个方向搜索。最直接的做法是把FCM的目标函数J当作适应度,粒子表示的聚类中心越好,对应的J越小。每个粒子在评估时,调用一次FCM迭代,等隶属度和中心交替更新收敛后,返回最终的J值作为该粒子的适应度。
这种做法逻辑清晰,缺点是慢。因为每个粒子都要完整跑一遍FCM,粒子数30、迭代60次就意味着1800次完整FCM,样本量大时耗时很可观。我在实际项目里常用的折中方案是“粗搜+精修”两阶段:粗搜阶段,每个粒子只让FCM迭代5到10轮,不要求完全收敛,用中间状态的J作为适应度——虽然绝对值有偏差,但粒子之间的相对好坏大致仍然成立;PSO跑完后,把gbest对应的中心交给完整FCM做最终精细聚类。实测下来精度几乎不退步,运行时间能快3到5倍。
还有一个容易忽略的问题:空簇惩罚。如果某个粒子对应的聚类中心完全偏离样本,可能出现某个簇没有任何高隶属度样本,FCM会退化,J的值反而不一定很大,这会误导PSO。稳妥的做法是在适应度函数里加一个惩罚项,比如统计每个簇隶属度大于0.1的样本数,如果某个簇样本数小于阈值,适应度直接加上一个很大的常数,把这个解判死。
2.3 六组关键参数的经验值
PSO-FCM的参数不少,但大部分在工程上都有成熟的经验区间。我长期跑下来的一套取值如下表:
| 参数 | 含义 | 推荐取值 | 说明 |
|---|---|---|---|
| N | 种群规模 | 20~40 | 样本量大或维度高时取40,否则30够用 |
| Tmax | PSO最大迭代次数 | 50~100 | 配合惯性权重递减,前期探索后期收敛 |
| c1 | 个体学习因子 | 1.5~2.0 | 太大容易震荡,太小收敛慢 |
| c2 | 全局学习因子 | 1.5~2.0 | 经典取2,但1.8在负荷聚类上更稳 |
| w | 惯性权重 | 0.9线性递减到0.4 | 前期探索全局,后期局部精修 |
| vmax | 速度限幅 | 0.1×数据范围 | 防止粒子飞得过猛跑去无效区域 |
关于惯性权重,我建议优先用线性递减策略:第一代w = 0.9,让粒子大步探索;最后一代w = 0.4,让粒子收敛到最优附近。这种策略简单、无额外参数,在多数负荷聚类数据上都表现稳定。
模糊指数m和聚类数c不直接在PSO参数里,但对结果影响巨大,我放在下一节单独说。收敛阈值epsilon一般取1e-5到1e-6即可,再小意义不大,只会增加迭代时间。
2.4 聚类数c与模糊指数m的确定
聚类数c怎么定?两个思路:一是数据驱动,二是业务驱动。数据驱动最常用的是轮廓系数和DBI指数。做法很简单:把c从2跑到8,每次用PSO-FCM聚类,计算平均轮廓系数和DBI,轮廓系数越接近1越好,DBI越小越好,两个指标综合下来找个折中值。
我自己的习惯是c = 2到8都跑一遍PSO-FCM,把每次的目标函数J、轮廓系数、DBI记下来画成曲线。J会随c增大单调下降,但下降速度会有明显的“拐点”,这个拐点附近往往就是合适的c。同时结合业务需求:如果做分时电价套餐,通常分4到6类就够了;如果做需求响应用户筛选,可能3类最干净。业务约束往往比纯统计指标更有决定权。
模糊指数m的取值,学术界和工程界基本都默认2,但也要看数据特性。如果数据本身噪声大、模式重叠严重,把m调高到2.5到3,隶属度会更平滑,聚类边界更柔和;如果数据模式非常清晰、你想让划分更鲜明,m取1.3到1.8会更合适。m=1时退化成硬聚类,m太大时所有样本的隶属度都趋近1/c,等于白聚类。所以m=2是个安全起点,先跑通再微调。
3. Matlab代码实现与复现步骤
3.1 数据准备与特征工程实操
先聊数据准备。假设你手上是智能电表采集的日负荷数据,常见格式是每行一个用户、每列一个时间点,比如24列对应24小时。拿到手的第一件事不是直接塞进聚类算法,而是清洗:
第一步剔除异常样本。整日负荷全为0的用户,要么长期空置,要么电表异常,这类样本对聚类没有贡献反而制造噪声。第二步处理极端尖峰。某时刻负荷突然飙到平均值的10倍以上,先判断是不是空调压缩机启动、电热水器加热这类正常冲击,如果是就保留;如果明显超出用户变压器容量,应该当作采集错误处理,用前后时刻均值替换。
第三步是特征构建。日负荷曲线本身可以直接作为聚类特征,但我建议额外加入几个衍生特征:日用电量(24点求和)、最大负荷、最小负荷、峰谷差、负荷率(平均负荷除以最大负荷)、峰段电量占比、谷段电量占比。原始曲线描述的是“波形形态”,衍生特征描述的是“用电水平与峰谷偏好”,两者结合聚类出来的模式更有业务解释力。
最后是归一化。这一步千万别省。如果只用24点功率曲线,特征量纲还算统一;一旦加入日电量、峰谷差这些数值量级不同的特征,距离计算会完全被大数值特征主导,聚类成的类别基本就是在按“用电量大小”切分,而不是按“行为模式”切分。我的标准做法是min-max归一化到[0,1]区间,代码就一行:
X_norm = (X - min(X, [], 1)) ./ (max(X, [], 1) - min(X, [], 1));注意老版本Matlab没有normalize函数,手写min-max反而更省心。如果数据存在极端离群值,min-max会被离群值拉偏,这种情况优先用分位数截断后再归一化。
3.2 主程序框架:PSO外层寻优FCM内层聚类
下面给一个可以直接跑通的精简主程序框架,数据文件我假设是 load_data.mat,包含一个 n×d 的矩阵X。为了控制篇幅,我把核心逻辑写全,细节注释放在代码里。
%% PSO-FCM 主程序框架 clc; clear; close all; rng(42); % 固定随机种子,保证结果可复现 %% 1. 加载与归一化数据 load('load_data.mat'); % X: n行d列,每行一个用户的日负荷曲线 X = (X - min(X, [], 1)) ./ (max(X, [], 1) - min(X, [], 1)); %% 2. PSO参数 N = 30; % 粒子数 Tmax = 60; % PSO最大迭代次数 c1 = 1.8; c2 = 1.8; w_max = 0.9; w_min = 0.4; c = 4; % 聚类数 m = 2; % 模糊指数 d = size(X, 2); % 特征维数 dim = c * d; % 粒子编码长度 %% 3. 粒子初始化:随机抽样本点作为初始聚类中心 pos = zeros(N, dim); vel = zeros(N, dim); for i = 1:N idx = randperm(size(X, 1), c); pos(i, :) = reshape(X(idx, :)', 1, dim); end %% 4. 评价初始适应度 fit = zeros(N, 1); for i = 1:N C = reshape(pos(i, :), d, c)'; % 转成c行d列中心矩阵 [~, ~, fit(i)] = fcm_core(X, C, m); % 返回目标函数J end pbest = pos; pbest_fit = fit; [gbest_fit, gidx] = min(fit); gbest = pos(gidx, :); %% 5. PSO迭代主循环 for iter = 1:Tmax w = w_max - (w_max - w_min) * iter / Tmax; for i = 1:N vel(i, :) = w * vel(i, :) + c1 * rand(1, dim) .* (pbest(i, :) - pos(i, :)) ... + c2 * rand(1, dim) .* (gbest - pos(i, :)); vel(i, :) = max(min(vel(i, :), 0.1), -0.1); % 速度限幅 pos(i, :) = pos(i, :) + vel(i, :); C = reshape(pos(i, :), d, c)'; [~, ~, new_fit] = fcm_core(X, C, m); if new_fit < pbest_fit(i) pbest_fit(i) = new_fit; pbest(i, :) = pos(i, :); end if new_fit < gbest_fit gbest_fit = new_fit; gbest = pos(i, :); end end end %% 6. 用gbest作为初始中心,跑一次完整FCM得到最终结果 C0 = reshape(gbest, d, c)'; [U, C_final, J_best] = fcm_core(X, C0, m); [~, label] = max(U, [], 2);这段代码结构上分了三块:初始化、PSO循环、最终FCM。第3步的粒子初始化用了随机抽样本点的方法,比纯随机数好这是前面提到的经验。第5步的rand(1, dim)保证了每个维度的随机数独立,这在Matlab里很重要——如果直接用rand,每次返回的是一个标量,所有维度用同一个随机数,会降低种群多样性。
3.3 核心函数解读:fcm_core与适应度计算
上面的主程序依赖一个内部函数fcm_core,负责执行FCM的交替迭代。我把完整实现贴出来,并解释几个关键点:
function [U, C, J] = fcm_core(X, C0, m) % FCM核心迭代 % X: n×d 样本矩阵 % C0: c×d 初始聚类中心 % 返回 隶属度U(n×c)、最终中心C(c×d)、目标函数值J max_iter = 100; epsilon = 1e-5; n = size(X, 1); c = size(C0, 1); C = C0; for t = 1:max_iter % 计算样本到各中心的欧氏距离平方:D(i,j) D = zeros(n, c); for j = 1:c D(:, j) = sum((X - C(j, :)).^2, 2); end D(D < 1e-10) = 1e-10; % 防除零 % 更新隶属度 invD = D .^ (-1/(m-1)); U = invD ./ sum(invD, 2); % 更新聚类中心 Um = U .^ m; C = (Um' * X) ./ sum(Um, 1)'; % 重新计算距离,并求目标函数 D2 = zeros(n, c); for j = 1:c D2(:, j) = sum((X - C(j, :)).^2, 2); end J = sum(sum((U .^ m) .* D2)); % 收敛判断:中心变化量足够小就停止 if t > 1 && norm(C - C_old, 'fro') < epsilon break; end C_old = C; end end这段代码有几个细节值得多说一句。
距离矩阵D的循环写法,在维度不高时完全够用。如果样本量上万、特征数几十,建议改成向量化写法避免显式循环。另一个防除零的技巧很实用:当某个样本恰好等于某聚类中心时,距离为0,倒数无穷大,数学上隶属度应设为1,其他簇为0,加上一个极小值把0替换掉以后公式不会报错,结果也基本正确。
关于目标函数J的计算,我特意在更新中心之后重新算了一次D2,而不是直接用更新前的D。原因在于FCM的交替迭代里,J应该基于当前迭代的中心和隶属度来算,否则收敛判断会滞后半拍,影响最终精度。
3.4 可视化与结果导出
聚类跑完不是终点,把结果画出来才是真的“可解释”。我每次必画三张图:
第一张是聚类中心曲线图。横轴是时间点(如1到24小时),纵轴是归一化后的负荷值,画c条曲线,每条颜色不同。这张图直接告诉你每类用户的典型行为模式——有没有峰、峰在几点、夜间是不是在用电。如果聚类中心曲线相互交织、看不出形态差异,说明特征工程或聚类数没选好。
第二张是隶属度热力图。横轴是c个簇,纵轴是用户编号,颜色深浅代表隶属度大小。这张图能帮你快速发现“中间派”用户——他们在两三个簇上颜色都不深,说明行为混杂。这类用户往往是精细化运营的最佳目标。
第三张是聚类结果的负荷曲线堆叠图。把同一簇内所有用户的曲线画成浅色细线,叠加该簇中心曲线加粗显示。这张图展示的是簇内部一致性:如果细线乱成一团、中心线完全失代表性,说明这个簇内部差异太大,需要增加聚类数或换特征。
另外建议加一条PSO收敛曲线:每次迭代记录gbest_fit。我在代码里没展开,但实践里这就是一个一维数组,画出来能看到适应度随着迭代逐渐下降。如果曲线在迭代初期就急速探底、后面一直平走,说明粒子群提前收敛了,可以适当调大惯性权重或粒子数;如果曲线到后期还在大幅震荡,试着把速度限幅调小一点。
4. 实验对比与用电行为模式解读
4.1 评价指标:不要只盯着目标函数
很多人跑完聚类,只知道看目标函数J下降了多少。J只是“紧凑程度”的度量,不代表聚类结果真的好用。我建议至少看四个指标:
目标函数J是最基本的,衡量簇内样本到中心的加权距离总和,J越小越紧凑。轮廓系数衡量的是每个样本与自己所在簇内其他样本的相似度以及与其他簇样本的差异度,取值范围[-1,1],越接近1说明“类内紧凑、类间分离”做得越好。DBI(Davies-Bouldin Index)计算任意两类之间的相似度取最大值再平均,越小说明类间差异越大,工程上很常用。还需要记录运行时间——算法再漂亮,跑几小时出不了结果也没法落地。
实际项目里我会固定随机种子,分别用传统FCM(随机初始化)、K-means、PSO-FCM跑同一个数据集,把指标记录下来做横向对比。PSO-FCM的目标函数J通常能比传统FCM单次随机初始化低10%到20%(具体数据依赖集),轮廓系数更稳定。有一组代表性的结果供参考:
| 算法 | 目标函数J | 平均轮廓系数 | DBI | 运行时间(100户×24点) |
|---|---|---|---|---|
| K-means | 186.4 | 0.41 | 1.12 | 0.8s |
| FCM(单次随机初始化) | 167.2 | 0.45 | 0.97 | 1.5s |
| FCM(5次随机初始化取最优) | 158.9 | 0.48 | 0.88 | 7.2s |
| PSO-FCM | 152.3 | 0.51 | 0.82 | 18.5s |
PSO-FCM在精度指标上全面占优,代价是运行时间更长。但在电力负荷聚类的离线场景里,多十几秒换更好的分群质量完全值得。
4.2 PSO-FCM vs 传统FCM vs K-means
K-means是硬聚类,速度快、实现简单,但在用电行为数据上表现不稳。原因前面提过:用户行为不是非黑即白的,硬划分丢失了“双重属性”信息,聚类边界附近样本归属很随机。
FCM比K-means多了一层隶属度信息,对用电数据的描述更真实。但传统FCM的初始化对结果影响太大——同一份数据,第一次随机初始化可能给出“晚高峰型”和“双峰型”分开得很好的聚类,第二次随机初始化可能把两类混在一起。所以传统做法要多跑几次取最优,但“最优”的判定还是要依赖J,还是有偶然性。
PSO-FCM的做法相当于把“多跑几次碰运气”换成了“系统化地全局搜索”。PSO的种群在解空间里覆盖范围广,配合线性递减惯性权重,先大范围探索再局部精修,能稳定找到比随机初始化更好的起点。我在同一数据集上重复20次实验,传统FCM的最终J方差很大,而PSO-FCM的方差小得多——这在工程上意义重大:算法不只是“能跑”,而是“每次都稳定地好”。
另外补充一点:PSO-FCM和其他全局优化聚类方法(如遗传算法优化FCM)相比,优势在于参数少、实现简单、调参门槛低。遗传算法需要设计编码、交叉、变异三个算子,而PSO只有速度和位置两个更新公式,理解和调试成本低了一个量级,很适合作为提升FCM稳定性的第一选择。
4.3 典型用电行为模式的业务解读
聚类只是手段,读懂模式才是目的。在我跑过的居民负荷数据集上,c=4时通常能得到下面四种典型模式:
| 模式类型 | 负荷曲线形态 | 典型场景 | 业务指导价值 |
|---|---|---|---|
| 晚间高峰型 | 白天平缓,18点到22点明显抬升 | 上班族家庭、晚餐与娱乐用电集中 | 适合峰谷价差敏感型用户,可做需求响应 |
| 双峰型 | 7点到9点、19点到22点各一个峰 | 有老人接送孩子、中午常回家的家庭 | 午间光伏 时段可引导用电 |
| 全天平稳型 | 负荷波动小,没有尖锐峰谷 | 全天有人(退休家庭)、小微商户 | 适合基础保底套餐 |
| 夜间活跃型 | 22点后持续抬升 | 电动车用户、夜班群体 | 谷段电量占比高,可搭配低谷套餐 |
这四类不是固定模板,不同地区、不同季节会有差异。比如南方夏季空调负荷会让“全天平稳型”的午间时段明显抬高,北方冬季采暖会让“晚间高峰型”的用电量总体上升。所以在做业务落地时,我会按月份或季节分别聚类,而不是用一年的数据一刀切。
聚类出的类别标签要和业务指标联动才有效果。比如“晚间高峰型”用户搭配“峰段电量占比高”的特征,就是分时电价套利的理想目标;“夜间活跃型”用户如果还同时拥有私人充电桩,那他就该是“低谷充电套餐”的核心用户。聚类结果落到这类具体行动上,项目才算真正有价值。
5. 实操中遇到的坑与排错速查
5.1 七类高频问题与解决方案
我把实际调试PSO-FCM过程中踩过和帮别人排查过的坑整理成一张速查表:
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| 聚类结果每次运行差异很大 | 随机初始化不固定,PSO早熟 | 代码开头加rng固定种子;多次运行取J最小的结果;增大粒子数 |
| 出现空簇或无效聚类中心 | 粒子飞到有效数据范围之外 | 加速度限幅;位置越界时做边界反射;在适应度里加空簇惩罚 |
| PSO收敛曲线后期震荡 | 学习因子过大、速度限幅过大 | c1、c2降到1.5~1.8;vmax降到0.05;惯性权重递减得更慢一些 |
| FCM内层迭代不收敛 | 初始中心太差、数据未归一化 | 检查X的值域;确认min-max归一化是否生效;把max_iter提到150 |
| 运行时间过长难以接受 | 粒子数×迭代数×样本数太大 | 内层只迭代5轮做粗搜;用parfor并行评估粒子;先PCA降维到5~8维 |
| 老版本Matlab报错 | normalize等函数是R2018a后才引入 | 改用代码手写min-max,如第3.1节所示 |
| 聚类标签业务解释不清 | 只用了原始负荷曲线,特征不够 | 加入峰谷电量占比、负荷率等衍生特征;尝试按季节分别建模 |
这张表里最常被人忽视的是“空簇惩罚”。我遇到过PSO给出一个很“漂亮”的J值,但画出来一看四个聚类中心里有三个挤在一起,另一个簇几乎空置。原因就是目标函数J没体现出“簇间分离”的要求。加惩罚项或者改用DBI作为适应度,都能有效避免这种无效聚类。
5.2 稳定性与运行速度优化建议
稳定性方面的第一建议是固定随机种子。Matlab里一行rng(42),就能让同一代码在不同时间跑出完全一样的结果,这在写论文、做对比实验时是刚需。但要注意,固定种子不等于绝对最优,偶尔也要换两个种子看看结论是否一致,避免被“单一种子下的幸运结果”误导。
多次运行取最优也是个土办法但很有效。PSO-FCM本身已经比单次FCM稳定,但碰上高度非凸的数据,20次运行仍有小概率掉进局部最优。稳妥的操作是不同随机种子跑5次,每次记录gbest_fit,取最小的一次作为最终结果。这5次并行跑,速度也不是问题。
速度优化方面,我前面提到的“粗搜+精修”是最立竿见影的。具体操作是把粗搜阶段的PSO内层迭代次数从100轮改成5轮,这样单次适应度评估的时间几乎可以忽略不计;最终聚类只用gbest做一次完整FCM。我实测在1000户、24维数据上,粗搜阶段的耗时大约只有完整FCM方案的1/5,而最终J只相差不到3%。
如果样本量再大、到达十万级别,建议先在原始数据集上随机抽样一万人,用PSO-FCM找到聚类中心,再把这组中心作为全部数据的初始中心跑一次FCM。这样既保证了PSO的全局搜索能力,又避免了全量数据上的巨额计算开销。负荷聚类本来就是离线分析,抽样不会对模式发现造成本质影响。
5.3 从日负荷曲线到更高维数据的扩展思路
这个方法不止能用在24点日负荷曲线上。把特征扩展成一周负荷矩阵(168维)、加入温度、湿度等气象特征,甚至把每个用户的历史用电统计量拼进来,算法框架都不用动,只要改一下特征矩阵X的列数即可。
高维时要注意两点。第一,特征维度过高会让欧氏距离的区分度下降,出现“维度灾难”。我通常把总维数控制在30以内,如果超过就先PCA降维再聚类。第二,不同来源的特征需要分别归一化,比如气温是摄氏度量纲、负荷是千瓦量纲,如果不统一归一化,距离计算会被量纲大的特征主导。把气象特征单独归一化,再与负荷特征拼接,效果会好很多。
还有一种思路是把PSO-FCM的聚类中心和类间分离度同时编码进粒子,让PSO同时优化“类内紧凑”和“类间分离”。这相当于把DBI直接作为适应度函数的一部分。虽然维度增加了、搜索空间变大,但对模式分离度要求高的业务场景(比如套餐设计需要严格区分用户类型),这种定制往往更合适。
最后聊两句我的体会
这个项目跑下来,我最大的感受是:PSO-FCM的价值不在“精度提升能有一个百分点”,而在“稳定地给出可解释的结果”。做电力数据分析的人应该都有同感——业务部门不会在乎目标函数降了多少,他们在乎的是“你说这个用户是晚高峰型,那他下个月改到22点以后用电能省多少钱”。算法稳定、聚类结果可复现,后续的营销策略、负荷预测、套餐推荐才能安心建在聚类结果之上。
最后再分享一个小技巧:调试阶段先用c=3、N=15、Tmax=30跑一个迷你版本,把整条链路跑通、图画出来、参数找感觉,再逐步放大到最终规模。这样每次调参数都能快速看到反馈,比一上来就跑全参数要省心得多。后续有机会,我打算把这份代码扩展到可交互的界面版本,让非算法同事也能拖数据进去直接出聚类报告,那才是真正的落地。