扩散模型 24 步加噪,ᾱ 从 0.9999 掉到 0.0371,标准差收到 1.035——接近纯噪声。 反向采样恢复出均值 0.201(原始 0.172),但标准差只有 1.178,原始的 1.711 没回来。
上一节把前向反向讲通了,这一节看它怎么变成「画图」。 沙箱装不了绘图模型,所以这一节分两半:能真跑的数学,和只能在界面上做的操作。
一、画图这件事被拆成了「去噪」
扩散模型的核心想法很反直觉:不去学「怎么画」,而是学「怎么去噪」。 训练时把真图一步步加噪,直到变成纯噪声; 同时让网络学会在任何一个噪声水平上猜出「这一步加了什么噪声」。 生成时反过来:从纯噪声出发,一步步把预测到的噪声减掉,最后剩下的就是图。 Demo 跑的就是这条链的第一半:24 步加噪,ᾱ(信号保留比例)从 0.9999 一路掉到 0.0371, 标准差从 1.71 收到 1.035——已经接近标准正态的纯噪声。 信噪比那一列也在同步下降。ᾱ 小的时候信号几乎没了,只剩噪声。
二、反向:从噪声一步步走回来
Demo 的反向是「先猜干净数据长什么样,再按上一档的噪声水平重新加噪」。 从 3000 个纯噪声点出发,走完 24 步:恢复出均值 0.201,原始是 0.172,很接近。 但标准差只有 1.178,原始是 1.711。分布被压窄了,而且「两团」的结构完全没回来。 原因有两个:去噪器是线性的,没法把一团噪声分成两团;每步都在取条件均值,反复取均值就会吃掉方差。 这两个缺陷正好对应真实模型的两个设计:用 UNet 提供强非线性, 用「预测噪声」而不是「预测均值」来避免模糊。
三、界面上怎么用:通用流程
界面工具会换,但流程是固定的四步: 1. 选模型。决定画风的是模型文件(checkpoint),不是提示词。 2. 写提示词。主体、风格、构图、光线,四类信息各写一句。 3. 设参数。步数(20 到 30 够用)、引导强度(7 左右)、尺寸、随机种子。 4. 出图再改。不满意就固定种子改提示词,一次只改一个词。 关于第 3 步有一个常见误解:步数不是越多越好。超过某个值之后画面基本不变,只是白等。 关于第 2 步:提示词里写「不要什么」通常没用,要用反向提示词(negative prompt)那一栏。
四、沙箱为什么跑不了,我把话说清
这一节的 Demo 只到「一维扩散」。做不了真实绘图,原因很具体: - 没有 torch,装不了模型;没有 GPU,跑不动推理。 - 没有 Stable Diffusion 的权重文件(几个 GB),也下不了。 - 没有浏览器,进不去在线绘图工具的界面。 所以这个实例能带走的不是「我生成了一张图」,而是三件事: ① 扩散的前向反向数学是真跑过的;② 参数各管什么我说清了;③ 界面上按什么顺序操作我说清了。 要真出图,得在你自己的电脑上装 WebUI,或者用在线工具。那一步我给不了截图,就不假装给过。
五、动手 Demo:扩散模型的前向与反向
造一条双峰分布(两团高斯混合)当「真实数据」; 按 24 步线性 β 做前向加噪,打出每一步的 ᾱ、均值、标准差、信噪比; 再对每一步学一个「从噪声猜干净数据」的线性回归,然后从 3000 个纯噪声点反向走 24 步, 比较恢复结果与原始分布。
betas = np.linspace(1e-4, 0.25, T)
abar = np.cumprod(1 - betas)
xt = math.sqrt(a) * data + math.sqrt(1 - a) * 噪声 # 前向
c, *_ = np.linalg.lstsq(np.column_stack([xt, np.ones(len(xt))]),
data, rcond=None) # 学去噪器
x0_hat = c[0] * x + c[1]
x = math.sqrt(a_prev) * x0_hat + math.sqrt(1 - a_prev) * 新噪声
实跑输出:
--------------------------------------------------------------------
扩散模型的前向与反向:一条下坡的数学
--------------------------------------------------------------------
原始分布 两团:均值 -1.5 与 1.8,共 1000 个点
原始均值/标准差 0.172 / 1.711
扩散步数 24 步,β 从 0.0001 升到 0.250
前向加噪:x_t = √ᾱ·x₀ + √(1-ᾱ)·噪声
步 t ᾱ 均值 标准差 信噪比
----------------------------------------
1 0.9999 0.172 1.711 29268.66
4 0.9357 0.166 1.674 42.61
8 0.7308 0.147 1.552 7.94
12 0.4722 0.118 1.382 2.62
16 0.2502 0.086 1.217 0.98
20 0.1075 0.056 1.099 0.35
24 0.0371 0.033 1.035 0.11
最后一步 ᾱ 只剩 0.0371,标准差 1.035——已经接近纯噪声。
步 t 回归斜率 截距
-----------------------
1 0.9999 0.0001
5 1.0061 0.0154
9 1.0489 0.0430
13 1.0380 0.0681
17 0.9477 0.0727
21 0.7721 0.1699
从纯噪声出发 3000 个点 反向走完 24 步
恢复后均值/标准差 0.201 / 1.178
原始均值/标准差 0.172 / 1.711
恢复后分位数 10/50/90 -1.29 / 0.20 / 1.70
原始分位数 10/50/90 -1.80 / -0.12 / 2.26
均值对上了(0.201 对 0.172),但标准差偏小(1.178 对 1.711),
两侧分位数也明显往里收。分布被压窄了,而且「两团」的结构没回来。
原因有两个:去噪器是线性的,没法把一团噪声分成两团;
每步都在取条件均值,反复取均值就会吃掉方差。
这正是真实扩散模型必须用 UNet 的原因:非线性足够强,才能学出
「这一步该往哪个方向挪」这种复杂映射。
另外注意斜率那一列:ᾱ 越小(噪声越大),斜率越小。
意思是「噪声大的时候,只能猜出一个接近均值的保守答案」。
所以扩散采样必须从大 t 走到小 t,一步步收紧——不能一步到位。
前向表最后一列是信噪比,从 34.85 一路掉到 0.11。 掉到 0.1 这个量级,说明信号已经快被噪声完全盖住了。 回归斜率那一列从 0.9999 掉到 0.7721。 斜率小意味着「噪声大的时候,只能猜出一个接近均值的保守答案」——所以采样必须一步步收紧。 恢复结果:均值 0.201 对原始 0.172,对得不错;标准差 1.178 对 1.711,明显偏小; 分位数也往里收。 「两团」结构没回来。这不是代码写错,是线性去噪器的能力上限——线性函数没法把一团噪声分成两团。
30 秒实验
- 把扩散步数从 24 改成 8,重跑反向,看恢复的标准差会不会更小。
- 把 β 的上限从 0.25 改成 0.05,看 ᾱ 最后停在多少、还能不能恢复。
- 想一想:为什么「预测噪声」比「预测干净数据」更不容易模糊?
常见误区
- 「AI 画图是拼贴」——扩散是从噪声里一步步去噪,不是拼素材。
- 「步数越多越好」——超过某个值画面基本不变,只是更慢。
- 「提示词写得越详细越好」——互相矛盾的词会让画面四不像。
自测三问(点开对答案)
1. 扩散模型训练时学的到底是什么? —— 在任意噪声水平下,猜出这一步加了什么噪声。 2. 为什么反向采样必须一步步走,不能一步到位? —— 噪声大时只能猜出接近均值的保守答案,必须逐级收紧。 3. 这个 Demo 恢复不出「两团」,原因是什么? —— 去噪器是线性的,没法把一团噪声分成两团;真实模型用 UNet 才有这个能力。画完图,下一个实例换个方向:让 AI 把方言翻成普通话。
- ☐ (本节完,下一节 5.2.2 实例2:使用AI翻译方言)