欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用PyTorch實現(xiàn)去噪擴(kuò)散模型的完整代碼

 更新時間:2024年01月08日 09:36:46   作者:deephub  
在本文中,我們將深入研究DDPM的復(fù)雜性,涵蓋其訓(xùn)練過程,包括正向和逆向過程,并探索如何執(zhí)行采樣,在整個探索過程中,我們將使用PyTorch從頭開始構(gòu)建DDPM,并完成其完整的訓(xùn)練,需要的朋友可以參考下

在深入研究去噪擴(kuò)散概率模型(DDPM)如何工作的細(xì)節(jié)之前,讓我們先看看生成式人工智能的一些發(fā)展,也就是DDPM的一些基礎(chǔ)研究。

VAE

VAE 采用了編碼器、概率潛在空間和解碼器。在訓(xùn)練過程中,編碼器預(yù)測每個圖像的均值和方差。然后從高斯分布中對這些值進(jìn)行采樣,并將其傳遞到解碼器中,其中輸入的圖像預(yù)計與輸出的圖像相似。這個過程包括使用KL Divergence來計算損失。VAEs的一個顯著優(yōu)勢在于它們能夠生成各種各樣的圖像。在采樣階段簡單地從高斯分布中采樣,解碼器創(chuàng)建一個新的圖像。

GAN

在變分自編碼器(VAEs)的短短一年之后,一個開創(chuàng)性的生成家族模型出現(xiàn)了——生成對抗網(wǎng)絡(luò)(GANs),標(biāo)志著一類新的生成模型的開始,其特征是兩個神經(jīng)網(wǎng)絡(luò)的協(xié)作:一個生成器和一個鑒別器,涉及對抗性訓(xùn)練過程。生成器的目標(biāo)是從隨機(jī)噪聲中生成真實的數(shù)據(jù),例如圖像,而鑒別器則努力區(qū)分真實數(shù)據(jù)和生成數(shù)據(jù)。在整個訓(xùn)練階段,生成器和鑒別器通過競爭性學(xué)習(xí)過程不斷完善自己的能力。生成器生成越來越有說服力的數(shù)據(jù),從而比鑒別器更聰明,而鑒別器又提高了辨別真實樣本和生成樣本的能力。這種對抗性的相互作用在生成器生成高質(zhì)量、逼真的數(shù)據(jù)時達(dá)到頂峰。在采樣階段,經(jīng)過GAN訓(xùn)練后,生成器通過輸入隨機(jī)噪聲產(chǎn)生新的樣本。它將這些噪聲轉(zhuǎn)換為通常反映真實示例的數(shù)據(jù)。

為什么我們需要另一個模型架構(gòu)

兩種模型都有不同的問題,雖然GANs擅長于生成與訓(xùn)練集中的圖像非常相似的逼真圖像,但VAEs擅長于創(chuàng)建各種各樣的圖像,盡管有產(chǎn)生模糊圖像的傾向。但是現(xiàn)有的模型還沒有成功地將這兩種功能結(jié)合起來——創(chuàng)造出既高度逼真又多樣化的圖像。這一挑戰(zhàn)給研究人員帶來了一個需要解決的重大障礙。

在第一篇GAN論文發(fā)表六年后,在VAE論文發(fā)表七年后,一個開創(chuàng)性的模型出現(xiàn)了:去噪擴(kuò)散概率模型(DDPM)。DDPM結(jié)合了兩個世界的優(yōu)勢,擅長于創(chuàng)造多樣化和逼真的圖像。

在本文中,我們將深入研究DDPM的復(fù)雜性,涵蓋其訓(xùn)練過程,包括正向和逆向過程,并探索如何執(zhí)行采樣。在整個探索過程中,我們將使用PyTorch從頭開始構(gòu)建DDPM,并完成其完整的訓(xùn)練。

這里假設(shè)你已經(jīng)熟悉深度學(xué)習(xí)的基礎(chǔ)知識,并且在深度計算機(jī)視覺方面有堅實的基礎(chǔ)。我們不會介紹這些基本概念,我們的目標(biāo)是生成人類確信其真實性的圖像。

DDPM

去噪擴(kuò)散概率模型(DDPM)是生成模型領(lǐng)域的一種前沿方法。與依賴顯式似然函數(shù)的傳統(tǒng)模型不同,DDPM通過對擴(kuò)散過程進(jìn)行迭代去噪來運行。這包括逐漸向圖像中添加噪聲并試圖去除該噪聲。其基本理論是基于這樣一種思想:通過一系列擴(kuò)散步驟轉(zhuǎn)換一個簡單的分布,例如高斯分布,可以得到一個復(fù)雜而富有表現(xiàn)力的圖像數(shù)據(jù)分布?;蛘哒f通過將樣本從原始圖像分布轉(zhuǎn)移到高斯分布,我們可以創(chuàng)建一個模型來逆轉(zhuǎn)這個過程。這使我們能夠從全高斯分布開始,以圖像分布結(jié)束,有效地生成新圖像。

DDPM的訓(xùn)練包括兩個基本步驟:產(chǎn)生噪聲圖像這是固定和不可學(xué)習(xí)的正向過程,以及隨后的逆向過程。逆向過程的主要目標(biāo)是使用專門的機(jī)器學(xué)習(xí)模型對圖像進(jìn)行去噪。

正向擴(kuò)散過程

正向過程是一個固定且不可學(xué)習(xí)的步驟,但是它需要一些預(yù)定義的設(shè)置。在深入研究這些設(shè)置之前,讓我們先了解一下它是如何工作的。

這個過程的核心概念是從一個清晰的圖像開始。在用“T”表示的特定步長上,少量噪聲按照高斯分布逐漸引入。

從圖像中可以看出,噪聲是在每一步遞增的,我們深入研究這種噪音的數(shù)學(xué)表示。

噪聲是從高斯分布中采樣的。為了在每一步引入少量的噪聲,我們使用馬爾可夫鏈。要生成當(dāng)前時間戳的圖像,我們只需要上次時間戳的圖像。馬爾可夫鏈的概念在這里是關(guān)鍵的,并對隨后的數(shù)學(xué)細(xì)節(jié)至關(guān)重要。

馬爾可夫鏈?zhǔn)且粋€隨機(jī)過程,其中過渡到任何特定狀態(tài)的概率僅取決于當(dāng)前狀態(tài)和經(jīng)過的時間,而不是之前的事件序列。這一特性簡化了噪聲添加過程的建模,使其更易于數(shù)學(xué)分析。

用beta表示的方差參數(shù)被有意地設(shè)置為一個非常小的值,目的是在每個步驟中只引入最少量的噪聲。

步長參數(shù)“T”決定了生成全噪聲圖像所需的步長。在本文中,該參數(shù)被設(shè)置為1000,這可能顯得很大。我們真的需要為數(shù)據(jù)集中的每個原始圖像創(chuàng)建1000個噪聲圖像嗎?馬爾可夫鏈方面被證明有助于解決這個問題。由于我們只需要上一步的圖像來預(yù)測下一步,并且每一步添加的噪聲保持不變,因此我們可以通過生成特定時間戳的噪聲圖像來簡化計算。采用對的再參數(shù)化技巧使我們能夠進(jìn)一步簡化方程。

將式(3)中引入的新參數(shù)納入式(2)中,對式(2)進(jìn)行了發(fā)展,得到了結(jié)果。

逆向擴(kuò)散過程

我們已經(jīng)為圖像引入了噪聲下一步就是執(zhí)行逆操作了。除非我們知道初始條件,即t = 0時的未去噪圖像,否則無法從數(shù)學(xué)上實現(xiàn)對圖像進(jìn)行逆向處理去噪。我們的目標(biāo)是直接從噪聲中采樣以創(chuàng)建新圖像,這里缺乏關(guān)于結(jié)果的信息。所以我需要設(shè)計一種在不知道結(jié)果的情況下逐步去噪圖像的方法。所以就出現(xiàn)了使用深度學(xué)習(xí)模型來近似這個復(fù)雜的數(shù)學(xué)函數(shù)的解決方案。

有了一點數(shù)學(xué)背景,模型將近似于方程(5)。一個值得注意的細(xì)節(jié)是,我們將堅持DDPM原始論文并保持固定的方差,盡管也有可能使模型學(xué)習(xí)它。

該模型的任務(wù)是預(yù)測當(dāng)前時間戳和前一個時間戳之間添加的噪聲的平均值。這樣做可以有效地去除噪音,達(dá)到預(yù)期的效果。但是如果我們的目標(biāo)是讓模型預(yù)測從“原始圖像”到最后一個時間戳添加的噪聲呢?

除非我們知道沒有噪聲的初始圖像,否則在數(shù)學(xué)上執(zhí)行逆向過程是具有挑戰(zhàn)性的,讓我們從定義后方差開始。

模型的任務(wù)是預(yù)測從初始圖像添加到時間戳t的圖像的噪聲。正向過程使我們能夠執(zhí)行這個操作,從一個清晰的圖像開始,并在時間戳t處進(jìn)展到一個有噪聲的圖像。

訓(xùn)練算法

我們假設(shè)用于進(jìn)行預(yù)測的模型體系結(jié)構(gòu)將是一個U-Net。訓(xùn)練階段的目標(biāo)是:對于數(shù)據(jù)集中的每個圖像,在[0,T]范圍內(nèi)隨機(jī)選擇一個時間戳,并計算正向擴(kuò)散過程。這產(chǎn)生了一個清晰的,有點噪聲的圖像,以及實際使用的噪聲。然后利用我們對逆向過程的理解,使用該模型來預(yù)測添加到圖像中的噪聲。有了真實的和預(yù)測的噪聲,我們似乎已經(jīng)進(jìn)入了一個有監(jiān)督的機(jī)器學(xué)習(xí)問題。

最主要的問題來了,我們應(yīng)該用哪個損失函數(shù)來訓(xùn)練我們的模型呢?由于處理的是概率潛在空間,Kullback-Leibler (KL)散度是一個合適的選擇。

KL散度衡量兩個概率分布之間的差異,在我們的例子中,是模型預(yù)測的分布和期望分布。在損失函數(shù)中加入KL散度不僅可以指導(dǎo)模型產(chǎn)生準(zhǔn)確的預(yù)測,還可以確保潛在空間表示符合期望的概率結(jié)構(gòu)。

KL散度可以近似為L2損失函數(shù),所以可以得到以下?lián)p失函數(shù):

最終我們得到了論文中提出的訓(xùn)練算法。

采樣

逆向流程已經(jīng)解釋完成了,下面就是如何使用了。從時刻T的一個完全隨機(jī)的圖像開始,并使用逆向過程T次,最終到達(dá)時刻0。這構(gòu)成了本文中概述的第二種算法

參數(shù)

我們有很多不同的參數(shù)beta,beta_tildes,alpha, alpha_hat 等等。目前都不知道如何選擇這些參數(shù)。但是此時已知的唯一參數(shù)是T,它被設(shè)置為1000。

對于所有列出的參數(shù),它們的選擇取決于beta。從某種意義上說,Beta決定了我們要在每一步中添加的噪聲量。因此,為了確保算法的成功,仔細(xì)選擇beta是至關(guān)重要的。其他的參數(shù)因為太多,請參考論文。

在原始論文的實驗階段探索了各種抽樣方法。最初的線性采樣方法圖像要么接收到的噪聲不足,要么變得過于嘈雜。為了解決這個問題,采用了另一種更常用的方法,即余弦采樣。余弦采樣提供了更平滑和更一致的噪聲添加。

模型Pytorch實現(xiàn)

我們將利用U-Net架構(gòu)進(jìn)行噪聲預(yù)測,之所以選擇U-Net,是因為U-Net是圖像處理、捕獲空間和特征地圖以及提供與輸入相同的輸出大小的理想架構(gòu)。

考慮到任務(wù)的復(fù)雜性和對每一步使用相同模型的要求(其中模型需要能夠以相同的權(quán)重去噪完全有噪聲的圖像和稍微有噪聲的圖像),調(diào)整模型是必不可少的。這包括合并更復(fù)雜的塊,并通過正弦嵌入步驟引入對所用時間戳的感知。這些增強(qiáng)的目的是使模型成為去噪任務(wù)的專家。在繼續(xù)構(gòu)建完整的模型之前,我們將介紹每個塊。

ConvNext塊

為了滿足提高模型復(fù)雜度的需要,卷積塊起著至關(guān)重要的作用。這里不能僅僅依賴于u-net論文中的基本塊,我們將結(jié)合ConvNext。

輸入由代表圖像的“x”和大小為“time_embedding_dim”的嵌入的時間戳可視化“t”組成。由于塊的復(fù)雜性以及與輸入和最后一層的殘差連接,在整個過程中,塊在學(xué)習(xí)空間和特征映射方面起著關(guān)鍵作用。

 class ConvNextBlock(nn.Module):
     def __init__(
         self,
         in_channels,
         out_channels,
         mult=2,
         time_embedding_dim=None,
         norm=True,
         group=8,
     ):
         super().__init__()
         self.mlp = (
             nn.Sequential(nn.GELU(), nn.Linear(time_embedding_dim, in_channels))
             if time_embedding_dim
             else None
         )
 
         self.in_conv = nn.Conv2d(
             in_channels, in_channels, 7, padding=3, groups=in_channels
         )
 
         self.block = nn.Sequential(
             nn.GroupNorm(1, in_channels) if norm else nn.Identity(),
             nn.Conv2d(in_channels, out_channels * mult, 3, padding=1),
             nn.GELU(),
             nn.GroupNorm(1, out_channels * mult),
             nn.Conv2d(out_channels * mult, out_channels, 3, padding=1),
         )
 
         self.residual_conv = (
             nn.Conv2d(in_channels, out_channels, 1)
             if in_channels != out_channels
             else nn.Identity()
         )
 
     def forward(self, x, time_embedding=None):
         h = self.in_conv(x)
         if self.mlp is not None and time_embedding is not None:
             assert self.mlp is not None, "MLP is None"
             h = h + rearrange(self.mlp(time_embedding), "b c -> b c 1 1")
         h = self.block(h)
         return h + self.residual_conv(x)

正弦時間戳嵌入

模型中的關(guān)鍵塊之一是正弦時間戳嵌入塊,它使給定時間戳的編碼能夠保留關(guān)于模型解碼所需的當(dāng)前時間的信息,因為該模型將用于所有不同的時間戳。

這是一個非常經(jīng)典的是實現(xiàn),并且應(yīng)用在各個地方,我們就直接貼代碼了

 class SinusoidalPosEmb(nn.Module):
     def __init__(self, dim, theta=10000):
         super().__init__()
         self.dim = dim
         self.theta = theta
 
     def forward(self, x):
         device = x.device
         half_dim = self.dim // 2
         emb = math.log(self.theta) / (half_dim - 1)
         emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
         emb = x[:, None] * emb[None, :]
         emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
         return emb

DownSample & UpSample

 class DownSample(nn.Module):
     def __init__(self, dim, dim_out=None):
         super().__init__()
         self.net = nn.Sequential(
             Rearrange("b c (h p1) (w p2) -> b (c p1 p2) h w", p1=2, p2=2),
             nn.Conv2d(dim * 4, default(dim_out, dim), 1),
         )
 
     def forward(self, x):
         return self.net(x)
 
 
 class Upsample(nn.Module):
     def __init__(self, dim, dim_out=None):
         super().__init__()
         self.net = nn.Sequential(
             nn.Upsample(scale_factor=2, mode="nearest"),
             nn.Conv2d(dim, dim_out or dim, kernel_size=3, padding=1),
         )
 
     def forward(self, x):
         return self.net(x)

時間多層感知器

這個模塊利用它來基于給定的時間戳t創(chuàng)建時間表示。這個多層感知器(MLP)的輸出也將作為所有修改后的ConvNext塊的輸入“t”。

這里,“dim”是模型的超參數(shù),表示第一個塊所需的通道數(shù)。它作為后續(xù)塊中通道數(shù)量的基本計算。

  sinu_pos_emb = SinusoidalPosEmb(dim, theta=10000)
 
   time_dim = dim * 4
 
   time_mlp = nn.Sequential(
       sinu_pos_emb,
       nn.Linear(dim, time_dim),
       nn.GELU(),
       nn.Linear(time_dim, time_dim),
   )

注意力

這是unet中使用的可選組件。注意力有助于增強(qiáng)剩余連接在學(xué)習(xí)中的作用。它通過殘差連接計算的注意機(jī)制和中低潛空間計算的特征映射,更多地關(guān)注從Unet左側(cè)獲得的重要空間信息。它來源于ACC-UNet論文。

gate 表示下塊的上采樣輸出,而x殘差表示在應(yīng)用注意的水平上的殘差連接。

 class BlockAttention(nn.Module):
     def __init__(self, gate_in_channel, residual_in_channel, scale_factor):
         super().__init__()
         self.gate_conv = nn.Conv2d(gate_in_channel, gate_in_channel, kernel_size=1, stride=1)
         self.residual_conv = nn.Conv2d(residual_in_channel, gate_in_channel, kernel_size=1, stride=1)
         self.in_conv = nn.Conv2d(gate_in_channel, 1, kernel_size=1, stride=1)
         self.relu = nn.ReLU()
         self.sigmoid = nn.Sigmoid()
 
     def forward(self, x: torch.Tensor, g: torch.Tensor) -> torch.Tensor:
         in_attention = self.relu(self.gate_conv(g) + self.residual_conv(x))
         in_attention = self.in_conv(in_attention)
         in_attention = self.sigmoid(in_attention)
         return in_attention * x

最后整合

將前面討論的所有塊(不包括注意力塊)整合到一個Unet中。每個塊都包含兩個殘差連接,而不是一個。這個修改是為了解決潛在的過度擬合問題。

 class TwoResUNet(nn.Module):
     def __init__(
         self,
         dim,
         init_dim=None,
         out_dim=None,
         dim_mults=(1, 2, 4, 8),
         channels=3,
         sinusoidal_pos_emb_theta=10000,
         convnext_block_groups=8,
     ):
         super().__init__()
         self.channels = channels
         input_channels = channels
         self.init_dim = default(init_dim, dim)
         self.init_conv = nn.Conv2d(input_channels, self.init_dim, 7, padding=3)
 
         dims = [self.init_dim, *map(lambda m: dim * m, dim_mults)]
         in_out = list(zip(dims[:-1], dims[1:]))
 
         sinu_pos_emb = SinusoidalPosEmb(dim, theta=sinusoidal_pos_emb_theta)
 
         time_dim = dim * 4
 
         self.time_mlp = nn.Sequential(
             sinu_pos_emb,
             nn.Linear(dim, time_dim),
             nn.GELU(),
             nn.Linear(time_dim, time_dim),
         )
 
         self.downs = nn.ModuleList([])
         self.ups = nn.ModuleList([])
         num_resolutions = len(in_out)
 
         for ind, (dim_in, dim_out) in enumerate(in_out):
             is_last = ind >= (num_resolutions - 1)
 
             self.downs.append(
                 nn.ModuleList(
                     [
                         ConvNextBlock(
                             in_channels=dim_in,
                             out_channels=dim_in,
                             time_embedding_dim=time_dim,
                             group=convnext_block_groups,
                         ),
                         ConvNextBlock(
                             in_channels=dim_in,
                             out_channels=dim_in,
                             time_embedding_dim=time_dim,
                             group=convnext_block_groups,
                         ),
                         DownSample(dim_in, dim_out)
                         if not is_last
                         else nn.Conv2d(dim_in, dim_out, 3, padding=1),
                     ]
                 )
             )
 
         mid_dim = dims[-1]
         self.mid_block1 = ConvNextBlock(mid_dim, mid_dim, time_embedding_dim=time_dim)
         self.mid_block2 = ConvNextBlock(mid_dim, mid_dim, time_embedding_dim=time_dim)
 
         for ind, (dim_in, dim_out) in enumerate(reversed(in_out)):
             is_last = ind == (len(in_out) - 1)
             is_first = ind == 0
 
             self.ups.append(
                 nn.ModuleList(
                     [
                         ConvNextBlock(
                             in_channels=dim_out + dim_in,
                             out_channels=dim_out,
                             time_embedding_dim=time_dim,
                             group=convnext_block_groups,
                         ),
                         ConvNextBlock(
                             in_channels=dim_out + dim_in,
                             out_channels=dim_out,
                             time_embedding_dim=time_dim,
                             group=convnext_block_groups,
                         ),
                         Upsample(dim_out, dim_in)
                         if not is_last
                         else nn.Conv2d(dim_out, dim_in, 3, padding=1)
                     ]
                 )
             )
 
         default_out_dim = channels
         self.out_dim = default(out_dim, default_out_dim)
 
         self.final_res_block = ConvNextBlock(dim * 2, dim, time_embedding_dim=time_dim)
         self.final_conv = nn.Conv2d(dim, self.out_dim, 1)
 
     def forward(self, x, time):
         b, _, h, w = x.shape
         x = self.init_conv(x)
         r = x.clone()
 
         t = self.time_mlp(time)
 
         unet_stack = []
         for down1, down2, downsample in self.downs:
             x = down1(x, t)
             unet_stack.append(x)
             x = down2(x, t)
             unet_stack.append(x)
             x = downsample(x)
 
         x = self.mid_block1(x, t)
         x = self.mid_block2(x, t)
 
         for up1, up2, upsample in self.ups:
             x = torch.cat((x, unet_stack.pop()), dim=1)
             x = up1(x, t)
             x = torch.cat((x, unet_stack.pop()), dim=1)
             x = up2(x, t)
             x = upsample(x)
 
         x = torch.cat((x, r), dim=1)
         x = self.final_res_block(x, t)
 
         return self.final_conv(x)

擴(kuò)散的代碼實現(xiàn)

最后我們介紹一下擴(kuò)散是如何實現(xiàn)的。由于我們已經(jīng)介紹了用于正向、逆向和采樣過程的所有數(shù)學(xué)理論,所里這里將重點介紹代碼。

 class DiffusionModel(nn.Module):
     SCHEDULER_MAPPING = {
         "linear": linear_beta_schedule,
         "cosine": cosine_beta_schedule,
         "sigmoid": sigmoid_beta_schedule,
     }
 
     def __init__(
         self,
         model: nn.Module,
         image_size: int,
         *,
         beta_scheduler: str = "linear",
         timesteps: int = 1000,
         schedule_fn_kwargs: dict | None = None,
         auto_normalize: bool = True,
     ) -> None:
         super().__init__()
         self.model = model
 
         self.channels = self.model.channels
         self.image_size = image_size
 
         self.beta_scheduler_fn = self.SCHEDULER_MAPPING.get(beta_scheduler)
         if self.beta_scheduler_fn is None:
             raise ValueError(f"unknown beta schedule {beta_scheduler}")
 
         if schedule_fn_kwargs is None:
             schedule_fn_kwargs = {}
 
         betas = self.beta_scheduler_fn(timesteps, **schedule_fn_kwargs)
         alphas = 1.0 - betas
         alphas_cumprod = torch.cumprod(alphas, dim=0)
         alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.0)
         posterior_variance = (
             betas * (1.0 - alphas_cumprod_prev) / (1.0 - alphas_cumprod)
         )
 
         register_buffer = lambda name, val: self.register_buffer(
             name, val.to(torch.float32)
         )
 
         register_buffer("betas", betas)
         register_buffer("alphas_cumprod", alphas_cumprod)
         register_buffer("alphas_cumprod_prev", alphas_cumprod_prev)
         register_buffer("sqrt_recip_alphas", torch.sqrt(1.0 / alphas))
         register_buffer("sqrt_alphas_cumprod", torch.sqrt(alphas_cumprod))
         register_buffer(
             "sqrt_one_minus_alphas_cumprod", torch.sqrt(1.0 - alphas_cumprod)
         )
         register_buffer("posterior_variance", posterior_variance)
 
         timesteps, *_ = betas.shape
         self.num_timesteps = int(timesteps)
 
         self.sampling_timesteps = timesteps
 
         self.normalize = normalize_to_neg_one_to_one if auto_normalize else identity
         self.unnormalize = unnormalize_to_zero_to_one if auto_normalize else identity
 
     @torch.inference_mode()
     def p_sample(self, x: torch.Tensor, timestamp: int) -> torch.Tensor:
         b, *_, device = *x.shape, x.device
         batched_timestamps = torch.full(
             (b,), timestamp, device=device, dtype=torch.long
         )
 
         preds = self.model(x, batched_timestamps)
 
         betas_t = extract(self.betas, batched_timestamps, x.shape)
         sqrt_recip_alphas_t = extract(
             self.sqrt_recip_alphas, batched_timestamps, x.shape
         )
         sqrt_one_minus_alphas_cumprod_t = extract(
             self.sqrt_one_minus_alphas_cumprod, batched_timestamps, x.shape
         )
 
         predicted_mean = sqrt_recip_alphas_t * (
             x - betas_t * preds / sqrt_one_minus_alphas_cumprod_t
         )
 
         if timestamp == 0:
             return predicted_mean
         else:
             posterior_variance = extract(
                 self.posterior_variance, batched_timestamps, x.shape
             )
             noise = torch.randn_like(x)
             return predicted_mean + torch.sqrt(posterior_variance) * noise
 
     @torch.inference_mode()
     def p_sample_loop(
         self, shape: tuple, return_all_timesteps: bool = False
     ) -> torch.Tensor:
         batch, device = shape[0], "mps"
 
         img = torch.randn(shape, device=device)
         # This cause me a RunTimeError on MPS device due to MPS back out of memory
         # No ideas how to resolve it at this point
 
         # imgs = [img]
 
         for t in tqdm(reversed(range(0, self.num_timesteps)), total=self.num_timesteps):
             img = self.p_sample(img, t)
             # imgs.append(img)
 
         ret = img  # if not return_all_timesteps else torch.stack(imgs, dim=1)
 
         ret = self.unnormalize(ret)
         return ret
 
     def sample(
         self, batch_size: int = 16, return_all_timesteps: bool = False
     ) -> torch.Tensor:
         shape = (batch_size, self.channels, self.image_size, self.image_size)
         return self.p_sample_loop(shape, return_all_timesteps=return_all_timesteps)
 
     def q_sample(
         self, x_start: torch.Tensor, t: int, noise: torch.Tensor = None
     ) -> torch.Tensor:
         if noise is None:
             noise = torch.randn_like(x_start)
 
         sqrt_alphas_cumprod_t = extract(self.sqrt_alphas_cumprod, t, x_start.shape)
         sqrt_one_minus_alphas_cumprod_t = extract(
             self.sqrt_one_minus_alphas_cumprod, t, x_start.shape
         )
 
         return sqrt_alphas_cumprod_t * x_start + sqrt_one_minus_alphas_cumprod_t * noise
 
     def p_loss(
         self,
         x_start: torch.Tensor,
         t: int,
         noise: torch.Tensor = None,
         loss_type: str = "l2",
     ) -> torch.Tensor:
         if noise is None:
             noise = torch.randn_like(x_start)
         x_noised = self.q_sample(x_start, t, noise=noise)
         predicted_noise = self.model(x_noised, t)
 
         if loss_type == "l2":
             loss = F.mse_loss(noise, predicted_noise)
         elif loss_type == "l1":
             loss = F.l1_loss(noise, predicted_noise)
         else:
             raise ValueError(f"unknown loss type {loss_type}")
         return loss
 
     def forward(self, x: torch.Tensor) -> torch.Tensor:
         b, c, h, w, device, img_size = *x.shape, x.device, self.image_size
         assert h == w == img_size, f"image size must be {img_size}"
 
         timestamp = torch.randint(0, self.num_timesteps, (1,)).long().to(device)
         x = self.normalize(x)
         return self.p_loss(x, timestamp)

擴(kuò)散過程是訓(xùn)練部分的模型。它打開了一個采樣接口,允許我們使用已經(jīng)訓(xùn)練好的模型生成樣本。

訓(xùn)練的要點總結(jié)

對于訓(xùn)練部分,我們設(shè)置了37,000步的訓(xùn)練,每步16個批次。由于GPU內(nèi)存分配限制,圖像大小被限制為128x128。使用指數(shù)移動平均(EMA)模型權(quán)重每1000步生成樣本以平滑采樣,并保存模型版本。

在最初的1000步訓(xùn)練中,模型開始捕捉一些特征,但仍然錯過了某些區(qū)域。在10000步左右,這個模型開始產(chǎn)生有希望的結(jié)果,進(jìn)步變得更加明顯。在3萬步的最后,結(jié)果的質(zhì)量顯著提高,但仍然存在黑色圖像。這只是因為模型沒有足夠的樣本種類,真實圖像的數(shù)據(jù)分布并沒有完全映射到高斯分布。

有了最終的模型權(quán)重,我們可以生成一些圖片。盡管由于128x128的尺寸限制,圖像質(zhì)量受到限制,但該模型的表現(xiàn)還是不錯的。

注:本文使用的數(shù)據(jù)集是森林地形的衛(wèi)星圖片,具體獲取方式請參考源代碼中的ETL部分。

總結(jié)

我們已經(jīng)完整的介紹了有關(guān)擴(kuò)散模型的必要知識,并且使用Pytorch進(jìn)行了完整的實現(xiàn),本文的代碼:

https://github.com/Camaltra/this-is-not-real-aerial-imagery/

以上就是使用PyTorch實現(xiàn)去噪擴(kuò)散模型的完整代碼的詳細(xì)內(nèi)容,更多關(guān)于PyTorch實現(xiàn)去噪擴(kuò)散模型的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Django發(fā)送html郵件的方法

    Django發(fā)送html郵件的方法

    這篇文章主要介紹了Django發(fā)送html郵件的方法,涉及Django框架操作郵件的相關(guān)技巧,需要的朋友可以參考下
    2015-05-05
  • Python實現(xiàn)將DOC文檔轉(zhuǎn)換為PDF的方法

    Python實現(xiàn)將DOC文檔轉(zhuǎn)換為PDF的方法

    這篇文章主要介紹了Python實現(xiàn)將DOC文檔轉(zhuǎn)換為PDF的方法,涉及Python調(diào)用系統(tǒng)win32com組件實現(xiàn)文件格式轉(zhuǎn)換的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07
  • pycharm雙擊無響應(yīng)(打不開問題解決辦法)

    pycharm雙擊無響應(yīng)(打不開問題解決辦法)

    這篇文章主要介紹了pycharm雙擊無響應(yīng)(打不開問題解決辦法),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • 詳細(xì)解析Python中__init__()方法的高級應(yīng)用

    詳細(xì)解析Python中__init__()方法的高級應(yīng)用

    這篇文章主要介紹了詳細(xì)解析Python中__init__()方法的高級應(yīng)用,包括在映射和elif序列等地方的更為復(fù)雜的用法,需要的朋友可以參考下
    2015-05-05
  • python機(jī)器學(xué)習(xí)之決策樹分類詳解

    python機(jī)器學(xué)習(xí)之決策樹分類詳解

    這篇文章主要介紹了python機(jī)器學(xué)習(xí)之決策樹分類,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • python?中的requirements.txt?文件的使用詳情

    python?中的requirements.txt?文件的使用詳情

    這篇文章主要介紹了python?中的requirements.txt文件的使用詳情,文章圍繞主題展開詳細(xì)內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-05-05
  • 如何使用PyCharm將代碼上傳到GitHub上(圖文詳解)

    如何使用PyCharm將代碼上傳到GitHub上(圖文詳解)

    這篇文章主要介紹了如何使用PyCharm將代碼上傳到GitHub上(圖文詳解),文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 使用Docker制作Python環(huán)境連接Oracle鏡像

    使用Docker制作Python環(huán)境連接Oracle鏡像

    這篇文章主要為大家介紹了使用Docker制作Python環(huán)境連接Oracle鏡像示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • keras的backend 設(shè)置 tensorflow,theano操作

    keras的backend 設(shè)置 tensorflow,theano操作

    這篇文章主要介紹了keras的backend 設(shè)置 tensorflow,theano操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • python使用requests庫實現(xiàn)輕松發(fā)起HTTP請求

    python使用requests庫實現(xiàn)輕松發(fā)起HTTP請求

    requests是Python中一個非常流行的用于發(fā)送HTTP請求的第三方庫,它提供了簡潔的API,使得發(fā)送各種HTTP請求變得非常容易,下面我們來看看具體實現(xiàn)方法吧
    2025-01-01

最新評論