🧠 第五章 深度学习算法
📄 一句话概括

扩散模型 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 秒实验

  1. 把扩散步数从 24 改成 8,重跑反向,看恢复的标准差会不会更小。
  2. 把 β 的上限从 0.25 改成 0.05,看 ᾱ 最后停在多少、还能不能恢复。
  3. 想一想:为什么「预测噪声」比「预测干净数据」更不容易模糊?

常见误区

  1. 「AI 画图是拼贴」——扩散是从噪声里一步步去噪,不是拼素材。
  2. 「步数越多越好」——超过某个值画面基本不变,只是更慢。
  3. 「提示词写得越详细越好」——互相矛盾的词会让画面四不像。
自测三问(点开对答案) 1. 扩散模型训练时学的到底是什么? —— 在任意噪声水平下,猜出这一步加了什么噪声。 2. 为什么反向采样必须一步步走,不能一步到位? —— 噪声大时只能猜出接近均值的保守答案,必须逐级收紧。 3. 这个 Demo 恢复不出「两团」,原因是什么? —— 去噪器是线性的,没法把一团噪声分成两团;真实模型用 UNet 才有这个能力。

画完图,下一个实例换个方向:让 AI 把方言翻成普通话。

相关笔记