1. 从一张城市热力图说起:为什么瞬时微气候场生成值得死磕
如果你在城市规划、建筑环境模拟或者数字孪生方向待过一阵子,大概率见过这样的场景:一张城市街区的温度分布图,红一块蓝一块,看起来挺唬人,但仔细一问——这是某个时刻的稳态结果,或者干脆是几小时平均下来的。真正做微气候分析的人都知道,城市里风的湍流、太阳辐射的遮挡、地表材质的蓄热差异,这些东西在几分钟甚至几秒内就能发生剧烈变化。你拿一个平均场去做行人热舒适评估,误差可能大到离谱。
这就是三维多变量瞬时城市微气候场生成这个方向的核心痛点。所谓“多变量”,通常指温度、风速矢量、相对湿度、平均辐射温度这几个关键量;“瞬时”意味着你要捕捉的是某个具体时刻的场分布,而不是时间平均;“三维”则要求你在垂直方向上也有解析度,毕竟城市冠层里不同高度的风温结构完全不是一回事。
传统做法无非两条路:一是计算流体力学(CFD)仿真,精度高但算一次动辄几小时到几天,根本没法做实时交互或者大规模场景遍历;二是降阶模型或者插值方法,快是快,但物理一致性差,多变量之间的耦合关系经常被破坏。Conditional Flow Matching(条件流匹配)这个生成式框架的出现,给了一个挺有意思的第三条路——它不像GAN那样训练不稳定,也不像扩散模型那样采样步数多到让人抓狂,而是直接学习从简单先验分布到目标数据分布的连续变换流,条件信息(比如建筑几何、来流风向、太阳位置)作为控制信号注入,一次前向传播就能吐出整个三维场。
这篇文章我打算把我在这个方向上踩过的坑、试过的方案、以及最终跑通的一套流程完整拆开讲。适合谁看?如果你正在做城市微气候快速预测、建筑环境数字孪生、或者对生成式模型在物理场上的应用感兴趣,那接下来的内容应该能帮你省下不少试错时间。我会从整体设计思路讲到具体实现细节,包括条件编码怎么设计、流匹配的损失函数怎么推导、三维场的表示方式怎么选、以及训练过程中那些文档里不会写的调参经验。
2. 整体方案设计:为什么选流匹配而不是扩散或GAN
2.1 生成式模型选型的核心考量
做物理场生成,第一件事不是急着搭网络,而是想清楚你要的到底是什么。城市微气候场有几个很别扭的特性:空间维度高(一个64×64×32的网格就是十几万个点)、多变量耦合强(温度场和风场之间通过平流和湍流扩散紧密关联)、条件依赖复杂(建筑布局一变,整个流场结构全变)。这三点决定了你不能随便拿个图像生成的模型来套。
我最初试过条件GAN,判别器用PatchGAN的结构。结果训练了三天,模式崩塌得厉害——生成的风场在某些区域反复出现同样的涡结构,温度场则倾向于输出模糊的平均值。后来分析原因,GAN的对抗训练在高维物理场上本身就很难平衡,判别器太强导致生成器梯度消失,太弱又起不到约束作用。而且GAN对多变量联合分布的建模能力有限,它容易学到边缘分布但抓不住变量间的相关性。
扩散模型我也试过。DDPM那套东西在图像上确实work,但放到三维场上有两个致命问题:一是采样步数,就算用DDIM加速,50步采样生成一个场也要好几秒,做实时交互根本不够;二是扩散过程对物理场的低频结构破坏比较严重,噪声调度没调好的话,生成的风场会出现不自然的棋盘格伪影。
Conditional Flow Matching吸引我的地方在于它的数学形式特别干净。它的核心思想是:不去学一个从噪声到数据的随机过程,而是直接学一个确定性的人生轨迹——从先验分布(比如标准高斯)中的一个点,沿着一条连续可微的路径,流到目标数据分布中的对应点。条件信息通过条件向量场注入,控制流的走向。训练目标就是让网络预测的向量场和真实路径上的切向量对齐,损失函数是简单的均方误差,没有对抗、没有噪声调度,训练稳定性好很多。
更关键的是,流匹配的采样是常微分方程求解,用欧拉法或者龙格-库塔法,步数可以压到10步以内,甚至5步就能出不错的结果。这对实时应用来说太重要了。
2.2 条件注入方式的设计取舍
条件信息怎么进网络,直接决定了模型能不能学会“建筑布局变了,流场该怎么变”。我试过三种方案:
第一种是早期融合,把建筑几何的体素表示和噪声场在输入层直接拼接。简单粗暴,但效果一般,因为几何信息和流场信息的语义层次差太多,网络在浅层很难建立有效关联。
第二种是交叉注意力,把几何特征作为Key和Value,流场特征作为Query,让模型自己学该关注哪些建筑。这个方案效果最好,但计算量也最大,尤其是三维场做全局注意力,显存直接爆炸。后来我改成了局部窗口注意力,只在建筑表面附近的区域做交叉注意力,远处用普通卷积,显存降下来了,效果也没怎么掉。
第三种是自适应归一化,类似StyleGAN里那个AdaIN,把条件编码成缩放和平移系数,作用在中间层的特征上。这个方案最轻量,但表达能力有限,适合条件维度不高的情况。我最后用的是交叉注意力加自适应归一化的混合方案——浅层用AdaIN做全局调制,深层在关键区域用交叉注意力做精细控制。
注意:条件编码的维度不要一上来就搞很大。我见过有人把建筑几何编码成1024维的向量,结果训练时条件信号被淹没在噪声里。一般256维左右就够了,关键是要让条件编码和流场特征在同一个语义空间里。
2.3 三维场的表示方式选择
三维场怎么存、怎么算,这个看似基础的问题其实很影响最终效果。我试过稠密体素,就是老老实实一个64×64×32的网格,每个点存温度、风速三个分量、湿度、辐射温度,一共6个通道。优点是实现简单,卷积操作直接往上套;缺点是显存占用大,而且城市微气候场里大量区域是空气,稠密表示浪费了很多计算。
后来换成稀疏八叉树,只在有建筑或者场梯度大的地方细化。这个方案在内存上确实省了不少,但实现复杂度陡增,而且流匹配的ODE求解在非均匀网格上要做插值,引入额外误差。最终我还是回到了稠密体素,但做了两个优化:一是用混合精度训练,把显存占用压下来;二是在垂直方向做非均匀采样,近地面密一点,高空稀一点,因为城市冠层的主要变化都集中在低层。
3. 核心细节拆解:流匹配的数学原理与网络架构
3.1 条件流匹配的损失函数到底在优化什么
流匹配的数学形式看起来简单,但里面有几个关键点如果理解偏了,训练出来的模型就是另一回事了。我先用最直白的话把原理捋一遍。
假设你有一个先验分布 $p_0$(标准高斯),一个数据分布 $p_1$(真实微气候场),还有一条从 $p_0$ 到 $p_1$ 的概率路径 $p_t$。流匹配的目标是学一个时变向量场$v_\theta(x, t, c)$,其中 $c$ 是条件信息,使得沿着这个向量场积分,能把先验样本推到数据样本上。
训练的时候,你不需要知道真实的概率路径,只需要构造条件概率路径。最常用的是最优传输路径,也就是让先验样本和目标样本之间走直线:
$$x_t = (1-t) x_0 + t x_1$$
其中 $x_0 \sim p_0$,$x_1 \sim p_1$。这条路径上的切向量就是 $x_1 - x_0$,是一个常数。所以损失函数就变成:
$$\mathcal{L} = \mathbb{E}{t, x_0, x_1} | v\theta(x_t, t, c) - (x_1 - x_0) |^2$$
你看,没有对抗、没有噪声预测、没有变分下界,就是一个回归问题。这也是流匹配训练稳定的根本原因。
但这里有个坑:条件信息 $c$ 的注入方式会影响路径的构造。如果条件只是简单地拼在输入里,那模型学到的其实是边缘分布 $p(x_1|c)$ 的流,变量间的耦合关系可能被忽略。我的做法是在构造训练对的时候,对同一条件采样多个目标场,让模型看到条件相同但实现不同的样本,这样它才能学到条件分布而不是确定性映射。
3.2 网络架构的逐层设计逻辑
我最终用的骨干网络是一个三维U-Net变体,编码器四层下采样,解码器四层上采样,中间加了一个时间嵌入模块和条件注入模块。
时间 $t$ 的嵌入用的是正弦位置编码加两层MLP,输出维度128。这个设计是从扩散模型那边借过来的,实测比直接输入标量 $t$ 效果好很多,因为正弦编码能让网络更容易区分不同的时间尺度。
条件注入分两级:全局条件(来流风向、太阳高度角、季节类型)通过AdaIN作用在每个残差块上;空间条件(建筑几何、地表材质分布)通过交叉注意力注入。交叉注意力的Key和Value来自建筑几何编码器,Query来自当前层的特征图。为了控制计算量,我只在编码器的最后两层和解码器的前两层做交叉注意力,浅层和深层都用AdaIN。
实操心得:交叉注意力的头数不要设太多。我一开始用了8个头,结果发现不同头学到的注意力图几乎一样,纯属浪费。后来降到4个头,效果没变,显存省了将近三分之一。三维场的空间相关性很强,不需要那么多独立的注意力子空间。
3.3 多变量耦合的显式建模技巧
城市微气候场里,温度和风场是强耦合的——风把热量从高温区带到低温区,温度梯度又反过来影响风场。如果网络只是把6个通道拼在一起做卷积,它可能学到一些相关性,但未必能抓住物理上的耦合机制。
我的做法是在U-Net的瓶颈层加了一个变量交互模块。具体来说,把6个通道分成三组:热力学变量(温度、湿度、辐射温度)、动力学变量(风速三个分量)、以及一个辅助的湍流强度通道。每组先过一个独立的卷积块提取特征,然后通过一个跨组注意力让热力学特征和动力学特征互相查询。这个模块参数量不大,但对生成场的物理一致性提升很明显——消融实验里去掉它之后,温度和风场的相关系数从0.87掉到了0.72。
另外,边界条件的处理也很关键。城市微气候场的地面是壁面,顶部是开放边界,侧面是来流和出流。我在损失函数里加了一个边界惩罚项,对地面附近的风速做无滑移约束,对顶部做零梯度约束。这个惩罚项的权重不能太大,否则会压制生成场的多样性,我一般设在0.01到0.05之间。
4. 实操全流程:从数据准备到模型部署
4.1 训练数据的生成与预处理
流匹配是监督学习,你需要成对的(条件,目标场)数据。条件包括建筑几何、来流风向风速、太阳位置、地表材质;目标场就是CFD算出来的瞬时三维场。
数据来源无非两种:高保真CFD仿真或者风洞实验测量。风洞数据精度高但成本极高,而且很难覆盖多种建筑布局。我主要用CFD,具体是大涡模拟(LES),因为瞬时场需要解析湍流结构,RANS的时均结果不够用。
仿真设置上,计算域取10倍建筑高度的水平范围,垂直方向取5倍建筑高度。网格在建筑表面附近加密到0.5米,远场逐渐放粗到5米。时间步长取0.1秒,总共模拟200秒,取后100秒的瞬时场作为数据。每个案例大概产生1000个瞬时场,我总共跑了200个案例,覆盖不同的建筑密度、来流风向和太阳高度角。
预处理有几个关键步骤:归一化、坐标对齐、数据增强。归一化我用的不是简单的min-max,而是按变量分别做零均值单位方差标准化,因为温度的量级和风速差了好几个数量级。坐标对齐是把所有案例的建筑群中心移到计算域中心,避免模型学到位置偏差。数据增强主要是水平旋转和镜像翻转,因为城市微气候场在水平方向上没有绝对的方向偏好。
注意:数据增强不要用垂直翻转。城市冠层里近地面和高空的结构完全不同,垂直翻转会引入物理上不合理的样本。
4.2 训练配置与关键参数
训练用的是PyTorch,混合精度,单卡A100 80G。批量大小设成8,因为每个样本是64×64×32×6的体素,再大就爆显存了。优化器用AdamW,学习率初始3e-4,余弦退火到1e-6,权重衰减1e-5。
流匹配的采样时间 $t$ 我从均匀分布里采,但后来发现对 $t$ 做非均匀采样效果更好——在 $t$ 接近0和1的地方多采一些,因为路径两端的变化更剧烈。具体做法是用Beta分布,参数设成(2, 2),这样中间区域的采样密度会低一些。
训练轮数大概500个epoch,每个epoch跑完整个数据集。损失曲线在前100个epoch下降很快,之后进入平台期,300个epoch之后基本收敛。我一般会在验证损失连续20个epoch不下降时停止训练,防止过拟合。
学习率预热很重要。前5个epoch用线性预热从1e-6升到3e-4,否则初期梯度太大会把网络带偏。这个技巧在扩散模型里很常见,流匹配同样适用。
4.3 采样加速与实时推理
训练完之后,采样就是解一个ODE:
$$\frac{dx}{dt} = v_\theta(x, t, c)$$
从 $t=0$ 的先验样本积分到 $t=1$。最简单的欧拉法:
$$x_{t+\Delta t} = x_t + v_\theta(x_t, t, c) \cdot \Delta t$$
步数取10步,$\Delta t = 0.1$。实测下来,10步欧拉法的生成质量已经和100步差不了多少,因为流匹配的路径接近直线,曲率很小。
如果要更快,可以用中点法或者四阶龙格-库塔,但步数少的时候提升有限。我试过5步中点法,生成一个场大概0.3秒,比10步欧拉法快一倍,质量下降在可接受范围内。
实时推理的时候,我把模型导出成TorchScript,然后用TensorRT做推理优化。FP16精度下,一个64×64×32的场生成时间大概0.15秒,基本能满足交互式应用的需求。
实操心得:采样步数不是越多越好。我试过50步,生成质量反而比10步差了一点,因为累积的数值误差开始显现。流匹配的ODE求解有个“甜点区”,一般在8到15步之间。
5. 常见问题与排查技巧实录
5.1 生成场出现棋盘格伪影怎么办
这是三维卷积网络最常见的问题之一,根源通常是转置卷积的步长和卷积核大小不匹配。我在解码器里一开始用了步长2、核大小2的转置卷积,结果生成的风场在垂直方向出现了明显的周期性条纹。
解决办法有两个:一是把转置卷积换成最近邻上采样加普通卷积,这样不会引入棋盘格;二是用步长2、核大小4的转置卷积,让卷积核覆盖范围重叠。我最后用的是第一种方案,虽然参数量稍微多了一点,但生成质量稳定很多。
另外,归一化层的位置也有影响。如果把BatchNorm放在转置卷积之后,统计量的计算会受到棋盘格的影响,进一步放大伪影。我改成GroupNorm之后,这个问题基本消失了。
5.2 多变量之间的物理一致性怎么保证
生成场里温度和风场不匹配,是另一个高频问题。比如风从建筑间隙吹过,温度场却没有相应的平流冷却特征。这通常是因为损失函数只监督了每个变量的边缘分布,没有显式约束变量间的耦合。
我的做法是在损失函数里加一个物理一致性项。具体来说,对生成场计算温度平流项 $u \cdot \nabla T$,然后和真实场的平流项做对比。这个项的权重不用太大,0.1左右就能起到明显的约束作用。另外,在验证指标里加入变量间相关系数,训练时监控这个指标,如果掉得厉害就说明耦合出了问题。
还有一个技巧是在条件里显式加入物理参数,比如理查森数或者稳定度参数。这些参数编码了热力学和动力学之间的耦合强度,模型看到它们之后更容易学到正确的耦合关系。
5.3 训练损失震荡不收敛的排查思路
流匹配虽然稳定,但也不是完全不会出问题。我遇到过几次损失震荡的情况,排查下来主要有三个原因:
第一是学习率太大。流匹配的损失尺度比扩散模型小,学习率设成1e-3就容易震荡。降到3e-4之后稳定了很多。
第二是条件编码的方差太大。如果建筑几何编码的数值范围没有归一化好,条件信号会淹没流场信号。解决办法是对条件编码也做标准化,让它的均值和方差在一个合理范围内。
第三是批量大小太小。流匹配的梯度估计方差比想象中大,批量大小低于4的时候训练会很不稳定。如果显存不够,可以用梯度累积来模拟大批量。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 损失震荡不下降 | 学习率过大 | 打印梯度范数 | 降低学习率到3e-4 |
| 生成场模糊 | 训练不充分 | 检查验证损失 | 增加训练轮数 |
| 变量间相关性差 | 耦合建模不足 | 计算相关系数 | 加物理一致性损失 |
| 棋盘格伪影 | 转置卷积配置不当 | 可视化中间特征 | 换最近邻上采样 |
| 采样速度慢 | ODE步数过多 | 计时不同步数 | 降到10步以内 |
5.4 跨场景泛化的避坑经验
训练好的模型换一个城市布局还能不能用,这是实际部署时最关心的问题。我的经验是:如果新场景的建筑密度和高度分布与训练集差异不大,直接推理问题不大;如果差异很大,至少要做少量微调。
微调的时候不要全网络一起调,只调条件注入模块和解码器的最后两层就够了。这样既能适应新场景,又不会破坏已经学好的流场生成能力。微调数据量不用很大,几十个案例就能看到明显提升。
另外,在条件编码里加入场景的统计特征(比如平均建筑高度、建筑密度、天空视角因子)能显著提升泛化能力。这些特征计算成本很低,但对模型理解场景差异帮助很大。
6. 写在最后的一些个人体会
这个方向我断断续续做了大半年,从最开始拿扩散模型硬套,到后来转向流匹配,中间踩的坑比预想的多得多。最大的体会是:生成式模型在物理场上的应用,难点不在模型本身,而在怎么把物理约束有效地注入到学习框架里。流匹配提供了一个很灵活的骨架,条件注入、损失设计、网络架构,每个环节都可以针对物理特性做定制。
另一个感受是,数据质量比模型容量重要。我试过用同样的网络结构,在低质量CFD数据上训练,生成场怎么看都不对劲;换成高质量LES数据之后,即使网络小一点,效果也明显更好。如果条件允许,在数据生成上多花点时间,比在模型上调参划算得多。
最后分享一个小技巧:在训练初期用较小的空间分辨率(比如32×32×16),等模型基本收敛后再切换到全分辨率微调。这样能省下大量训练时间,而且最终效果和从头训练全分辨率差不多。我现在的标准流程都是先低分辨率预训练100个epoch,再高分辨率微调200个epoch,总时间比直接高分辨率训练少了将近一半。