DiffusionModel-Classifier Guidance《Diffusion Models Beat GANs on Image Synthesis》原理+核心代码解读

论文
https://arxiv.org/pdf/2105.05233
代码
https://github.com/openai/guided-diffusion
参考了一下大佬的博客:
classifier guided diffusion

在这里插入图片描述

GAN牺牲多样性从而提高了介绍,
文章提到了一种名为“Classifier Guidance”的方法,用于在扩散模型的采样过程中引入分类器的梯度,以指导生成过程。这种方法允许在生成图像时根据分类器的梯度来调整模型的采样过程,从而实现对特定类别的生成。

  • 分类器的作用:
    • 分类器 p(y|x) 可以根据输入图像 x 预测它属于类别 y 的概率。
    • 分类器不仅用于分类,还用于指导生成模型生成特定类别的图像
  • P Φ ( y ∣ x t , t ) P_{\Phi}(y|x_t,t) PΦ(yxt,t):我们可以在噪声图像 x t x_t xt上训练分类器
  • ∇ x t log ⁡ P Φ ( y ∣ x t , t ) \nabla_{x_t} \log P_{\Phi}(y|x_t,t) xtlogPΦ(yxt,t):引导扩散采样过程朝向任意类标签 y

先验知识

多元高斯分布对数似然函数

多元高斯分布(Multivariate Gaussian Distribution)是指具有多个随机变量的高斯分布。在多元高斯分布中,随机变量是一个多维向量,而不是单个标量。多元高斯分布可以用来描述多维数据的分布特征,比如多维空间中的数据点分布情况。

多元高斯分布的概率密度函数可以表示为:

f ( x ∣ μ , Σ ) = 1 ( 2 π ) n / 2 ∣ Σ ∣ 1 / 2 exp ⁡ ( − 1 2 ( x − μ ) T Σ − 1 ( x − μ ) ) f(x|\mu,\Sigma) = \frac{1}{(2\pi)^{n/2}|\Sigma|^{1/2}} \exp\left(-\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu)\right) f(xμ,Σ)=(2π)n/2∣Σ1/21exp(21(xμ)TΣ1(xμ))

其中 x x x 是一个 n n n维向量, μ \mu μ是多元高斯分布的均值向量, Σ \Sigma Σ 是多元高斯分布的协方差矩阵, n n n 是向量的维度。

l ( μ , Σ ∣ x ( i ) ) = log ⁡ ∏ i = 1 m f X ( i ) ( x ( i ) ∣ μ , Σ ) = log ⁡ ∏ i = 1 m 1 ( 2 π ) p / 2 ∣ Σ ∣ 1 / 2 exp ⁡ ( − 1 2 ( x ( i ) − μ ) T Σ − 1 ( x ( i ) − μ ) ) = ∑ i = 1 m ( − p 2 l o g ( 2 π ) − 1 2 l o g ∣ Σ ∣ − 1 2 ( x ( i ) − μ ) T Σ − 1 ( x ( i ) − μ ) ) l ( μ , Σ ; ) = − m p 2 l o g ( 2 π ) − m 2 l o g ∣ Σ ∣ − 1 2 ∑ i = 1 m ( x ( i ) − μ ) T Σ − 1 ( x ( i ) − μ ) \begin{aligned} l(\mu,\Sigma|\mathbf{x^{(i)}})& =\log\prod_{i=1}^mf_{\mathbf{X}(\mathrm{i})}(\mathbf{x}^{(\mathrm{i})}|\mu,\boldsymbol{\Sigma}) \\ &=\log \prod_{i=1}^m\frac1{(2\pi)^{p/2}\left|\Sigma\right|^{1/2}}\exp\left(-\frac12(\mathbf{x}^{(\mathrm{i})}-\mu)^\mathrm{T}\boldsymbol{\Sigma}^{-1}(\mathbf{x}^{(\mathrm{i})}-\mu)\right) \\ &=\sum_{i=1}^m\left(-\frac p2\mathrm{log}(2\pi)-\frac12\mathrm{log}|\Sigma|-\frac12(\mathbf{x}^{(\mathrm{i})}-\mu)^\mathrm{T}\mathbf{\Sigma}^{-1}(\mathbf{x}^{(\mathrm{i})}-\mu)\right) \\ l(\mu,\Sigma;)& =-\frac{mp}{2}\mathrm{log}(2\pi)-\frac{m}{2}\mathrm{log}|\Sigma|-\frac{1}{2}\sum_{i=1}^{m}(\mathbf{x}^{(\mathrm{i})}-\mu)^{\mathrm{T}}\mathbf{\Sigma}^{-1}(\mathbf{x}^{(\mathrm{i})}-\mu) \end{aligned} l(μ,Σ∣x(i))l(μ,Σ;)=logi=1mfX(i)(x(i)μ,Σ)=logi=1m(2π)p/2Σ1/21exp(21(x(i)μ)TΣ1(x(i)μ))=i=1m(2plog(2π)21log∣Σ∣21(x(i)μ)TΣ1(x(i)μ))=2mplog(2π)2mlog∣Σ∣21i=1m(x(i)μ)TΣ1(x(i)μ)

  • μ \mu μ是多元高斯分布的均值向量
  • Σ \Sigma Σ是多元高斯分布的协方差矩阵
  • X = { x 1 , x 2 , . . . , x N } X = \{x_1, x_2, ..., x_N\} X={x1,x2,...,xN}是观测数据集
  • n n n 是数据的维度。
  • 多元高斯分布的协方差矩阵 Σ \Sigma Σ是对称和半正定矩阵

生成式模型的效果

两大目标:真实性(Quality)和多样性(Diversity)

Inception score

Inception Score基于Inception-V3模型的分类概率来评估生成照片的质量,通过使用预训练的卷积神经网络(通常是Inception网络)来评估生成图像的质量和多样性。
K L   d i v e r g e n c e = p ( y ∣ x ) ∗ ( l o g ( p ( y ∣ x ) ) − l o g ( p ( y ) ) ) \mathrm{KL~divergence=p(y|x)*(log(p(y|x))-log(p(y)))} KL divergence=p(y∣x)(log(p(y∣x))log(p(y)))

  • p(y|x):表示在给定条件 𝑥下,观测到的概率 𝑦
  • p(y) 表示概率 𝑦的边际概率
  • 对KL散度对所有类别求和再取平均值,并且取一个e指数,即可得到Inception Score。一般生成5000张照片S的值在0~1000范围内。
  • 作者希望p(y|x)应该具有低熵(即越真实),p(y)应该具有高熵即越多样,因此,IS值越大越好
  • 缺点:缺乏跟真实照片之间的比较;缺乏类内多样性,例如每个类别只产生一张一模一样的照片,IS一样很高
# 用代码实现IS

def calculate_inception_score(p_yx, eps=1E-16):
    # calculate p(y)
    p_y = expand_dims(p_yx.mean(axis=0), 0)#对所有样本求平均
    # kl divergence for each image
    kl_d = p_yx * (log(p_yx + eps) - log(p_y + eps))
    # sum over classes
    sum_kl_d = kl_d.sum(axis=1)#对类别求和
    # average over images,对所有图像的散度取平均值
    avg_kl_d = mean(sum_kl_d)
    # undo the logs
    is_score = exp(avg_kl_d)
    return is_score

Frechlet Inception Distance(FID)

FID是基于Inception-V3模型(预训练好的图像分类模型)的feature vectors来计算真实图片与生成图片之间的距离,用高斯分布来表示,FID就是计算两个分布之间的Wasserstein-2距离。将真实图片和预测图片分别经过Inception模型中,得到2048维度(特征的维度)的embedding vector。把生成和真实的图片同时放入Inception-V3中,然后将feature vectors取出来用于比较。
d 2 = ∣ ∣ μ 1 − μ 2 ∣ ∣ 2 + T r ( C 1 + C 2 − 2 ∗ ( C 1 ∗ C 2 ) ) d^2 = ||\mu_1 -\mu_2||^2 + Tr(C_1 + C_2 - 2*\sqrt{(C_1 *C_2)}) d2=∣∣μ1μ22+Tr(C1+C22(C1C2) )

  • μ1,μ2为均值,c1,c2为协方差,Tr为矩阵的迹
  • FID越低,说明预测分布越接近于真实的分布
  • 可以评估类内多样性,例如每个类别只产生一张一模一样的照片,FID会比较高,也就意味着评估效果比较差
# 用代码实现FID

def calculate_fid(act1, act2):
    # 计算mean和covariance统计量
    #act1,act2是两个隐变量(分别对应真实和预测的照片送入inception-v3得到的隐变量)
    mu1, sigma1 = act1.mean(axis=0), cov(act1, rowvar=False)
    mu2, sigma2 = act2.mean(axis=0), cov(act2, rowvar=False)
    
    # 计算mean之间的平方差之和
    ssdiff = numpy.sum((mu1 - mu2)**2.0)
    
    # 计算cov之积的平方根
    covmean = sqrtm(sigma1.dot(sigma2))
    
    # 检查从sqrt中移除的复数部分
    if iscomplexobj(covmean):
        covmean = covmean.real

    # 计算score
    fid = ssdiff + trace(sigma1 + sigma2 - 2.0 * covmean)
    return fid

Precision和Recall

评估多样性和分布的覆盖度
在这里插入图片描述
Precision:预测分布/真实分布
Recall:真实分布/预测分布

可学习的方差 Σ θ ( x t , t ) = exp ⁡ ( v log ⁡ β t + ( 1 − v ) log ⁡ β ~ t ) \Sigma_\theta(x_t,t)=\exp(v\log\beta_t+(1-v)\log\tilde{\beta}_t) Σθ(xt,t)=exp(vlogβt+(1v)logβ~t)

结构的优化

Unet的改变

  • 增加深度的同时保持模型大小相对恒定。
  • 增加attention heads的数量。
  • 使用在32x32、16x16和8x8分辨率上的attention,而不仅仅是16x16分辨率。
  • 使用BigGAN的残差块进行激活的上采样和下采样。
  • 对残差连接进行重缩放,使用 1 2 \frac{1}{\sqrt{2}} 2 1

Adaptive Group Normalization 自适应GN

Adaptive Group Normalization在组标准化操作之后将时间步长和类嵌入合并到每个残差块中
A d a G N ( h , y ) = y s G r o u p N o r m ( h ) + y b AdaGN(h, y) = y_s GroupNorm(h) + y_b AdaGN(h,y)=ysGroupNorm(h)+yb

  • h 是残差块中第一层卷积后的中间激活
  • y = [ys, yb] 是通过时间步和类别嵌入的线性投影得到的。这种层将时间步和类别信息整合到每个残差块中,有助于模型学习到更丰富的特征表示。

基于分类器的条件采样算法的原理与效果

q ^ \hat{q} q^的逆向扩散

保留原来的扩散模型不变,额外引入classifier.
先定义一个马尔科夫类型的扩散过程 q ^ \hat{q} q^,并 q ^ ( y ∣ x 0 ) \hat{q}(y|x_{0}) q^(yx0)都有对应便签,即每个训练样本都有对应标签:
q ^ ( x 0 ) : = q ( x 0 ) q ^ ( y ∣ x 0 ) : = K n o w n   l a b e l s   p e r   s a m p l e q ^ ( x t + 1 ∣ x t , y ) : = q ( x t + 1 ∣ x t ) q ^ ( x 1 : T ∣ x 0 , y ) : = ∏ t = 1 T q ^ ( x t ∣ x t − 1 , y ) \begin{aligned} \hat{q}(x_{0})& :=q(x_0) \\ \hat{q}(y|x_{0})& :=\mathrm{Known~labels~per~sample} \\ \hat{q}(x_{t+1}|x_{t},y)& :=q(x_{t+1}|x_t) \\ \hat{q}(x_{1:T}|x_{0},y)& :=\prod_{t=1}^T\hat{q}(x_t|x_{t-1},y) \end{aligned} q^(x0)q^(yx0)q^(xt+1xt,y)q^(x1:Tx0,y):=q(x0):=Known labels per sample:=q(xt+1xt):=t=1Tq^(xtxt1,y)

  • 训练样本不变
  • 训练样本有标签
  • 定义 q ^ \hat{q} q^与q有一样的加噪过程
  • q ^ ( x 1 : T ∣ x 0 , y ) \hat{q}(x_{1:T}|x_{0},y) q^(x1:Tx0,y)马尔科夫类型

证明:分别证明 q ^ \hat{q} q^加噪条件分布、联合分布和边缘分布,在不加y条件的情况下, q ^ \hat{q} q^ q {q} q,的表现相同;并且进一步表明逆扩散条件分布也相同:

加噪条件分布相同:
q ^ ( x t + 1 ∣ x t ) → 全概率公式 ∫ y q ^ ( x t + 1 , y ∣ x t ) d y → 联合概率 ∫ y q ^ ( x t + 1 ∣ x t , y ) q ^ ( y ∣ x t ) d y → 代入 q ^ 的定义 ∫ y q ( x t + 1 ∣ x t ) q ^ ( y ∣ x t ) d y = q ( x t + 1 ∣ x t ) ∫ y q ^ ( y ∣ x t ) d y = q ( x t + 1 ∣ x t ) = q ^ ( x t + 1 ∣ x t , y ) \begin{aligned} \hat{q}(x_{t+1}|x_{t})& \xrightarrow{\text{全概率公式}}\int_{y}\hat{q}(x_{t+1},y|x_{t}) dy \\ &\xrightarrow{\text{联合概率}}\int_{y}\hat{q}(x_{t+1}|x_{t},y)\hat{q}(y|x_{t}) dy \\ &\xrightarrow{\text{代入}\hat{q}的定义}\int_{y}q(x_{t+1}|x_{t})\hat{q}(y|x_{t}) dy \\ &=q(x_{t+1}|x_t)\int_y\hat{q}(y|x_t) dy \\ &=q(x_{t+1}|x_t) \\ &=\hat{q}(x_{t+1}|x_{t},y) \end{aligned} q^(xt+1xt)全概率公式 yq^(xt+1,yxt)dy联合概率 yq^(xt+1xt,y)q^(yxt)dy代入q^的定义 yq(xt+1xt)q^(yxt)dy=q(xt+1xt)yq^(yxt)dy=q(xt+1xt)=q^(xt+1xt,y)
联合概率分布相同:
q ^ ( x 1 : T ∣ x 0 ) = ∫ y q ^ ( x 1 : T , y ∣ x 0 ) d y = ∫ y q ^ ( y ∣ x 0 ) q ^ ( x 1 : T ∣ x 0 , y ) d y = ∫ y q ^ ( y ∣ x 0 ) ∏ t = 1 T q ^ ( x t ∣ x t − 1 , y ) d y = ∫ y q ^ ( y ∣ x 0 ) ∏ t = 1 T q ( x t ∣ x t − 1 ) d y = ∏ t = 1 T q ( x t ∣ x t − 1 ) ∫ y q ^ ( y ∣ x 0 ) d y = ∏ t = 1 T q ( x t ∣ x t − 1 ) = q ( x 1 : T ∣ x 0 ) \begin{aligned} \hat{q}(x_{1:T}|x_{0})& =\int_{y}\hat{q}(x_{1:T},y|x_{0}) dy \\ &=\int_{y}\hat{q}(y|x_{0})\hat{q}(x_{1:T}|x_{0},y) dy \\ &=\int_y\hat{q}(y|x_0)\prod_{t=1}^T\hat{q}(x_t|x_{t-1},y) dy \\ &=\int_y\hat{q}(y|x_0)\prod_{t=1}^Tq(x_t|x_{t-1}) dy \\ &=\prod_{t=1}^Tq(x_t|x_{t-1})\int_y\hat{q}(y|x_0) dy \\ &=\prod_{t=1}^Tq(x_t|x_{t-1}) \\ &=q(x_{1:T}|x_0) \end{aligned} q^(x1:Tx0)=yq^(x1:T,yx0)dy=yq^(yx0)q^(x1:Tx0,y)dy=yq^(yx0)t=1Tq^(xtxt1,y)dy=yq^(yx0)t=1Tq(xtxt1)dy=t=1Tq(xtxt1)yq^(yx0)dy=t=1Tq(xtxt1)=q(x1:Tx0)

边缘分布相同:
q ^ ( x t ) = ∫ x 0 : t − 1 q ^ ( x 0 , . . . , x t ) d x 0 : t − 1 = ∫ x 0 : t − 1 q ^ ( x 0 ) q ^ ( x 1 , . . . , x t ∣ x 0 ) d x 0 : t − 1 = ∫ x 0 : t − 1 q ( x 0 ) q ( x 1 , . . . , x t ∣ x 0 ) d x 0 : t − 1 = ∫ x 0 : t − 1 q ( x 0 , . . . , x t ) d x 0 : t − 1 = q ( x t ) \begin{aligned} \hat{q}(x_{t})& =\int_{x_{0:t-1}}\hat{q}(x_{0},...,x_{t}) dx_{0:t-1} \\ &=\int_{x_{0:t-1}}\hat{q}(x_{0})\hat{q}(x_{1},...,x_{t}|x_{0}) dx_{0:t-1} \\ &=\int_{x_{0:t-1}}q(x_{0})q(x_{1},...,x_{t}|x_{0}) dx_{0:t-1} \\ &=\int_{x_{0:t-1}}q(x_0,...,x_t) dx_{0:t-1} \\ &=q(x_t) \end{aligned} q^(xt)=x0:t1q^(x0,...,xt)dx0:t1=x0:t1q^(x0)q^(x1,...,xtx0)dx0:t1=x0:t1q(x0)q(x1,...,xtx0)dx0:t1=x0:t1q(x0,...,xt)dx0:t1=q(xt)
注意: p θ , ϕ ( y ∣ x t , t ) p_{\theta,\phi}(y|x_{t},t) pθ,ϕ(yxt,t)= p ϕ ( y ∣ x t , t ) p_{\phi}(y|x_{t},t) pϕ(yxt,t)and ϵ θ ( x t , t ) = ϵ θ ( x t ) \epsilon_θ(x_t, t) = \epsilon_θ(x_t) ϵθ(xt,t)=ϵθ(xt) for brevity

逆向过程:可以知道任意时刻t的分布 x t x_t xt x t − 1 x_{t-1} xt1无关
q ^ ( y ∣ x t , x t + 1 ) → 贝叶斯公式 , 把 x t 看成是固定的 q ^ ( x t + 1 ∣ x t , y ) q ^ ( y ∣ x t ) q ^ ( x t + 1 ∣ x t ) → 加噪条件分布相同 q ^ ( x t + 1 ∣ x t ) q ^ ( y ∣ x t ) q ^ ( x t + 1 ∣ x t ) = q ^ ( y ∣ x t ) \begin{aligned} \hat{q}(y|x_{t},x_{t+1})& \xrightarrow{\text{贝叶斯公式},把x_t看成是固定的}\hat{q}(x_{t+1}|x_{t},y)\frac{\hat{q}(y|x_{t})}{\hat{q}(x_{t+1}|x_{t})} \\ &\xrightarrow{加噪条件分布相同}\hat{q}(x_{t+1}|x_t)\frac{\hat{q}(y|x_t)}{\hat{q}(x_{t+1}|x_t)} \\ &=\hat{q}(y|x_t) \end{aligned} q^(yxt,xt+1)贝叶斯公式,xt看成是固定的 q^(xt+1xt,y)q^(xt+1xt)q^(yxt)加噪条件分布相同 q^(xt+1xt)q^(xt+1xt)q^(yxt)=q^(yxt)

给定条件( x t + 1 x_{t+1} xt+1,y)下的逆扩散条件分布

q ^ ( x t ∣ x t + 1 , y ) → 把条件分布写成联合分布除以边缘分布 q ^ ( x t , x t + 1 , y ) q ^ ( x t + 1 , y ) → 进一步拆分联合分布 q ^ ( x t , x t + 1 , y ) q ^ ( y ∣ x t + 1 ) q ^ ( x t + 1 ) → 进一步拆分边缘分布 q ^ ( x t ∣ x t + 1 ) q ^ ( y ∣ x t , x t + 1 ) q ^ ( x t + 1 ) q ^ ( y ∣ x t + 1 ) q ^ ( x t + 1 ) → 消元 q ^ ( x t ∣ x t + 1 ) q ^ ( y ∣ x t , x t + 1 ) q ^ ( y ∣ x t + 1 ) → 与 x t + 1 无关(之前推导的) q ^ ( x t ∣ x t + 1 ) q ^ ( y ∣ x t ) q ^ ( y ∣ x t + 1 ) → 边缘分布相同 q ( x t ∣ x t + 1 ) q ^ ( y ∣ x t ) q ^ ( y ∣ x t + 1 ) \begin{aligned} \hat{q}(x_{t}|x_{t+1},y)& \xrightarrow{把条件分布写成联合分布除以边缘分布}\frac{\hat{q}(x_{t},x_{t+1},y)}{\hat{q}(x_{t+1},y)} \\ & \xrightarrow{进一步拆分联合分布}\frac{\hat{q}(x_t,x_{t+1},y)}{\hat{q}(y|x_{t+1})\hat{q}(x_{t+1})} \\ & \xrightarrow{进一步拆分边缘分布}\frac{\hat{q}(x_t|x_{t+1})\hat{q}(y|x_t,x_{t+1})\hat{q}(x_{t+1})}{\hat{q}(y|x_{t+1})\hat{q}(x_{t+1})} \\ & \xrightarrow{消元}\frac{\hat{q}(x_{t}|x_{t+1})\hat{q}(y|x_{t},x_{t+1})}{\hat{q}(y|x_{t+1})} \\ & \xrightarrow{与x_{t+1}无关(之前推导的)}\frac{\hat{q}(x_t|x_{t+1})\hat{q}(y|x_t)}{\hat{q}(y|x_{t+1})} \\ & \xrightarrow{边缘分布相同}\boxed{\red{\frac{q(x_t|x_{t+1})\hat{q}(y|x_t)}{\hat{q}(y|x_{t+1})}}} \end{aligned} q^(xtxt+1,y)把条件分布写成联合分布除以边缘分布 q^(xt+1,y)q^(xt,xt+1,y)进一步拆分联合分布 q^(yxt+1)q^(xt+1)q^(xt,xt+1,y)进一步拆分边缘分布 q^(yxt+1)q^(xt+1)q^(xtxt+1)q^(yxt,xt+1)q^(xt+1)消元 q^(yxt+1)q^(xtxt+1)q^(yxt,xt+1)xt+1无关(之前推导的) q^(yxt+1)q^(xtxt+1)q^(yxt)边缘分布相同 q^(yxt+1)q(xtxt+1)q^(yxt)

  • q:训练好的扩散过程
  • q ^ ( y ∣ x t + 1 ) \hat{q}(y|x_{t+1}) q^(yxt+1):可以看成一个常量,因为它与 x t x_t xt无关
  • q ^ ( y ∣ x t + 1 ) ∼ Z q ( x t ∣ x t + 1 ) q ^ ( y ∣ x t ) \hat{q}(y|x_{t+1}){\sim}Zq(x_t|x_{t+1})\hat{q}(y|x_t) q^(yxt+1)Zq(xtxt+1)q^(yxt)
    • Z:归一化常数
    • 已经有 q ( x t ∣ x t + 1 ) q(x_t|x_{t+1}) q(xtxt+1)的近似估计, p θ ( x t ∣ x t + 1 ) p_\theta(x_t|x_{t+1}) pθ(xtxt+1)
  • q ^ ( y ∣ x t ) \hat{q}(y|x_{t}) q^(yxt):只有这个未知。可以用一个在从 q ( x t ) q(x_t) q(xt)采样的 x t x_t xt训练的classifier p ϕ ( y ∣ x t ) p_\phi(y|x_t) pϕ(yxt)

Conditional Reverse Noising Process

在生成过程中加入一个额外的标签 𝑦 来引导生成的样本从而将无条件的扩散模型变成有条件的扩散模型,已经证明:

p θ , ϕ ( x t ∣ x t + 1 , y ) = Z p θ ( x t ∣ x t + 1 ) p ϕ ( y ∣ x t ) p_{\theta,\phi}(x_t|x_{t+1},y)=Zp_\theta(x_t|x_{t+1})p_\phi(y|x_t) pθ,ϕ(xtxt+1,y)=Zpθ(xtxt+1)pϕ(yxt)

  • z:归一化常数,保证所有的可能xt概率之和为1
  • y:标签
  • p θ , ϕ ( x t ∣ x t + 1 , y ) p_{\theta,\phi}(x_t|x_{t+1},y) pθ,ϕ(xtxt+1,y),给定条件y下的条件概率分布
  • φ,θ:分类器参数和模型参数
  • p ϕ ( y ∣ x t ) p_\phi(y|x_t) pϕ(yxt): 这是在给定数据 x t x_t xt 的条件下类别标签 ( y ) 的概率,由分类器决定,参数由 ( \phi ) 参数化。分类器用于引导扩散过程生成与特定类别一致的数据。
  • p θ ( x t ∣ x t + 1 ) p_\theta(x_t|x_{t+1}) pθ(xtxt+1): 这是扩散模型的转移概率,由参数 θ \theta θ 参数化。它代表了在没有考虑类别标签的情况下,给定后续时间步的数据 x t + 1 x_{t+1} xt+1,时间步 t t t 的数据的概率。

之前的diffusion model采样的Gaussian sidtribution:
p θ ( x t ∣ x t + 1 ) = N ( μ , Σ ) log ⁡ p θ ( x t ∣ x t + 1 ) = − 1 2 ( x t − μ ) T Σ − 1 ( x t − μ ) + C \begin{aligned} p_{\theta}(x_{t}|x_{t+1})& =\mathcal{N}(\mu,\Sigma) \\ \log p_{\theta}(x_{t}|x_{t+1})& =-\frac{1}{2}(x_{t}-\mu)^{T}\Sigma^{-1}(x_{t}-\mu)+C \end{aligned} pθ(xtxt+1)logpθ(xtxt+1)=N(μ,Σ)=21(xtμ)TΣ1(xtμ)+C

p ϕ p_{\phi} pϕ可微,那么就可以进行泰勒展开,展开到第二项, log ⁡ p ϕ ( y ∣ x t ) \log p_{\phi}(y|x_{t}) logpϕ(yxt)相对于 Σ \Sigma Σ的曲率很小,我们的泰勒展开展开到一阶:
log ⁡ p ϕ ( y ∣ x t ) ≈ log ⁡ p ϕ ( y ∣ x t ) ∣ x t = μ + ( x t − μ ) ∇ x t log ⁡ p ϕ ( y ∣ x t ) ∣ x t = μ = ( x t − μ ) g + C 1 \begin{aligned} \log p_{\phi}(y|x_{t})& \approx\log p_{\phi}(y|x_{t})|_{x_{t}=\mu}+(x_{t}-\mu) \nabla_{x_{t}}\log p_{\phi}(y|x_{t})|_{x_{t}=\mu} \\ &=(x_t-\mu)g+C_1 \end{aligned} logpϕ(yxt)logpϕ(yxt)xt=μ+(xtμ)xtlogpϕ(yxt)xt=μ=(xtμ)g+C1

  • g = ∇ x t log ⁡ p ϕ ( y ∣ x t ) ∣ x t = μ g=\nabla_{x_t}\log p_\phi(y|x_t)|_{x_t=\mu} g=xtlogpϕ(yxt)xt=μ
  • C 1   i s   a   c o n s t a n t . C_1\mathrm{~is~a~constant.} C1 is a constant.
    ⇓ \Downarrow
    log ⁡ ( p θ ( x t ∣ x t + 1 ) p ϕ ( y ∣ x t ) ) = log ⁡ p θ ( x t ∣ x t + 1 ) + log ⁡ p ϕ ( y ∣ x t ) ≈ − 1 2 ( x t − μ ) T Σ − 1 ( x t − μ ) + ( x t − μ ) g + C 2 → 展开,然后利用二次型的标准形式和对称矩阵性质 − 1 2 ( x t − μ − Σ g ) T Σ − 1 ( x t − μ − Σ g ) + 1 2 g T Σ g + C 2 → 常数项 − 1 2 ( x t − μ − Σ g ) T Σ − 1 ( x t − μ − Σ g ) + C 3 → 转换成高斯分布的对数似然函数 log ⁡ p ( z ) + C 4 , z ∼ N ( μ + Σ g , Σ ) \begin{aligned} \log(p_\theta(x_t|x_{t+1})p_\phi(y|x_t))&=\log p_{\theta}(x_{t}|x_{t+1})+\log p_{\phi}(y|x_{t})\\& \approx-\frac12(x_t-\mu)^T\Sigma^{-1}(x_t-\mu)+(x_t-\mu)g+C_2 \\ & \xrightarrow{展开,然后利用二次型的标准形式和对称矩阵性质}-\frac{1}{2}(x_{t}-\mu-\Sigma g)^{T}\Sigma^{-1}(x_{t}-\mu-\Sigma g)+\frac{1}{2}g^{T}\Sigma g+C_{2} \\ & \xrightarrow{常数项}-\frac{1}{2}(x_{t}-\mu-\Sigma g)^{T}\Sigma^{-1}(x_{t}-\mu-\Sigma g)+C_{3} \\ &\xrightarrow{转换成高斯分布的对数似然函数}\boxed{\red{\log p(z)+C_{4},z\sim\mathcal{N}(\mu+\Sigma g,\Sigma)}} \end{aligned} log(pθ(xtxt+1)pϕ(yxt))=logpθ(xtxt+1)+logpϕ(yxt)21(xtμ)TΣ1(xtμ)+(xtμ)g+C2展开,然后利用二次型的标准形式和对称矩阵性质 21(xtμΣg)TΣ1(xtμΣg)+21gTΣg+C2常数项 21(xtμΣg)TΣ1(xtμΣg)+C3转换成高斯分布的对数似然函数 logp(z)+C4,zN(μ+Σg,Σ)

在实际应用中,由于直接从这个条件概率分布中采样通常是不可行的,我们需要使用一些近似方法,比如重参数化技巧(reparameterization trick)或者蒙特卡洛方法(Monte Carlo methods),来从这个分布中生成样本
Σ θ ( X t , t ) = e x p ( v l o g β t + ( 1 − v ) l o g β ~ t ) \Sigma_\theta\left(X_t,t\right)=exp(vlog\beta_t+(1-v)log\widetilde{\beta}_t) Σθ(Xt,t)=exp(vlogβt+(1v)logβ t)
β ~ t = 1 − α ˉ t − 1 1 − α ˉ t β t \tilde{\beta}_t=\frac{1-\bar{\alpha}_{t-1}}{1-\bar{\alpha}_t}\beta_t β~t=1αˉt1αˉt1βt
在这里插入图片描述

Classifier 算法

Classifier guided diffusion sampling(stochastic)

与DDPM不同:算出 μ , Σ \mu,\Sigma μΣ后对均值进行一个偏移g
x t − 1 ← s a m p l e   f r o m   N ( μ + s Σ ∇ x t log ⁡ p ϕ ( y ∣ x t ) , Σ ) x_{t-1}\leftarrow\mathrm{sample~from~}\mathcal{N}(\mu+s\Sigma \nabla_{x_{t}}\log p_{\phi}(y|x_{t}),\Sigma) xt1sample from N(μ+sΣxtlogpϕ(yxt),Σ)

  • ∇ x t log ⁡ P Φ ( y ∣ x t , t ) \nabla_{x_t} \log P_{\Phi}(y|x_t,t) xtlogPΦ(yxt,t):当前分类器以 x t x_t xt作为输入时,其输出的概率向量对 x t x_t xt自动微分的一个梯度
  • s:缩放量,可以理解为条件执导的程度
    在这里插入图片描述

Classifier guided DDIM sampling

使用score-based conditoning trick
∇ x t log ⁡ p θ ( x t ) = − 1 1 − α ˉ t ϵ θ ( x t ) ⇓ ∇ x t log ⁡ ( p θ ( x t ) p ϕ ( y ∣ x t ) ) = ∇ x t log ⁡ p θ ( x t ) + ∇ x t log ⁡ p ϕ ( y ∣ x t ) = − 1 1 − α ˉ t ϵ θ ( x t ) + ∇ x t log ⁡ p ϕ ( y ∣ x t ) \nabla_{x_t}\log p_\theta(x_t)=-\frac{1}{\sqrt{1-\bar{\alpha}_t}}\epsilon_\theta(x_t)\\\Downarrow\\\begin{aligned} \nabla_{x_{t}}\log(p_{\theta}(x_{t})p_{\phi}(y|x_{t}))& =\nabla_{x_{t}}\log p_{\theta}(x_{t})+\nabla_{x_{t}}\log p_{\phi}(y|x_{t}) \\ &=-\frac{1}{\sqrt{1-\bar{\alpha}_{t}}}\epsilon_{\theta}(x_{t})+\nabla_{x_{t}}\log p_{\phi}(y|x_{t}) \end{aligned} xtlogpθ(xt)=1αˉt 1ϵθ(xt)xtlog(pθ(xt)pϕ(yxt))=xtlogpθ(xt)+xtlogpϕ(yxt)=1αˉt 1ϵθ(xt)+xtlogpϕ(yxt)
可以定义DDIM中新的噪音量:
ϵ ^ ( x t ) : = ϵ θ ( x t ) − 1 − α ˉ t ∇ x t log ⁡ p ϕ ( y ∣ x t ) \hat{\epsilon}(x_t):=\epsilon_\theta(x_t)-\sqrt{1-\bar{\alpha}_t} \nabla_{x_t}\log p_\phi(y|x_t) ϵ^(xt):=ϵθ(xt)1αˉt xtlogpϕ(yxt)
在这里插入图片描述

Conditional Sampling for DDIM

代码详解

classifier

分类器的作用通常是对输入的数据进行分类或者识别,根据模型的训练,可以对输入的图像或数据进行分类预测。

#\guided_diffusion\script_util.py
def create_classifier(
    image_size,  # 图像大小
    classifier_use_fp16,  # 是否使用 FP16
    classifier_width,  # 分类器宽度
    classifier_depth,  # 分类器深度
    classifier_attention_resolutions,  # 分类器注意力分辨率
    classifier_use_scale_shift_norm,  # 是否使用尺度平移归一化
    classifier_resblock_updown,  # 是否使用上下采样的残差块
    classifier_pool,  # 池化方式
):
    # 根据图像大小选择通道倍增率
    if image_size == 512:
        channel_mult = (0.5, 1, 1, 2, 2, 4, 4)
    elif image_size == 256:
        channel_mult = (1, 1, 2, 2, 4, 4)
    elif image_size == 128:
        channel_mult = (1, 1, 2, 3, 4)
    elif image_size == 64:
        channel_mult = (1, 2, 3, 4)
    else:
        raise ValueError(f"不支持的图像大小: {image_size}")

    # 解析分类器注意力分辨率
    attention_ds = []
    for res in classifier_attention_resolutions.split(","):
        attention_ds.append(image_size // int(res))

    # 创建 EncoderUNetModel 模型
    return EncoderUNetModel(
        image_size=image_size,
        in_channels=3,  # 输入通道数为 3
        model_channels=classifier_width,  # 模型通道数
        out_channels=1000,  # 输出通道数为 1000
        num_res_blocks=classifier_depth,  # 残差块数量
        attention_resolutions=tuple(attention_ds),  # 注意力分辨率
        channel_mult=channel_mult,  # 通道倍增率
        use_fp16=classifier_use_fp16,  # 是否使用 FP16
        num_head_channels=64,  # 注意力头通道数
        use_scale_shift_norm=classifier_use_scale_shift_norm,  # 是否使用尺度平移归一化
        resblock_updown=classifier_resblock_updown,  # 是否使用上下采样的残差块
        pool=classifier_pool,  # 池化方式
    )

cond_fn和model_fn

用于计算分类器的梯度,这个梯度随后可以用于引导扩散模型的采样过程

  • cond_fn返回的是 s × ▽ X t l o g p ϕ ( y ∣ X t ) s\times\bigtriangledown_{X_t}logp_\phi\left(y|X_t\right) s×Xtlogpϕ(yXt),s 是 args.classifier_scale
  • model_fn 函数用于生成样本,它接受输入样本 x、时间步 t 和标签 y 作为参数。它使用扩散模型 model 生成样本,并根据 args.class_cond 参数决定是否使用标签 y 进行条件引导。
#scripts/classifier_sample.py
def main():
    args = create_argparser().parse_args()  # 解析命令行参数

    dist_util.setup_dist()  # 设置分布式计算环境
    logger.configure()  # 配置日志记录

    logger.log("creating model and diffusion...")  # 记录日志
    model, diffusion = create_model_and_diffusion(
        **args_to_dict(args, model_and_diffusion_defaults().keys())
    )  # 创建模型和扩散对象
    model.load_state_dict(
        dist_util.load_state_dict(args.model_path, map_location="cpu")
    )  # 加载模型参数
    model.to(dist_util.dev())  # 将模型移动到指定设备
    if args.use_fp16:
        model.convert_to_fp16()  # 将模型转换为FP16精度
    model.eval()  # 设置模型为评估模式

    logger.log("loading classifier...")  # 记录日志
    classifier = create_classifier(**args_to_dict(args, classifier_defaults().keys()))  # 创建分类器
    classifier.load_state_dict(
        dist_util.load_state_dict(args.classifier_path, map_location="cpu")
    )  # 加载分类器参数
    classifier.to(dist_util.dev())  # 将分类器移动到指定设备
    if args.classifier_use_fp16:
        classifier.convert_to_fp16()  # 将分类器转换为FP16精度
    classifier.eval()  # 设置分类器为评估模式
def cond_fn(x, t, y=None):
#x 是当前的样本数据,t 是时间步,y 是可选的标签。
    assert y is not None
    # 启用梯度计算
    with th.enable_grad():
        # 将输入 x 转换为可求导的张量
        #从 x 创建一个新的变量 x_in,这个变量是 x 的一个副本,但是它的 requires_grad 属性被设置为 True,这意味着在反向传播中需要计算它的梯度。
        x_in = x.detach().requires_grad_(True)
        
        # 使用分类器模型计算输入 x 在时间步 t 的 logits
        logits = classifier(x_in, t)
        
        # 计算 logits 的对数 softmax 概率,这给出了每个类别的对数概率。
        log_probs = F.log_softmax(logits, dim=-1)
        
        # 获取对应于标签 y 的对数概率
        selected = log_probs[range(len(logits)), y.view(-1)]
        
        # 计算对数概率关于输入 x_in 的梯度
        return th.autograd.grad(selected.sum(), x_in)[0] * args.classifier_scale

def model_fn(x, t, y=None):
    assert y is not None
    # 使用扩散模型生成样本
    return model(x, t, y if args.class_cond else None)

logger.log("sampling...")
all_images = []
all_labels = []
# 循环生成样本,直到生成指定数量的样本
while len(all_images) * args.batch_size < args.num_samples:
    model_kwargs = {}
    # 随机生成一批标签
    classes = th.randint(
        low=0, high=NUM_CLASSES, size=(args.batch_size,), device=dist_util.dev()
    )
    # 将标签添加到模型参数中
    model_kwargs["y"] = classes
    # 选择采样方法,使用 p_sample_loop 或 ddim_sample_loop
    sample_fn = (
        diffusion.p_sample_loop if not args.use_ddim else diffusion.ddim_sample_loop
    )
    # 使用采样方法生成样本
    sample = sample_fn(
        model_fn,
        (args.batch_size, 3, args.image_size, args.image_size),
        clip_denoised=args.clip_denoised,
        model_kwargs=model_kwargs,
        cond_fn=cond_fn,
        device=dist_util.dev(),
    )
    # 将样本归一化到 0-255 之间
    sample = ((sample + 1) * 127.5).clamp(0, 255).to(th.uint8)
    # 将样本的维度调整为 (batch_size, height, width, channel)
    sample = sample.permute(0, 2, 3, 1)
    # 将样本转换为连续内存
    sample = sample.contiguous()
    # 将生成的样本和标签添加到列表中
    gathered_samples = [th.zeros_like(sample) for _ in range(dist.get_world_size())]
    dist.all_gather(gathered_samples, sample)  # 收集生成的图像
    all_images.extend([sample.cpu().numpy() for sample in gathered_samples])  # 扩展图像列表
    gathered_labels = [th.zeros_like(classes) for _ in range(dist.get_world_size())]
    dist.all_gather(gathered_labels, classes)  # 收集标签
    all_labels.extend([labels.cpu().numpy() for labels in gathered_labels])  # 扩展标签列表
    logger.log(f"created {len(all_images) * args.batch_size} samples")  # 记录生成的样本数量


p_sample

  • p_sample 函数:

    • 该函数从模型在给定时间步 t 上采样 x_{t-1}。
    • 它首先使用 p_mean_variance 函数计算 x_{t-1} 的均值和方差。
      然后,它生成一个与 x 形状相同的噪声 noise。
    • 如果 cond_fn 不为空,则使用 condition_mean 函数根据条件修正均值 out[“mean”]。
    • 最后,它使用均值、方差和噪声计算 x_{t-1} 的样本,并返回一个包含样本和 x_0 预测的字典。
  • p_sample_loop 函数:

    • 该函数从模型中生成样本,并返回一个不可微分的样本批次。
    • 使用 p_sample_loop_progressive 函数生成样本,并返回最后一个时间步的样本。
  • p_sample_loop_progressive 函数:

    • 该函数从模型中生成样本,并从扩散的每个时间步生成中间样本。
    • 它首先根据 shape 生成一个噪声 img。
    • 然后,它从 self.num_timesteps 到 0 反向遍历时间步 i。在每个时间步,它使用
    • p_sample 函数生成一个样本,并将该样本作为字典返回。它还将当前样本 img 更新为新生成的样本。

与之前DDPM不同的部分:mean要修正

#**p_sample中的cond_fn在condition_mean处起作用**
    if cond_fn is not None:
        out["mean"] = self.condition_mean(  
        # 对 mean 进行修正
            cond_fn, out, x, t, model_kwargs=model_kwargs
        )
def p_sample(
    self,
    model,
    x,
    t,
    clip_denoised=True,
    denoised_fn=None,
    cond_fn=None,
    model_kwargs=None,
):
    """
    从模型在给定时间步上采样 x_{t-1}。

    :param model: 要从中采样的模型。
    :param x: 当前时间步 x_{t-1} 的张量。
    :param t: t 的值,从第一个扩散步骤的 0 开始。
    :param clip_denoised: 如果为 True,则将 x_start 预测裁剪到 [-1, 1]。
    :param denoised_fn: 如果不为 None,则是一个应用于 x_start 预测的函数,
                        在用于采样之前。
    :param cond_fn: 如果不为 None,则是一个梯度函数,其作用类似于模型。
    :param model_kwargs: 如果不为 None,则是一个包含要传递给模型的额外关键字参数的字典。
                        这可用于条件化。
    :return: 包含以下键的字典:
             - 'sample': 从模型中随机采样的样本。
             - 'pred_xstart': x_0 的预测值。
    """
    out = self.p_mean_variance(
        # 基于 xt 计算一些 x_{t-1} 的变量
        model,
        x,
        t,
        clip_denoised=clip_denoised,
        denoised_fn=denoised_fn,
        model_kwargs=model_kwargs,
    )
    noise = th.randn_like(x)
    nonzero_mask = (
        (t != 0).float().view(-1, *([1] * (len(x.shape) - 1)))
    )  # 当 t == 0 时没有噪声
    if cond_fn is not None:
        out["mean"] = self.condition_mean(  
        # 对 mean 进行修正
            cond_fn, out, x, t, model_kwargs=model_kwargs
        )
    sample = out["mean"] + nonzero_mask * th.exp(0.5 * out["log_variance"]) * noise
    #重参数化
    return {"sample": sample, "pred_xstart": out["pred_xstart"]}


def p_sample_loop(
    self,
    model,
    shape,
    noise=None,
    clip_denoised=True,
    denoised_fn=None,
    cond_fn=None,
    model_kwargs=None,
    device=None,
    progress=False,
):
    """
    从模型中生成样本。

    :param model: 模型模块。
    :param shape: 样本的形状,(N, C, H, W)。
    :param noise: 如果指定,则来自编码器的噪声用于采样。
                  应该与 `shape` 形状相同。
    :param clip_denoised: 如果为 True,则将 x_start 预测裁剪到 [-1, 1]。
    :param denoised_fn: 如果不为 None,则是一个应用于 x_start 预测的函数,
                        在用于采样之前。
    :param cond_fn: 如果不为 None,则是一个梯度函数,其作用类似于模型。
    :param model_kwargs: 如果不为 None,则是一个包含要传递给模型的额外关键字参数的字典。
                        这可用于条件化。
    :param device: 如果指定,则用于创建样本的设备。
                   如果未指定,则使用模型参数的设备。
    :param progress: 如果为 True,则显示 tqdm 进度条。
    :return: 一个不可微分的样本批次。
    """
    final = None
    for sample in self.p_sample_loop_progressive(
        model,
        shape,
        noise=noise,
        clip_denoised=clip_denoised,
        denoised_fn=denoised_fn,
        cond_fn=cond_fn,
        model_kwargs=model_kwargs,
        device=device,
        progress=progress,
    ):
        final = sample
    return final["sample"]


def p_sample_loop_progressive(
    self,
    model,
    shape,
    noise=None,
    clip_denoised=True,
    denoised_fn=None,
    cond_fn=None,
    model_kwargs=None,
    device=None,
    progress=False,
):
    """
    从模型中生成样本,并从扩散的每个时间步生成中间样本。

    参数与 p_sample_loop() 相同。
    返回一个字典生成器,其中每个字典都是 p_sample() 的返回值。
    """
    if device is None:
        device = next(model.parameters()).device
    assert isinstance(shape, (tuple, list))
    if noise is not None:
        img = noise
    else:
        img = th.randn(*shape, device=device)
    indices = list(range(self.num_timesteps))[::-1]

    if progress:
        # 延迟导入,这样我们就不依赖于 tqdm。
        from tqdm.auto import tqdm

        indices = tqdm(indices)

    for i in indices:
        t = th.tensor([i] * shape[0], device=device)
        with th.no_grad():
            out = self.p_sample(
                model,
                img,
                t,
                clip_denoised=clip_denoised,
                denoised_fn=denoised_fn,
                cond_fn=cond_fn,
                model_kwargs=model_kwargs,
            )
            yield out
            img = out["sample"]

ddim_sample

与DDIM不同的部分,DDIM详细推导指路这篇博客:https://blog.csdn.net/hwjokcq/article/details/138873313?spm=1001.2014.3001.5501

if cond_fn is not None:
    out = self.condition_score(cond_fn, out, x, t, model_kwargs=model_kwargs)

#guided_diffusion\gaussian_diffusion.py
def ddim_sample(
        self,
        model,
        x,
        t,
        clip_denoised=True,
        denoised_fn=None,
        cond_fn=None,
        model_kwargs=None,
        eta=0.0,
    ):
        """
        Sample x_{t-1} from the model using DDIM.

        Same usage as p_sample().
        """
        # Calculate mean and variance of the prediction
        out = self.p_mean_variance(
            model,
            x,
            t,
            clip_denoised=clip_denoised,
            denoised_fn=denoised_fn,
            model_kwargs=model_kwargs,
        )
        
        # Condition the score if a condition function is provided
        if cond_fn is not None:
            out = self.condition_score(cond_fn, out, x, t, model_kwargs=model_kwargs)

        # Predict epsilon based on x_start or x_prev prediction
        eps = self._predict_eps_from_xstart(x, t, out["pred_xstart"])
        
        # Calculate alpha_bar and sigma
        alpha_bar = _extract_into_tensor(self.alphas_cumprod, t, x.shape)
        alpha_bar_prev = _extract_into_tensor(self.alphas_cumprod_prev, t, x.shape)
        sigma = (
            eta
            * th.sqrt((1 - alpha_bar_prev) / (1 - alpha_bar))
            * th.sqrt(1 - alpha_bar / alpha_bar_prev)
        )
        
        # Generate noise and predict the mean
        noise = th.randn_like(x)
        mean_pred = (
            out["pred_xstart"] * th.sqrt(alpha_bar_prev)
            + th.sqrt(1 - alpha_bar_prev - sigma ** 2) * eps
        )
        
        # Apply noise only for t != 0
        nonzero_mask = (
            (t != 0).float().view(-1, *([1] * (len(x.shape) - 1)))
        )
        sample = mean_pred + nonzero_mask * sigma * noise
        
        return {"sample": sample, "pred_xstart": out["pred_xstart"]}

    def ddim_reverse_sample(
        self,
        model,
        x,
        t,
        clip_denoised=True,
        denoised_fn=None,
        model_kwargs=None,
        eta=0.0,
    ):
        """
        Sample x_{t+1} from the model using DDIM reverse ODE.
        """
        # Ensure eta is 0 for deterministic path
        assert eta == 0.0, "Reverse ODE only for deterministic path"
        
        # Calculate mean and variance of the prediction
        out = self.p_mean_variance(
            model,
            x,
            t,
            clip_denoised=clip_denoised,
            denoised_fn=denoised_fn,
            model_kwargs=model_kwargs,
        )
        
        # Predict epsilon based on the model outputs
        eps = (
            _extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x.shape) * x
            - out["pred_xstart"]
        ) / _extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x.shape)
        
        alpha_bar_next = _extract_into_tensor(self.alphas_cumprod_next, t, x.shape)

        # Calculate the mean prediction for the reverse ODE
        mean_pred = (
            out["pred_xstart"] * th.sqrt(alpha_bar_next)
            + th.sqrt(1 - alpha_bar_next) * eps
        )

        return {"sample": mean_pred, "pred_xstart": out["pred_xstart"]}

    def ddim_sample_loop(
        self,
        model,
        shape,
        noise=None,
        clip_denoised=True,
        denoised_fn=None,
        cond_fn=None,
        model_kwargs=None,
        device=None,
        progress=False,
        eta=0.0,
    ):
        """
        Generate samples from the model using DDIM.

        Same usage as p_sample_loop().
        """
        final = None
        for sample in self.ddim_sample_loop_progressive(
            model,
            shape,
            noise=noise,
            clip_denoised=clip_denoised,
            denoised_fn=denoised_fn,
            cond_fn=cond_fn,
            model_kwargs=model_kwargs,
            device=device,
            progress=progress,
            eta=eta,
        ):
            final = sample
        return final["sample"]

    def ddim_sample_loop_progressive(
        self,
        model,
        shape,
        noise=None,
        clip_denoised=True,
        denoised_fn=None,
        cond_fn=None,
        model_kwargs=None,
        device=None,
        progress=False,
        eta=0.0,
    ):
        """
        Use DDIM to sample from the model and yield intermediate samples from
        each timestep of DDIM.

        Same usage as p_sample_loop_progressive().
        """
        if device is None:
            device = next(model.parameters()).device
        assert isinstance(shape, (tuple, list))
        if noise is not None:
            img = noise
        else:
            img = th.randn(*shape, device=device)
        indices = list(range(self.num_timesteps))[::-1]

        if progress:
            # Lazy import so that we don't depend on tqdm.
            from tqdm.auto import tqdm

            indices = tqdm(indices)

        for i in indices:
            t = th.tensor([i] * shape[0], device=device)
            with th.no_grad():
                out = self.ddim_sample(
                    model,
                    img,
                    t,
                    clip_denoised=clip_denoised,
                    denoised_fn=denoised_fn,
                    cond_fn=cond_fn,
                    model_kwargs=model_kwargs,
                    eta=eta,
                )
                yield out
                img = out["sample"]
#guided_diffusion\gaussian_diffusion.py
def condition_score(self, cond_fn, p_mean_var, x, t, model_kwargs=None):
        """
        计算在模型的评分函数受到 cond_fn 条件约束时,p_mean_variance 输出会是什么。

        有关 cond_fn 的详细信息,请参阅 condition_mean()。

        与 condition_mean() 不同,这里使用的是 Song 等人(2020年)的条件策略。

        Parameters:
        - cond_fn: 条件函数,用于对模型的评分函数进行条件约束。
        - p_mean_var: 模型输出的平均值和方差。
        - x: 输入数据。
        - t: 时间步长。
        - model_kwargs: 模型参数(可选)。

        Returns:
        - out: 更新后的 p_mean_var 输出。

        """
        # 计算 alpha_bar
        alpha_bar = _extract_into_tensor(self.alphas_cumprod, t, x.shape)

        # 预测 epsilon
        eps = self._predict_eps_from_xstart(x, t, p_mean_var["pred_xstart"])
        # 根据 Song 等人的策略,对 epsilon 进行调整
        eps = eps - (1 - alpha_bar).sqrt() * cond_fn(
            x, self._scale_timesteps(t), **model_kwargs
        )

        # 复制 p_mean_var
        out = p_mean_var.copy()
        # 通过调整后的 epsilon 预测 x_start
        out["pred_xstart"] = self._predict_xstart_from_eps(x, t, eps)
        # 计算 posterior 的均值和方差
        out["mean"], _, _ = self.q_posterior_mean_variance(
            x_start=out["pred_xstart"], x_t=x, t=t
        )
        
        return out
  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于PyTorch实现的Stable Diffusion图片融合代码,其中包括了模型的定义、训练和推理过程: ```python import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.utils.data import DataLoader from torchvision import transforms from torchvision.datasets import ImageFolder from tqdm import tqdm class Unet(nn.Module): def __init__(self): super(Unet, self).__init__() self.down1 = nn.Conv2d(3, 64, kernel_size=4, stride=2, padding=1) self.down2 = nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1) self.down3 = nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1) self.down4 = nn.Conv2d(256, 512, kernel_size=4, stride=2, padding=1) self.down5 = nn.Conv2d(512, 512, kernel_size=4, stride=2, padding=1) self.down6 = nn.Conv2d(512, 512, kernel_size=4, stride=2, padding=1) self.down7 = nn.Conv2d(512, 512, kernel_size=4, stride=2, padding=1) self.down8 = nn.Conv2d(512, 512, kernel_size=4, stride=2, padding=1) self.up1 = nn.ConvTranspose2d(512, 512, kernel_size=4, stride=2, padding=1) self.up2 = nn.ConvTranspose2d(1024, 512, kernel_size=4, stride=2, padding=1) self.up3 = nn.ConvTranspose2d(1024, 512, kernel_size=4, stride=2, padding=1) self.up4 = nn.ConvTranspose2d(1024, 512, kernel_size=4, stride=2, padding=1) self.up5 = nn.ConvTranspose2d(1024, 256, kernel_size=4, stride=2, padding=1) self.up6 = nn.ConvTranspose2d(512, 128, kernel_size=4, stride=2, padding=1) self.up7 = nn.ConvTranspose2d(256, 64, kernel_size=4, stride=2, padding=1) self.up8 = nn.ConvTranspose2d(128, 3, kernel_size=4, stride=2, padding=1) def forward(self, x): down1 = F.leaky_relu(self.down1(x), negative_slope=0.2) down2 = F.leaky_relu(self.down2(down1), negative_slope=0.2) down3 = F.leaky_relu(self.down3(down2), negative_slope=0.2) down4 = F.leaky_relu(self.down4(down3), negative_slope=0.2) down5 = F.leaky_relu(self.down5(down4), negative_slope=0.2) down6 = F.leaky_relu(self.down6(down5), negative_slope=0.2) down7 = F.leaky_relu(self.down7(down6), negative_slope=0.2) down8 = F.leaky_relu(self.down8(down7), negative_slope=0.2) up1 = F.leaky_relu(self.up1(down8), negative_slope=0.2) up2 = F.leaky_relu(self.up2(torch.cat([up1, down7], dim=1)), negative_slope=0.2) up3 = F.leaky_relu(self.up3(torch.cat([up2, down6], dim=1)), negative_slope=0.2) up4 = F.leaky_relu(self.up4(torch.cat([up3, down5], dim=1)), negative_slope=0.2) up5 = F.leaky_relu(self.up5(torch.cat([up4, down4], dim=1)), negative_slope=0.2) up6 = F.leaky_relu(self.up6(torch.cat([up5, down3], dim=1)), negative_slope=0.2) up7 = F.leaky_relu(self.up7(torch.cat([up6, down2], dim=1)), negative_slope=0.2) up8 = torch.sigmoid(self.up8(torch.cat([up7, down1], dim=1))) return up8 class DiffusionModel(nn.Module): def __init__(self, num_steps, betas, model): super(DiffusionModel, self).__init__() self.num_steps = num_steps self.betas = betas self.model = model self.noise_schedule = nn.Parameter(torch.zeros(num_steps)) def forward(self, x): z = torch.randn(x.shape).to(x.device) x_prev = x for i in range(self.num_steps): t = (i + 1) / self.num_steps noise_level = (self.noise_schedule[i] ** 0.5).view(-1, 1, 1, 1) x_tilde = x_prev * noise_level + (1 - noise_level ** 2) ** 0.5 * z x_prev = x_prev + self.betas[i] * (self.model(x_tilde) - x_prev) return x_prev def train(model, dataloader, optimizer, device): model.train() for x, _ in tqdm(dataloader): x = x.to(device) optimizer.zero_grad() loss = ((model(x) - x) ** 2).mean() loss.backward() optimizer.step() def validate(model, dataloader, device): model.eval() total_loss = 0 with torch.no_grad(): for x, _ in tqdm(dataloader): x = x.to(device) loss = ((model(x) - x) ** 2).mean() total_loss += loss.item() * x.shape[0] return total_loss / len(dataloader.dataset) def main(): device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(256), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) dataset = ImageFolder('path/to/dataset', transform=transform) dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4) model = DiffusionModel(1000, torch.linspace(1e-4, 0.1, 1000), Unet()).to(device) optimizer = optim.Adam(model.parameters(), lr=1e-4) for epoch in range(10): train(model, dataloader, optimizer, device) val_loss = validate(model, dataloader, device) print(f'Epoch {epoch}: validation loss {val_loss:.4f}') torch.save(model.state_dict(), 'path/to/model') if __name__ == '__main__': main() ``` 在训练完成后,可以使用以下代码来融合两张图片: ```python import torch from PIL import Image from torchvision import transforms def main(): device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(256), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) # 加载模型 model = DiffusionModel(1000, torch.linspace(1e-4, 0.1, 1000), Unet()).to(device) model.load_state_dict(torch.load('path/to/model', map_location=device)) # 加载图片 image1 = Image.open('path/to/image1').convert('RGB') image2 = Image.open('path/to/image2').convert('RGB') x1 = transform(image1).unsqueeze(0).to(device) x2 = transform(image2).unsqueeze(0).to(device) # 融合图片 alpha = torch.linspace(0, 1, 11) for a in alpha: x = a * x1 + (1 - a) * x2 y = model(x).squeeze(0).detach().cpu() y = y * 0.5 + 0.5 # 反归一化 y = transforms.ToPILImage()(y) y.save(f'path/to/result_{a:.1f}.jpg') if __name__ == '__main__': main() ``` 该代码将两张图片进行线性插值,得到11张融合后的图片,其中`alpha`参数指定了插值的权重。在融合过程中,需要进行反归一化操作,将输出的图片转换为PIL格式,并保存到指定路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值