搞风电、负荷预测或者电力系统规划的朋友,应该都体会过场景分析的份量。风电出力的随机性、负荷波动的时变性,叠加起来就是一大把不确定的东西,蒙特卡洛一抽就是几百上千条曲线。模型本身不挑数据,但求解器扛不住,尤其做随机优化、可靠性评估或者输电网规划的时候,场景一多,整数变量组合直接爆炸,原来几分钟能算完的问题拖到一晚上都解不动。所以场景缩减这件事,本质就是在计算代价和不确定性保真度之间找平衡。
我这次用的方案是基于DBSCAN密度聚类做风电-负荷确定性场景缩减,全程在Matlab里实现。相比K-means这种需要预指定簇数的算法,DBSCAN用密度结构自动决定分几类,风电-负荷场景里常见的离群曲线也能单独识别出来,最后输出的是一组带概率的确定性代表场景,可以直接喂给后续优化模型。
需要说明一下:项目标题没有给出详细的原始代码或者数据说明,下面讲到的参数标定方法、代码框架和工程细节,都是基于我在电力系统场景分析里最常见的做法补全的。整体思路和DBSCAN的核心逻辑是不变的,你可以直接照搬改数据。
1. 为什么场景缩减偏偏选DBSCAN:问题背景与算法选型分析
1.1 风电-负荷场景缩减到底解决什么问题
场景缩减解决的核心矛盾,是随机规划里“场景数量-求解规模-精度损失”三者之间的冲突。拿风电并网调度来说,一个典型的两阶段随机优化模型,如果每个时段风电有10种可能出力,24小时就有10的24次方种组合,这显然没法直接算。工程上通常的做法是先通过预测误差采样或者历史数据抽样生成几百上千个初始场景,然后通过场景缩减技术挑选出少量代表性场景,每个场景附带一个概率值。后面的优化、评估、决策,全部基于这些代表场景展开。
这里有个关键点,叫“确定性场景缩减”。它的意思是说,缩减之后得到的不是一堆模糊的分布区间,而是具体的、确定性的风电出力和负荷曲线,每条曲线都代表一类典型运行工况。DBSCAN聚类的做法正好契合这个逻辑:每一簇代表一类相似的风-荷组合模式,取簇内代表性样本作为典型场景,概率按簇内样本占比计算,输出结果就是一组“确定性曲线+概率”的组合。
1.2 K-means、层次聚类与DBSCAN:为什么偏偏是密度聚类
做场景缩减,最常见的三个候选算法是K-means、层次聚类和DBSCAN。先看K-means,它最大的问题就是你得先知道最终要分成几类。我最早做场景缩减踩的第一个坑就是拍脑袋定簇数,定少了把早晚高峰迥异的负荷场景硬揉成一团,定多了又出现一堆只有两三个样本的小簇,优化模型里根本没意义。虽然可以用肘部法则、轮廓系数来辅助选K,但它对初始质心敏感,而且对不规则分布的场景数据适应性一般。
层次聚类不需要预指定簇数,但它的计算复杂度偏高,而且一旦合并或分裂操作完成就没法撤销。DBSCAN的思路完全不同,它不关心你要分几类,它只问一句话:哪些场景的密度是连在一起的?密度够的聚成一簇,密度不够、周围稀稀拉拉的场景直接标记为噪声。这种思路对风电-负荷数据有天然优势——风电出力经常存在明显的聚类特性,比如“大风天”、“小风天”、“爬坡天”,这些模式之间的边界并不规整,完全不是球形分布,K-means处理这种非凸形状的簇结构会比较吃力,而DBSCAN可以聚成任意形状。
1.3 DBSCAN在风电-负荷场景中的适配点与取舍
DBSCAN对场景缩减有三个明显的适配性优势。
第一,不需要预先指定簇数。场景缩减的簇数由数据密度结构自动决定,你可以根据输出的簇数和噪声比例来判断参数标定是否合理,而不是先拍脑袋定“我就要10个场景”。
第二,内置噪声识别机制。风电和负荷数据里经常有极端场景,比如台风天出力骤降、负荷尖峰异常,这些曲线往往不是主流模式,但它们可能恰恰是规划里不能忽略的边界情况。DBSCAN会把这类离群曲线识别出来,你可以在缩减时选择丢弃,也可以单独保留作为极端场景研究。
第三,参数可解释性相对强。Eps和MinPts两个参数直观,一个控制邻域半径,一个控制密度阈值,配合k-距离图可以比较清晰地标定。
当然也必须承认它的取舍。DBSCAN在数据规模较大的时候,如果直接算全量距离矩阵,O(n²)的复杂度会比较吃力。另外它处理高维数据的表现受距离度量影响较大。针对风电-负荷这种24小时×2维的曲线数据,合理做数据规约和维度处理,能有效缓解这些问题。整体上,对于中小规模(几百到几千条曲线)的风电-负荷场景缩减,DBSCAN是比K-means更稳的选择。
2. 数据准备与预处理:场景缩减的第一步往往最容易被忽略
2.1 初始场景集的生成方式
做场景缩减之前,得先有一批初始场景。我这次用的是简化模拟数据生成方式,方便演示流程:设定一个有典型日特性的风电基准曲线(夜间出力高、白天出力低),一个带早晚高峰的负荷基准曲线,然后叠加上随机扰动。实际工程中你不会这么搞,常见的做法是从风电、负荷的历史预测误差分布中做蒙特卡洛采样,或者直接对历史场景做重采样。
rng(42); nScen = 1000; % 初始场景数量 nHour = 24; % 调度时段数 t = (0:nHour-1)'; % 风电基准曲线:模拟夜间高、白天低的典型风电日特性 windBase = 0.55 + 0.25 * cos(2*pi*t/24*1.5 - 0.8); windBase = max(0.05, min(0.95, windBase)); % 负荷基准曲线:模拟8点和19点左右两个高峰 loadBase = 1000 + 220 * exp(-((t-8).^2)/10) + 280 * exp(-((t-19).^2)/12); % 叠加随机扰动生成初始场景集 windScen = windBase + 0.18 * randn(nScen, nHour); windScen = max(0, min(1, windScen)); % 风电出力限制在[0,1]区间 loadScen = loadBase + 90 * randn(nScen, nHour);这里需要留意的是随机扰动用了正态分布,实际上风电预测误差更常见的是带偏态或重尾的分布,比如beta分布或者混合高斯分布。如果你手上的数据是从预测系统里拿到的误差统计,最好用实际误差分布来采样,这会影响后续聚类对极端场景的识别能力。另外,初始场景数不必盲目追求大,1000和5000对聚类结果的影响远不如参数标定和预处理大。
2.2 量纲归一化:风电与负荷必须放在同一度量下
这是整个流程里最容易被新手跳过、但对结果影响最大的一个步骤。风电出力是0到1之间的标幺值,负荷可能是几百到几千兆瓦的绝对数值,如果不做归一化,两个量纲放在一个48维向量里,欧氏距离几乎会被负荷这一维完全主导。最后的聚类结果实际上就是“负荷曲线聚类”,风电信息形同虚设。
我常用的做法是分别把风电和负荷各自线性归一化到[0,1]区间,然后拼成一个48维向量。拼完之后,风电通道和负荷通道在距离计算中的权重基本均衡。不要用全局min-max,因为风电和负荷分布区间不同,全局缩放依然会让数值大的负荷主导距离。
% 分别归一化到[0,1] windMin = min(windScen(:)); windMax = max(windScen(:)); loadMin = min(loadScen(:)); loadMax = max(loadScen(:)); windNorm = (windScen - windMin) / (windMax - windMin); loadNorm = (loadScen - loadMin) / (loadMax - loadMin); % 拼接成 nScen x 48 的特征矩阵 dataMat = [windNorm, loadNorm];还有一种思路是给风电和负荷各分配一个权重系数,比如你更关心风电不确定性,就可以把风电通道放大。这个取决于具体业务需求,不是越精细越好,保证两者数量级一致、不互相淹没,是底线。有人说“权重怎么定”?我的经验是先跑一版等权归一化,看聚类结果中风电和负荷的信息保留度,再决定要不要调整权重。
2.3 距离度量与高维曲线场景的特殊性
DBSCAN的核心依赖是“距离”,所以距离怎么算必须想清楚。默认用的欧氏距离直观也高效,但存在两个问题。
一个是高维诅咒。48维空间里,样本之间的距离会趋向均匀化,也就是说,很多点之间的距离差异不大,Eps参数的敏感性会上升。针对风电-负荷场景,我的建议不是盲目上马氏距离或者DTW,而是先考虑降维。比如对24小时风电曲线提取均值、峰值、波动幅度等特征,或者用PCA把48维压到10维以内。这不仅能缓解高维问题,还能降低计算量。我这次为了演示核心流程,还是保留48维全曲线,但你在实际工程中可以根据场景形态选择先做PCA或者特征提取。
另一个是曲线错峰的误判问题。如果两天的风电曲线形态完全一样,只是峰值时段错开两三个小时,欧氏距离会判定它们相距很远,但物理上它们可能代表同一类天气过程。解决思路有两个:一是用DTW距离,它对曲线伸缩有容忍度,但计算复杂度高、Matlab里实现和DBSCAN结合不太方便;二是先做维度规约,把时序曲线压成特征,从根上降低错峰影响。对大多数场景缩减场景,欧氏距离配合归一化和降维处理已经够用,不必为了完美而牺牲效率。
3. 核心参数标定:Eps与MinPts的工程化整定方法
3.1 先理解密度可达:核心点、边界点与噪声点
DBSCAN里三个基本角色,一定要先弄明白:
- 核心点:邻域半径Eps内至少包含MinPts个样本点,它代表某个密度区域的内核。
- 边界点:本身邻域内样本数少于MinPts,但它落在某个核心点的邻域内,归为该簇的边界。
- 噪声点:既不是核心点,也不在任何核心点的邻域内,不对应任何簇。
把一堆曲线想象成一片云层上的不同“雨团”:核心点就是雨团中心,雨滴密集;边界点就是云团边缘,稀疏一点但仍连着;噪声点是孤立的零星水汽,怎么都归不进去。理解了这个模型,你就知道参数标定的目标是什么——让核心点恰好覆盖“真正的长尾场景”,让边界点自然衔接各个典型模式,噪声点则帮你识别出那些需要单独关注的稀有工况。
3.2 k-距离图选Eps:最有工程价值的一个步骤
Eps选多少,直接决定簇数多少和噪声占比。选小了,场景被打得稀碎,本来同类天气的出力曲线被拆成五六个小簇;选大了,不同物理特征的风-荷模式被强行捏合成一团,场景缩减的区分度就没了。
工程上最常用的标定方法是k-距离图。具体做法:对每个样本点,找到它到第k近邻的距离,排序后画出来。这里的k取MinPts-1。曲线会出现明显的“肘部”,拐点附近的距离值就是合适的Eps候选值。逻辑很简单:在密度均匀的区域,k-距离变化平缓;一旦进入稀疏区,距离快速拉大,这个“从平缓转陡峭”的位置就是密度结构的边界。
% 计算距离矩阵 D = pdist2(dataMat, dataMat, 'euclidean'); % 取 k = MinPts - 1 = 5,先算出每个样本到第6近邻的距离 k = 5; sortedD = sort(D, 2); kDist = sortedD(:, k + 1); % 降序排列并绘制 [kDistSorted, ~] = sort(kDist, 'descend'); figure; plot(kDistSorted, 'LineWidth', 1.2); grid on; xlabel('样本索引(按距离降序)'); ylabel('第 k 近邻距离'); title('k-距离图:用于选择 Eps 参数'); % 自动寻找拐点区域(基于相邻差分最大位置) dk = abs(diff(kDistSorted)); [~, idxElbow] = max(dk); epsEst = kDistSorted(idxElbow); hold on; yline(epsEst, 'r--', 'Eps估计值');k-距离图的解读不要死抠数学拐点,要结合物理场景看。我通常会在图上把曲线的“膝盖”区域放大,然后在这个范围内试两三个Eps值,分别跑一遍聚类,看簇数和噪声占比哪个更符合业务预期。自动找拐点的代码只是一个快速起点,不是最终答案。
3.3 MinPts的经验取值与参数联调
MinPts控制密度的“最小规模”:一个区域至少要有多少个点聚集在一起,才称得上一个簇。取太小,比如MinPts=1,任何孤立点都自成一簇,聚类形同虚设;取太大,比如MinPts=50,小簇全部被合并或被标成噪声,极端场景很容易全丢。
经验上,MinPts一般取数据维度的2倍左右,数据量为1000左右时取4到10是常见区间。我建议从MinPts=2×dim开始试,对48维数据就是MinPts≈6到8。如果噪声点比例偏高,可以适当减小MinPts,让更多边缘场景有资格入簇;如果簇数过碎,则增大MinPts。
参数标定的完整流程我建议这么做:
- 先固定MinPts=2×dim,画k-距离图确定Eps候选区间。
- 在候选区间内取3个Eps值(偏小、居中、偏大),分别跑DBSCAN。
- 记录每组参数下的簇数、噪声比例、最大簇样本数。
- 结合业务需求选择参数组合:希望保留更多典型模式,选偏小的Eps;希望场景高度概括,选偏大的Eps。
- 微调MinPts,观察噪声比例变化,控制在10%以内。
4. Matlab代码实现:从聚类到代表性场景提取的完整流程
4.1 数据准备与DBSCAN聚类主程序
Matlab从R2019a开始自带dbscan函数,属于Statistics and Machine Learning Toolbox,调用方式很简单,输入数据矩阵、Eps、MinPts即可。如果没有这个工具箱,需要用自定义实现,我也提供一个精简的教学版本,逻辑清晰可以直接用。
先说自带函数的用法:
minPts = 6; epsVal = epsEst; % 用上一节k-距离图标定的值 % 自带dbscan函数,返回簇号:-1表示噪声,非负整数表示簇类 if exist('dbscan', 'file') [idx, coreD] = dbscan(dataMat, epsVal, minPts); fprintf('自动检测到工具箱,使用自带dbscan\n'); else [idx, ~] = mydbscan(D, epsVal, minPts); fprintf('未检测到工具箱,使用自定义DBSCAN实现\n'); end fprintf('聚类簇数:%d\n', max(idx)); fprintf('噪声点数量:%d(占比%.2f%%)\n', sum(idx == -1), sum(idx == -1) / nScen * 100);如果没有统计工具箱,可以用下面这个精简版自定义函数。它基于距离矩阵实现,先以邻域内样本数判断核心点,再用队列扩散完成簇的合并,和标准DBSCAN逻辑一致。
function [idx, isCore] = mydbscan(D, eps, minPts) n = size(D, 1); idx = zeros(n, 1); % 0=未访问,-1=噪声,>0=簇编号 isCore = false(n, 1); cluster = 0; for i = 1:n if idx(i) ~= 0 continue; end % 找所有在Eps邻域内的样本 neighbors = find(D(i, :) <= eps); if numel(neighbors) < minPts idx(i) = -1; % 暂时标记为噪声 continue; end % 新建一个簇 cluster = cluster + 1; idx(i) = cluster; isCore(i) = true; seed = neighbors; % 待扩散队列 pos = 1; while pos <= numel(seed) j = seed(pos); pos = pos + 1; if idx(j) == -1 idx(j) = cluster; % 边界点属于当前簇 continue; elseif idx(j) ~= 0 continue; % 已在其他簇中,跳过 end idx(j) = cluster; nb = find(D(j, :) <= eps); if numel(nb) >= minPts isCore(j) = true; seed = [seed, nb]; % 核心点继续扩散 end end end end这段代码有两个细节想说明一下。第一个是边界点被标记为-1后,在后续BFS扩散中遇到它时会被重新归类到当前簇,这正好符合DBSCAN的定义。第二个是整个流程里没有使用“visited”标志,而是直接用idx的非零状态作为访问标记,逻辑上是自洽的。
4.2 代表性场景提取:选质心最近样本而不是直接取质心
聚类完成后的关键决策是:怎么从每个簇里挑出那一条“代表性场景”。
做法一:直接取簇内样本均值作为代表场景。优点是曲线平滑、代表了簇的“平均形态”,缺点是平均会抹掉一些物理上重要的波动细节,比如风电爬坡的斜率、负荷峰值的尖锐程度。如果你后面的优化模型对曲线形态敏感,直接用均值可能引入偏差。
做法二:取簇内离质心最近的原始场景作为代表。这样得到的曲线是真实存在的场景,保留了原始曲线形态,而且和质心非常接近,几乎等价于“平均形态”,但又不失真。我一般选这个做法,尤其在工程报告里更好解释——“这条曲线代表了一类典型场景”。
代码实现如下:
nCluster = max(idx); repIdx = zeros(nCluster, 1); prob = zeros(nCluster, 1); validMask = idx > 0; validTotal = sum(validMask); for c = 1:nCluster members = find(idx == c); prob(c) = numel(members) / validTotal; if numel(members) == 1 repIdx(c) = members; continue; end % 簇内所有样本到质心的距离 center = mean(dataMat(members, :), 1); dm = sum((dataMat(members, :) - center).^2, 2); [~, imin] = min(dm); repIdx(c) = members(imin); end % 取出代表场景的原始曲线(未归一化) repWind = windScen(repIdx, :); repLoad = loadScen(repIdx, :);这里有一个容易错的点:在归一化空间里算质心和距离,代表场景编号拿到后,要去原始数据矩阵里取曲线还原,千万别在归一化的数据里取完再反变换。因为反变换时一旦极小值和最大值搞错,曲线还原就会出偏差。直接用原始矩阵索引取,既简单又不会错。
4.3 概率分配:噪声点到底算不算分母
场景缩减最后输出给优化模型的,不只是曲线本身,还有每条曲线的概率。我计算概率的分母用的是有效样本数(剔除噪声点后),而不是原始场景总数。
原因在于:噪声点被识别为离群场景后,它们不代表任何一类“典型模式”,把它们留在分母里,会让每个代表场景的概率被人为稀释,反而扭曲了典型场景的真实占比。如果你的噪声点比例很小(5%以内),分母用哪个影响不大;如果噪声比例超过10%,你更应该回头检查参数标定,而不是纠结分母。
如果你希望保留极端场景,有两条路:一是把噪声点单独输出为“稀有场景”列表,不参与聚类概率分配,只在后续分析中单独研究;二是调小Eps,让那些极端场景各自成簇。但各自成簇的坏处是簇内样本数很少,概率权重很低,对优化模型影响有限,还需要你额外判断是否重要。我通常倾向于前者:参数标定尽量让噪声比例控制在一个合理范围内,同时单独把噪声场景保留下来做敏感性分析。
4.4 缩减效果评价与可视化
聚类缩减做完了,怎么判断效果好坏?不画图直接说“簇数挺合理”是不够的。我至少会做两组评价。
第一组是可视化对比。把原始场景集中随机抽若干条灰色细线画出来,再把代表场景用彩色粗线叠加,能直观看到代表曲线是否覆盖了原始场景的分布主体。第二组是数值指标,用概率加权后的缩减场景均值、标准差和原始场景的均值、标准差做对比,误差越小说明缩减对统计特征的保真度越高。
% 原始场景的统计特征 meanOrigWind = mean(windScen, 1); stdOrigWind = std(windScen, 0, 1); % 缩减后场景的统计特征(概率加权) meanRedWind = sum(repWind .* prob, 1); stdRedWind = sqrt(sum(((repWind - meanRedWind).^2) .* prob, 1)); % 平均绝对误差 errMean = mean(abs(meanOrigWind - meanRedWind)); errStd = mean(abs(stdOrigWind - stdRedWind)); fprintf('均值曲线MAE:%.4f,标准差曲线MAE:%.4f\n', errMean, errStd); % 绘图对比:风电场景 figure('Position', [100 100 1200 420]); subplot(1,2,1); hold on; plot(1:nHour, windScen(1:200,:)', 'Color', [0.75 0.75 0.75]); plot(1:nHour, repWind', 'LineWidth', 1.8); xlabel('小时'); ylabel('风电出力 (p.u.)'); title('风电场景缩减对比'); legend({'原始场景(抽样显示)', '代表场景'}, 'Location', 'best'); grid on; subplot(1,2,2); hold on; plot(1:nHour, loadScen(1:200,:)', 'Color', [0.75 0.75 0.75]); plot(1:nHour, repLoad', 'LineWidth', 1.8); xlabel('小时'); ylabel('负荷 (MW)'); title('负荷场景缩减对比'); grid on;这里要注意:均值误差低不代表场景缩减质量一定好,因为它只看一阶矩。更严格的做法是计算缩减前后场景集的Wasserstein距离,不过计算复杂度高一些,工程上一般用均值方差对比作为快速判据。如果你特别关心极端场景保真度,可以额外对比原始场景集和缩减场景集中最大出力、最小出力的分位数差异。
5. 场景缩减实践中的常见问题与避坑技巧
5.1 高频问题排查速查表
实际跑起来,你可能会遇到下面这些情况。我把最典型的几类整理成一张对照表,排查起来很方便。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 簇数量过多、过于零碎 | Eps取值偏小,密度连不上 | 在k-距离图拐点右侧取更大的Eps |
| 噪声点占比过大(超过15%) | MinPts偏大或Eps偏小 | 减小MinPts,或适度增大Eps |
| 场景聚类结果几乎全部合并成一类 | Eps太大,密度过度连通 | 往k-距离图拐点左侧取更小的Eps |
| 聚类结果只反映负荷特性,风电特征消失 | 数据未归一化或风电权重被淹没 | 分别归一化,检查风电通道的方差贡献 |
| 曲线形态差别很大但被分为同一簇 | 欧氏距离对时序错峰太敏感 | 先做PCA降维,或提取均值/方差/峰值等特征 |
| 计算时间过长 | 距离矩阵O(n²)太大 | 缩减初始场景数,或调用自带dbscan的KDTree加速 |
| 不同批次聚类结果不稳定 | 数据扰动大、参数在临界区 | 微调Eps稳定在拐点明显区域,或增大MinPts |
5.2 噪声点比例是一个极其有用的诊断信号
我自己使用的一个经验指标:噪声比例超过10%,先别急着往下做,回去调参数。这不是说噪声点有错,而是高噪声比例通常说明Eps或者MinPts没有匹配好数据的密度结构。
有一次我做风电场景缩减,初始场景集里夹了不少风电突降的极端曲线,一开始噪声比例高达18%。我以为是正常的,结果一检查发现是Eps偏小,那些极端曲线和主流曲线之间距离明显但没被连接起来。把Eps调大之后,部分极端曲线成了边界点,进入邻近簇,噪声降到6%左右,场景缩减效果明显更合理。
但反过来,如果噪声比例已经降到3%以下,说明几乎所有场景都被硬塞进了某个簇,这可能掩盖了真实的离群模式。做规划时,极端出力场景恰恰是系统最紧张的时刻,全丢了风险很大。所以我的建议是:噪声比例控制在3%-10%之间,少于3%要警惕过度拟合,多于10%要重新审视参数。
5.3 场景缩减结果到底该看什么:三条判断标准
很多人缩减完,看到簇数和噪声比例觉得没问题,就直接把结果丢给优化模型了。我建议再多加三个检查。
第一条,看代表场景的物理合理性。风电曲线是否落在合理出力区间,负荷曲线是否保留早晚高峰特性。如果某个代表场景的曲线形态扭曲到不符合物理常识,大概率是参数或者数据预处理出了问题。
第二条,看均值方差的保真度。前面已经给了代码,这里再强调一点:不光要看风电,也要看负荷的一二阶矩误差。因为场景缩减输出的是一组联合场景,风电和负荷的协方差结构如果被破坏,对随机优化的影响很大。
第三条,看代表场景的累积概率分布。你可以把原始场景集和缩减场景集的某一个关键指标(比如日均风电出力、日峰值负荷)的累积分布函数画在一起,两条曲线越贴近说明缩减保真度越高。这一步对说服团队里不熟悉算法的同学特别有用,直观且有力。
5.4 关于“要不要保留噪声点”的一个工程建议
直接丢弃噪声点是最简单的做法,适合噪声比例小的情况。但遇到以下两类情况,我会单独保留噪声点:
第一类是噪声点里含有负荷尖峰或者风电极端低出力场景。这类场景虽然发生概率低,但一旦发生,对电力系统的安全运行影响极大。丢弃它们在概率上是合理的,但在工程决策里不可接受。我会把它们单独列出来,作为“极端场景集”提供给规划人员做校核。
第二类是噪声点数量虽然少、但曲线形态有清晰物理含义。比如一整簇“风电骤降+负荷攀升”的曲线,因为距离别的主流簇太远而被打成噪声,但它们实际上代表一个明确的运行风险模式。这种情况下,我会调整Eps或者直接用HDBSCAN这类带层次结构的密度聚类算法,把这簇曲线单独识出来。
5. 结尾:一点个人实操体会
说实话,DBSCAN这个算法我最早接触的时候,是在处理交通轨迹聚类,后面迁移到电力场景缩减才发现它在风-荷联合场景里格外顺手。整个过程做下来,最深的体会是:场景缩减的瓶颈从来不在算法本身,而在参数标定和对业务场景的理解。k-距离图我建议每次必画,哪怕你已经很有经验了,因为它能迅速帮你判断当前数据的密度结构是不是和预期一致。
最后再分享一个小技巧:跑完聚类之后,把每个簇的样本数打出来,看看是不是均匀分布。如果出现一个簇占了60%以上的样本,另一个簇只占2个样本,那说明Eps选得过大或过小,场景缩减的区分度不够。这种时候不要硬调,重新画一次k-距离图,往往几分钟就能找到合适区间。场景缩减这个东西,说白了就是用最小代价保留最核心的不确定性信息,DBSCAN只是实现这个目标的一个趁手工具。