ARTICLE DETAIL

资讯详情

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

GAN生成风光场景:从原理到Python实现的完整指南

GAN生成风光场景:从原理到Python实现的完整指南 风电、光伏出力的随机性和波动性是电力系统分析里绕不开的老大难问题。做调度优化、可靠性评估、规划容量配置的朋友应该都有感触手头那几条典型日曲线根本不够用真要刻画不确定性得有一整套能代表各种天气状况的场景集。这几年我一直在折腾新能源场景生成从传统统计模型一路做到深度生成模型今天就把基于生成对抗网络的数据驱动场景生成方法连同可复现的Python代码一次讲透。这套方法的核心是训练两个互连的深度神经网络——生成器负责“伪造”场景判别器负责“打假”两者互相博弈最终让生成器学会真实风电/光伏出力的统计规律。相比传统概率模型它不需要预先假定数据服从什么分布能从历史数据里直接把复杂的时空相关性学出来。尤其适合做随机机组组合、配电网规划、储能容量配置这类需要大量高质量场景的工程问题。不管你是刚入门深度学习的研究生还是想给现有优化框架换一套更准的场景输入的在职工程师这篇文章都能给你一套能直接抄作业的方案。1. 场景生成的需求与痛点为什么传统概率模型不够用1.1 电力系统分析里“场景”到底是个什么概念先对齐一下基本概念。电力系统里说的“场景”指的是一组完整的时间序列用来刻画某个时段内风电出力、光伏出力或者负荷的随机变化。比如调度部门要做未来24小时的机组组合不能只用一个点预测值因为实际风光出力绝不会正好落在预测值上。合理做法是生成几百上千条可能的出力曲线每条曲线代表一种可能的“天气剧本”然后把这组场景扔进随机优化模型里求一个在所有可能性下都能接受的调度方案。这里有个关键矛盾场景太少代表性不够最优解容易偏保守或者偏冒险场景太多随机优化模型的计算量会爆炸。所以场景生成和场景削减是一对配套动作生成足够多样的候选场景再用聚类或者快速前向选择压缩成几十个带概率的典型场景。传统做法是先用统计模型生成大量场景再削减现在用GAN可以直接生成分布更真实的原始场景集削减后的质量也更高。1.2 传统概率方法最大的问题是先验假设太强老一代的场景生成方法核心思路都是“先假设分布再拟合参数”。最常见的是给风速假设一个Weibull分布给光伏辐照度假设一个Beta分布然后用历史数据估计形状参数和尺度参数再从这个分布里抽样生成场景。这种思路简单直接但有几个硬伤。第一个问题单一的边缘分布拟合得好不代表时间序列的时序相关性就对。风速序列有明显的自相关性和日周期性——凌晨和午后不一样连续几小时的风速不会突变。直接用独立抽样生成的场景每一时刻的分布是对的但连起来看完全是白噪声中间那些突变根本不符合物理规律。第二个问题多维相关性很难建模。风电场群之间的出力有关联风电和光伏之间也有互补性这种跨变量、跨空间的相关性用传统Copula方法也能做但需要手动选择合适的Copula函数和边缘分布参数估计复杂而且高维情况下很容易过拟合或者欠拟合。一句话总结传统方法是用“人为设定的数学公式”去套“真实物理过程”一旦公式选不对生成的场景就是失真。1.3 数据驱动方法的核心优势是不预设规律GAN这类深度生成模型的思路完全不同。它不关心风速服从什么分布也不关心相关性的函数形式是什么只做一件事从历史数据里学真实分布的映射。给定一堆真实的风电出力序列生成器网络吃进去随机噪声吐出一段逼真的出力曲线判别器网络同时看真实序列和生成序列判断哪个是真哪个是假。两者对抗训练到最后生成器学到的就是真实数据的概率分布而且没有显式假设限制。这样做的好处非常明显第一能自动学习复杂的高阶统计特征包括非高斯分布、长尾特性、时序依赖第二不需要针对每个场站重新设计概率模型换一个数据集重新训练就能用第三生成速度极快训练完成后几毫秒就能生成成千上万条场景比蒙特卡洛抽样结合物理模型模拟快几个数量级。2. 两个互连的深度神经网络GAN如何学习可再生能源场景2.1 生成器与判别器一场造假与打假的博弈标题里提到的“两个互连的深度神经网络”指的就是生成器Generator和判别器Discriminator。很多人第一次接触GAN会被“互连”这个词搞晕其实它们的连接方式非常简单生成器的输出当作判别器的输入。生成器产出一段假的风电出力序列送给判别器打分判别器同时看真实历史序列和生成序列输出一个代表“真实性”的分数。生成器的目标是让这个分数越来越高也就是让判别器分不清真假判别器的目标是准确分辨输入到底来自真实数据还是生成数据。这是个典型的零和博弈。最开始生成器输出的是纯粹的噪声平滑曲线判别器一眼就能识破。但随着训练推进生成器会逐步调整网络权重学着捕捉真实序列的均值、方差、波动规律、自相关性。到后期生成器输出的场景曲线和真实曲线的统计特征会非常接近判别器几乎无法区分。这个过程的数学本质是生成器在最小化生成分布与真实分布之间的某种距离——原始GAN用的是JS散度WGAN换成了Wasserstein距离后者训练更稳定也是我在实际项目中首选的方案。2.2 损失函数与训练流程核心机制是“交替优化”GAN的训练不是一次反向传播能搞定的必须让两个网络交替更新。以最常用的WGAN-GP为例判别器也叫Critic的损失函数包含两部分真实样本的评分均值减去生成样本的评分均值再加上一个梯度惩罚项。梯度惩罚是为了满足Lipschitz约束防止判别器在训练中震荡或者梯度爆炸。生成器的损失更简单生成样本的判别器评分取负。生成器希望判别器给它的输出打高分所以这个值越小说明生成质量越好。实际训练时每更新一次生成器通常先更新五次判别器这样能让判别器保持足够强的鉴别能力反过来给生成器更有效的梯度信号。# 判别器更新真实样本评分高生成样本评分低GP保证稳定 for _ in range(5): z torch.randn(batch_size, noise_dim, devicedevice) fake_batch gen(z).detach() d_loss -torch.mean(disc(real_batch)) torch.mean(disc(fake_batch)) d_loss lambda_gp * gradient_penalty(disc, real_batch, fake_batch, device) d_opt.zero_grad() d_loss.backward() d_opt.step() # 生成器更新让判别器对生成样本打分更高 z torch.randn(batch_size, noise_dim, devicedevice) fake_batch gen(z) g_loss -torch.mean(disc(fake_batch)) g_opt.zero_grad() g_loss.backward() g_opt.step()2.3 为什么选WGAN-GP而不是原始GAN做时序场景生成首选的框架是WGAN-GP而不是原始GAN这是踩过不少坑之后的结论。原始GAN用Sigmoid输出概率配合交叉熵损失在图像生成上表现不错但换到连续值的时间序列上问题非常明显训练很容易不稳定生成器loss和判别器loss交替震荡经常出现模式坍塌——生成器只学会输出几条固定的典型曲线多样性完全不够。WGAN-GP直接去掉Sigmoid层让判别器输出一个无界的分数用Wasserstein距离度量两个分布的差异。这个距离的几何意义更平滑即使生成分布和真实分布没有重叠梯度也不会消失。加了梯度惩罚之后判别器不会变得过于敏感训练过程稳健很多。下表是我在两个方案的对比中总结出的关键差异维度原始GANWGAN-GP判别器输出概率值0-1无界评分损失函数交叉熵Wasserstein距离梯度惩罚训练稳定性较差易震荡稳定收敛性好多样性容易模式坍塌生成场景多样性高对连续值时序数据不推荐推荐3. Python代码实现从数据预处理到模型训练3.1 数据准备归一化和滑窗切分做场景生成第一个要处理的难题是数据格式。风电出力原始数据是一条长序列比如一个风电场一年的15分钟级出力数据有35040个点。直接用整条序列训练不现实标准做法是用滑窗把它切成固定长度的样本每个样本就是一段时序场景。我用24步对应24小时按小时粒度举例切片步长可以设为1实现数据增广。切完窗之后所有样本需要归一化到[0,1]区间。风电出力通常已经以额定容量为基准做了标幺化但依然建议用MinMaxScaler再压一遍因为判别器对输入尺度非常敏感。如果数据里有大量零值时段无风时段不要急着过滤掉这些零值本身就是真实分布的一部分GAN需要学到“什么时候应该输出接近0”。def create_samples(series, seq_len24, stride1): samples [] for i in range(0, len(series) - seq_len 1, stride): samples.append(series[i:iseq_len]) return np.array(samples).reshape(-1, seq_len)3.2 模型定义生成器和判别器的网络结构设计生成器的网络结构不需要太复杂。输入是长度为noise_dim的随机噪声向量经过三个全连接层逐步升维最后输出长度为seq_len的序列。激活函数中间层用ReLU最后一层用Sigmoid保证输出落在[0,1]区间对应归一化后的出力值。判别器与生成器镜像输入长度为seq_len的序列经过两个LeakyReLU全连接层压成一个标量评分。这里特别注意判别器不要用ReLU要用LeakyReLU负斜率设成0.2否则梯度容易死掉。class Generator(nn.Module): def __init__(self, noise_dim100, seq_len24, hidden_dim128): super().__init__() self.net nn.Sequential( nn.Linear(noise_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim * 2), nn.ReLU(), nn.Linear(hidden_dim * 2, seq_len), nn.Sigmoid() ) def forward(self, z): return self.net(z) class Discriminator(nn.Module): def __init__(self, seq_len24, hidden_dim128): super().__init__() self.net nn.Sequential( nn.Linear(seq_len, hidden_dim * 2), nn.LeakyReLU(0.2), nn.Linear(hidden_dim * 2, hidden_dim), nn.LeakyReLU(0.2), nn.Linear(hidden_dim, 1) ) def forward(self, x): return self.net(x)3.3 完整训练流程梯度惩罚的实现细节WGAN-GP里最核心的代码是梯度惩罚项。具体做法是在真实样本和生成样本之间随机插值得到一组中间样本然后让判别器对这组插值样本打分再用torch.autograd.grad计算梯度。梯度惩罚要求这个梯度的范数尽量接近1偏离越大惩罚越重。这个约束保证了判别器满足Lipschitz条件是WGAN-GP训练稳定的关键。def gradient_penalty(disc, real, fake, device): batch_size real.size(0) alpha torch.rand(batch_size, 1).to(device) interpolated alpha * real (1 - alpha) * fake interpolated.requires_grad_(True) out disc(interpolated) grads torch.autograd.grad( outputsout, inputsinterpolated, grad_outputstorch.ones_like(out), create_graphTrue, retain_graphTrue )[0] grad_norm grads.view(batch_size, -1).norm(2, dim1) return ((grad_norm - 1) ** 2).mean()训练参数方面我的建议是生成器和判别器都用Adam优化器学习率设置成1e-4beta1取0.5beta2取0.9。这两个beta值和PyTorch默认的(0.9, 0.999)不一样改低beta1是为了避免训练震荡。每次判别器更新5次、生成器更新1次的频率也需要保持这是WGAN-GP标准做法。训练的终止条件我一般不看固定epoch而是看生成样本的分布指标是否趋稳后面第4章会讲评估方法。3.4 场景生成和应用训练完成后怎么用模型训练完成后生成场景极其简单从标准正态分布采样一批随机噪声一次性送给生成器前向传播一次得到的就是完整场景集。不需要对每个时刻单独预测这比自回归模型快太多。生成的时候可以控制场景数量1000条场景也就是一瞬间的事。gen.eval() with torch.no_grad(): z torch.randn(n_scenarios, noise_dim, devicedevice) scenarios gen(z).cpu().numpy()需要注意生成的场景是归一化值使用前要反归一化还原成真实出力。如果之前用MinMaxScaler就用inverse_transform还原。还原之后如果需要把场景用于机组组合或生产模拟建议额外加一道“物理约束过滤”比如出力不能超过场站装机容量、爬坡率不超过设计限值。GAN学到的是统计规律不保证每条生成的场景都严格满足物理约束这个问题可以用投影方法修正也可以留到场景削减时处理。4. 生成效果怎么评估分布、时序与场景削减4.1 分布拟合度看均值、方差、分位数很多人训练完GAN看一眼loss降下去了就以为搞定了这是大忌。GAN的生成器和判别器loss没有绝对意义不能当质量指标。评估生成场景质量必须把生成场景和真实场景放到一起做统计检验。最基础的做法是逐时段的分布对比。把全部真实样本和全部生成样本按时间点对齐比较每个时刻的均值曲线、方差带、以及5%、50%、95%分位数。画在一张图上如果有明显的系统性偏差——比如生成曲线的均值整体偏低或者波动带比真实数据窄很多——说明生成器还没有学到完整的分布。real_mean real_np.mean(axis0) syn_mean scenarios.mean(axis0) real_q np.percentile(real_np, [5, 50, 95], axis0) syn_q np.percentile(scenarios, [5, 50, 95], axis0)再量化一点可以计算两个分布之间的Wasserstein距离或者KL散度。KL散度在高维空间计算不稳定我一般用一维投影的方式分别算每个时刻的KL散度再取平均作为参考性指标。真正精确的比较用最大均值差异MMD更靠谱它在再生核希尔伯特空间里比较两个分布的嵌入均值实现也不复杂。4.2 时序自相关曲线平滑度与持续性检验分布拟合得再像也不代表时序动态特征正确。真实风电出力有强持续性——这一小时出力高下一小时大概率也高真实光伏出力有严格的光照时段限制——夜里必须接近零中午呈钟形曲线。生成器如果只学会分布没学会时序生成的场景就会像白噪声一样剧烈抖动或者光照时段错乱。检查时序动态特性最直观的工具是自相关函数ACF。对每条序列求自相关系数再取平均对比真实序列和生成序列的ACF曲线。如果两条曲线趋势一致——真实序列滞后1小时的相关系数0.9生成序列滞后1小时也接近0.9——说明生成器掌握了持续性特征。如果生成序列的ACF衰减明显快于真实序列说明生成场景的时序波动过于剧烈。def acf_curve(x, max_lag24): x x - x.mean() denom np.sum(x * x) res [] for lag in range(max_lag 1): if lag 0: res.append(1.0) else: res.append(np.sum(x[:-lag] * x[lag:]) / denom) return np.array(res)4.3 场景削减从一千条场景到十个代表性场景生成场景的最终归宿是作为随机优化的输入。1000条场景直接扔进机组组合模型计算量太大所以通常要做场景削减。最常用的是K-means聚类加概率赋值把生成场景聚成K类每一类的聚类中心作为典型场景该类样本数量占总样本数的比例作为场景概率。K的选择没有绝对标准一般看实际问题对分辨率的要求。储能在日内调度的场景K取5-10就够年度规划可能需要20-30。削减完以后别忘了验证一下典型场景集的统计特性还保真——计算削减后场景集的期望曲线和方差与原1000条场景集对比偏差应该在3%以内。from sklearn.cluster import KMeans kmeans KMeans(n_clusters10, random_state0, n_init20).fit(scenarios) typical_scenarios kmeans.cluster_centers_ # 代表场景 probabilities np.bincount(kmeans.labels_, minlength10) / len(kmeans.labels_) # 场景概率5. 全程踩坑记录训练稳定性、模式坍塌与调参心得5.1 模式坍塌的表现与对策GAN做时序生成最常遇到的问题就是模式坍塌具体表现是生成的1000条场景里仔细看只有五六种不同的“模板”同一模板内的曲线几乎完全一样只有细微噪声差异。从均值曲线上看可能无异常但看多样性就露馅了。解决办法按优先级排序第一换成WGAN-GP框架第二把噪声维度调大至少80-128维给生成器足够的“表达空间”第三适当降低学习率到5e-5训练不稳经常是学习率过大导致的第四引入minibatch discrimination让判别器同时看整批样本如果整批都长得一样就判假。最后这个手段我很少用前三种已经能解决绝大多数情况。5.2 训练不稳定的调试loss记下来但别只看loss训练WGAN时我习惯每50个epoch打印一次生成器和判别器的loss数值。正常的WGAN训练中生成器loss是缓慢下降趋势判别器loss在零附近震荡偏正。如果判别器loss持续大幅为正且快速攀升说明判别器太强生成器的更新跟不上如果生成器loss剧烈震动毫无趋势说明学习率太大或者梯度惩罚系数不到。梯度惩罚系数lambda_gp默认是10一般不用动。但如果你发现自己数据量很小比如少于一万条样本可以把lambda_gp降到5降低约束强度容错更高。另外batch size不建议太小32起步64更稳。batch太小判别器对整批的统计估计噪声会很大。我的经验是batch size直接决定了训练上限追求极致效果就先调batch。5.3 数据相关的小技巧零值、缺失值和归一化新能源出力数据有几个特有的坑。第一个是零值比例高夜间风电可能连续十几个小时接近零光伏更是夜里全零。如果数据里零值占比超过30%建议在预处理时把24小时样本按“日类型”分开比如分成晴日、多云日、阴雨日三种每个类型单独训练一个条件生成器。这样生成场景的物理语义更清晰否则模型会把晴日和阴雨日混在一起平滑掉。第二个是缺失值处理。气象原因导致的通讯中断、停机检修在历史数据里很常见。不要用线性插值去填长时间缺失段会引入不存在的坡段特征。建议缺失超过6个连续时段就直接丢弃这段样本缺失少的用前后均值填充。第三是归一化别用StandardScaler。风电出力归一化后的目标区间必须是[0,1]生成器最后用Sigmoid输出才匹配。StandardScaler会把数据变成均值为0、方差为1的标准正态分布大部分值落在[-3,3]和Sigmoid输出空间不一致训练难度大幅增加。5.4 一条好用的扩展路径条件生成与多变量场景这篇文章讲的是最基础的非条件GAN场景生成实际工程里更常用的是条件GANcGAN。比如调度模型不仅需要风电出力场景还需要同时给出温度和负荷的场景或者预测明天是晴天希望生成的光伏场景集中在高辐照区间。这时候只要在生成器和判别器的输入里拼接一个条件向量就能控制生成场景的属性。我在一个风电场群联合出力场景生成项目里试过cGAN条件是“季节天气类型”效果比全局一个GAN好很多生成场景的日均发电量分布和真实数据几乎重合。实现上逻辑很简单生成器的输入改成[noise, condition]拼接判别器的输入改成[sequence, condition]拼接训练时把每条样本对应的条件一起喂进去即可。如果你是做实际工程强烈建议直接上cGAN非条件GAN当作入门练手就好。还有一个我自己测试下来很稳的小技巧训练完成后不要着急用生成器出结果先用真实样本把判别器单独训练50步再看判别器给生成场景的平均评分。如果1000条真实样本和1000条生成样本的判别器平均分差距在5%以内说明生成分布已经相当接近差距超过20%那就还需要继续训练或者调网络结构。这个方法比画图判断更快适合在训练过程中随时监测。扩展方面除了cGAN时间序列GANTimeGAN和扩散模型Diffusion Model也越来越多人用在新能源场景生成上。TimeGAN加入了监督损失和嵌入网络对时序动态的刻画更精细但训练复杂度更高扩散模型最近在图像生成上大放异彩放在时序场景生成上也有不少新论文生成质量普遍被评价高于GAN但采样速度慢、计算成本高。从落地角度讲目前工业项目里GAN仍是性价比最高的选择尤其是WGAN-GP加cGAN这套组合稳定性、生成质量、计算效率三者平衡得很好。
返回列表