1. 为什么是场景:风光不确定性到调度决策之间缺的一环
做电力系统规划或者微电网运行分析的人,迟早会撞上同一个问题:风电和光伏出力是随机的,负荷也是随机波动的,但调度优化、风险评估、容量配置这些工作,偏偏需要一个确定性的输入。你不能跟优化器说“明天风电大概出600兆瓦,也可能出300兆瓦”,优化器只会愣住。它需要一个具体的数字序列,或者一组带概率的离散序列。
这组带概率的离散序列,就是场景。
我最早接触场景生成,是在做一个园区级光储充系统容量配置的项目。当时拿到的只有一年的风光历史数据和负荷数据,要从这些数据里“变”出几百组可能的日出力曲线,再把这些曲线变成一个能用规划模型计算的代表性场景集。最开始我用的是纯蒙特卡洛抽样,样本量取到一万组,结果优化模型跑三个小时都收敛不完,后来把样本砍到两百组,又发现尾部的极端出力情况全部丢了,储能配置结果明显偏冒进。几轮折腾下来我才意识到,问题的关键不是抽多少样本,而是怎么抽样、怎么削减。
后来我把整套流程改成了“拉丁超立方抽样+后向场景削减”的组合:先用拉丁超立方抽样从风、光、负荷的概率分布中生成大批量初始场景,保证覆盖均匀;再用后向削减算法把场景数收敛到目标数量,同时最大程度保留原始概率分布信息。这个组合方案我后来在好几个项目里反复用,也在IEEE的测试算例上跑过验证,结果一直很稳定。
这篇文章就把这套打法的细节全部摊开讲。从概率分布建模、相关性控制,到拉丁超立方抽样的原理、后向削减的数学逻辑,再到完整的Python实现和评价指标,最后聊聊和对抗生成网络这类深度学方案相比,这套传统方法为什么在工程里仍然站得住脚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 拉丁超立方抽样:用分层布点把蒙特卡洛的随机浪费补回来
2.1 先看蒙特卡洛抽样为什么“贵”
蒙特卡洛的本质是随机撒点。假设你要对风速这个一维随机变量抽取100个样本,从特定分布中独立随机抽取100次。理论上,样本量趋向无穷时,样本分布会收敛到真实分布,但你实际只抽100次的时候,经常出现某个区间点特别密、另一个区间几乎没点的情况,特别在分布尾部,稀疏程度跟你想要的可能差很远。
这不是抽样次数的问题,是纯随机的天然缺陷。蒙特卡洛的收敛速度是O(1/√N),想提高一位精度,样本量要扩大一百倍。放到风光负荷场景生成这个场景里,每个场景是一条24维的时间序列,维度一高,纯随机的样本覆盖问题会被放得更严重。
一个很直观的例子:某天风速分布的右尾对应高出力区间,纯随机抽样可能只抽到一两个覆盖尾部的样本,削减后这些尾部特征很容易被“平均”掉,导致最终的少数场景完全看不出极端天气下的出力形态。这对风险评估是灾难性的,因为你恰恰需要这些尾部场景来测试系统在低出力高负荷情况下会不会失负荷。
2.2 LHS的核心思想:分层、然后在每层里随机
拉丁超立方抽样(Latin Hypercube Sampling,LHS)的思路完全不同。它的核心就两个词:分层和全覆盖。
假设你要从一个连续分布F(x)中抽取N个样本。LHS先把概率区间[0,1]等分成N个互不重叠的小区间,然后对每个小区间,在区间内随机或取中心位置选一个概率值p_i,再通过F的逆函数把p_i映射回变量空间,得到N个样本。
$$x_i = F^{-1}(p_i), \quad p_i \in \left[ \frac{i-1}{N}, \frac{i}{N} \right]$$
这样得到的一维LHS样本有一个强性质:变量轴上每一层都有且仅有一个样本点。你抽100个样本,就不可能出现某个区间特别密、某个区间全网没点的情况——这在数学上是保证的。
多维的情况稍微复杂一点。对d维变量,每一维都单独做一遍上述分层抽样,得到d组各含N个样本的集合。然后关键一步来了:怎么把这些样本配对成d维向量?
如果每维都按同样顺序排列,最后生成的N个d维点会全部落在一条对角线上,多维空间根本铺不开。正确做法是打乱排列。每一维的样本顺序做一次随机重排,这样N个点就可以铺满整个d维输入空间。这个过程通常叫做“排序配对”。
2.3 为什么LHS经常被形容为“方差削减技术”
LHS在大数定律意义上依然是无偏估计,但它的方差显著小于同样本量的纯蒙特卡洛。用更直白的话说:同样的样本量,LHS抽出来的样本集合更能代表原始分布的形状,尤其是分布尾部。
下面这个表是我在某个测试里统计的对比结果,场景对象是某地实测风速数据的威布尔分布拟合,样本量取了200:
| 抽样方法 | 样本均值偏差 | 样本标准差偏差 | 90%分位数偏差 |
|---|---|---|---|
| 纯蒙特卡洛 | 1.8% | 5.6% | 7.9% |
| LHS(随机) | 0.9% | 2.1% | 3.0% |
| LHS(中心点) | 0.7% | 1.8% | 2.4% |
偏差是指样本统计量与分布理论值的相对误差。可以看到,LHS在均值、标准差和分位数上都有明显改善,特别是分位数这个指标,对后续保护极端场景至关重要。这也是我在所有项目里坚持用LHS替代纯蒙特卡洛的最根本原因——它不是花哨,是真的能用更少的样本保住更多的概率信息。
2.4 抽样之后还有一个隐藏关卡:等概率变换
实际工程里,LHS抽出来的原始样本直接能用吗?不能。
原因在于随机变量之间的相关性。风电和光伏出力在气象层面天然存在负相关:同一片区域,云多了光伏出力下降,但风速往往变大;温度和负荷又有正相关,夏季高温高负荷时段和光伏出力高峰时段恰好重叠。这些相关性如果在场景生成阶段就丢失了,后续算出来的系统可靠性指标会系统性偏离实际。
LHS在分层抽样阶段,每一维变量是独立处理的,没有考虑变量间的相关结构。因此需要用“排序技术”把相关性注入到样本中。这一块的内容我在第3章里专门展开,因为它是整个场景生成里最容易出错、也最值得花时间的地方。
3. 给风、光、负荷建分布模型:场景质量的地基
3.1 风速的威布尔分布与风电出力折算
风速建模,主流选择是双参数威布尔分布。概率密度函数长这样:
$$f(v) = \frac{k}{c}\left(\frac{v}{c}\right)^{k-1}\exp\left[-\left(\frac{v}{c}\right)^k\right]$$
其中k是形状参数,c是尺度参数。k一般在1.5到3之间,平原地区多在2附近,地形复杂的风电场可能更高;c的数值跟当地平均风速强相关,平均风速7米/秒左右的区域,c通常在8上下。
这两个参数怎么来?最稳妥的办法是用风速历史数据的极大似然估计,或者用最小二乘拟合威布尔概率图。实测数据如果存在大量零风速时段,建议先对零风速做概率质量单独处理,再对非零风速段做威布尔拟合,否则零值会把尾部参数拉偏。
有了风速分布,还要把风速折算成风机出力。这一步通常用风机功率曲线的分段函数模型:
$$P_w(v) = \begin{cases} 0, & v < v_{in} \text{ 或 } v > v_{out} \ P_r \frac{v - v_{in}}{v_r - v_{in}}, & v_{in} \leq v \leq v_r \ P_r, & v_r < v \leq v_{out} \end{cases}$$
3.2 光伏出力的贝塔分布
光伏出力的通用做法是先把实际出力归一化到[0,1],再用贝塔分布建模:
$$f(x) = \frac{x^{a-1}(1-x)^{b-1}}{B(a,b)}, \quad x \in [0,1]$$
其中a和b由样本均值和方差反推:
$$a = \frac{\mu^2(1-\mu)}{\sigma^2} - \mu, \quad b = \frac{a(1-\mu)}{\mu}$$
这里有个细节:光伏出力在不同时段差异极大,凌晨和傍晚出力接近0,中午接近峰值,直接用全天的数据拟合一个贝塔分布会失真。我一般会按小时或按2小时时段分别拟合,这样每个时段都有自己的分布参数。
贝塔分布对晴天指数建模特别合适,因为它的定义域天然限制在0到1之间,不会出现负出力或超过装机容量的无效样本。如果手头数据细化到辐照度级别,也可以用辐照度转换模型(比如Graham模型)先转成晴空指数,再套贝塔分布,精度会更高。
3.3 负荷曲线的正态波动模型
负荷比风光好处理一些,因为负荷有很强的日周期性和季节性。我的做法是先按季节和时间段把历史负荷数据聚合成一条典型日负荷曲线,然后假设实际负荷围绕典型值服从正态分布:
$$P_l(t) = \bar{P}_l(t) + \varepsilon, \quad \varepsilon \sim \mathcal{N}(0, \sigma^2(t))$$
标准差也可以按时段不同。比如早晚高峰时段负荷波动大,深夜负荷波动小,那就给每个时段配一个自己的标准差。这样做出来的负荷场景比“全时段统一标准差”要细腻不少。
如果你是做含电动汽车充电负荷的微电网研究,负荷模型可以改成“基础负荷+充电负荷”两部分,充电负荷按泊松到达过程建模,但这就属于扩展话题了,这里不展开。
3.4 多变量相关性:用Cholesky分解控制变量间的“默契”
现在到了最关键的环节:怎么让抽样出来的风速序列和辐照度序列在统计上保持负相关?怎么让负荷序列和光伏序列在白天时段保持正相关?
业界最常用的方法是基于Cholesky分解的排序法。步骤拆开讲:
第一步,确定变量之间的目标相关矩阵R。以风、光、负荷三个变量为例,R是一个3×3对称矩阵,对角线上全是1,非对角元素是历史数据两两之间的相关系数。
第二步,对R做Cholesky分解,得到下三角矩阵L,满足R = LL^T。
第三步,生成d维独立标准正态样本Z,左乘L得到相关正态样本Y = LZ。Y各列之间就具有R所描述的相关性。
第四步,把Y每一列按数值大小排序,记下排列顺序,再用这个排列顺序去重排对应变量的LHS样本。
这一步是整个流程中最容易让人绕晕的地方,我特意画个逻辑链:我们不是直接用Y的值,而是用Y的“排名”去重排LHS样本。为什么?因为Y服从正态分布,它的排名顺序等效于原始变量在各自分布下的秩相关(Spearman秩相关),而不需要真的知道原始变量的联合分布。
Cholesky分解之前有一个坑:R矩阵必须是对称正定的。实际操作中,如果你根据历史数据计算的相关矩阵偶尔不满足正定性,比如变量太多、相关性估计又粗糙,矩阵可能出现负特征值。解决办法有几种:可以做特征值分解把负特征值强制改成很小的正数再重构,或者用修正算法找到最近的正定矩阵。我在自己代码里直接用scipy.linalg.eigh检查最小特征值,小于零就修正,算是比较省事的方案。
3.5 排序重排后的等概率变换
相关正态样本的排列顺序要作用到LHS样本上,具体代码可以这么写:
python复制import numpy as np
from scipy.stats import norm, rankdata
def apply_correlation_ordering(lhs_samples, corr_matrix):
"""
lhs_samples: (n_samples, n_dims),每一列是一个变量的LHS样本
corr_matrix: (n_dims, n_dims) 目标相关矩阵
"""
n_samples, n_dims = lhs_samples.shape
L = np.linalg.cholesky(corr_matrix)
Z = np.random.randn(n_samples, n_dims)
Y = Z @ L.T
ordered = np.zeros_like(lhs_samples)
for j in range(n_dims):
rank = rankdata(Y[:, j], method='ordinal') - 1
ordered[:, j] = lhs_samples[rank, j]
return ordered
这段代码每看一次都要想一下逻辑:LHS样本本身已经覆盖了每一维的区间,排序重做只是改变变量的配对关系,不改变维度内部的分位覆盖。这样,风、光、负荷之间的相关性被注入成套,而每一维自己的边缘分布保持完好。
4. 后向削减算法:从上千个场景里拎出几十个“代表”
4.1 削减的本质与距离度量
样本抽出来了,可能有一千组甚至两千组。优化模型跑不动。所以要把场景数量降到目标值,同时保证削减后的场景集和原始场景集的“信息量”尽量接近。
这里的核心问题是:怎么定义信息量损失最小?
数学上,这对应最优场景削减问题,可以用概率测度之间的某种距离来量化,最常用的是Kantorovich距离,它是在所有可能的“配对方案”里找一个让各地场景配对成本最小的分配。但在直观实现上,这个距离一般转化成场景间的欧几里得距离来做贪心近似。
场景距离怎么定义也值得琢磨。如果你是24小时的时间序列场景,最直接的距离是逐点差的平方和再开方:
$$d(i, j) = \sqrt{\sum_{t=1}^{24}\left(\hat{P}_i(t) - \hat{P}_j(t)\right)^2}$$
注意,风、光、负荷的时间序列量纲不同,风速是米/秒,出力是兆瓦,如果直接拼在一起算距离,量纲大的变量会主导距离计算。先对每个变量做归一化或标准化,再拼接算距离,结果会合理得多。
4.2 后向削减的逐步执行逻辑
后向削减的英文是backward reduction,思路是“逐个减掉最冗余的场景”。完整步骤如下:
步骤1,计算所有场景两两之间的距离矩阵D,D[i][j]代表场景i和场景j之间的距离。
步骤2,对每个场景j,找到它到其他所有场景的最近距离:
$$\delta_j = \min_{i \neq j} d(i, j)$$
步骤3,在所有场景中找最近距离最小的那个场景t,即:
$$t = \arg\min_j \delta_j$$
步骤4,在剩余场景中找到距离t最近的场景s,即t的“最近邻居”。
步骤5,删除场景t,把场景t的概率累加到场景s上。
步骤6,重复步骤2到5,直到场景数量降到目标值。
这段逻辑背后的直觉很直白:每次被删掉的那个场景,是跟它“最像”的场景距离最近的那个,说明它在整个场景空间里最冗余,删掉它对整个概率分布的影响最小。它的概率并到最近邻居上,相当于把那部分质量转移给一个代理场景。
用代码实现如下:
python复制def backward_reduction(scenarios, probabilities, target_num):
"""
scenarios: (n_scenarios, n_dims) 场景矩阵
probabilities: (n_scenarios,) 每个场景的原始概率
target_num: 目标场景数
"""
S = scenarios.copy()
P = probabilities.copy() / probabilities.sum() # 归一化
while len(S) > target_num:
n = len(S)
# 计算距离矩阵
diff = S[:, np.newaxis, :] - S[np.newaxis, :, :]
dist = np.sqrt((diff ** 2).sum(axis=-1))
np.fill_diagonal(dist, np.inf) # 自身距离设为无穷
# 每个场景到最近邻居的距离
nearest_dist = dist.min(axis=1)
# 找到最近距离最小的场景
remove_idx = np.argmin(nearest_dist)
# 找到它的最近邻居
neighbor_idx = np.argmin(dist[remove_idx])
# 合并概率
P[neighbor_idx] += P[remove_idx]
# 删除场景
S = np.delete(S, remove_idx, axis=0)
P = np.delete(P, remove_idx)
return S, P
4.3 删除准则有两种流派
这里有个细节值得多说一句:后向削减的“删除准则”在不同论文里有两种实现。一种是我上面写的“找最近距离最小的场景删掉”,即先找每个场景的最近邻居距离,再找这个距离的最小值;另一种是“找距离和最小的场景删掉”,即计算每个场景到其他所有场景的距离之和,删掉总距离最小的那个。
两种方法在低维场景下结果接近,但高维场景下我实测“最近距离最小”准则的效果更好。原因在于它天然保持场景集的多样性——永远优先删掉那些旁边有一个几乎一模一样的“分身”的场景,而不是删掉“整体离大家都不远”的中心场景。对于保留极端场景,这种多样性保护非常重要。
4.4 削减到什么程度合适
后向削减算法的计算复杂度是O(N^3)左右,N是初始场景数,削减目标的设置在工程上一般是这么把握的:
- 做确定性优化(比如最典型的机组组合),目标场景数一般取10到30组,太多了求解器扛不住。
- 做概率性评估(比如概率潮流、可靠性评估),可以保留50到100组,保留的信息更充分。
- 初始场景数一般取目标场景数的20到50倍。你最终要20组,初始最少抽500组;要50组,初始可以抽1500到2000组。太少初始样本会导致削减后分布失真,太多则后续削减计算量增大明显。
4.5 削减之后一定要检查概率归一化
削减过程中,被删除场景的概率会逐步转移给保留场景。程序跑完以后,保留场景的概率总和理论上仍然是1,但由于浮点数累积误差,实际总和可能变成0.999999或者1.000001。做后续优化时如果概率不归一化,目标函数会出现0.01%级别的误差,在有些敏感约束下会引发边界条件的误判。我在代码里习惯在削减完直接加一步P /= P.sum(),一劳永逸。
5. 用Python完整跑通“抽样—削减—评价”链路
5.1 设定场景维度与分布参数
下面用一个假设的风光储系统算例来完整演示。假设场景维度为24小时,需要同时生成风电、光伏、负荷三类场景,初始采样500组,削减到20组。
分布参数按照工程经验设定:
| 变量 | 分布 | 参数 |
|---|---|---|
| 风速 | 威布尔 | k=2.0, c=8.0 |
| 光伏归一化出力 | 贝塔 | 按日照时段每个小时单独拟合,a=2.5, b=5.0 |
| 负荷 | 正态 | 典型日曲线+时段标准差 |
风机参数设切入风速3m/s、额定风速12m/s、切出风速25m/s,额定容量200MW。光伏装机100MW,负荷基准峰值80MW。
5.2 生成相关LHS样本
python复制import numpy as np
from scipy.stats import weibull_min, beta, norm, rankdata
# --- 1. 定义风机功率曲线函数
def wind_power(v, v_in=3, v_r=12, v_out=25, P_r=200):
p = np.zeros_like(v)
mask1 = (v >= v_in) & (v < v_r)
mask2 = (v >= v_r) & (v <= v_out)
p[mask1] = P_r * (v[mask1] - v_in) / (v_r - v_in)
p[mask2] = P_r
return p
# --- 2. LHS采样(分层区间随机)
def lhs_uniform(n_samples, n_dims):
samples = np.zeros((n_samples, n_dims))
for j in range(n_dims):
u = (np.arange(n_samples) + np.random.uniform(0, 1, n_samples)) / n_samples
np.random.shuffle(u)
samples[:, j] = u
return samples
n_samples = 500
n_dims = 24
# --- 3. 风、光、负荷的LHS分位数映射
wind_u = lhs_uniform(n_samples, n_dims)
wind_v = weibull_min.ppf(wind_u, c=2.0, scale=8.0) # 风速场景
wind_scenarios = wind_power(wind_v) # 转成出力
pv_u = lhs_uniform(n_samples, n_dims)
pv_a = np.linspace(2.5, 2.0, n_dims) # 简化:每小时不同参数
pv_b = np.linspace(5.0, 7.0, n_dims)
pv_scenarios = np.zeros_like(pv_u)
for t in range(n_dims):
pv_scenarios[:, t] = beta.ppf(pv_u[:, t], pv_a[t], pv_b[t]) * 100 # 100MW装机
# 日照时段外的光伏强制为0(这里简化为0~8时、18~24时为0)
pv_scenarios[:, :8] = 0.0
pv_scenarios[:, 18:] = 0.0
load_base = np.array([38, 36, 35, 34, 34, 36, 40, 48, 58, 65, 70, 72,
73, 72, 70, 68, 69, 74, 78, 76, 70, 60, 50, 42])
load_std = np.ones(n_dims) * 5.0
load_u = lhs_uniform(n_samples, n_dims)
load_scenarios = norm.ppf(load_u, loc=load_base, scale=load_std) * 0.5 + load_base
5.3 注入相关性排序
python复制corr_matrix = np.array([
[1.0, -0.4, 0.2], # 风与光伏负相关,风与负荷弱正相关
[-0.4, 1.0, 0.3], # 光与负荷白天正相关
[0.2, 0.3, 1.0]
])
all_scenarios = np.stack([wind_scenarios, pv_scenarios, load_scenarios], axis=1) # (500, 3, 24)
flat_for_corr = all_scenarios.reshape(500, -1) # (500, 72)
# 对每一维做LHS,只是为了演示相关排序:这里直接用生成结果做排序重排
L = np.linalg.cholesky(corr_matrix)
# 注意:这里真正的实现是按三个变量分别排序,为节省篇幅用近似做法,
# 完整代码应分别对 wind/pv/load 三个子矩阵做 rank-correlation inject
实际工程中,更严谨的做法是把风、光、负荷三个变量分别做LHS,再把三块矩阵合并成一个大矩阵,然后对整个大矩阵做基于规则的相关性重排。简化起见,这里用同样的排序逻辑作用于已有的分层样本。
5.4 削减与评价
python复制# 合并三个场景通道为一条场景向量(用于距离计算)
scenario_matrix = np.hstack([wind_scenarios, pv_scenarios, load_scenarios]) # (500, 72)
probs = np.ones(n_samples) / n_samples
reduced_scenarios, reduced_probs = backward_reduction(scenario_matrix, probs, 20)
评价削减质量的标准做法有两个:
一是统计分布对比,看削减后的均值序列和标准差序列与原始500组场景差多少。经验上,均值偏差控制在3%以内算合格,标准差偏差控制在5%以内算合格。
二是KL散度。把两类场景分别聚成直方图或核密度估计,再算KL散度。理论上,削减策略越好,KL散度越小。
6. 和对抗生成网络比,为什么这套传统方案在工程里依然能打
最近两三年,一搜“场景生成”四个字,满屏都是对抗生成网络和扩散模型的论文。我项目里也有同事用生成对抗网络来做风电场景生成,效果确实让人眼前一亮,尤其是生成出来的时间序列,平滑度和形态多样性都挺像真实数据。但我要给一个相对冷静的结论:对抗生成网络在这个问题上不是“更先进的取代者”,而是“另一种适用边界的工具”,在很多工程场景里,用LHS+后向削减这种传统统计学方案,反而更稳妥。
对抗生成网络的核心框架我就不重复了,本质上是让生成器和判别器对抗训练,最终让生成器学会把随机噪声映射成逼真的样本。它在几个方面确实有优势:
- 不需要显式建模联合分布,能直接从历史数据中学到复杂的时间相关性。
- 风光出力的“块状特征”比如云层移动导致光伏出力的连续跌落,传统参数分布很难精确刻画,对抗生成网络可以学到这种模式。
- 生成的样本天然带相关性,不需要额外做Cholesky排序。
但对抗生成网络的问题也很明显。训练需要大量高质量历史数据,小样本场景比如一个新型园区只有一年数据,生成器很容易过拟合,效果甚至不如直接LHS。训练过程对超参数异常敏感,判别器和生成器的平衡一不好,训练直接崩溃。更尴尬的是,即使生成出来的场景看起来合理,你没办法保证它不会生成出超过装机容量的出力,也没法保证尾部分布的物理合理性。对这些“物理约束”,对抗生成网络天生无法原生嵌入,只能靠后处理强行裁剪。
另外还有一个工程上要命的点:对抗生成网络一次生成就是N组场景,但你的优化模型还是只需要20组。所以哪怕用对抗生成网络,最后还是得接过削减这个环节。换句话说,对抗生成网络和削减不是替代关系,而是串联关系——用对抗生成网络生成一千组候选场景,再用后向削减挑出二十组代表。
我自己的取舍标准很简单:
- 如果历史数据充足、研究目标是深挖极端天气事件下的出力形态,对抗生成网络值得优先考虑。
- 如果数据量一般、研究重点是系统规划或运行优化的快速迭代,传统LHS+削减是更稳妥、更可解释的选择。
- 如果是发论文,可以尝试混合方案:对抗生成网络做初始场景桶,再后向削减做代表性场景提取,这个角度其实还能出不少创新点。
7. 实操中容易踩的坑,以及我现在会怎么避免
第一个坑,也是最常见的坑,就是把相关矩阵作用到LHS样本的顺序搞反了。很多人一看Cholesky分解,以为直接把相关正态变量套到原分布上就行,结果出来的样本每维分布完全变了形。正确做法是先保留LHS样本不变,只调整排列顺序。这一点我在第3章已经强调了,但实际写代码时还是特别容易手滑,建议写完以后做一步验证:抽取结果以后重新算一遍风、光、负荷两两相关系数,看看跟目标相关矩阵的偏差是否在0.05以内。
第二个坑,是场景削减时把维度过分拉高。24小时两小时一个节点是12维,把风速、辐照、负荷全堆进去就变成几十维。高维空间里欧几里得距离的可区分性会下降,距离分布越来越集中,削减算法会退化成“随机删”,这是高维诅咒的一个经典表现。我的经验是把风、光、负荷先各自归一化再拼接,甚至可以先对时序数据做PCA降维再算距离,削减结果经常会让人惊喜。
第三个坑,是初始样本量太小。有些同学想省事,抽80组场景削成20组,结果最终场景的均值偏差高达10%以上。这个问题在后面做敏感性分析时会放大——你得到的可能不是“代表20个场景”,而是“20个抽得不太好的样本”。我现在的基本规范是:目标20组,初始至少500组;目标50组,初始至少1000组。计算量多不了多少,但结果质量明显不同。
第四个坑,是削减的时候没有考虑场景的物理量纲差异。风速6到10m/s的波动和负荷50到80MW的波动,数值量级差了十倍,直接算距离会让负荷完全主导整个削减过程。风、光、负荷先各自标准化(减均值除标准差),削出来的场景才能兼顾三个变量的代表性。
第五个坑,是削减结果缺少事后校验。很多文章写完结论就直接用削减后的场景跑优化了,根本不检查削减后的场景集合在关键统计量上还有没有代表性。我的习惯是每次削减完都打印三行报告:均值偏差、标准差偏差、最大单点偏差。如果哪个超了阈值,回去调参数重新来,绝不硬着头皮往下跑。
8. 从一次项目复盘说起
最后再分享一个真实项目里的经验。
我以前做某个区域电网的新能源消纳评估,需要把全年8760小时的风光负荷数据压缩成24个典型日场景。当时选了LHS+后向削减方案,初始抽了2000组场景削到24组。第一次跑完,均值序列对比下来偏差只有1.8%,看起来很漂亮,但等我们拿到调度模型里一算,失负荷概率跟用全年数据直接算差了整整0.6个百分点。后来排查发现,问题出在“削减”阶段:我把所有季节混在一起削减,最终场景里夏季高温高负荷的极端时段全部被“平均”掉了。
这之后我养成了一个习惯:按季节分别生成和削减场景,每个季节单独出6个典型日,三个季节共18个场景,再补几个全年极端工况场景。效果立竿见影,后续跟完整时序模拟的对账,偏差一下子缩小到0.1个百分点以内。
这件事给我的教训是:场景生成和削减不是一个纯粹统计学的技术问题,它最终要服务的是系统分析的目标。做规划,你需要场景覆盖长尾极端情况;做运行优化,你需要场景贴近典型运行方式。同样的数据、同样的模型、同样的算法参数,在不同目标下最佳的削减策略可能完全不同。
所以,拿到LHS和削减算法以后,先别急着抄参数,多问自己一句:我的优化模型到底怕什么样的场景失真?想清楚了再动手,你就能少走很多弯路。
