文章目錄
- 1. 前言
- 2. 前向擴散過程(Forward Diffusion)
- 3. 反向生成過程(Reverse Process)
- 4. 訓練和推理過程中的偽代碼
- 5. 訓練過程代碼實現(Training)
- 5.1 時間嵌入模塊——TimeEmbedding
- 5.2 前向擴散過程——GaussianDiffusionTrainer
- 6. 推理過程代碼實現(Sampling)
1. 前言
??擴散模型參考的論文:Denoising Diffusion Probabilistic Models。擴散模型(Denoising Diffusion Probabilistic Models, DDPM)是一種生成模型,它通過學習逆轉一個逐步向數據中添加噪聲的過程來生成新數據。這種方法受到了非平衡熱力學的啟發,其中系統從有序狀態逐漸過渡到無序狀態(即增加了噪聲)。DDPM的目標是學習如何逆轉這個過程,從而從完全隨機的狀態恢復出有意義的數據。擴散模型的基本思想是:通過添加噪聲將原始數據逐步擾亂成高斯分布(前向擴散過程),然后學習如何逐步去噪恢復原始數據(反向生成過程)。
補充知識(重參數采樣(參數重整化)):
-
如果隨機變量 X \mathbf{X} X 服從均值為 μ \mu μ,方差為 σ 2 \sigma^2 σ2 的高斯分布(正態分布),即 X ~ N ( μ , σ 2 ) \mathbf{X} \sim \mathcal{N}(\mu, \sigma^2) X~N(μ,σ2)。對隨機變量 X \mathbf{X} X 進行標準化的步驟為: X ? μ σ = Z ~ N ( 0 , 1 ) \frac{\mathbf{X} - \mu}{\sigma} = \mathbf{Z} \sim \mathcal{N}(0, 1) σX?μ?=Z~N(0,1),這里的 Z \mathbf{Z} Z 是一個新的隨機變量,隨機變量 Z \mathbf{Z} Z 服從均值為0,方差為1的標準正態分布。 X ? μ \mathbf{X} - \mu X?μ 表示將原分布中心平移到原點(去中心化),除以 σ \sigma σ 表示將尺度標準化為單位標準差。
-
我們先從標準正態分布 Z ~ N ( 0 , 1 ) \mathbf{Z} \sim \mathcal{N}(0, 1) Z~N(0,1) 進行采樣,經過線性變換 X = μ + σ ? Z \mathbf{X}=\mu+\sigma\cdot \mathbf{Z} X=μ+σ?Z 得到的數據分布,等價于直接從均值為 μ \mu μ,方差為 σ 2 \sigma^2 σ2 的正態分布 X ~ N ( μ , σ 2 ) \mathbf{X} \sim \mathcal{N}(\mu, \sigma^2) X~N(μ,σ2) 中進行采樣。下面給出具體推導和驗證:
(1)標準正態分布的性質: Z ~ N ( 0 , 1 ) \mathbf{Z} \sim \mathcal{N}(0, 1) Z~N(0,1),即均值為 0,方差為 1。
(2)線性變換的均值和方差:對 Z \mathbf{Z} Z 進行線性變換 X = μ + σ ? Z \mathbf{X}=\mu+\sigma\cdot \mathbf{Z} X=μ+σ?Z,均值為: E [ X ] = E [ μ + σ ? Z ] = σ ? E [ Z ] + μ = σ ? 0 + μ = μ \mathbb{E}[\mathbf{X}] =\mathbb{E}[\mathbf{\mu+\sigma\cdot \mathbf{Z}}]= \sigma \cdot \mathbb{E}[\mathbf{Z}] + \mu = \sigma \cdot 0 + \mu = \mu E[X]=E[μ+σ?Z]=σ?E[Z]+μ=σ?0+μ=μ;方差為: D [ X ] = D [ μ + σ ? Z ] = σ 2 ? D [ Z ] = σ 2 ? 1 = σ 2 \mathbb{D}[\mathbf{X}] =\mathbb{D}[\mathbf{\mu+\sigma\cdot \mathbf{Z}}] = \sigma^2 \cdot \mathbb{D}[\mathbf{Z}] =\sigma^2 \cdot 1= \sigma^2 D[X]=D[μ+σ?Z]=σ2?D[Z]=σ2?1=σ2。
(3)正態分布的封閉性:線性變換不改變正態性,因此對 Z \mathbf{Z} Z 進行線性變換 X = μ + σ ? Z \mathbf{X}=\mu+\sigma\cdot \mathbf{Z} X=μ+σ?Z 仍服從正態分布,即 X ~ N ( μ , σ 2 ) \mathbf{X} \sim \mathcal{N}(\mu, \sigma^2) X~N(μ,σ2)。
2. 前向擴散過程(Forward Diffusion)
??前向加噪過程是一個固定的馬爾可夫鏈,對干凈的原始圖像數據 x 0 \mathbf{x}_0 x0? 逐步添加噪聲依次得到 x 1 , x 2 , … , x T \mathbf{x}_1, \mathbf{x}_2, \ldots,\mathbf{x}_T x1?,x2?,…,xT?,直到第 T T T 步時圖像數據 x T \mathbf{x}_T xT? 接近高斯噪聲(純噪聲,完全看不出內容)。下面給出前向擴散過程中的核心數學公式:
??(1)基于前一時刻( t ? 1 t-1 t?1)的圖像數據 x t ? 1 \mathbf{x}_{t-1} xt?1? 和當前時刻( t t t)加入的標準高斯噪聲 ε t ~ N ( 0 , 1 ) \pmb{\varepsilon}_{t}\sim \mathcal{N}(0, 1) εt?~N(0,1),我們可獲取 t t t 時刻噪聲圖像 x t \mathbf{x}_t xt? 對應的數學表達式:
x t = 1 ? β t x t ? 1 + β t ε t ( 1 ) \mathbf{x}_t = \sqrt{1 - \beta_t} \, \mathbf{x}_{t-1} + \sqrt{\beta_t} \, \pmb{\varepsilon}_{t} \qquad \qquad (1) xt?=1?βt??xt?1?+βt??εt?(1)
??其中 β t \beta_t βt? 是在 ( 0 , 1 ) (0,1) (0,1) 之間逐步增長的噪聲強度( β t \beta_t βt? 可看作加入的高斯噪聲權重),一般來說隨著 t t t 的增大, β t \beta_t βt? 的取值就越大。圖像數據從 x t ? 1 \mathbf{x}_{t-1} xt?1? 到 x t \mathbf{x}_{t} xt? 的加噪過程服從正態分布,如下所示:
q ( x t ∣ x t ? 1 ) = N ( x t ; 1 ? β t x t ? 1 , β t I ) ( 2 ) q(\mathbf{x}_t | \mathbf{x}_{t-1}) = \mathcal{N}(\mathbf{x}_t; \sqrt{1 - \beta_t}\mathbf{x}_{t-1}, \beta_t \mathbf{I}) \qquad \qquad (2) q(xt?∣xt?1?)=N(xt?;1?βt??xt?1?,βt?I)(2)
??其中, x t \mathbf{x}_t xt? 是以均值為 1 ? β t x t ? 1 \sqrt{1 - \beta_t}\mathbf{x}_{t-1} 1?βt??xt?1?,方差為 β t \beta_t βt? 的高斯分布, I \mathbf{I} I 表示單位矩陣。
??(2)任意時刻的噪聲圖像 x t \mathbf{x}_t xt? 可直接由 x 0 \mathbf{x}_0 x0? 計算(無需迭代),我們定義 α t = 1 ? β t \alpha_t=1 - \beta_t αt?=1?βt?,計算公式如下:
x t = α  ̄ t x 0 + 1 ? α  ̄ t ε t ( 3 ) \mathbf{x}_t= \sqrt{\overline{\alpha}_t} \, \mathbf{x}_0 + \sqrt{1 - \overline{\alpha}_t} \, \pmb{\varepsilon}_t \qquad \qquad (3) xt?=αt??x0?+1?αt??εt?(3)
??其中, α ˉ t = ∏ s = 1 t ( 1 ? β s ) = ( 1 ? β 1 ) ? ( 1 ? β 2 ) … ( 1 ? β t ) = α 1 ? α 2 … α t \bar{\alpha}_t = \prod_{s=1}^{t}(1 - \beta_s)=(1 - \beta_1)\cdot(1 - \beta_2)\ldots(1 - \beta_t)=\alpha_1 \cdot \alpha_2 \ldots \alpha_t αˉt?=∏s=1t?(1?βs?)=(1?β1?)?(1?β2?)…(1?βt?)=α1??α2?…αt?。圖像數據從 x 0 \mathbf{x}_{0} x0? 到 x t \mathbf{x}_{t} xt? 的加噪過程服從正態分布,如下所示:
q ( x t ∣ x 0 ) = N ( x t ; α ˉ t x 0 , ( 1 ? α ˉ t ) I ) ( 4 ) q(\mathbf{x}_t \mid \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t; \sqrt{\bar{\alpha}_t}\mathbf{x}_0, (1 - \bar{\alpha}_t)\mathbf{I}) \qquad \qquad (4) q(xt?∣x0?)=N(xt?;αˉt??x0?,(1?αˉt?)I)(4)
??其中, x t \mathbf{x}_t xt? 是以均值為 α ˉ t x 0 \sqrt{\bar{\alpha}_t}\mathbf{x}_0 αˉt??x0?,方差為 ( 1 ? α ˉ t ) (1 - \bar{\alpha}_t) (1?αˉt?) 的高斯分布, I \mathbf{I} I 表示單位矩陣。
3. 反向生成過程(Reverse Process)
??這個過程是一個可學習的馬爾可夫鏈,目標是從純高斯噪聲 x T \mathbf{x}_T xT? 開始一步步去噪直到生成清晰的圖像。反向去噪的核心數學公式定義為:
x t ? 1 = 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) + σ t z ( 5 ) \mathbf{x}_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( \mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right) + \sigma_t \mathbf{z} \qquad \qquad (5) xt?1?=αt??1?(xt??1?αt??1?αt??εθ?(xt?,t))+σt?z(5)
??其中, ε θ ( x t , t ) \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) εθ?(xt?,t) 是 UNet網絡輸出的預測噪聲; σ t = β t ? 1 ? α ˉ t ? 1 1 ? α ˉ t \sigma_t=\sqrt{\beta_t \cdot \frac{1 - \bar{\alpha}_{t-1}}{1 - \bar{\alpha}_t}} σt?=βt??1?αˉt?1?αˉt?1???; z \mathbf{z} z 是標準高斯噪聲 z ~ N ( 0 , 1 ) \mathbf{z}\sim \mathcal{N}(0, 1) z~N(0,1)。高斯噪聲數據從 x t \mathbf{x}_{t} xt? 到 x t ? 1 \mathbf{x}_{t-1} xt?1? 的去噪過程服從正態分布,如下所示:
p θ ( x t ? 1 ∣ x t , x 0 ) = N ( x t ? 1 ; 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) , β t ? 1 ? α ˉ t ? 1 1 ? α ˉ t I ) ( 6 ) p_{\theta}(\mathbf{x}_{t-1} \mid \mathbf{x}_t, \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_{t-1};\frac{1}{\sqrt{\alpha_t}} \left(\mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right), \beta_t \cdot \frac{1 - \bar{\alpha}_{t-1}}{1 - \bar{\alpha}_t}\mathbf{I}) \qquad \qquad (6) pθ?(xt?1?∣xt?,x0?)=N(xt?1?;αt??1?(xt??1?αt??1?αt??εθ?(xt?,t)),βt??1?αˉt?1?αˉt?1??I)(6)
??其中, x t ? 1 \mathbf{x}_{t-1} xt?1? 是以均值為 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) \frac{1}{\sqrt{\alpha_t}} \left( \mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right) αt??1?(xt??1?αt??1?αt??εθ?(xt?,t)),方差為 β t ? 1 ? α ˉ t ? 1 1 ? α ˉ t \beta_t \cdot \frac{1 - \bar{\alpha}_{t-1}}{1 - \bar{\alpha}_t} βt??1?αˉt?1?αˉt?1?? 的高斯分布, I \mathbf{I} I 表示單位矩陣。
4. 訓練和推理過程中的偽代碼
1. 訓練流程(Training)
??從上面公式 (3) 可知: x t = α  ̄ t x 0 + 1 ? α  ̄ t ε t \mathbf{x}_t= \sqrt{\overline{\alpha}_t} \, \mathbf{x}_0 + \sqrt{1 - \overline{\alpha}_t} \, \pmb{\varepsilon}_t xt?=αt??x0?+1?αt??εt?,因此 ε θ ( α  ̄ t x 0 + 1 ? α  ̄ t ε , t ) = ε θ ( x t , t ) \pmb{\varepsilon}_{\theta}(\sqrt{\overline{\alpha}_t} \, \mathbf{x}_0 + \sqrt{1 - \overline{\alpha}_t} \, \pmb{\varepsilon},t)=\pmb{\varepsilon}_\theta(\mathbf{x}_t, t) εθ?(αt??x0?+1?αt??ε,t)=εθ?(xt?,t) 表示UNet網絡在 t t t 時刻輸出的預測噪聲。
(1)第一步:隨機采樣時間步數(時間 t t t 不固定),并對時間步數 t t t 進行位置編碼。例如 t = 100 t=100 t=100(從1到T=1000中隨機選),隨后編碼—>Time step embedding(t),這里的Time step embedding 使用標準的位置編碼方式。
(2)第二步:隨機采樣標準高斯噪聲 ε ~ N ( 0 , 1 ) \pmb{\varepsilon} \sim \mathcal{N}(0, 1) ε~N(0,1),利用公式 (3) x t = α  ̄ t x 0 + 1 ? α  ̄ t ε \mathbf{x}_t= \sqrt{\overline{\alpha}_t} \, \mathbf{x}_0 + \sqrt{1 - \overline{\alpha}_t} \, \pmb{\varepsilon} xt?=αt??x0?+1?αt??ε 對原始圖像數據 x 0 \mathbf{x}_0 x0? 進行加噪,得到噪聲圖像 x 100 \mathbf{x}_{100} x100?。
(3)第三步:將得到的噪聲圖像 x 100 \mathbf{x}_{100} x100? 和編碼后的時間步 Time step embedding(t) 共同送到類似于UNet神經網絡模型中,Unet網絡模型會輸出一個預測噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?。
(4)第四步:計算預測噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ? 與 真實噪聲 ε \pmb{\varepsilon} ε 之間的誤差(MSE Loss,使用均方誤差損失函數),優化網絡參數,使得模型越來越擅長預測噪聲。
2. 推理過程(Sampling / Generation)
??推理就是生成新樣本的過程,也叫采樣,采樣過程的核心思想是:逐步(迭代)去噪聲。
(1)第一步:假設 T = 1000 \mathbf{T}=1000 T=1000,從標準正態分布中隨機生成一張全是高斯噪聲的圖像 x 1000 \mathbf{x}_{1000} x1000?。
(2)第二步:把 x 1000 \mathbf{x}_{1000} x1000? 和 t = 1000 \mathbf{t}=1000 t=1000 輸入到 UNet 網絡中來獲取預測噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?。
(3)第三步:將預測的噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?( ε θ ( x t , t ) \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) εθ?(xt?,t)) 代入公式 (5) x t ? 1 = 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) + σ t z \mathbf{x}_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( \mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right) + \sigma_t \mathbf{z} xt?1?=αt??1?(xt??1?αt??1?αt??εθ?(xt?,t))+σt?z 中,獲取前一時刻 x 999 \mathbf{x}_{999} x999? 的圖像數據;
(4)第四步:逐步迭代,將 x T = x 999 , x 998 , … , x 2 \mathbf{x}_{T}=\mathbf{x}_{999},\mathbf{x}_{998}, \ldots,\mathbf{x}_{2} xT?=x999?,x998?,…,x2? 和 t = 999 , 998 , … , 2 \mathbf{t}=999,998,\ldots,2 t=999,998,…,2 依次輸入到 UNet 網絡模型中來獲取預測噪聲,直到獲得 x 1 \mathbf{x}_{1} x1? 時的圖像數據。
(5)第五步:將 x 1 \mathbf{x}_{1} x1? 和 t = 1 \mathbf{t}=1 t=1 輸入到 UNet 網絡中來預測噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?,接著將預測的噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?( ε θ ( x t , t ) \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) εθ?(xt?,t)) 代入 x t ? 1 = 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) \mathbf{x}_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( \mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right) xt?1?=αt??1?(xt??1?αt??1?αt??εθ?(xt?,t)) 中,獲取圖像數據 x 0 \mathbf{x}_{0} x0? 。
5. 訓練過程代碼實現(Training)
參考來源:【代碼精讀】Diffusion Model 擴散模型
5.1 時間嵌入模塊——TimeEmbedding
class TimeEmbedding(nn.Module):"""定義``時間嵌入``模塊"""def __init__(self, T, d_model, dim):"""初始的time-embedding是由一系列不同頻率的正弦、余弦函數采樣值表示,即:[[sin(w_0*x), cos(w_0*x)],[sin(w_1*x), cos(w_1*x)],...,[sin(w_T)*x, cos(w_T*x)]], 維度為 T * d_model在本實例中,頻率范圍是[0:T], x在1e-4~1范圍,共d_model // 2個離散點;將sin, cos并在一起組成d_model個離散點Args:T: int, 總迭代步數,本實例中T=1000d_model: 輸入維度(通道數/初始embedding長度)dim: 輸出維度(通道數)"""assert d_model % 2 == 0super().__init__()# 前兩行計算x向量,共64個點emb = torch.arange(0, d_model, step=2) / d_model * math.log(10000)emb = torch.exp(-emb)# T個時間位置組成頻率部分pos = torch.arange(T).float()# 兩兩相乘構成T*(d_model//2)的矩陣,并assert形狀emb = pos[:, None] * emb[None, :]assert list(emb.shape) == [T, d_model // 2]# 計算不同頻率sin, cos值,判斷形狀,并reshape到T*d_modelemb = torch.stack([torch.sin(emb), torch.cos(emb)], dim=-1)assert list(emb.shape) == [T, d_model // 2, 2]emb = emb.view(T, d_model)# MLP層,通過初始編碼計算提取特征后的embedding# 包含兩個線性層,第一個用swish激活函數,第二個不使用激活函數self.timembedding = nn.Sequential(nn.Embedding.from_pretrained(emb),nn.Linear(d_model, dim),Swish(),nn.Linear(dim, dim),)self.initialize()def initialize(self):for module in self.modules():if isinstance(module, nn.Linear):init.xavier_uniform_(module.weight)init.zeros_(module.bias)def forward(self, t):emb = self.timembedding(t)return emb
??TimeEmbedding 類是擴散模型中用于生成時間步嵌入的核心組件,其作用是將離散的時間步轉換為富含時序信息的連續向量表示。擴散模型通過多步迭代逐步去噪,模型需感知當前所處的時間步以調整去噪行為。TimeEmbedding 將時間步編碼為高維向量,使模型能捕獲不同時間步的動態特征。其設計借鑒了Transformer的位置編碼思想,但針對擴散模型的時間特性進行了調整。
5.2 前向擴散過程——GaussianDiffusionTrainer
# ``extract``函數的作用是從v這一序列中按照索引t取出需要的數,然后reshape到輸入數據x的維度
def extract(v, t, x_shape):"""Extract some coefficients at specified timesteps, then reshape to[batch_size, 1, 1, 1, 1, ...] for broadcasting purposes."""device = t.device# ``torch.gather``的用法建議看https://zhuanlan.zhihu.com/p/352877584的第一條評論# 在此處的所有調用實例中,v都是一維,可以看作是索引取值,即等價v[t], t大小為[batch_size, 1]out = torch.gather(v, index=t, dim=0).float().to(device)# 再把索引到的值reshape到[batch_size, 1, 1, ...], 維度和x_shape相同return out.view([t.shape[0]] + [1] * (len(x_shape) - 1))# ``GaussianDiffusionTrainer``包含了Diffusion Model的前向過程(加噪) & 訓練過程
class GaussianDiffusionTrainer(nn.Module):def __init__(self, model, beta_1, beta_T, T):"""初始化前向模型Args:model: 骨干模型,主流為U-Net+Attentionbeta_1: beta的起始值,本實例中取1e-4beta_T: bata在t=T時的值,本實例中取0.2T: 時間步數, 本實例中取1000"""super().__init__()# 參數賦值self.model = modelself.T = T# 等間隔得到beta_1到beta_T之間共T個step對應的beta值,組成序列存為類成員(后邊可以用``self.betas``訪問)self.register_buffer('betas', torch.linspace(beta_1, beta_T, T).double())# 根據公式,令alphas = 1 - betasalphas = 1. - self.betas# 根據公式,計算alpha連乘結果,存為alphas_bar# ``torch.cumprod``用于計算一個序列每個數與其前面所有數連乘的結果,得到一個序列,長度等于原序列長度# 例如:# a = torch.tensor([2,3,1,4])# b = torch.cumprod(a, dim=0)其實就等于torch.tensor([2, 2*3, 2*3*1, 2*3*1*4]) = torch.tensor([2, 6, 6, 24])alphas_bar = torch.cumprod(alphas, dim=0)# calculations for diffusion q(x_t | x_{t-1}) and others# 根據公式計算sqrt(alphas_bar)以及sqrt(1-alphas_bar)分別作為正向擴散的均值和標準差,存入類成員# 可用``self.sqrt_alphas_bar``和``sqrt_one_minus_alphas_bar``來訪問self.register_buffer('sqrt_alphas_bar', torch.sqrt(alphas_bar))self.register_buffer('sqrt_one_minus_alphas_bar', torch.sqrt(1. - alphas_bar))def forward(self, x_0):"""Algorithm 1."""# 從0~T中隨機選batch_size個時間點t = torch.randint(self.T, size=(x_0.shape[0], ), device=x_0.device)# 參數重整化技巧,先生成均值為0方差為1的高斯分布,再通過乘標準差加均值的方式用于間接采樣noise = torch.randn_like(x_0)x_t = (extract(self.sqrt_alphas_bar, t, x_0.shape) * x_0 +extract(self.sqrt_one_minus_alphas_bar, t, x_0.shape) * noise)# 做一步反向擴散,希望模型可以預測出加入的噪聲,也就是公式中的z_tloss = F.mse_loss(self.model(x_t, t), noise, reduction='none')return loss
??GaussianDiffusionTrainer 類用于實現擴散模型的 前向過程(加噪) 和 訓練目標(噪聲預測)。核心思想是逐步向輸入數據 x 0 x_0 x0? 添加高斯噪聲,并訓練模型預測噪聲。extract 函數用于從序列 v 中按索引 t 提取值,并將其形狀調整為與輸入數據 x 的維度匹配,以便廣播計算。
-
計算 α t \alpha_t αt? 和 β t \beta_t βt?
??假設 beta_1=0.0001、beta_T=0.02,通過代碼self.register_buffer('betas', torch.linspace(beta_1, beta_T, T).double())
可得到 β t \beta_t βt? = self.betas,那么 α t \alpha_t αt? = 1. - self.betas。 β t \beta_t βt? 和 α t \alpha_t αt? 的形狀大小均為 (1000, ) 。 -
計算 α ˉ t \bar{\alpha}_t αˉt?
??通過代碼torch.cumprod(alphas, dim=0)
來獲取 α ˉ t \bar{\alpha}_t αˉt? = alphas_bar。 -
計算 α ˉ t \sqrt{\bar{\alpha}_t} αˉt?? 和 1 ? α ˉ t \sqrt{1-\bar{\alpha}_t} 1?αˉt??
?? α ˉ t \sqrt{\bar{\alpha}_t} αˉt?? = self.sqrt_alphas_bar、 1 ? α ˉ t \sqrt{1-\bar{\alpha}_t} 1?αˉt?? = self.sqrt_one_minus_alphas_bar。 -
隨機采樣時間步數
??t = torch.randint(self.T, size=(x_0.shape[0], ), device=x_0.device)
,假設 self.T = 1000、batch_size = 8,那么時間 t 的取值范圍是 [0, 1000),例如 t = tensor([902, 605, 411, 926, 894, 297, 147, 79], device=‘cuda:0’)。 -
利用公式(3)計算 x t \mathbf{x}_t xt?
x_t = (extract(self.sqrt_alphas_bar, t, x_0.shape) * x_0 +extract(self.sqrt_one_minus_alphas_bar, t, x_0.shape) * noise)
- UNet 網絡預測噪聲
??通過代碼self.model(x_t, t)
可得到預測噪聲 ε θ \pmb{\varepsilon}_{\theta} εθ?, ε θ \pmb{\varepsilon}_{\theta} εθ? 的shape為 [8, 3, 32, 32]。隨后利用均方誤差損失函數計算損失——loss = F.mse_loss(self.model(x_t, t), noise, reduction='none')
。
6. 推理過程代碼實現(Sampling)
class GaussianDiffusionSampler(nn.Module):def __init__(self, model, beta_1, beta_T, T):"""所有參數含義和``GaussianDiffusionTrainer``(前向過程)一樣"""super().__init__()self.model = modelself.T = T# 這里獲取betas, alphas以及alphas_bar和前向過程一模一樣self.register_buffer('betas', torch.linspace(beta_1, beta_T, T).double())alphas = 1. - self.betasalphas_bar = torch.cumprod(alphas, dim=0)# 這一步是方便后面運算,相當于構建alphas_bar{t-1}alphas_bar_prev = F.pad(alphas_bar, [1, 0], value=1)[:T] # 把alpha_bar的第一個數字換成1,按序后移# 根據公式,后向過程中的計算均值需要用到的系數用coeff1和coeff2表示self.register_buffer('coeff1', torch.sqrt(1. / alphas))self.register_buffer('coeff2', self.coeff1 * (1. - alphas) / torch.sqrt(1. - alphas_bar))# 根據公式,計算后向過程的方差self.register_buffer('posterior_var', self.betas * (1. - alphas_bar_prev) / (1. - alphas_bar))def predict_xt_prev_mean_from_eps(self, x_t, t, eps):"""該函數用于反向過程中,條件概率分布q(x_{t-1}|x_t)的均值Args:x_t: 迭代至當前步驟的圖像t: 當前步數eps: 模型預測的噪聲,也就是z_tReturns:x_{t-1}的均值,mean = coeff1 * x_t - coeff2 * eps"""assert x_t.shape == eps.shapereturn (extract(self.coeff1, t, x_t.shape) * x_t -extract(self.coeff2, t, x_t.shape) * eps)def p_mean_variance(self, x_t, t):"""該函數用于反向過程中,計算條件概率分布q(x_{t-1}|x_t)的均值和方差Args:x_t: 迭代至當前步驟的圖像t: 當前步數Returns:xt_prev_mean: 均值var: 方差"""# below: only log_variance is used in the KL computations# 這一步我略有不解,為什么要把算好的反向過程的方差大部分替換成betas。# 我猜測,后向過程方差``posterior_var``的計算過程僅僅是betas乘上一個(1 - alpha_bar_{t-1}) / (1 - alpha_bar_{t}),# 由于1 - alpha_bar_{t}這個數值非常趨近于0,分母為0會導致nan,# 而整體(1 - alpha_bar_{t-1}) / (1 - alpha_bar_{t})非常趨近于1,所以直接用betas近似后向過程的方差,# 但是t = 1 的時候(1 - alpha_bar_{0}) / (1 - alpha_bar_{1})還不是非常趨近于1,所以這個數值要保留,# 因此就有拼接``torch.cat([self.posterior_var[1:2], self.betas[1:]])``這一步var = torch.cat([self.posterior_var[1:2], self.betas[1:]])var = extract(var, t, x_t.shape)# 模型前向預測得到eps(也就是z_t)eps = self.model(x_t, t)# 計算均值xt_prev_mean = self.predict_xt_prev_mean_from_eps(x_t, t, eps=eps)return xt_prev_mean, vardef forward(self, x_T):"""Algorithm 2."""# 反向擴散過程,從x_t迭代至x_0x_t = x_Tfor time_step in reversed(range(self.T)):print(time_step)# t = [1, 1, ....] * time_step, 長度為batch_sizet = x_t.new_ones([x_T.shape[0], ], dtype=torch.long) * time_step# 計算條件概率分布q(x_{t-1}|x_t)的均值和方差mean, var= self.p_mean_variance(x_t=x_t, t=t)# no noise when t == 0# 最后一步的高斯噪聲設為0(我認為不設為0問題也不大,就本實例而言,t=0時的方差已經很小了)if time_step > 0:noise = torch.randn_like(x_t)else:noise = 0x_t = mean + torch.sqrt(var) * noiseassert torch.isnan(x_t).int().sum() == 0, "nan in tensor."x_0 = x_t# ``torch.clip(x_0, -1, 1)``,把x_0的值限制在-1到1之間,超出部分截斷return torch.clip(x_0, -1, 1)
-
獲取 α t \alpha_t αt? 、 β t \beta_t βt? 、 α ˉ t \bar{\alpha}_t αˉt? 和 α ˉ t ? 1 \bar{\alpha}_{t-1} αˉt?1?
?? β t \beta_t βt? = self.betas, α t \alpha_t αt? = 1. - self.betas 、 α ˉ t \bar{\alpha}_t αˉt? = alphas_bar 和 α ˉ t ? 1 \bar{\alpha}_{t-1} αˉt?1? = alphas_bar_prev。 -
獲取系數
??self.coeff1 = 1 α t \frac{1}{\sqrt{\alpha_t}} αt??1?、self.coeff2 = 1 α t ( 1 ? α t 1 ? α  ̄ t ) \frac{1}{\sqrt{\alpha_t}} \left( \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}}\right) αt??1?(1?αt??1?αt??) 和 self.posterior_var = β t ? 1 ? α ˉ t ? 1 1 ? α ˉ t \beta_t \cdot \frac{1 - \bar{\alpha}_{t-1}}{1 - \bar{\alpha}_t} βt??1?αˉt?1?αˉt?1??。 -
獲取前一時刻高斯噪聲 x t ? 1 \mathbf{x}_{t-1} xt?1? 的方差和均值
?? 通過代碼var = torch.cat([self.posterior_var[1:2], self.betas[1:]])
來獲取方差 β t ? 1 ? α ˉ t ? 1 1 ? α ˉ t \beta_t \cdot \frac{1 - \bar{\alpha}_{t-1}}{1 - \bar{\alpha}_t} βt??1?αˉt?1?αˉt?1??;
?? 通過代碼eps = self.model(x_t, t)
來獲取網絡的預測噪聲 ε θ ( x t , t ) \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) εθ?(xt?,t);
?? 通過代碼xt_prev_mean = self.predict_xt_prev_mean_from_eps(x_t, t, eps=eps)
來獲取預測的均值 1 α t ( x t ? 1 ? α t 1 ? α  ̄ t ε θ ( x t , t ) ) \frac{1}{\sqrt{\alpha_t}} \left( \mathbf{x}_t - \frac{1 - \alpha_t}{\sqrt{1 - \overline{\alpha}_t}} \, \pmb{\varepsilon}_\theta(\mathbf{x}_t, t) \right) αt??1?(xt??1?αt??1?αt??εθ?(xt?,t))。 -
計算前一時刻的高斯噪聲 x t ? 1 \mathbf{x}_{t-1} xt?1?
??通過代碼x_t = mean + torch.sqrt(var) * noise
來計算前一時刻的高斯噪聲 x t ? 1 \mathbf{x}_{t-1} xt?1? ,該代碼對應于公式(5)。
參考博客:
DDPM解讀(一)| 數學基礎,擴散與逆擴散過程和訓練推理方法
超詳細的擴散模型(Diffusion Models)原理+代碼
擴散模型 (Diffusion Model) 簡要介紹與源碼分析
參考視頻:
2025版最新的Diffusion Model | 擴散模型原理及代碼實現,3小時快速上手!(附帶源碼)
大白話AI | 圖像生成模型DDPM | 擴散模型 | 生成模型 | 概率擴散去噪生成模型
添【大白話01】一文理清 Diffusion Model 擴散模型 | 原理圖解+公式推導
【較真系列】講人話-Diffusion Model全解(原理+代碼+公式)