想象博物馆里挂着一幅精美的风景画。突然,一场沙尘暴从窗外涌来——不是瞬间吞没,而是阵风加沙,画面慢慢模糊、斑驳,最后完全变成一片混沌的灰黄。如果你把这全程录成录像带,你会得到「从清晰画作到纯沙子」的完整记录。 现在,那位见过十万幅画的修复师站到沙堆前。他没有原图,但他太懂画了——盯着沙堆猜「这下面大概是棵树,还是个人的轮廓」,然后小心吹掉一层沙,再看、再猜、再吹。一步、百步、千步……画回来了。更妙的是,沙堆里那点随机气流,让每次「复原画」都略有不同:有的明朗、有的忧郁。 这就是扩散模型(Diffusion Model)的底层直觉:前向加噪是那场沙尘暴,把图像一步步弄成纯噪声;反向去噪是那位修复师,训练神经网络从噪声里一点点「吹」出图像。DDPM、Stable Diffusion 这些名字,都是把修复师请进更聪明的流程,让他又快又好地批量作画。当真实数据不够、标注太贵时,模型画出的「仿真作品」便成了宝贵的合成数据,补足下游模型的训练短板。

岗位关联

扩散模型是当前图像合成质量的「天花板」。学会用它批量生产合成数据,等于把「数据不够」的瓶颈变成一条可规模化的生产线——这是 CV 算法工程师从「调参」走向「造数据」的关键一跃。

目录
  1. 可视化演示
  2. 扩散模型原理
  3. 前向加噪过程
  4. 反向去噪过程
  5. DDPM与合成数据
  6. Stable Diffusion
  7. 条件扩散模型
  8. 扩散合成选型
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示扩散模型生成图像的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

扩散模型生成图像:扩散模型 扩散模型前向加噪与反向去噪原理 DDPM在图像合成数据中的应用 核心机制可视化 扩散模型生成图像整体架构与关键组件

扩散模型原理

沙尘暴埋画,听起来是灾难,但对 AI 研究者来说,这恰恰是一场可以倒放的录像。热力学告诉我们,把一杯墨水滴进清水,墨会自发晕开,而要让墨重新聚回一滴,几乎不可能——这就是「熵增」:世界倾向于从有序走向无序。扩散模型正是借用了这个物理直觉:与其费力直接生成图像,不如先学会「如何把图像弄乱」,再训练一个网络学会「把乱掉的图像恢复原状」。

这里的关键在于,毁掉一幅画不需要学习:往画上不断叠加高斯噪声(一种可数学描述的灰白随机颗粒),有固定的规则可循,模型把这个过程叫前向加噪;而恢复一幅画需要学习:从纯噪声出发,逐步预测并去掉噪声,模型把这个过程叫反向去噪。前向过程定义了「怎么一步步变脏」,反向过程则让神经网络(修复师的大脑)学会「怎么一步步变干净」。

整个扩散模型的学习目标,是让反向去噪的分布逼近真实图像分布:修复师看过的画越多,对「沙堆下面长什么样」的猜测就越准。DDPM、Stable Diffusion 等模型虽然结构各异,但都共享这一套「先毁后修」的框架。前向加噪的数学描述如下:

所以修复师并不需要发明沙尘暴,他只需要拿到这套「吹沙进度表」,再把全部精力放在学习反向修复上——这就是扩散模型简洁而优雅的地方。

q(xt∣xt−1)=N(xt; 1−βt xt−1, βtI)\displaystyle q(x_t \mid x_{t-1}) = \mathcal{N}\left(x_t;\, \sqrt{1-\beta_t}\,x_{t-1},\, \beta_t I\right)
这个公式描述「沙尘暴的每一步如何吹」:从上一时刻的画面上保留一部分,再加入一小团高斯噪声。
  1. 条件分布 q代表前向过程从上一时刻图像得到当前图像的转移概率,就是沙尘暴的「局部剧本」
    q(xt∣xt−1)\displaystyle q(x_t \mid x_{t-1})
  2. 高斯分布 N括号里第一项是均值,第二项是方差,表示每一步吹来的沙服从这个随机分布
    N(xt; 1−βt xt−1, βtI)\displaystyle \mathcal{N}\left(x_t;\, \sqrt{1-\beta_t}\,x_{t-1},\, \beta_t I\right)
  3. 保留系数 √(1-β_t)上一幅画面被保留的比例,β_t 是这步的噪声强度,β_t 越大,保留越少
    1−βt xt−1\displaystyle \sqrt{1-\beta_t}\,x_{t-1}
  4. 方差 β_t II 是单位矩阵,β_t 控制这颗噪声颗粒的「力气大小」
    βtI\displaystyle \beta_t I
  5. 噪声调度 β_t一开始 β_t 很小,随着步数增大而增大,对应沙尘暴越吹越猛
    β1<β2<⋯<βT\displaystyle \beta_1 < \beta_2 < \cdots < \beta_T

前向加噪过程

沙尘暴不是一秒吞没画面的,它有一张「阵风强度表」:第一阵风只是扬起浮尘,画面还很清晰;第二阵风带起更多沙粒,轮廓开始模糊;越往后,风力越猛,直到画面完全消失。扩散模型的前向加噪正是这样——每一步按照预设的噪声调度(即 β_t 序列)给图像添加少量高斯噪声,把这一步的操作记作一个简单的线性混合。

这个过程是马尔可夫链:当前状态只依赖上一步,不依赖更早的历史。就像修复师观察沙尘录像时发现,画面「下一步脏到什么程度」只由「当前画面」决定,而不必回放之前的每一帧。因为每一步的规则都固定且不涉及任何神经网络,前向加噪也被称为「固定过程」,训练时不需要为它更新任何参数。

前向过程还有一个实用的数学性质:给定原始图像 x₀,我们不必真的循环 T 步,而可以一步算出任意时刻 t 的加噪图。这是因为多个高斯分布的叠加仍然是高斯分布,把从第 1 步到第 t 步的保留系数连乘,就得到累积系数 ᾱ_t:

当 t 足够大时,ᾱ_t 趋近于 0,x_t 几乎完全由噪声主导——画面终于被沙尘暴彻底掩埋,变成一堆纯沙子。而这个「纯噪声终态」正是反向去噪的起点。

xt=αˉt x0+1−αˉt ε,ε∼N(0,I)\displaystyle x_t = \sqrt{\bar{\alpha}_t}\,x_0 + \sqrt{1 - \bar{\alpha}_t}\,\varepsilon,\qquad \varepsilon \sim \mathcal{N}(0, I)
这个公式是扩散模型的「加速键」:训练时想给图像加多少沙,一行公式直接算出来,不用真的吹 T 次风。
  1. 原始图像 x₀博物馆里那幅还没被沙尘暴碰过的干净画
    x0\displaystyle x_0
  2. 累积保留系数 √ᾱ_t前 t 步所有保留比例的连乘,表示原画还剩多少「真身」可见
    αˉt=∏s=1t(1−βs)\displaystyle \bar{\alpha}_t = \prod_{s=1}^{t} (1 - \beta_s)
  3. 衰减后的原图 √ᾱ_t x₀原画在 t 步后被缩小的影子,随着 t 增大逐渐趋近于零
    αˉt x0\displaystyle \sqrt{\bar{\alpha}_t}\,x_0
  4. 噪声幅度 √(1-ᾱ_t)累积的沙量,t 越大,沙子越多,最终接近 1
    1−αˉt\displaystyle \sqrt{1 - \bar{\alpha}_t}
  5. 标准高斯噪声 ε一张随机生成的灰白颗粒图,代表沙尘暴的本质成分
    ε∼N(0,I)\displaystyle \varepsilon \sim \mathcal{N}(0, I)

反向去噪过程

修复师站在那堆纯沙子前,他要做的是「倒放录像」:但录像带只有前半段,后半段的画面全都得靠他脑补。每吹掉一层沙,他都要判断「这层沙下面大概是什么颜色、什么形状」,然后根据判断继续吹。神经网络在这里扮演的正是修复师的大脑:它输入当前画面和时间步 t,输出对这层沙(即噪声)的预测。

反向过程需要拟合的条件分布 q(x_{t-1}|x_t) 在数学上无法直接计算,因此扩散模型用一个参数化网络 p_θ 去逼近它。实际工程中,这个网络通常采用 U-Net 结构——它擅长同时捕捉图像的全局结构和局部纹理,正好匹配「既要看懂沙堆大势,又要抠细节」的需求。网络接收两个输入:当前加噪图 x_t,以及表示「现在吹到第几步」的时间步 t(通过时间嵌入告诉网络当前该用多大力气)。

从纯噪声出发,修复师一步接一步地迭代:预测噪声 → 从当前画面中减去这部分 → 加上一小团随机风(保证多样性)→ 进入下一步。经过 T 次迭代,得到一张清晰的生成图像。标准的 DDPM 采样更新公式如下:

修复师就这样吹一层、看一层。有趣的是,由于每步都保留了随机扰动,即使面对同一堆沙子,他吹出的每幅画都可能不同——这正是扩散模型能产生多样化合成数据的根源。

xt−1=1αt(xt−1−αt1−αˉt εθ(xt,t))+σtz,z∼N(0,I)\displaystyle x_{t-1} = \frac{1}{\sqrt{\alpha_t}}\left(x_t - \frac{1 - \alpha_t}{\sqrt{1 - \bar{\alpha}_t}}\,\varepsilon_\theta(x_t, t)\right) + \sigma_t z,\qquad z \sim \mathcal{N}(0, I)
这个公式是修复师的「吹沙手法」:先估算这层沙的位置并吹掉,再补一阵随机微风,避免每幅画吹得一模一样。
  1. 去噪系数 1/√α_t把吹掉沙后的画面按比例「放大」回接近正常亮度,防止画面越吹越暗
    1αt\displaystyle \frac{1}{\sqrt{\alpha_t}}
  2. 预测噪声 ε_θ(x_t, t)U-Net 修复师看到当前沙堆后,猜「当前这层沙长什么样」
    εθ(xt,t)\displaystyle \varepsilon_\theta(x_t, t)
  3. 噪声缩放系数 (1-α_t)/√(1-ᾱ_t)根据当前步数计算这层沙占整堆沙的比例,防止吹多或吹少
    1−αt1−αˉt\displaystyle \frac{1 - \alpha_t}{\sqrt{1 - \bar{\alpha}_t}}
  4. 随机扰动 σ_t z每次吹沙后刻意保留的一阵随机风,让生成结果具有多样性
    σtz\displaystyle \sigma_t z

DDPM与合成数据

2020 年,Ho 等人提出了 DDPM(Denoising Diffusion Probabilistic Models),相当于给修复师写了一套标准作业手册。这位「老实修复师」的工序很完整:前向过程按固定调度加噪,后向过程用 U-Net 预测噪声,训练目标则被简化成一个非常干净的均方误差(MSE)损失——网络预测的噪声和真实添加的噪声越接近越好。它不玩对抗游戏,也不容易「走火入魔」,训练过程异常稳定。

DDPM 的训练流程可以概括为四步:随机抽一个时间步 t;从标准高斯分布采样噪声 ε;用前向公式得到加噪图 x_t;让网络预测噪声并与真实 ε 算 MSE 损失,反向传播更新参数。这个简单的目标函数,让修复师只需要反复练习「猜沙」,不需要额外学习任何辅助任务:

DDPM 生成图像的逼真度达到甚至超过了同期 GAN 的水平,而且几乎不会出现 GAN 那种「只会画几种图」的模式崩塌问题。在合成数据场景中,DDPM 的价值很直接:当真实数据只有几百张时,可以用它生成大量高仿真图像,作为样本增强材料或直接补充训练集,让下游分类、检测模型有更多「复习题」可做。

L=Ex0,ε,t[∥ε−εθ(xt,t)∥2]\displaystyle \mathcal{L} = \mathbb{E}_{x_0, \varepsilon, t}\left[\left\| \varepsilon - \varepsilon_\theta(x_t, t) \right\|^2\right]
这个损失函数是修复师的「模拟考」:把已知答案(真实噪声)和网络预测摆在一起算差距,差距越小,手艺越好。
  1. 期望 E对所有训练图像、所有随机噪声、所有时间步求平均,保证修复师在任何阶段都合格
    Ex0,ε,t\displaystyle \mathbb{E}_{x_0, \varepsilon, t}
  2. 真实噪声 ε前向加噪时实际混入画面的沙子,是这场考试的「标准答案」
    ε\displaystyle \varepsilon
  3. 预测噪声 ε_θ(x_t, t)网络看到加噪图后给出的猜测,修复师交出的「答卷」
    εθ(xt,t)\displaystyle \varepsilon_\theta(x_t, t)
  4. 平方误差 ‖·‖²逐像素对比答案与答卷的差距,差距越小,说明吹沙手艺越高
    ∥ε−εθ∥2\displaystyle \left\| \varepsilon - \varepsilon_\theta \right\|^2

Stable Diffusion

DDPM 这位老修复师什么都好,就是太慢——在 512×512 的高清画布上吹 1000 下,普通 GPU 要跑到天荒地老。Stable Diffusion 的聪明之处在于换个战场:与其在大画布上来回折腾,不如先在「草稿纸」上把构图定下来。这张草稿纸就是潜在空间:用 VQ-VAE/VAE 这类自编码器,把 512×512×3 的图像压缩成 64×64×C 的小尺寸特征图,再在压缩后的特征图上做扩散。

这个「先压缩、再扩散、最后解压」的流程,让计算量骤降一个数量级,普通消费级显卡也能跑得动。Stable Diffusion 还在流程里加入了文本条件:用 CLIP 文本编码器把「一只穿宇航服的猫」这样的描述变成向量,作为修复师的「任务卡」输入 U-Net,让生成的画面听话。它的核心操作非常简单:对潜在表示 z 做加噪和去噪,而不是对像素直接操作:

Stable Diffusion 的「算力友好」特性,让它成为大批量合成数据的首选工具:用户只需要写提示词,就能批量产出不同风格的训练图像,无论是给检测模型做负样本,还是给少样本分类器补数据,都非常顺手。

z=E(x),x^=D(z),zt=αˉt z0+1−αˉt ε\displaystyle z = E(x),\qquad \hat{x} = D(z),\qquad z_t = \sqrt{\bar{\alpha}_t}\,z_0 + \sqrt{1 - \bar{\alpha}_t}\,\varepsilon
修复师的改良版「草稿纸工作法」:所有吹沙动作都在小尺寸草稿上进行,定稿后再放大成正式画作,因此又快又省力。
  1. 编码器 E把高清大图压缩进潜在空间,相当于把 512px 画布缩成 64px 草稿纸
    z=E(x)\displaystyle z = E(x)
  2. 解码器 D把草稿纸上的结果放大还原成高清图像,誊清成正式画作
    x^=D(z)\displaystyle \hat{x} = D(z)
  3. 潜在表示 z图像在压缩空间中的「浓缩信息」,信息量小但足以还原画面
    z0,zt\displaystyle z_0, z_t
  4. 潜在空间加噪扩散过程在 z 上进行,公式结构与像素空间完全一致,只是「战场」变小了
    zt=αˉt z0+1−αˉt ε\displaystyle z_t = \sqrt{\bar{\alpha}_t}\,z_0 + \sqrt{1 - \bar{\alpha}_t}\,\varepsilon

条件扩散模型

博物馆老板发现修复师虽然有手艺,但「让他画什么全凭缘分」——想让他画一幅海边日落,他可能吹出一幅雪山。于是老板给他一张任务卡:上面写着类别或描述文字。修复师吹沙时,每猜一步都会偷瞄任务卡,让脑补方向朝着任务卡靠拢。这就是条件扩散模型:把类别标签 y 或文本向量 c 作为额外输入,让 U-Net 输出条件化的噪声预测 ε_θ(x_t, t, c)。

条件信息的注入方式主要有两种。早期的分类器引导(classifier guidance)在采样时额外训练一个分类器,用分类器的梯度把采样方向「掰」向目标类别;后来提出的无分类器引导(classifier-free guidance, CFG)更聪明:训练时随机丢弃条件(比如 10% 的概率不输入标签),让网络同时学会有条件预测和无条件预测,推理时把两者的差距放大,实现更强的控制。

CFG 之所以成为主流,是因为它不需要额外训练分类器,效果却更好。它的数学形式非常简洁:用引导强度 w 控制「听任务卡的程度」,w 越大,生成结果越贴近条件,但过大时画面会过饱和、失真:

条件扩散模型让「按需合成」成为现实:自动驾驶团队缺「雨夜街道」,就生成雨夜街道;医学影像缺「罕见病灶」,就以病灶标签为条件生成样本。合成数据从此不再靠运气,而是按订单生产。

ε~θ(zt,c)=εθ(zt)+w(εθ(zt,c)−εθ(zt))\displaystyle \tilde{\varepsilon}_\theta(z_t, c) = \varepsilon_\theta(z_t) + w\left(\varepsilon_\theta(z_t, c) - \varepsilon_\theta(z_t)\right)
修复师的「任务卡用法」:先凭直觉吹(无条件),再对照任务卡吹(有条件),两者差距越大,说明任务卡越重要,按差距放大 w 倍。
  1. 条件预测 ε_θ(z_t, c)瞥了任务卡之后,修复师给出的吹沙方向
    εθ(zt,c)\displaystyle \varepsilon_\theta(z_t, c)
  2. 无条件预测 ε_θ(z_t)完全不看任务卡,凭经验直觉吹沙的方向
    εθ(zt)\displaystyle \varepsilon_\theta(z_t)
  3. 方向差任务卡带来的「修正矢量」,指向条件希望画面靠拢的方向
    εθ(zt,c)−εθ(zt)\displaystyle \varepsilon_\theta(z_t, c) - \varepsilon_\theta(z_t)
  4. 引导强度 w决定修正力度的大小,w=0 时完全不听任务卡,w 越大越听话
    w≥0\displaystyle w \geq 0
  5. 修正后预测在凭直觉的方向上叠加修正矢量,得到最终吹沙方向
    ε~θ(zt,c)\displaystyle \tilde{\varepsilon}_\theta(z_t, c)

扩散合成选型

老板发现修复师虽然能按订单作画,但价格不菲——每幅画吹 1000 下,耗时太长。修复师自己也琢磨出「跳着吹」的诀窍:既然每层沙的分布可推算,那就不必每层都停下来看一眼,用DDIM这类采样器可以把 1000 步压缩到 50 步以内,质量损失却很小。核心技巧是在更新公式中去掉每一步的随机扰动项,用确定性的方式大步回退:

扩散合成数据的优点很突出:生成质量业界最高、多样性好、条件控制能力强;但缺点也明显:训练和采样都比 GAN 慢,算力成本高,偶发伪影(比如手指多一根、文字乱码)需要过滤。选型时,如果任务对图像真实性要求极高且算力充足,扩散模型是首选;如果要求实时、低算力,GAN 或 VAE 更务实。工程上还应注意:合成数据要与真实数据按比例混用,并经过去重、质量筛选和人工抽检,防止模型被合成分布的「审美偏好」带偏。

x^0=xt−1−αˉt εθ(xt,t)αˉt,xt−1=αˉt−1 x^0+1−αˉt−1 εθ(xt,t)\displaystyle \hat{x}_0 = \frac{x_t - \sqrt{1 - \bar{\alpha}_t}\,\varepsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}},\qquad x_{t-1} = \sqrt{\bar{\alpha}_{t-1}}\,\hat{x}_0 + \sqrt{1 - \bar{\alpha}_{t-1}}\,\varepsilon_\theta(x_t, t)
修复师的「跳步秘籍」:先直接估算出原画的样子,再按比例跳到更早的一步——跳过中间步骤,效率飞升。
  1. 估计原画 x̂₀从当前沙堆反推最可能的干净画面,相当于修复师脑补出的「原稿」
    x^0=xt−1−αˉt εθαˉt\displaystyle \hat{x}_0 = \frac{x_t - \sqrt{1 - \bar{\alpha}_t}\,\varepsilon_\theta}{\sqrt{\bar{\alpha}_t}}
  2. 确定性地跳步根据估计的原画和当前噪声,直接跳到目标时间步,不再随机绕路
    xt−1=αˉt−1 x^0+1−αˉt−1 εθ\displaystyle x_{t-1} = \sqrt{\bar{\alpha}_{t-1}}\,\hat{x}_0 + \sqrt{1 - \bar{\alpha}_{t-1}}\,\varepsilon_\theta

AI 应用场景

医学影像小样本增强

阶段:数据准备与增强

解决的问题:罕见病影像样本往往只有几百张,标注成本高昂,传统旋转、裁剪等增强手段无法创造新的病变形态。条件扩散模型以「病灶类别」为条件,生成逼真的合成影像,让诊断模型见到更多样化的病例,降低漏诊率。

具体案例:某医院团队用 DDPM 以「肺结节」「间质性肺炎」等标签为条件,从 500 张真实 CT 切片扩出 5000 张合成切片,与真实数据按 1:3 混合训练分类模型,在独立测试集上的敏感度提升了 12 个百分点。

自动驾驶长尾场景合成

阶段:训练数据构建

解决的问题:雨夜、逆光、动物横穿马路等极端场景真实采集又贵又少,构成自动驾驶感知模型的「长尾短板」。Stable Diffusion 配合文本条件,可以按需批量生成指定天气、光照、路况的图像,补齐稀有场景。

具体案例:某自动驾驶团队用 Stable Diffusion 生成 2 万张「雨天夜间 + 行人+车灯眩光」的合成街景,用来微调目标检测模型,在雨夜场景的 mAP 从 58% 提升到 71%,且未明显牺牲晴天场景精度。

电商商品图库扩充

阶段:数据合成与标注

解决的问题:电商推荐模型需要大量商品展示图,但新品上架初期图片稀缺。条件扩散模型可按商品类别和风格提示词批量生成合成商品图,作为冷启动阶段的训练素材。

具体案例:某电商平台用条件扩散模型为 3000 个新品各生成 20 张不同背景、不同角度的合成商品图,扩充训练集后,新品推荐点击率预估模型的 AUC 提升了约 2 个百分点。

Python 演示

python88 行
import torchimport torch.nn as nnimport torchvisionimport torchvision.transforms as T  class NoiseNet(nn.Module):    """简化版 U-Net:负责预测每一步被混入的噪声(猜沙子)。"""    def __init__(self):        super().__init__()        self.encoder = nn.Sequential(            nn.Conv2d(1, 16, 3, padding=1), nn.ReLU(),            nn.Conv2d(16, 32, 3, stride=2, padding=1), nn.ReLU(),        )        self.decoder = nn.Sequential(            nn.ConvTranspose2d(32, 16, 4, stride=2, padding=1), nn.ReLU(),            nn.Conv2d(16, 1, 3, padding=1),        )        self.step_embed = nn.Linear(1, 32)     def forward(self, x, t):        h = self.encoder(x)        b, c, hh, ww = h.shape        te = self.step_embed(t.view(-1, 1)).view(b, 32, 1, 1)        return self.decoder(h + te)  def make_beta_schedule(T=80):    return torch.linspace(1e-4, 0.02, T)  class SimpleDDPM:    def __init__(self, T=80):        self.T = T        self.net = NoiseNet()        self.betas = make_beta_schedule(T)        self.alphas = 1 - self.betas        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)     def train_step(self, x, opt):        t = torch.randint(0, self.T, (x.shape[0],))        a_bar = self.alphas_cumprod[t].view(-1, 1, 1, 1)        noise = torch.randn_like(x)        x_t = torch.sqrt(a_bar) * x + torch.sqrt(1 - a_bar) * noise        pred = self.net(x_t, t.float())        loss = nn.functional.mse_loss(pred, noise)        opt.zero_grad()        loss.backward()        opt.step()        return loss.item()     @torch.no_grad()    def sample(self, n=4):        x = torch.randn(n, 1, 28, 28)        for i in range(self.T - 1, -1, -1):            t = torch.full((n,), i, dtype=torch.float)            pred = self.net(x, t)            alpha = self.alphas[i]            a_bar = self.alphas_cumprod[i]            coef = (1 - alpha) / torch.sqrt(1 - a_bar)            x = (x - coef * pred) / torch.sqrt(alpha)            if i > 0:                a_bar_prev = self.alphas_cumprod[i - 1]                sigma = torch.sqrt((1 - alpha) * (1 - a_bar_prev) / (1 - a_bar))                x = x + sigma * torch.randn_like(x)        return x  def main():    transform = T.Compose([T.ToTensor(), T.Normalize([0.5], [0.5])])    ds = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)    loader = torch.utils.data.DataLoader(        torch.utils.data.Subset(ds, range(2000)), batch_size=64, shuffle=True    )    model = SimpleDDPM()    opt = torch.optim.Adam(model.net.parameters(), lr=1e-3)    for epoch in range(3):        total = 0        for images, _ in loader:            total += model.train_step(images, opt)        print('epoch', epoch, 'loss', round(total / len(loader), 4))    imgs = (model.sample(4) + 1) / 2    torchvision.utils.save_image(imgs, 'synthetic_digits.png', nrow=4)    print('saved synthetic_digits.png')  if __name__ == '__main__':    main()
目的:在 MNIST 子集上训练一个简化版 DDPM,演示「加噪-去噪」完整流程,并生成 4 张合成手写数字图,作为合成数据生产的最小可行示例。
  1. t = torch.randint(0, self.T, (x.shape[0],))为每张图随机抽一个时间步,让修复师见识不同脏污程度的画面。
  2. x_t = torch.sqrt(a_bar) * x + torch.sqrt(1 - a_bar) * noise用前向闭式公式一步算出加噪图,不用真循环 80 次。
  3. pred = self.net(x_t, t.float())网络扮演修复师,根据加噪图和时间步预测混入的沙子。
  4. loss = nn.functional.mse_loss(pred, noise)拿预测沙子和真实沙子算账,差距越小手艺越好。
  5. for i in range(self.T - 1, -1, -1)从最脏的一帧倒着吹回去,一步步走到干净图像。
  6. coef = (1 - alpha) / torch.sqrt(1 - a_bar)计算当前这层沙占整堆沙的比例,决定吹多大力。

常见误区

误区修复师看过的画都被他记在脑子里了,生成的图像不就是把训练集里的某张图重新打印一遍吗?用合成数据会不会直接把别人的图泄进来?

正解扩散模型学到的是数据分布的整体规律,而不是逐张死记硬背。修复师看十万幅画后学会的是「树的纹理、脸的轮廓、光影怎么落」,而不是拿记忆库做拼贴。只有当某张图在训练集中出现极多次时,模型才会「背下来」,因此生产合成数据前对训练集做去重、生成后做相似度排查是必要的工程习惯。

为何常见:扩散模型在图像细节上有时会与训练集里的图高度相似,让使用者误以为模型是在「查库复制」,忽略了它本质上是概率分布采样。

误区AI 生成图看起来那么真,文字、手指、边缘都很完美,直接拿去做合成数据应该没问题,不用检查。

正解扩散模型仍会出错,常见的伪影有:手指多一根、文字乱码、边缘扭曲。修复师吹沙时也有吹过头的时候。合成数据用于训练前必须经过质量过滤,比如用检测器筛选、人工抽检、按类别统计分布,否则会把模型的「幻觉」也教给下游模型。

为何常见:模型生成的样例图往往经过精心挑选才展示出来,让人低估了错误率;批量生成时伪影比例会明显上升。

误区DDPM 训练用了 1000 步,推理时也应该老老实实走 1000 步,少走一步质量就差一分。

正解训练时的 1000 步是给网络足够的「练习机会」,推理时则可以用 DDIM 等采样器压缩到 20-50 步,质量损失很小。修复师练手艺时确实要反复吹一千下,但出师后掌握了「跳步」诀窍,几下就能吹出相近效果。步数是效率与质量的可调旋钮,而不是固定值。

为何常见:很多人把训练配置和推理配置混为一谈,忽略了 DDIM 这样专为加速推理设计的确定性采样器。

误区既然扩散模型能无限生成,那就生成 100 万张图扔进训练集,下游模型一定越来越强。

正解合成数据的价值在于多样性覆盖,而不是盲目堆量。如果生成的图像分布高度集中在某几种风格,模型反而会在合成分布上过拟合,真实场景表现下降。修复师的仿真作品再好,学徒也不能只看仿真作品——还得搭配真实写生。工程上建议按 1:3 到 1:1 的比例与真实数据混合,并按任务做消融实验验证收益。

为何常见:「数据越多越好」是传统深度学习时代的惯性思维,忽略了生成数据与真实数据之间存在分布偏差这一前提。

面试题

Q1. 扩散模型「前向加噪」过程的本质是什么?

单选题 · easy

  • A.通过学习逐步还原图像
  • B.按固定规则逐步加噪声← 正确答案
  • C.用对抗网络制造噪声
  • D.直接对图像做高斯模糊

答案:B。前向加噪是沙尘暴把画掩埋的过程,规则固定、无参数参与,逐步叠加高斯噪声直到变成纯噪声。A 是反向去噪的工作,C 是 GAN 的思路,D 的模糊与加噪是两回事,前向加的是随机噪声而不是确定性模糊。

Q2. DDPM 的反向去噪网络主要预测什么?

单选题 · easy

  • A.原始图像本身
  • B.去噪后的图像
  • C.添加的高斯噪声← 正确答案
  • D.图像的边缘信息

答案:C。DDPM 的简化训练目标是让网络预测前向过程实际添加的噪声 ε,损失函数是预测噪声与真实噪声的 MSE。预测完成后,从当前图像中减去预测噪声即可朝清晰方向走一步。预测原图或边缘都不是 DDPM 的标准做法。

Q3. 反向去噪过程中每一步加入随机扰动的主要作用是什么?

单选题 · mid

  • A.提升训练时的速度
  • B.让生成结果具有多样性← 正确答案
  • C.降低采样的计算复杂度
  • D.帮助网络做梯度更新

答案:B。修复师吹沙时保留的「一阵随机风」对应公式中的 σ_t z,它让每次采样都能走出略有差异的轨迹,从而生成多样化的图像。训练速度和计算复杂度由采样器与步数决定,与随机扰动无关;梯度更新只发生在训练阶段。

Q4. Stable Diffusion 使用「潜在空间扩散」的主要优势是什么?

单选题 · mid

  • A.生成图像的纹理更精细
  • B.大幅降低计算开销和显存占用← 正确答案
  • C.完全舍弃了扩散过程
  • D.不需要训练任何神经网络

答案:B。Stable Diffusion 把 512×512 图像压缩成 64×64 的潜在表示,再在压缩后的特征图上做扩散,计算量比像素空间扩散降低一个数量级。它的扩散过程仍然存在,U-Net 和自编码器都是训练出来的,只是换了个小战场干活。

Q5. 无分类器引导(CFG)中的引导强度 w 起什么作用?

单选题 · mid

  • A.控制生成图像的清晰程度
  • B.调节条件信息对生成的控制力度← 正确答案
  • C.决定每批生成图片的数量
  • D.修正训练数据集的偏差

答案:B。CFG 公式把无条件预测与条件预测的差值放大 w 倍叠加回去,w 越大画面越贴近条件描述,但过大时会出现过饱和、失真。它控制的是「任务卡」的影响力,与清晰度、批量大小和数据集偏差没有直接关系。

Q6. 用扩散模型生成合成数据时,下列哪项做法最合理?

单选题 · hard

  • A.让合成数据完全替代真实数据
  • B.按比例混合真实数据与合成数据← 正确答案
  • C.只用小参数模型来做生成
  • D.直接使用未过滤的生成图像

答案:B。合成数据存在分布偏差,完全替代真实数据会让模型在真实场景掉点。正确做法是按比例混合,并经过质量筛选与人工抽检。模型大小和生成质量相关,但「小模型」不是合理原因;未过滤的生成图可能携带伪影,引入训练集会污染下游模型。

Q7. DDPM 训练时的 MSE 损失计算的是哪两者之间的差异?

单选题 · hard

  • A.生成图像与真实图像
  • B.预测噪声与真实噪声← 正确答案
  • C.加噪图像与干净图像
  • D.相邻时间步的图像差

答案:B。DDPM 的核心简化是让网络 ε_θ 预测前向过程混入的噪声 ε,损失为 ‖ε − ε_θ(x_t, t)‖²。它并不直接比较生成图与真实图(那是 GAN 判别器的思路),也不比较加噪前后的像素差。

Q8. 下列哪些属于条件扩散模型的条件控制方式?

多选题 · mid

  • A.类别标签引导← 正确答案
  • B.文本语义引导← 正确答案
  • C.分类器引导← 正确答案
  • D.对抗判别引导

答案:ABC。类别标签和 CLIP 文本向量都能作为条件输入,分类器引导通过额外分类器的梯度修正采样方向,也属于条件控制。对抗判别引导是 GAN 的机制,扩散模型不用判别器做条件控制。

Q9. 扩散合成数据的主要优点包括哪些?

多选题 · mid

  • A.生成图像质量高← 正确答案
  • B.样本多样性较好← 正确答案
  • C.条件控制能力较强← 正确答案
  • D.训练成本最低廉

答案:ABC。扩散模型生成质量在生成式模型中处于第一梯队,多样性好且可通过条件精确控制。但它的训练和采样成本较高,不是最便宜的选择,因此 D 不成立。

Q10. 关于扩散模型采样步数与生成质量的关系,正确的理解是?

多选题 · hard

  • A.步数越多计算开销越大← 正确答案
  • B.DDIM 可以减少采样步数← 正确答案
  • C.步数越少质量一定越高
  • D.步数多少对质量没有影响

答案:AB。采样每一步都要过一遍网络,步数越多计算量越大;DDIM 通过确定性更新将步数从 1000 压缩到几十步,质量仍可接受。步数过少会导致细节粗糙,因此「越少越高」和「没有影响」都不正确。