深度学习:扩散模型:class1

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
深度学习:扩散模型:class1 [2026/03/13 13:33] 张叶安深度学习:扩散模型:class1 [2026/04/01 13:15] (当前版本) – [前向过程单步公式讲解] 张叶安
行 1: 行 1:
 +====== 显示图片 ======
 +
 <code> <code>
 import torch                              # 导入 PyTorch 库,用于张量操作 import torch                              # 导入 PyTorch 库,用于张量操作
行 46: 行 48:
  
 TensorFlow:(𝐻,𝑊,3) TensorFlow:(𝐻,𝑊,3)
 +
 +====== 对称处理 ======
 +
  
 为了让数据和高斯噪声处在同一个零中心对称空间里,要对像素值进行仿射变换: 为了让数据和高斯噪声处在同一个零中心对称空间里,要对像素值进行仿射变换:
行 54: 行 59:
  
 现在想它取值范围为[-1,1] 现在想它取值范围为[-1,1]
 +
 +变换形式为$y=ax+b$
 +
 +当$x=0$时,$y=-1$,当$x=1$时,$y=1$
 +
 +$y=2x-1$,$x=\frac{1}{2}y+\frac{1}{2}$
  
 仿射变换矩阵为: 仿射变换矩阵为:
行 131: 行 142:
 可以看出图片的形状没有变 可以看出图片的形状没有变
  
 +====== 扩散过程 ======
  
 扩散过程是基于一个**方差调度表**构建的,这个调度表决定了在扩散过程的每一个时间步中加入噪声的强度。为此,我们定义如下几个量: 扩散过程是基于一个**方差调度表**构建的,这个调度表决定了在扩散过程的每一个时间步中加入噪声的强度。为此,我们定义如下几个量:
  
-  * `betas`:$\beta_t$ +  * betas:$\beta_t$ 
-  * `alphas`:$\alpha_t = 1 - \beta_t$ +  * alphas:$\alpha_t = 1 - \beta_t$ 
-  * `alphas_sqrt`:$\sqrt{\alpha_t}$ +  * alphas_sqrt:$\sqrt{\alpha_t}$ 
-  * `alphas_prod`:$\bar{\alpha}_t = \prod_{i=0}^{t}\alpha_i$ +  * alphas_prod:$\bar{\alpha}_t = \prod_{i=0}^{t}\alpha_i$ 
-  * `alphas_prod_sqrt`:$\sqrt{\bar{\alpha}_t}$+  * alphas_prod_sqrt:$\sqrt{\bar{\alpha}_t}$
  
  
行 144: 行 156:
  
  
-1. `betas`:$\beta_t$+1. betas:$\beta_t$
  
 它表示**第 $t$ 步加入噪声的强度**,也可以理解为该步的“噪声方差比例”。 它表示**第 $t$ 步加入噪声的强度**,也可以理解为该步的“噪声方差比例”。
行 158: 行 170:
  
  
-2. `alphas`:$\alpha_t = 1 - \beta_t$ +2. alphas:$\alpha_t = 1 - \beta_t$ 
  
 它表示**第 $t$ 步保留下来的原始信号比例**。 它表示**第 $t$ 步保留下来的原始信号比例**。
行 179: 行 191:
  
  
-3. `alphas_sqrt`:$\sqrt{\alpha_t}$+3. alphas_sqrt:$\sqrt{\alpha_t}$
  
 这个量是在实际加噪公式里直接使用的系数。 这个量是在实际加噪公式里直接使用的系数。
行 191: 行 203:
   * $x_{t-1}$ 是上一步图像   * $x_{t-1}$ 是上一步图像
   * $x_t$ 是当前这一步加噪后的图像   * $x_t$ 是当前这一步加噪后的图像
-  * $\epsilon_t \sim \mathcal{N}(0, I)$ 是高斯噪声+  * $\epsilon_t \sim \mathcal{N}(0, I)$ 是高斯噪声(符合[[概率论:随机变量及其分布#2.4.3 常见连续型分布|高斯分布]]的噪声)
  
 **直观理解:** **直观理解:**
行 197: 行 209:
 $\sqrt{\alpha_t}$ 是**上一步图像在当前步中的保留权重**。 $\sqrt{\alpha_t}$ 是**上一步图像在当前步中的保留权重**。
  
-之所以取平方根,是因为扩散模型里控制的是**方差**,而真正乘在样本上的系数是标准差,所以会出现平方根。+之所以取平方根,是因为扩散模型里控制的是[[概率论:数字特征#4.2 方差|方差]],而真正乘在样本上的系数是标准差,所以会出现平方根。
  
 +注:变量乘以标准差,才能得到对应的方差。如X=>3X,方差是原来的9倍。
  
  
-4. `alphas_prod`:$\bar{\alpha}_t = \prod_{i=0}^{t}\alpha_i$+ 
 + 
 + 
 +4. alphas_prod:$\bar{\alpha}_t = \prod_{i=0}^{t}\alpha_i$
  
 它表示从第 $0$ 步到第 $t$ 步,**所有保留比例连乘之后的结果**。 它表示从第 $0$ 步到第 $t$ 步,**所有保留比例连乘之后的结果**。
行 218: 行 234:
  
  
-5. `alphas_prod_sqrt`:$\sqrt{\bar{\alpha}_t}$+5. alphas_prod_sqrt:$\sqrt{\bar{\alpha}_t}$
  
 这个量是从原图 $x_0$ 直接生成第 $t$ 步噪声图 $x_t$ 时的关键系数。 这个量是从原图 $x_0$ 直接生成第 $t$ 步噪声图 $x_t$ 时的关键系数。
行 559: 行 575:
  
  
-反向过程+====== 反向过程 ====== 
  
 反向过程 $p$ 的目的是:根据扩散链中的当前样本 $x_t$,去近似恢复前一步的样本 $x_{t-1}$。在实际情况下,这种近似 $p(x_{t-1}|x_t)$ 必须在不知道 $x_0$ 的前提下完成。 反向过程 $p$ 的目的是:根据扩散链中的当前样本 $x_t$,去近似恢复前一步的样本 $x_{t-1}$。在实际情况下,这种近似 $p(x_{t-1}|x_t)$ 必须在不知道 $x_0$ 的前提下完成。
行 683: 行 700:
  
 {{.:pasted:20260313-133231.png}} {{.:pasted:20260313-133231.png}}
 +
 +
 +
 +如果噪声 $\epsilon$ 能够被正确预测,那么我们就可以利用公式 $x_0=\frac{1}{\sqrt{\bar{\alpha}_t}}(x_t-\sqrt{1-\bar{\alpha}_t}\epsilon)$来预测 $x_0$:
 +
 +<code python>
 +import torch                              # 导入 PyTorch 库,用于张量操作
 +import torch.nn.functional as F           # 导入 PyTorch 的函数式 API
 +import numpy as np                        # 导入 NumPy 库,用于数值计算
 +import matplotlib as mpl                  # 导入 Matplotlib 库的主模块
 +import matplotlib.pyplot as plt           # 导入 Matplotlib 的绘图模块
 +import imageio.v2 as imageio              # 导入 imageio 库,用于读取图片文件
 +
 +mpl.rcParams['figure.figsize'] = (12, 8)  # 设置 Matplotlib 图表默认尺寸
 +
 +# 读取图片,像素值归一化到[0,1],转为 PyTorch 浮点张量
 +img = torch.FloatTensor(imageio.imread('imgs/hills_2.png') / 255)
 +
 +# 值域转换函数:将图像从 [0,1] 转换到 [-1,+1](扩散模型标准输入值域)
 +def input_T(input):
 +    # [0,1] -> [-1,+1]
 +    return 2 * input - 1
 +
 +# 值域转换函数:将图像从 [-1,+1] 转换到 [0,1](用于显示)
 +def output_T(input):
 +    # [-1,+1] -> [0,1]
 +    return (input + 1) / 2
 +
 +# 显示图像函数,自动转换值域并裁剪到 [0,1]
 +def show(input):
 +    plt.imshow(output_T(input).clip(0, 1))
 +
 +
 +# ========== 扩散模型超参数设置 ==========
 +T = 100  # 时间步总数
 +
 +# 定义 beta 调度(线性调度)
 +betas = torch.linspace(0.0001, 0.02, T)  # 从 0.0001 线性增加到 0.02
 +
 +# 计算 alpha 相关参数
 +alphas = 1 - betas                       # alpha_t = 1 - beta_t
 +alphas_cumprod = torch.cumprod(alphas, dim=0)  # 累积乘积:ᾱ_t = ∏(1-β_s)
 +alphas_cumprod_sqrt = torch.sqrt(alphas_cumprod)  # √ᾱ_t
 +
 +# 设置前向跳转的时间步
 +t_step = 50
 +
 +
 +def forward_jump(t, condition_img, return_noise=False):
 +    """
 +        forward jump: 0 -> t
 +        直接跳转加噪:从初始时刻 0 直接加噪到时刻 t
 +        
 +        根据扩散模型公式:x_t = √ᾱ_t * x_0 + √(1-ᾱ_t) * ε
 +        其中 ε ~ N(0, I)
 +    """
 +    assert t >= 0
 +
 +    mean = alphas_cumprod_sqrt[t] * condition_img
 +    std = (1 - alphas_cumprod[t]).sqrt()
 +
 +    # 从正态分布采样
 +    if not return_noise:
 +        return mean + std * torch.randn_like(condition_img)
 +    else:
 +        noise = torch.randn_like(condition_img)
 +        return mean + std * noise, noise
 +
 +
 +# ========== 主程序 ==========
 +if __name__ == "__main__":
 +    # 将图像转换到 [-1, 1] 值域
 +    img_ = input_T(img)
 +
 +    # 执行前向跳转加噪,获取加噪后的图像和噪声
 +    x_t, noise = forward_jump(t_step, img_, return_noise=True)
 +
 +    # 根据 x_t 和噪声预测 x_0
 +    # 由 x_t = √ᾱ_t * x_0 + √(1-ᾱ_t) * ε 可得:
 +    # x_0 = (x_t - √(1-ᾱ_t) * ε) / √ᾱ_t
 +    x_0_pred = (x_t - (1 - alphas_cumprod[t_step]).sqrt() * noise) / alphas_cumprod_sqrt[t_step]
 +
 +    # 可视化结果:x_t(加噪图像)、x_0 预测值、原始 x_0
 +    plt.subplot(1, 3, 1)
 +    show(x_t)
 +    plt.title(r'$x_t$ ($\ell_1$: {:.3f})'.format(F.l1_loss(x_t, img_)))
 +    plt.axis('off')
 +
 +    plt.subplot(1, 3, 2)
 +    show(x_0_pred)
 +    plt.title(r'$x_0$ prediction ($\ell_1$: {:.3f})'.format(F.l1_loss(x_0_pred, img_)))
 +    plt.axis('off')
 +
 +    plt.subplot(1, 3, 3)
 +    show(img_)
 +    plt.title(r'$x_0$')
 +    plt.axis('off')
 +
 +    plt.suptitle(f'x_0 Prediction from x_t (t={t_step})', y=0.98)
 +    plt.tight_layout()
 +    plt.show()
 +
 +</code>
 +
 +
 +{{.:pasted:20260313-134755.png}}
 +
 +这里的含义是:
 +
 +  * 第一幅图显示当前带噪样本 $x_t$
 +  * 第二幅图显示根据预测噪声反推出的 $x_0$,记作 $x_0\_pred$
 +  * 第三幅图显示真实的干净样本 $x_0$
 +
 +如果预测的噪声足够准确,那么 $x_0\_pred$ 就会接近真实的 $x_0$。
 +
 +
 +
 +近似或已知 $x_0$ 后,可以进一步近似 $t-1$ 步的均值
 +
 +一旦我们得到了对 $x_0$ 的近似(或者直接知道真实的 $x_0$),就可以利用公式 (4) 来近似第 $t-1$ 步对应分布的均值:
 +
 +<code python>
 +# estimate mean
 +mean_pred = x_0_pred * (alphas_cumprod_sqrt[t_step-1] * betas[t_step]) / (1 - alphas_cumprod[t_step]) \
 +          + x_t * (alphas_sqrt[t_step] * (1 - alphas_cumprod[t_step-1])) / (1 - alphas_cumprod[t_step])
 +
 +# let's compare it to ground truth mean of the previous step (requires knowledge of x_0)
 +mean_gt = alphas_cumprod_sqrt[t_step-1] * img_
 +</code>
 +
 +其中:
 +
 +  * `mean_pred`:根据预测得到的 $x_0$ 和当前样本 $x_t$ 计算出的反向过程均值近似
 +  * `mean_gt`:真实的前一步均值,它需要知道真实的 $x_0$
 +
 +这里的 `mean_gt` 本质上对应的是前向过程中从干净图像得到的“真实均值”。
 +
 +----
 +
 +为什么 `mean_pred` 的误差通常会更大
 +
 +由于公式 (4) 中的反向过程均值估计 $\tilde{\mu}_\theta$,本质上是对带噪的 $x_t$ 和估计出来的 $x_0$ 做一种线性组合,因此它通常会比前向过程直接得到的均值误差更大。
 +
 +原因是:
 +
 +  * $x_t$ 本身仍然含有噪声
 +  * $x_0\_pred$ 只是估计值,不是真实值
 +  * 因此两者线性组合得到的均值仍然会带来额外误差
 +
 +而前向过程中的均值通常只是对干净样本乘上一个标量,因此更精确。
 +
 +下面的代码展示了这种比较:
 +
 +<code python>
 +plt.subplot(1,3,1)
 +show(x_t)
 +plt.title('$x_t$   ($\ell_1$: {:.3f})'.format(F.l1_loss(x_t, img_)))
 +
 +plt.subplot(1,3,2)
 +show(mean_pred)
 +plt.title(r'$\tilde{\mu}_{t-1}$' + '  ($\ell_1$: {:.3f})'.format(F.l1_loss(mean_pred, img_)))
 +
 +plt.subplot(1,3,3)
 +show(mean_gt)
 +plt.title(r'$\mu_{t-1}$' + '  ($\ell_1$: {:.3f})'.format(F.l1_loss(mean_gt, img_)))
 +</code>
 +
 +{{.:pasted:20260313-135602.png}}
 +
 +这三幅图分别表示:
 +
 +  * 左图:当前带噪图像 $x_t$
 +  * 中图:反向过程估计的均值 $\tilde{\mu}_{t-1}$
 +  * 右图:真实均值 $\mu_{t-1}$
 +
 +比较它们的 $\ell_1$ 误差,就可以看出估计值和真实值之间的差别。
 +
 +----
 +
 +得到 `mean_pred` 之后,就可以定义前一步的分布
 +
 +当我们获得了 `mean_pred`,也就是 $\tilde{\mu}_t$ 之后,就可以将前一步的条件分布定义为:
 +
 +$$\tilde{\beta}_t = \beta_t \tag{6}$$
 +
 +$$p_\theta(x_{t-1}|x_t) := \mathcal{N}(x_{t-1}; \tilde{\mu}_\theta(x_t,t), \tilde{\beta}_t I) \tag{7}$$
 +
 +这个定义表示:
 +
 +  * 均值使用网络估计出来的 $\tilde{\mu}_\theta(x_t,t)$
 +  * 方差使用 $\tilde{\beta}_t$
 +  * 从而构造出反向过程中的高斯分布
 +
 +
 +
 +重要说明
 +
 +> 下面的实验应被看作一种模拟。
 +> 在实际训练和推理中,网络必须预测以下三者之一:
 +>   * $\epsilon$
 +>   * $x_0$
 +>   * $\tilde{\mu}_\theta$
 +>
 +> 而这里的 $\epsilon$ 值只是直接代入使用的,并不是真实模型预测出来的结果。
 +
 +
 +
 +`reverse_step` 函数
 +
 +下面这个函数实现了一次反向扩散步骤:
 +
 +<code python>
 +def reverse_step(epsilon, x_t, t_step, return_noise=False):
 +    
 +    # estimate x_0 based on epsilon
 +    x_0_pred = (x_t - (1 - alphas_cumprod[t_step]).sqrt() * epsilon) / (alphas_cumprod_sqrt[t_step])
 +
 +    if t_step == 0:
 +        sample = x_0_pred
 +        noise = torch.zeros_like(x_0_pred)
 +    else:
 +        # estimate mean
 +        mean_pred = x_0_pred * (alphas_cumprod_sqrt[t_step-1] * betas[t_step]) / (1 - alphas_cumprod[t_step]) \
 +                  + x_t * (alphas_sqrt[t_step] * (1 - alphas_cumprod[t_step-1])) / (1 - alphas_cumprod[t_step])
 +
 +        # compute variance
 +        beta_pred = betas[t_step].sqrt() if t_step != 0 else 0
 +
 +        sample = mean_pred + beta_pred * torch.randn_like(x_t)
 +
 +        # this noise is only computed for simulation purposes (since x_0_pred is not known normally)
 +        noise = (sample - x_0_pred * alphas_cumprod_sqrt[t_step-1]) / (1 - alphas_cumprod[t_step-1]).sqrt()
 +
 +    if return_noise:
 +        return sample, noise
 +    else:
 +        return sample
 +</code>
 +
 +----
 +
 +`reverse_step()` 的逻辑讲解
 +
 +1. 根据 $\epsilon$ 先恢复 $x_0$
 +
 +<code python>
 +x_0_pred = (x_t - (1 - alphas_cumprod[t_step]).sqrt() * epsilon) / (alphas_cumprod_sqrt[t_step])
 +</code>
 +
 +这一行对应公式 (5):
 +
 +$$x_0=\frac{1}{\sqrt{\bar{\alpha}_t}}(x_t-\sqrt{1-\bar{\alpha}_t}\epsilon)$$
 +
 +作用是:
 +
 +  * 输入当前带噪样本 $x_t$
 +  * 输入预测得到的噪声 $\epsilon$
 +  * 反推出干净图像的估计值 $x_0$
 +
 +----
 +
 +2. 如果已经到达 $t=0$
 +
 +<code python>
 +if t_step == 0:
 +    sample = x_0_pred
 +    noise = torch.zeros_like(x_0_pred)
 +</code>
 +
 +当 `t_step == 0` 时,说明已经到达反向过程的最后一步,不需要再继续去噪。
 +
 +因此:
 +
 +  * 直接把 $x_0\_pred$ 当作最终结果
 +  * 此时噪声设为全零
 +
 +----
 +
 +3. 如果 $t > 0$,先估计反向均值
 +
 +<code python>
 +mean_pred = x_0_pred * (alphas_cumprod_sqrt[t_step-1] * betas[t_step]) / (1 - alphas_cumprod[t_step]) \
 +          + x_t * (alphas_sqrt[t_step] * (1 - alphas_cumprod[t_step-1])) / (1 - alphas_cumprod[t_step])
 +</code>
 +
 +这一行对应公式 (4):
 +
 +$$\tilde{\mu}_\theta = \frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1-\bar{\alpha}_t}x_0 + \frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1-\bar{\alpha}_t}x_t$$
 +
 +只不过代码里用的是预测值 `x_0_pred` 来代替真实的 $x_0$。
 +
 +意思是:
 +
 +  * 综合当前带噪样本 $x_t$
 +  * 再结合恢复出的干净样本估计 $x_0\_pred$
 +  * 得到前一步分布的均值估计 `mean_pred`
 +
 +----
 +
 +4. 计算标准差
 +
 +<code python>
 +beta_pred = betas[t_step].sqrt() if t_step != 0 else 0
 +</code>
 +
 +这里使用:
 +
 +  * 方差 $\tilde{\beta}_t = \beta_t$
 +  * 所以标准差为 $\sqrt{\beta_t}$
 +
 +这是为了后面从高斯分布中采样:
 +
 +$$x_{t-1} \sim \mathcal{N}(\tilde{\mu}_\theta, \tilde{\beta}_t I)$$
 +
 +----
 +
 +5. 从反向分布中采样
 +
 +<code python>
 +sample = mean_pred + beta_pred * torch.randn_like(x_t)
 +</code>
 +
 +这一步就是从高斯分布中真正采样出 $x_{t-1}$。
 +
 +也就是:
 +
 +  * 均值为 `mean_pred`
 +  * 标准差为 `beta_pred`
 +  * 加上一份标准高斯噪声
 +
 +对应采样形式可以写成:
 +
 +$$x_{t-1} = \tilde{\mu}_\theta + \sqrt{\tilde{\beta}_t}\,z,\quad z\sim\mathcal{N}(0,I)$$
 +
 +----
 +
 +6. 计算 `noise` 仅用于模拟分析
 +
 +<code python>
 +noise = (sample - x_0_pred * alphas_cumprod_sqrt[t_step-1]) / (1 - alphas_cumprod[t_step-1]).sqrt()
 +</code>
 +
 +这里的 `noise` 只是为了实验分析而额外算出来的。
 +
 +因为在真实的反向扩散推理中:
 +
 +  * 我们并不知道真实的 $x_0$
 +  * 这里只是因为做实验,才利用 `x_0_pred` 反推一个噪声值
 +
 +所以注释里才特别说明:
 +
 +  * 这个噪声只是为了模拟目的而计算
 +  * 并不是实际系统中能直接获得的量
 +
 +
 +
 +7. 是否返回噪声
 +
 +<code python>
 +if return_noise:
 +    return sample, noise
 +else:
 +    return sample
 +</code>
 +
 +如果 `return_noise=True`:
 +
 +  * 返回当前反向步骤采样结果 `sample`
 +  * 同时返回额外计算的 `noise`
 +
 +否则:
 +
 +  * 只返回 `sample`
 +
 +
 +
 +
 +
 +这个 `reverse_step()` 函数做的事情是:
 +
 +  * 先根据预测噪声 $\epsilon$ 还原出 $x_0$
 +  * 再根据 $x_t$ 和 $x_0$ 的估计值计算反向分布均值
 +  * 然后从这个高斯分布中采样得到 $x_{t-1}$
 +
 +也就是说,它实现了扩散模型中的一次“去噪反推”。
 +
 +
 +{{.:pasted:20260313-135843.png}}
 +
 +{{.:pasted:20260313-135917.png}}

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • 深度学习/扩散模型/class1.1773379983.txt.gz
  • 最后更改: 2026/03/13 13:33
  • 张叶安