☰
结合拉丁超立方抽样与场景削减的风光出力不确定性预测分析
2026/10/22 16:06:19 网站建设 项目流程

简介:围绕基于拉丁超立方抽样与样本削减的风光出力及负荷场景预测,这份材料以单个Matlab脚本形式提供完整实现流程,适用于新能源并网规划、电力调度辅助决策以及风光预测方法研究等场景。方法层面,先用拉丁超立方抽样在多维气象空间中均匀取样,充分考虑风速、辐射强度、温度等因素的交互影响;随后通过样本削减策略从大量历史观测中筛选代表性子集,在降低计算复杂度的同时维持预测精度。脚本内含分步注释,覆盖数据预处理、场景生成、负荷与风光出力建模以及误差指标对比等环节,结构紧凑但流程完整,便于快速理解方法逻辑并移植到自身课题中。压缩包仅1KB,rar格式,共1个m脚本文件,下载后可直接查看或运行。已有2593人学习,对于提升可再生能源出力不确定性建模能力具有实际参考价值。

1. 场景预测分析:风光出力不确定性的最后一道闸

做新能源并网或配电网规划的人大多会撞上同一个问题:风速、辐照度和负荷都是随机过程,蒙特卡洛抽个几千次,调度模型根本跑不动。我最早接触这个组合方案时也以为拉丁超立方抽样是某种更高级的蒙特卡洛,用下来才发现,真正让你从“概率密度函数”走到“可决策场景集”的,是拉丁超立方抽样与样本削减这两个动作的配合:前者用几百个分层样本稳定覆盖风光出力及负荷的概率空间,后者把场景数压缩到10~20个且不丢掉极端情况。这套方案主要面向需要搭建源-荷不确定性模型的工程师,给出从抽样到削减再到预测分析的完整落地路径,每个参数都写清楚它在工程里到底负责什么。全文代码基于 Python 3 常见科学计算库,可以直接用一个模拟项目X的数据跑通。

2. 拉丁超立方抽样:为什么它能用更少样本覆盖风光出力的概率空间

2.1 从逆变换采样到分层抽样:LHS 的数学基础与工程直觉

很多教材会直接给出 LHS 的定义:把 k 个随机变量的定义域都等分成 N 个区间,每个区间恰好抽取一个样本,从而保证 N 个样本在多维空间中分布均匀。这个定义的工程含义是,蒙特卡洛要靠大数定律把误差压到 1/√N,而 LHS 通过分层约束让误差大致维持在 1/N 的量级,在同样样本量下,累积概率函数的收敛速度明显更快。也就是说,你不需要抽 5000 次,往往 200~500 次就能让均值、方差和分位数接近真值。

这里给一个直观图景:用横轴代表风速、纵轴代表辐照度,蒙特卡洛完全随机撒点,很可能左下角挤成一堆而右上角一个点也没有;LHS 强制每个维度都“每列必有一针”,于是二维平面上 N 个点像渔网一样铺满整个空间。对风电出力这类分布严重偏态(风电出力大量落在低出力区)的变量,LHS 的强制分层相当于主动去覆盖尾部高比例区间,不会让样本全部堆在众数附近。

落地时是先对均匀分布样本做 LHS,再通过逆变换采样映射到目标分布。风电出力常建模为 Beta 分布,光照出力也常用 Beta,负荷则多用正态或对数正态。每类变量的分布参数要来自历史数据拟合,而不是随便拍脑袋,后面第 4 章会演示怎么把预测均值接到分布里。

2.2 带相关性约束的 LHS:风光出力不是独立随机变量

如果直接对风电、光伏、负荷各自独立做逆变换,得到的三个序列完全不相关,这在物理上是错的:同一股气团过境时,大面积的风电和光伏出力的波动会呈现正相关;而夏季高温时光伏高发伴随空调负荷升高,负荷与光伏又可能出现正相关。真实场景需要一组服从目标相关性矩阵的抽样样本。工程上最常用的是 Iman-Conover 方法:先生成独立的 LHS 矩阵,把每列按标准正态排序得到秩,然后对目标相关矩阵做 Cholesky 分解得到变换矩阵,通过对排序后的数据做线性变换强制引入秩相关,最后用排序修正保证每列还是从原始分布取值。

注意,Cholesky 分解要求目标相关矩阵必须是半正定的。人工设定的相关矩阵经常会因为拍进去数值太满而变成非半正定,这在第 5 章避坑部分会细讲,到时会给出修正代码。实际项目中我一般分两块设置:块间(风/光/负荷之间)设 3×3 相关矩阵;块内(时序相邻时刻之间)设带状相关矩阵,因为风速、辐照度和负荷都有持续性,相邻时刻不可能完全跳变。这个矩阵是 LHS 自由度最大的一环,也最影响场景真实性。

2.3 用 Python 生成风光出力的 LHS 初始场景集

先给一个最小可用的 LHS 均匀抽样函数:

import numpy as np from scipy import stats as st def lhs_uniform(n_samples: int, n_vars: int, seed: int = 42) -> np.ndarray: rng = np.random.default_rng(seed) result = np.zeros((n_samples, n_vars)) for j in range(n_vars): # 对本维度的N个区间做随机排列,确保每个区间恰好抽一个点 perm = rng.permutation(n_samples) # 区间内均匀分布,让点落在区间内的随机位置 u = rng.uniform(0.0, 1.0, n_samples) # 映射到 [0,1) result[:, j] = (perm + u) / n_samples return result

n_samples是场景数,n_vars是随机变量的总维度。对组合场景来说,n_vars等于时间点数乘变量数,比如要生成 24 小时的风、光、负荷三维序列,n_vars = 24 * 3 = 72。perm + u的做法保证了每个维度上 N 个样本均匀覆盖 [0,1),而且各维度之间没有人为对齐。这里每个区间内还要再取一次随机位置,是为了保证样本间不产生周期性伪影,这是 LHS 相对网格抽样更精确的原因。

接下来,把均匀样本映射到实际分布。例如,风电出力用 Beta(a=2, b=6),光伏出力用 Beta(a=1.3, b=1.2)(仅示意,具体参数要拟合),负荷用正态分布:

def transform_to_marginal(u: np.ndarray, dist_type: str, params: dict) -> np.ndarray: if dist_type == 'beta': rv = st.beta(a=params['a'], b=params['b']) elif dist_type == 'norm': rv = st.norm(loc=params['loc'], scale=params['scale']) else: raise ValueError(f"Unknown dist_type: {dist_type}") # 逆变换采样,把均匀分布的LHS样本变成目标分布的分位数 return rv.ppf(u)

ppf是概率密度函数的逆函数,也叫分位数函数。这里要注意u不能取到 0 或 1,否则 Beta 分布会返回 0 或容量上限,所以在区间内加均匀抖动正好避免了边界问题。对风电出力,我还要把结果乘上装机容量,并裁剪到 [0, capacity];对负荷,我一般不直接抽绝对值,而是抽“负荷残差”叠加在点预测曲线上,这样既保留了预测趋势,又体现了预测不确定性。

相关性排序的完整实现如下:

def corr_lhs(u: np.ndarray, target_corr: np.ndarray, seed: int = 1): n, m = u.shape rng = np.random.default_rng(seed) # 每列按LHS取值排序,记录原始排名样本;Iman-Conover的关键是按秩操作 for j in range(m): u[:, j] = st.norm.ppf(u[:, j]) Q = np.linalg.cholesky(target_corr) # 对标准正态化后的样本做相关变换 X = u @ Q.T # 再对每列排序,并把原始LHS样本按新秩重新排列,保持边缘分布不变 for j in range(m): ranks = st.rankdata(X[:, j]) u_sorted = np.sort(u[:, j]) u[:, j] = u_sorted[(ranks - 1).astype(int)] return st.norm.cdf(u)

这段代码先用st.norm.ppf把均匀样本变成标准正态,再用 Cholesky 分解施加目标相关矩阵,最后把原始 LHS 样本按照变换后的列的顺序重排,从而让边缘分布保持为均匀分布而相关结构趋于目标。第 5 章会提到,这个方法的坑在于 Cholesky 分解前必须确认target_corr是半正定矩阵。

3. 样本削减:把上千个场景压到可用的十个场景

3.1 为什么要削减:调度计算复杂度与场景代表性的平衡

LHS 减少了抽样次数,但几百个场景交给随机规划仍然很吃力:一个含 10 个常规机组、24 个时段的随机机组组合问题,场景数从 100 增到 300,求解时间和内存占用往往都出现量级增长。样本削减的目标就是在保持场景集相对原始场景集“足够接近”的前提下,把场景数压缩到 10~30 个,并给每个保留场景分配一个概率权重。这个接近程度的度量通常用场景集之间的 Wasserstein 距离或 Kantorovich 距离。工程上更直接的验证方式是:把削减前后的场景集分别代入同一个调度模型,观察期望发电成本或弃风弃光率变化是否在 1% 以内,如果是,说明削减没有丢掉决策关键信息。

因此,我的经验是不要单纯追求轮廓系数高。轮廓系数描述的是类内紧凑和类间分离,但它对调度目标并不敏感:它可能选择 K=2 就把所有场景分成“高出力”和“低出力”两类,但这远远无法刻画“晚高峰风电爬坡失败”这类极端事件。所以先确定你需要哪些边界场景(例如净负荷最大、最小爬坡、持续阴天),再在保留这些场景的前提下确定 K 值。

3.2 三种削减方法对比:k-means、层次聚类、快速前向选择

目前主流方法有三大类。第一类是 k-means 聚类:把每个场景当作高维空间中的一个点(维度=时间点数×变量数),先随机初始化 K 个质心,迭代把场景分配到最近质心,然后重新计算质心。k-means 速度快、扩展性好,3000 个 72 维场景也能在一秒内收敛。缺点是质心只是类内均值,不一定对应一个真实场景,且对随机初始值敏感。第二类是层次聚类(凝聚式):从每个样本单独成类开始,反复合并距离最近的两类,直到达到 K 类。这个方法的输出是真实的样本轨迹(类中离总中心最近的样本),更稳健,但计算量是 O(N²),样本超过 2000 就很慢。第三类是快速前向选择(FFS):通过贪心选点与重分配概率权重来最小化 Wasserstein 距离,在概率分布意义上有严格的最优性,但实现复杂,且原版复杂度高。实际项目中 k-means 用的最多;若需要可复现结果是层次聚类更稳。

方法输出形态速度适用规模注意点
k-means中心点向量快N>1000需要标准化、多次初始化
层次聚类真实样本点较慢N<2000选择离中心最近的样本点作为场景
FFS真实样本点+权重慢N<500可保证Wasserstein距离最小

3.3 削减的实现与评价指标

下面这段代码是 k-means 削减的最小实现,并返回典型场景和对应概率:

import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler def reduce_by_kmeans(scenarios: np.ndarray, original_weights: np.ndarray, n_clusters: int = 10, seed: int = 0): """ 对场景矩阵做 k-means 削减。 scenarios: (N, D),N为原始场景数,D为时间点*变量数 original_weights: (N,) 原始场景权重,一般全为 1/N """ n = scenarios.shape[0] # 标准化缩放,避免风、光、负荷数量级不同导致聚类被主导 scaler = StandardScaler() X = scaler.fit_transform(scenarios) # 多次随机初始化,避免局部最优 km = KMeans(n_clusters=n_clusters, random_state=seed, n_init=10, max_iter=300) labels = km.fit_predict(X) # 典型场景取类内样本均值(在原始尺度下计算) centers = np.zeros((n_clusters, scenarios.shape[1])) probs = np.zeros(n_clusters) for k in range(n_clusters): mask = (labels == k) centers[k] = scenarios[mask].mean(axis=0) # 权重 = 类内原始权重之和,一般就是类内样本数/n probs[k] = original_weights[mask].sum() / original_weights.sum() return centers, probs, labels

参数n_clusters是你要保留的场景数,工程上落在 10~30 个。n_init=10表示每次选择质心随机初始化 10 次,取目标函数最小的一组,能有效避免因初值不同导致的削减结果抖动。max_iter=300是单次迭代上限。StandardScaler很重要——负荷的数值可能是几十兆瓦,风电是几百兆瓦,光出力可能也是几百兆瓦,如果不标准化,负荷维度在欧氏距离中几乎被淹没。注意标准化后聚类质心做逆变换没必要,因为我们只按标签在原始场景空间内求均值,而不是用质心向量。

评价削减质量时,我一般看三个指标:一是削减前后场景集的 Wasserstein 距离,越小越好;二是削减后的权重分布是否均匀,如果一个场景占 70% 权重而另外 9 个只占 30%,说明削减过度;三是对比原始场景与削减场景的累计频率直方图,看分位数(比如 10% 分位、90% 分位)是否基本吻合。快速粗略的做法是画一个网格对比图,把每个时间断面原始样本的 5%~95% 区间和削减场景落在区间内的比例算出来。

4. 从 LHS 到削减再到预测分析:组合场景的完整流水线

4.1 组合场景构造:时序相关性如何放到多维矩阵里

前面 2.2 节设置了变量间的相关矩阵,但工程里还有一个隐藏维度:时间。24 小时的风电出力不是一个独立向量,而是连续性很强的时序过程。如果对每个时刻单独采样,削减得到的典型场景会出现明显的锯齿状跳变,比如第 10 时段的出力 120 兆瓦、第 11 时段直接掉到 10 兆瓦,物理上不可能。我几乎每个来问的同事都会在这里翻车。

常见做法是让 LHS 的相关矩阵同时包含块内时间相关。一个简单而有效的构造方法是:先给目标组合矩阵设置 3×3 的变量相关(风、光、负荷),再用 AR(1) 模型描述每类变量的时间相关,最后用 Kronecker 积把二者合成一个 (3T)×(3T) 的相关矩阵。这样 LHS 排序后的样本序列既有变量间相关性,又有相邻时刻的平滑性。AR(1) 系数可以用历史每 15 分钟出力残差的 lag-1 自相关系数估计。

Kronecker 积合成的假设是变量相关和时间相关互相独立,实际中在光伏出力的下午时段这个假设会偏保守,但比完全忽略时间连续性好很多。若你手头有完整历史时间序列,也可以直接用实测场景的秩相关矩阵当作目标矩阵,这时不需要 Kronecker 积,只要数据覆盖场景范围广即可。

4.2 预测分析的分层结构:从整体到概率区间再到极端场景

光有场景集还不够,预测分析的输出要能支撑决策。我习惯把分析拆成三层:第一层是时段统计量——每个时段的风电、光伏、负荷均值、方差、峰谷差;第二层是概率区间——每个时段选取场景集的 10%、50%、90% 分位,画出套在场景外的概率带;第三层是极端场景识别——在削减后的典型场景中找出“风电低、光伏低、负荷高”的组合,这类场景直接决定是否需要切负荷或购买备用容量。

这里要特别注意,从削减后的场景取分位数是有偏的,因为只有 K 个离散场景,分位数会呈阶梯状。更稳妥的做法是对原始 LHS 场景(数百个)取分位数,用削减场景做优化,用原始场景做评估。我把这个原则叫作“生成用原始集,决策用削减集,评估再回原始集”。

4.3 一个最小可复现的生成-削减-预测分析流水线

下面是一个整合函数,给的是 4 时段的小规模示意,方便你在笔记本上验证整套逻辑,24 时段直接把 T 改大即可。输入是预测均值序列和风机、光伏、负荷的标准差参数。

import numpy as np from scipy import stats as st def scenario_pipeline(mu_w, mu_pv, mu_load, sigma_w, sigma_pv, sigma_load, T, n_samples=500, n_clusters=10, seed=3): """ mu_w, mu_pv, mu_load: 长度为T的预测均值序列 sigma_*: 对应残差标准差序列长度T 返回: centers(削减场景), probs(概率), raw_scen(原始场景矩阵), q1, q9 """ rng = np.random.default_rng(seed) n_vars = 3 * T # 第一步:独立LHS均匀抽样 u = np.zeros((n_samples, n_vars)) for j in range(n_vars): perm = rng.permutation(n_samples) v = rng.uniform(0, 1, n_samples) u[:, j] = (perm + v) / n_samples # 第二步:按变量类型分别做逆变换,生成标准化残差 w_raw = st.norm.ppf(u[:, 0:T]) # 风残差 pv_raw = st.norm.ppf(u[:, T:2*T]) # 光残差 load_raw = st.norm.ppf(u[:, 2*T:3*T]) # 负荷残差 # 第三步:叠加预测均值与尺度,得到场景矩阵 scenarios = np.zeros((n_samples, n_vars)) scenarios[:, 0:T] = mu_w.reshape(1, -1) + w_raw * sigma_w.reshape(1, -1) scenarios[:, T:2*T] = mu_pv.reshape(1, -1) + pv_raw * sigma_pv.reshape(1, -1) scenarios[:, 2*T:3*T] = mu_load.reshape(1, -1) + load_raw * sigma_load.reshape(1, -1) # 第四步:对场景进行削减 original_weights = np.full(n_samples, 1/n_samples) centers, probs, labels = reduce_by_kmeans(scenarios, original_weights, n_clusters=n_clusters) # 第五步:评估原始场景的概率区间 q1 = np.percentile(scenarios, 10, axis=0).reshape(3, T) q9 = np.percentile(scenarios, 90, axis=0).reshape(3, T) return centers, probs, scenarios, q1, q9

代码里有几个可以按业务改的点:sigma_*建议用实测历史误差的逐时段标准差,而不是一个全局常数。对光伏,最好把辐照度转为装机利用率后再拟合,避免将多云与晴天的方差混在一起。函数的输出中centers是(n_clusters, 3*T),每个中心场景都是一条完整时序;结合probs就能直接组成随机优化的输入。q1和q9是按列计算的,取出每一列的 10% 和 90% 分位,足以画概率带。

注意,这个简化流水线先用独立正态残差近似,没有加时序相关,因此生成的场景可能有轻微锯齿。要修正,需要在scenarios生成后做一次指数平滑或对残差序列做滚动滤波。我常加一个简单的高斯滤波核,核宽度取 2~3,既能消除锯齿又能保留主要波动。

面向决策时,场景权重还要做归一化。由于 k-means 削减返回的probs已经归一化了,如果后面要并入随机规划模型,直接在目标函数里乘这个权重即可,不需要再除以总数。许多新手在这里会重复归一化,造成场景权重比实际概率小了一个数量级。

5. 场景法避坑指南:参数设置、时序陷阱与相关性失真

5.1 削减后中心点权重分配错误

现象:用 k-means 削减出来的中心点只代表类内均值,但有人把中心点直接当成新场景,并给每个中心点都分配 1/K 的概率,导致削减后的概率分布与原始分布完全错位。原因:中心点位于类的重心,不代表这个类覆盖的概率范围,而且如果一个类包含 300 个原始场景,另一个只包含 5 个,二者权重显然不该相等。解决:按类内样本数(或类内原始权重之和)占总样本数的比例分配概率,也就是reduce_by_kmeans函数里的probs[k] = original_weights[mask].sum() / original_weights.sum()。检查方法是打印probs,如果最重的场景超过 0.5,要么增加n_clusters,要么检查数据是否被孤立点控制。

5.2 时序相关性被切断,场景像白噪声

现象:生成的场景曲线每分钟都在剧烈震荡,风电出力从 150 兆瓦跳到 20 兆瓦再跳回 140 兆瓦,拿给调度的人直接说不可能。原因:LHS 对每个时刻独立采样,没有时间相关约束。解决:在 4.1 节用 AR(1) 系数构造时间相关矩阵,或者对残差场景做卷积平滑。也可以从历史数据里随机抽取一整天的实际时序作为模板,再把 LHS 生成的扰动叠加到模板上,这个方法更能保留真实爬坡率。

def smooth_scenarios(scenarios, T, kernel_size=3): kernel = np.hanning(kernel_size) kernel = kernel / kernel.sum() smoothed = np.zeros_like(scenarios) for i in range(scenarios.shape[0]): for j in range(3): # 风、光、负荷三个块分别平滑 block = scenarios[i, j * T:(j+1) * T] smoothed[i, j*T:(j+1)*T] = np.convolve(block, kernel, mode='same') return smoothed

kernel_size=3只对相邻时刻做加权平均,不会抹掉 4 小时以上的尺度响应。如果你要保留爬坡极限,还需要在平滑后检查相邻时刻差值的绝对值是否超过从物理爬坡率推算的上限,超出的场景直接丢弃或截断。

5.3 相关矩阵非半正定导致 Cholesky 报错

现象:np.linalg.cholesky(corr_matrix)抛LinAlgError: Matrix is not positive definite。原因:人工填的风光负荷相关矩阵违背了谱性质,比如令corr(wind, load) = -0.9,corr(load, pv) = -0.8,corr(wind, pv) = 0.6,三者构成的矩阵特征值出现负数。解决:用 Higham 修正法找到最近半正定矩阵。这里给出一个轻量实现:

def nearest_psd(matrix): eig_vals, eig_vecs = np.linalg.eigh(matrix) eig_vals = np.clip(eig_vals, a_min=0, a_max=None) psd = eig_vecs @ np.diag(eig_vals) @ eig_vecs.T # 归一化对角线为1,保持其为相关矩阵 d = np.sqrt(np.diag(psd)) psd = psd / np.outer(d, d) return (psd + psd.T) / 2

这段代码对特征值做截断,再用对角缩放改回相关矩阵。注意修正后的相关矩阵会与初始输入有偏差,偏差过大会影响取样分布,所以最好一开始就用历史样本估算相关矩阵,而不是手搓数值。如果目标矩阵比较稳定,也可以先用投影法迭代几次,迭代次数控制在 10 次以内。

5.4 场景数 K 只凭轮廓系数选

现象:轮廓系数在 K=2 或 K=3 达到最高,于是把场景削减成 3 个,结果优化结果完全不反映极端场景。原因:轮廓系数是从几何角度衡量聚类质量,而离散化后的典型场景要服务的是概率化决策——需要覆盖尾巴分布。解决:不要只用一个指标。我一般把 K 当作一个待调节参数,先设 10、15、20、25,对每个 K 做一次削减,然后调用目标模型算期望成本,观察优化目标在 K 超过多少后趋于平稳。通常成本曲线会在 K=15~20 附近进入平台期,平台起点就是可以接受的 K。同时手动检查 K 是否覆盖了“风光零出力而负荷峰值”和“风光大发而负荷低谷”这两类极端场景。

5.5 k-means 的随机性:同一份数据每次跑出不同结果

现象:前后两次运行生成的削减场景序列不同,导致后续优化方案发生不应有的变化。原因:k-means 初始质心随机,目标函数可能有多个局部极小值。解决:固定random_state;在代码中调高n_init(如 10~50);或者在多次运行中取目标函数最小的结果,我常用一个循环跑 5 次 k-means,用km.inertia_选择最好的一组。若业务要求完全确定,可以直接改做层次聚类,它没有随机初始化问题,但内存占用更高;对 3000 以上的场景,可以先随机抽样一部分样本跑层次聚类得到质心种子,再喂给 k-means。

另一个容易被忽视的随机源头是lhs_uniform里的种子不一致:如果种子不一致,初始场景集也会变化,从而影响削减结果。推荐在整个流水线里用一个全局种子管理对象管理所有抽样环节,保证结果可复现。我通常把种子写在配置文件里,并在输出文件名里带上哈希值,方便回溯。

6. 实战进阶:用场景概率质量评估代替主观拍脑袋

6.1 场景概率准确性评估:Pinball Loss 与样本外验证

削减完成的场景集到底准不准,不能只看削减前后的距离。更贴近业务的做法是做样本外验证:把历史数据按时间切成训练段和测试段,从训练段拟合分布参数并生成场景,再用测试段的实际风、光、负荷时序去检验。检验指标是 Pinball Loss,它衡量分位数预测的质量:对每个时段,如果实际值落在场景分位数区间内,损失小;落在区间外,损失按距离线性惩罚。用削减场景计算分位数会因场景过少而不连续,所以我习惯用原始 LHS 场景集去算分位数,用削减场景集去算调度方案,两者结合才是完整的“场景预测分析”。

Pinball Loss 的工程实现很简单:

def pinball_loss(y_true, q_pred, q_level=0.9): diff = y_true - q_pred loss = np.where(diff >= 0, q_level * diff, (q_level - 1) * diff) return np.mean(loss)

调用时q_pred用场景集的 90% 分位数,y_true是测试段真实出力。要注意如果q_level取 0.5,Pinball Loss 就是平均绝对误差,正好与你用中位数场景预测时的 MAE 对应。评判标准不是看单个小时内 loss 多大,而是看多个测试日累积的 loss 在 K 变化时是否显著降低,若降低不明显,说明你再增加场景数也只是在拟合噪声,有浪费算力的嫌疑。

6.2 面向决策的场景削减:把弃电和切负荷成本加进距离度量

遇到配电网和微电网的规划问题时,k-means 的欧氏距离并不能真实反映调度代价:一个场景偏离均值 10% 可能毫无影响,但另一个场景净负荷斜率变化 3% 就可能触发备用不足。常见做法是构造加权特征距离:把净负荷序列及其差分(爬坡率)拼接成一个增广向量,再按事先算好的经济成本系数对不同的特征施加不同权重。代码上只需要在reduce_by_kmeans前做一次特征变换。

我做过某微电网项目时,就在场景矩阵后面拼接了两列:一列是全时段净负荷总和放大 100 倍,另一列是最大爬坡绝对值的倒数作为惩罚项。削减得到的典型场景虽然与纯欧氏距离不同,但最终决策方案在极端事件下不缺电、不弃风,运行成本更低。这类工程调整没有标准公式,但思路是——把目标函数里最敏感的特征显式放进距离度量,不要让聚类算法替你做经济判断。

6.3 一个最后的小技巧:保留削减前后的全局分布对比

过去我总把削减结果交给下游就完事。有一天复盘时发现,削减后的 10 个场景里,风电满发、光伏不出力的组合只有一种,而原始场景集中这种组合占了 7%,我却没有发现。现在我在每个交付物上都附一张两列缩略图,或者一张 3×3 的散点矩阵图,左边是原始场景各时段的分位数包络,右边是削减场景的点位分布;如果削减场景的密度包络与原始包络差异超过 5%,我就重新调整聚类特征或增加 K。这个习惯看似简单,却能避免把置信区间算错的尴尬。

做场景生成和削减这件事,最大的教训来自我早期把权重分配写错还硬着头皮交付,直到下游调度模型跑出负成本才反应过来原因。从那以后我保持了一个习惯:在流水线里强制断言所有概率权重之和等于 1,且所有削减场景都通过物理爬坡约束检查。写复盘时我会把经典的坑标在脚本注释里,下次直接跳过。希望这篇复盘能帮到你,少走一点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询