1. 为什么有了扩散模型,还要折腾Flow Matching
用过扩散模型做生成的人,大概率经历过这样一种分裂感:训练的时候明明是每一步都在和噪声做斗争,采样的时候又要靠一套单独设计的采样器才能把样本“捞”出来,中间还要调一堆步数和噪声计划表。这种训练与采样不对称的别扭感,正是Flow Matching出现时最打动我的地方。Flow Matching(FM)是一种把生成问题建模成“从噪声分布到数据分布的连续路径”的框架,它不去预测每一步的噪声,而是直接学习一个驱动概率流动的向量场,训练完以后沿着这个向量场走一条常微分方程(ODE)的轨迹就能产出样本。如果你已经在扩散模型上投入过不少时间,理解FM会非常快,因为本质上它是把扩散模型的训练目标和采样目标统一到了一起;如果你从零开始,FM反而是比传统扩散模型更直白的一个切入点。这篇内容我会从原理到实现,再到采样和踩坑,尽量把这条链路上的每个环节都讲透。
1.1 扩散模型的“两套逻辑”是怎么来的
扩散模型的基本想法是逐步加噪把数据变成噪声,然后学习反向去噪。训练时我们用预定义好的噪声调度表,把一张干净图片变成带噪声的图片,然后让模型预测噪声;采样时我们从随机噪声出发,通过迭代去噪还原图片。问题在于,训练用的“前向加噪过程”和采样用的“反向去噪过程”并不是同一个过程的严格逆过程,因为每一步都引入了随机性。这种不对称导致三个后果:一是采样步数少了质量掉得很快;二是训练目标和最终评估指标之间存在一个比较隐蔽的gap;三是每套扩散模型都会绑定一套自己的采样器设置,换调度表又要重新调参。
FM就是想把这些东西收进同一个表述里,不再有加噪和去噪两套话语体系,只有“从初始分布流到目标分布”这一件事。我当时第一次看到FM的损失函数时,第一反应是“就这?这不就是个回归吗?”但恰恰是这种简短,把前面说的所有别扭都消解掉了。对于已经熟悉扩散模型的读者,最需要做的改变不是学一堆新东西,而是忘掉几个旧习惯,比如时间步上的权重设计、采样末端的去噪偏移矫正,这些在FM里都属于“多余动作”。
1.2 用“水流”来理解FM在做什么
把数据分布想象成一条河道里的水位,梯度下降的过程就是河水流向低处。FM里的“流动”也是类似的意思:我们有一个初始分布p0,比如标准高斯噪声;有一个最终分布p1,就是我们要生成的数据分布。中间每一个时间点t都对应一个过渡分布pt,如果把时间看成连续的,pt就是在时间里慢慢“流”过去的。FM要学习的是一个向量场ut(x),它告诉你在位置x、时间t这一点,粒子下一秒该往哪个方向移动。
哪里有概率,哪里就有流动的方向和速度;模型学会了整个场,就学会了一种从噪声“运输”到数据的确定性路径。这个思路在物理上很自然,但当变成算法时,它先要回答一个关键问题:向量场到底长什么样?我们不知道答案,但我们可以自己设计一条路径、自己定义想要的流动过程,然后让模型去模仿。这正是FM和扩散模型最大的哲学差异:扩散模型把路径看作是加噪过程的产物,路径是“发现”的;FM把路径看作是设计对象,路径是“构造”的。构造的自由度给了我们很大的调参空间,也带来了新的权衡问题。
2. 核心机制拆解:概率路径、向量场和条件流匹配
2.1 概率路径与向量场的基本关系
给定一条平滑的粒子运动轨迹,每个粒子有初始位置x0和终点x1,在t时刻的位置可以记为xt = path(x0, x1, t)。这个运动过程会推动初始分布逐渐变成目标分布。向量场ut是每个粒子的瞬时速度:ut(xt) = d xt / dt。对于一条确定的路径,解析地求导数就能得到整个空间的速度场。然后流匹配的原始目标是想让模型学到的速度场vt(x, t)逼近这个真正的ut(x),用的损失是均方误差。
问题在于,直接计算ut是很昂贵的:你需要对整条路径上的所有粒子求期望,而且粒子之间还有复杂的耦合关系。举个例子,如果目标分布是一个双峰分布,从标准高斯出发的两批粒子可能在路径中途交叉,交叉区域的向量场方向完全相反,要精确表达这种场需要非常强的模型容量和非常大的训练开销。这意味着原始形式的流匹配目标在实际中几乎没法训练。所以FM在这个方向上的核心贡献不是提出“匹配流”这个概念,而是给出了一个可计算的替代目标,让原本不可行的训练变成了一个普通的监督回归任务。
2.2 条件流匹配:从全局场退到单样本条件场
既然全局向量场难算,那就退一步,给每个样本x1单独定义一个“条件”路径。最常用的线性高斯路径是xt = (1 - t) * x0 + t * x1,其中x0是采样的高斯噪声,x1是训练样本。这条路径从x0出发、x1结束,它的速度恒定为x1 - x0。于是条件流匹配(CFM)的损失就变成了一个非常简单的监督回归:给定xt和时间t,模型要预测x1 - x0这个向量。
扩散模型和FM的对应关系可以整理成下面这张表,方便那些有扩散模型基础的人快速迁移:
| 维度 | 扩散模型 | Flow Matching |
|---|---|---|
| 前向过程 | 按噪声调度表逐步加噪 | 在初始噪声和数据之间做插值路径 |
| 训练目标 | 预测噪声 | 预测位移向量x1 - x0 |
| 采样方式 | 迭代去噪,带随机重采样 | 沿ODE确定性积分 |
| 时间含义 | t=0是干净数据,t=1是纯噪声 | t=0是起点噪声,t=1是目标数据 |
| 后续调参点 | 噪声调度表、采样器、CFG等 | 路径定义、时间分布、ODE求解器 |
看起来只是换了个回归目标,但推理逻辑完全不同。扩散模型里t=0对应干净数据、t=1对应纯噪声,采样时要设计噪声调度;FM里t=0是起点噪声、t=1是终点数据,整个路径是确定性的线性运动,中途没有任何随机重采样,训练和采样终于在同一套坐标下对齐。
2.3 为什么线性路径不一定是最优选择
用线性插值的好处是简单、稳定、速度可解析计算。但它不是唯一选择,更不是最优选择。因为不同的路径会带来不同的概率密度演变,线性路径往往让粒子在早期运动得太快、后期又太慢,导致模型在t接近0或1时需要处理一些比较极端的向量。这一点在训练高维数据时会变成噪声大、收敛慢的隐患。
另一类常见选择是带噪声扰动的路径:xt = (1 - t) * x0 + t * x1 + sigma_t * z,其中z是额外的高斯扰动。这样在端点处不至于让路径坍缩到某个确定点,密度始终是宽的,模型拟合起来更平滑。sigma_t的选择直接影响训练难度和采样质量。后面我会专门用一节来讲sigma_t这个参数的实战取舍,因为这是我自己训练FM时踩过的最大的一类坑:调不好,样本要么糊成一片,要么出现肉眼可见的散点伪影。
3. 亲手实现一个Flow Matching:数据、网络与损失
3.1 用二维高斯混合做“试验田”
我建议第一次接触FM的人先别急着上高维图像,先用二维高斯混合数据把整个链路跑通。这个数据能直观看到粒子流动的过程,验证损失在下降,也能马上发现向量场学歪了在哪里。我们构造几个高斯簇作为目标分布,初始分布用标准高斯。每次训练取一个batch,对每个样本x1,采样x0,按t的均匀分布采样时间,算线性路径xt和速度target = x1 - x0;把xt和时间t的编码喂给一个网络,让网络输出预测速度,计算MSE损失并反向传播。
核心训练循环用伪代码写出来大概是这样:
for x1 in data_batch: x0 = randn_like(x1) # 采样初始噪声 t = uniform(0, 1) # 均匀采样时间 xt = (1 - t) * x0 + t * x1 # 线性插值路径 target = x1 - x0 # 位移向量 pred = model(xt, time_encode(t)) loss = mse(pred, target) loss.backward()看起来和训练一个最简单的回归网络没有区别,这就是FM最大的吸引力——它把一个生成模型的训练简化到了回归的复杂度。我最初跑通这个实验的时候,把生成结果画出来,看到散点从一团高斯慢慢拉长、分开、汇聚成几个簇,整个过程比第一次调通GAN还要兴奋,因为你能非常清楚地看到模型在“学”一条流动路径,而不是在黑盒里变魔术。
3.2 网络结构怎么改:把“噪声预测头”换成“速度预测头”
如果你之前写过扩散模型,只需要把输出从“预测噪声”改成“预测速度”,其他结构基本可以保留。我自己用的网络是一个带残差连接的多层感知机,输入维度是数据维度加时间维度,输出维度等于数据维度;时间用正弦位置编码编码成向量,再和位置拼在一起。不要小看这个结构选择:输入尺度对训练影响很大,如果xt分布在[-2,2]而时间t在[0,1],直接拼接会导致时间特征被淹没,需要用可学习的特征层或把t编码后做层归一化再拼进去。
更细一点说,在二维实验里我建议至少观察两点:一是预测速度的分布是否和真实速度分布大致重合,二是t接近0和1时的损失曲线是否呈对称的双峰。这两个观察能帮你快速定位“时间注入不足”和“端点路径过于陡峭”两类常见问题。不要一上来就套很深的U-Net或Transformer结构,先用简单的网络把数据流跑通,再加容量,这样每一步都能明确归因。
3.3 训练目标、学习率和batch size的实践值
我一般用AdamW做优化器,学习率从1e-3起步,配合warmup和余弦衰减;二维实验里batch size取512到1024都行,训练几千步就能看到很漂亮的速度场。没有太多花哨的正则,但有一个细节很关键:时间t一定要在[0,1]内,且最好用均匀采样。为什么均匀采样就够了?因为CFM是对时间区间做无偏期望,均匀采样保证每个时间点的向量场都被覆盖到。
不过实际中t接近0和1时损失会有波动,因为那附近的xt几乎等于x0或x1,速度项仍然很大。可以加一个简单的时间加权或者限制t的范围,比如限制在[0.01,0.99],能稳定训练又不会影响采样质量。千万不要用固定的时间网格来训练,比如只采样t=0.1、0.5、0.9这几个点。固定网格会严重破坏路径插值的一致性,具体表现是生成样本会有明显的条带状分布——粒子都往几个预设时间点对应的位置挤,中间区域没人管。这个问题在扩散模型里不常见,因为扩散模型的调度表本就是按固定网格设计的,但FM的时间是连续均匀采样的,换成固定网格等于自废武功。
4. 采样端到端:ODE求解器和步数策略
4.1 为什么FM的采样是ODE而不是SDE
训练完向量场vθ(x,t)之后,生成过程被定义为一个从t=0到t=1的常微分方程:d x / dt = vθ(x, t)。初始状态x0从标准高斯分布采样,然后让x沿速度场流动,到达t=1就得到生成样本。整个过程没有随机噪声注入,所以是确定性的ODE,这也是FM采样比传统扩散模型更可控的原因。
从概率角度看,对初始分布做沿速度场的变换,得到的就是学习到的目标分布;如果向量场学得够好,不同簇的数据会在同一个向量场下流向不同区域,最终形成多模态的样本。这里有个很有意思的点:因为整个映射是确定性的,同一个x0永远会生成同一个x1。这带来一个很实用的性质——潜在空间插值。你可以把两个不同样本对应的x0找出来,在x0空间做线性插值,再送回ODE里积分,得到的就是两个样本之间的平滑过渡。这种可控性在图像编辑、风格融合、数据增强等场景里非常值钱,也是我在实际项目里更愿意用FM做底层生成器的根本原因。
4.2 欧拉法和中点法的实际差异
最简单的采样器是欧拉法:x = x + h * vθ(x, t),步长h取1/N,N是总步数。在二维小实验上,欧拉法十几步就能出不错的结果;但高维数据上,十几步欧拉法会带来不小的偏差,因为线性路径要求的速度场在弯曲区域变化快。中点法(Heun)在每一步先估计中点速度,再用中点的速度更新,能显著减少弯曲轨迹的误差。
我实测下来的经验是:100步Heun的结果可以作为基准,50步以内的欧拉法在细节上会差不少;少步数场景比如10步以下,建议直接用中点法,或者配合蒸馏后的模型,而不是强行调大欧拉法的步长。步数的含义和扩散模型里的网络评估次数(NFE)一致,FM的吸引力在于它把“训练目标”和“采样轨迹”统一之后,少步采样变得可预期得多。如果项目要求实时生成,我的做法通常是先训练一个标准FM模型,再用蒸馏方案压到4步到8步,而不是直接在原模型上开大步长试。
4.3 采样步数的分配策略
FM的ODE形式天然适合蒸馏:先训练一个大模型,再让一个学生模型在相同时间区间上模仿大模型的输出向量,或者直接用大模型生成一组(x0, x1)的配对数据,用这些配对数据做回归训练。这比扩散模型蒸馏的一个便利之处是,配对关系天然存在,不需要额外设计复杂的去噪循环。少步采样的另一个思路是重排时间步,比如在t=[0,1]上不做均匀等分,而是按模型的误差分布分配步数——接近数据的区域多放几步,靠近噪声区域少放几步。
这类时间步重排技巧实现成本很低,只需要在采样循环里改一个步长数组就能生效,有时能带来和额外增加步数接近的收益。我在一个模拟项目X里试过,8步非均匀采样比8步均匀采样的指标高出将近一个百分点,代价只是花十分钟统计了一下每段时间步上的平均误差。这种“花小钱办大事”的优化点,往往比闷头调网络结构更值得优先尝试。
5. 训练里的坑:速度目标、正则化与批量设计
5.1 预测速度与预测噪声的等价性陷阱
很多从扩散模型转过来的人会以为FM只是把epsilon换成了x1-x0,所以微调一下就行。但这里有个隐蔽问题:扩散模型里有独立的调度表控制噪声强度,而FM的条件路径直接把调度信息揉进了速度向量里。如果你沿用扩散模型的那些超参,比如给损失加时间权重、或者对采样结果做最后一步的调整,很可能会把路线带偏。
FM的损失里不需要额外的时间权重,CFM目标已经是对每个时间点一致的期望;而采样最后一步也不需要像扩散模型那样做“去噪漂移”,直接输出即可。我见过不少实验,加了这些“旧习惯”之后指标不升反降,最后定位到的问题都是两个框架的隐含假设不一样。建议切换框架时把所有“经验性补偿”全部清空,只保留最朴素的损失定义,等基线稳定后再一点点加回必要的技巧。
5.2 数据尺度对向量场的巨大影响
FM对数据的尺度和中心化非常敏感。很多公开数据集在预处理时会做标准化,这个操作对扩散模型一般是标配;在FM里如果不做同样的缩放,不同特征维度的速度向量数值差距会非常大,导致模型把能力浪费在预测大数值分量上,小数值的分量学不出来。一个可信的做法是在训练前把数据标准化到均值为0、方差为1,训练完采样时再把输出反标准化回去。
如果你处理的是文本嵌入或表格特征,也要先做特征维度的缩放或至少做PCA白化,不然任何保结构的生成方法都很难收敛。我踩过的具体案例是:某次做表格数据生成,忘记对数值型特征做标准差缩放,训练损失降得很快,但采样结果在几个数值范围特别大的维度上完全失真。后来检查了数据集统计,发现那几个维度的速度向量数值比其他维度高出两个数量级,模型几乎把全部容量都用在拟合它们上面了。这个教训让我养成了一个习惯:训练前必做逐特征的统计信息打印,确认数据的scale是健康的。
5.3 条件路径参数sigma:太小尖峰、太大糊化
在2.3里提到过带噪声扰动的路径,这里展开讲讲sigma_t的实践值。对二维标准高斯到二维高斯混合数据,sigma_t取0.1到0.3之间比较稳;对图像数据,经常配合设置端点sigma。端点sigma是路径扰动在t=1时的标准差,如果设成0,训练时数据点会被准确吸附,数据密度是尖峰分布,损失下降好看但生成样本会有明显的“记忆”伪影和散点噪声;如果设得太大,目标分布被糊化,生成结果会偏模糊。
一个可复现的经验是:让sigma_t保持一个常数,或者从某个小值线性下降到0。常数版本实现最简单,效果也稳定;线性下降在步数充足时能拿到更锐利的样本,但调参成本更高。如果你发现生成样本边缘发虚,优先检查是不是sigma_t设大了;如果你发现样本出现孤立噪点,优先检查是不是端点sigma设成了0导致路径过于尖锐。这类问题在二维实验里用肉眼就能分辨,所以我才反复强调先用小维度把链路跑通再用到真实数据上。
6. 边界情况与进阶方向:从FM到最优传输
6.1 线性路径不是“免费午餐”
CFM训练目标是让模型恢复我们预设路径的向量场,但预设路径是任意选的,不同路径的最终生成质量可能差别很大。线性路径计算最简单,但它在很多分布上不是“最优传输”路径:最优传输路径希望粒子流动过程中总代价最小,线性路径没有考虑数据本身的几何结构。所以在某些任务上,直线路径会让不同簇的数据在路径中途发生纠缠,对应的向量场存在复杂交叉,模型拟合难度上升。
这也是为什么FM的进阶文献里会有各种改进,本质上都是在换路径的定义。有的方法做minibatch最优传输,有的方法用sinkhorn近似,有的方法直接学习路径参数。它们的共同出发点都是同一个观察:路径设计决定了向量场的复杂度和可学习性,而向量场的复杂度又直接决定了最终模型的容量需求。如果你在调网络结构、加模型宽度、加训练步数上都试了,但生成质量始终卡在某个瓶颈,那大概率是路径本身需要换,而不是模型不够强。
6.2 最优传输路径的实际收益
如果能得到真正的最优传输路径,粒子不会交叉,向量场更平滑,训练更容易收敛,采样步数可以压得更低。但直接求解高维空间里的最优传输映射几乎不可行,所以很多方法退一步:在每批样本内做匹配,让x0和x1之间存在某种配对关系,而不是完全独立抽样。这种“batch内的最优传输”思路在实现上只增加了一个匹配步骤,却比纯随机配对收敛得快不少。
用二维实验就能看清楚:随机配对时路径交叉明显,模型需要学一个拧巴的场,训练损失曲线并不稳定;配对后路径平顺,同样的网络结构和步数下生成质量明显上一个台阶。我自己的经验是,在项目初期先在batch内做简单的最优传输匹配,观察有没有收益,再决定要不要上更复杂的全局路径优化。多数情况下,这个batch内的匹配版本已经能带来足够大的提升,再往上的复杂度收益递减。
6.3 一些更值得尝试的扩展方向
除了最优传输路径,另一个方向是把FM和离散数据结合起来。原始FM假设连续空间,对图像、音频这类连续信号没问题;但文本、离散token等数据,需要额外的编码技巧或者定义离散路径上的向量场,目前已经有了配套的做法。带条件生成也是高频需求:类别条件、文本条件、多模态条件,结构上只需要在模型输入端拼上条件向量即可,损失函数不用改;我试过在二维混合数据上加条件标签,效果立竿见影。
还有一个值得关注的性质是分布外插值。因为FM是沿着连续路径做确定性映射,你可以把一个真实样本的终点反推回某个初始噪声,再用这个噪声去和另一个类别的样本噪声做插值,生成的结果是两个分布中间的过渡形态。这种操作在可视化数据分布、探查类别边界、生成困难样本这些任务里非常有用。我最后分享一个小技巧:如果你的数据分布存在明显的多模态,建议在训练到后半段时把batch size调大,因为batch越大,随机采样x0时覆盖的初始分布越完整,学出来的向量场在分支区域越平滑。
如果只让我留一个建议,那就是在训练任何FM模型前,先把数据预处理、路径定义和采样器这三件事钉死,再去调网络结构。因为这三件事决定了模型的优化难度和最终上限,而网络结构在同等容量下带来的差异往往没有路径设计那么明显。希望这篇拆解能帮你降低上手成本,少走一些弯路。