AIGC基础:从VAE到DDPM原理、代码详解
?作者 |?王建周
單位 |?來也科技AI團隊負責人
研究方向 |?分布式系統、CV、NLP
前言
AIGC 目前是一個非常火熱的方向,DALLE-2,ImageGen,Stable Diffusion 的圖像在以假亂真的前提下,又有著腦洞大開的藝術性,以下是用開源的 Stable Diffusion 生成的一些圖片。
這些模型后邊都使用了 Diffusion Model 的技術,但是缺乏相關背景知識去單純學習 Diffusion Model 門檻會比較高,不過沿著 AE、VAE、CVAE、DDPM 這一系列的生成模型的路線、循序學習會更好的理解和掌握,本文將從原理、數學推導、代碼詳細講述這些模型。
AE (AutoEncoder)
AE 模型作用是提取數據的核心特征(Latent Attributes),如果通過提取的低維特征可以完美復原原始數據,那么說明這個特征是可以作為原始數據非常優秀的表征。
AE 模型的結構如下圖:
訓練數據通過 Encoder 得到 Latent,Latent 再通過 Decoder 得到重建數據,通過重建數據和訓練的數據差異來構造訓練 Loss,代碼如下(本文所有的場景都是 mnist,編碼器和解碼器都用了最基本的卷積網絡):
class?DownConvLayer(tf.keras.layers.Layer):def?__init__(self,?dim):super(DownConvLayer,?self).__init__()self.conv?=?tf.keras.layers.Conv2D(dim,?3,?activation=tf.keras.layers.ReLU(),?use_bias=False,?padding='same')self.pool?=?tf.keras.layers.MaxPool2D(2)def?call(self,?x,?training=False,?**kwargs):x?=?self.conv(x)x?=?self.pool(x)return?xclass?UpConvLayer(tf.keras.layers.Layer):def?__init__(self,?dim):super(UpConvLayer,?self).__init__()self.conv?=?tf.keras.layers.Conv2D(dim,?3,?activation=tf.keras.layers.ReLU(),?use_bias=False,?padding='same')#?通過UpSampling2D上采樣self.pool?=?tf.keras.layers.UpSampling2D(2)def?call(self,?x,?training=False,?**kwargs):x?=?self.conv(x)x?=?self.pool(x)return?x#?示例代碼都是通過非常簡單的卷積操作實現編碼器和解碼器 class?Encoder(tf.keras.layers.Layer):def?__init__(self,?dim,?layer_num=3):super(Encoder,?self).__init__()self.convs?=?[DownConvLayer(dim)?for?_?in?range(layer_num)]def?call(self,?x,?training=False,?**kwargs):for?conv?in?self.convs:x?=?conv(x,?training)return?xclass?Decoder(tf.keras.layers.Layer):def?__init__(self,?dim,?layer_num=3):super(Decoder,?self).__init__()self.convs?=?[UpConvLayer(dim)?for?_?in?range(layer_num)]self.final_conv?=?tf.keras.layers.Conv2D(1,?3,?strides=1)def?call(self,?x,?training=False,?**kwargs):for?conv?in?self.convs:x?=?conv(x,?training)#?將圖像轉成和輸入圖像shape一致reconstruct?=?self.final_conv(x)return?reconstructclass?AutoEncoderModel(tf.keras.Model):def?__init__(self):super(AutoEncoderModel,?self).__init__()self.encoder?=?Encoder(64,?layer_num=3)self.decoder?=?Decoder(64,?layer_num=3)def?call(self,?inputs,?training=None,?mask=None):image?=?inputs[0]#?得到圖像的特征表示latent?=?self.encoder(image,?training)#?通過特征重建圖像reconstruct_img?=?self.decoder(latent,?training)return?reconstruct_img@tf.functiondef?train_step(self,?data):img?=?data["image"]with?tf.GradientTape()?as?tape:reconstruct_img?=?self((img,),?True)trainable_vars?=?self.trainable_variables#?利用l2?loss?來判斷重建圖片和原始圖像的一致性l2_loss?=?(reconstruct_img?-?img)?**?2l2_loss?=?tf.reduce_mean(tf.reduce_sum(l2_loss,?axis=(1,?2,?3)))gradients?=?tape.gradient(l2_loss,?trainable_vars)self.optimizer.apply_gradients(zip(gradients,?trainable_vars))return?{"l2_loss":?l2_loss}通過 AE 模型可以看到,只要有有效的數據的 Latent Attribute 表示,那么就可以通過 Decoder 來生成新數據,但是在 AE 模型中,Latent 是通過已有數據生成的,所以沒法生成已有數據外的新數據。
所以我們設想,是不是可以假設 Latent 符合一定分布規律,只要通過有限參數能夠描述這個分布,那么就可以通過這個分布得到不在訓練數據中的新 Latent,利用這個新 Latent 就能生成全新數據,基于這個思路,有了 VAE(Variational AutoEncoder 變分自編碼器)。
VAE
VAE 中假設 Latent Attributes (公式中用 z)符合正態分布,也就是通過訓練數據得到的 z 滿足以下條件:
因為 z 是向量,所 都是向量,分別為正態分布的均值和方差。有了學習得到正態分布的參數 ,那么就可以從這個正態分布中采樣新的 z,新的 z 通過解碼器得到新的數據。
所以在訓練過程中需要同時優化兩點:
1. 重建的數據和訓練數據差異足夠小,也就是生成 x 的對數似然越高,一般依然用 L2 或者 L1 loss;
2.? 定義的正態分布需要和標準正態分布的一致,這里用了 KL 散度來約束兩個分布一致;
Loss 公式定義如下,其中 和 為生成分布, 為編碼分布, 為從正態分布中采樣的先驗分布:
Loss 的證明如下:
因為我們的目標是最大化對數似然生成分布 ,也就是最小化負的公式 15,也就是公式 1 的 Loss。
所以 VAE 的結構如下:
注意的是在上圖中有一個采樣 z 的操作,這個操作不可導導致無法對進行優化,所以為了反向傳播優化,用到重參數的技巧,也就是將 z 表示成 的數學組合方式且該組合方式可導,組合公式如下:
?
可以證明重參數后的模型 f 輸出期望是不變的(z 是連續分布)。
在計算 定義的正態分布和 定義的正態分布的 KL 散度時,用了數學推導進行簡化。
對公式 28 的 log 部分繼續簡化:
令:
將公式 32 和 33 帶入公式 28 得到:
因為:
將公式 37、38、45 帶入公式 34 得到最終的 KL 散度 Loss 公式:
因為 非負,所以我們通過神經網絡來學習 。
有了前邊的鋪墊,所以 VAE 的實現上也比較簡單,代碼如下:
class?VAEModel(tf.keras.Model):def?__init__(self,?inference=False):super(VAEModel,?self).__init__()self.inference?=?inferenceself.encoder?=?Encoder(64,?layer_num=3)self.decoder?=?Decoder(64,?layer_num=3)#?mnist?的size是28,這里為了簡單對齊大小,縮放成了32self.img_size?=?32#?z的維度self.latent_dim?=?64#?通過全連接來學習隱特征z正態分布的均值self.z_mean_mlp?=?tf.keras.Sequential([tf.keras.layers.Dense(self.latent_dim?*?2,?activation="relu"),tf.keras.layers.Dense(self.latent_dim,?use_bias=False),])#?通過全連接來學習隱特征z正態分布的方差的對數log(o^2)self.z_log_var_mlp?=?tf.keras.Sequential([tf.keras.layers.Dense(self.latent_dim?*?2,?activation="relu"),tf.keras.layers.Dense(self.latent_dim,?use_bias=False),])#?通過全連接將z?縮放成上采樣輸入適配的shapeself.decoder_input_size?=?[int(self.img_size?/?(2?**?3)),?64]self.decoder_dense?=?tf.keras.layers.Dense(self.decoder_input_size[0]?*?self.decoder_input_size[0]?*?self.decoder_input_size[1],activation="relu")def?sample_latent(self,?bs,?image):#?推理階段的z直接可以從標準正態分布中采樣,因為訓練的decoder已經可以從標準高斯分布生成新的圖片了if?self.inference:z?=?tf.keras.backend.random_normal(shape=(bs,?self.latent_dim))z_mean,?z_log_var?=?None,?Noneelse:x?=?imagex?=?self.encoder(x)x?=?tf.keras.layers.Flatten()(x)z_mean?=?self.z_mean_mlp(x)z_log_var?=?self.z_log_var_mlp(x)epsilon?=?tf.keras.backend.random_normal(shape=(bs,?self.latent_dim))'''實現重參數采樣公式17u?+?exp(0.5*log(o^2))*e=u?+exp(0.5*2*log(o))*e=u?+?exp(log(o))*e=u?+?o*e'''z?=?z_mean?+?tf.exp(0.5?*?z_log_var)?*?epsilonreturn?z,?z_mean,?z_log_vardef?call(self,?inputs,?training=None,?mask=None):#?推理生成圖片時,image為Nonebs,?image?=?inputs[0],?inputs[1]z,?z_mean,?z_log_var?=?self.sample_latent(bs,?image)latent?=?self.decoder_dense(z)latent?=?tf.reshape(latent,[-1,?self.decoder_input_size[0],?self.decoder_input_size[0],?self.decoder_input_size[1]])#?通過z重建圖像reconstruct_img?=?self.decoder(latent,?training)return?reconstruct_img,?z_mean,?z_log_vardef?compute_loss(self,?reconstruct_img,?z_mean,?z_log_var,?img):#?利用l2?loss?來判斷重建圖片和原始圖像的一致性l2_loss?=?(reconstruct_img?-?img)?**?2l2_loss?=?tf.reduce_mean(tf.reduce_sum(l2_loss,?axis=(1,?2,?3)))#?實現公式48kl_loss?=?-0.5?*?(1?+?z_log_var?-?tf.square(z_mean)?-?tf.exp(z_log_var))kl_loss?=?tf.reduce_mean(tf.reduce_sum(kl_loss,?axis=1))total_loss?=?kl_loss?+?l2_lossreturn?{"l2_loss":?l2_loss,?"total_loss":?total_loss,?"kl_loss":?kl_loss}@tf.functiondef?forward(self,?data,?training):img?=?data["img_data"]bs?=?tf.shape(img)[0]reconstruct_img,?z_mean,?z_log_var?=?self((bs,?img),?training)return?self.compute_loss(reconstruct_img,?z_mean,?z_log_var,?img)def?train_step(self,?data):with?tf.GradientTape()?as?tape:result?=?self.forward(data,?True)trainable_vars?=?self.trainable_variablesgradients?=?tape.gradient(result["total_loss"],?trainable_vars)self.optimizer.apply_gradients(zip(gradients,?trainable_vars))return?result生成的圖片效果如下:
在我們大多數生成場景,都需要帶有控制條件,比如我們在生產手寫數字的時候,我們需要明確的告訴模型,生成數字 0 的圖片,基于這個需求,有了 Conditional Variational AutoEncoder(CVAE)。
CVAE
CVAE 的改進思路比較簡單,就是訓練階段的 z 同時由 x 和控制條件 y 決定,同時生成的 x 也是由 y 和 z 同時決定,Loss 如下:
而? q(z|y) 我們仍然期望符合標準正態分布,對 VAE 代碼改動非常少,簡單的實現方法就是對條件 y 有一個 embedding 表示,這個 embedding 表示參與到 encoder 和 decoder 的訓練,代碼如下:
class?CVAEModel(VAEModel):def?__init__(self,?inference=False):super(CVAEModel,?self).__init__(inference=inference)#?定義label的Embeddingself.label_dim?=?128self.label_embedding?=?tf.Variable(initial_value=tf.keras.initializers.HeNormal()(shape=[10,?self.label_dim]),trainable=True,)self.encoder_y_dense?=?tf.keras.layers.Dense(self.img_size?*?self.img_size,?activation="relu")self.decoder_y_dense?=?tf.keras.layers.Dense(self.decoder_input_size[0]?*?self.decoder_input_size[0]?*?self.decoder_input_size[1],?activation="relu")def?call(self,?inputs,?training=None,?mask=None):#?推理生成圖片時,image為Nonebs,?image,?label?=?inputs[0],?inputs[1],?inputs[2]label_emb?=?tf.nn.embedding_lookup(self.label_embedding,?label)label_emb?=?tf.reshape(label_emb,?[-1,?self.label_dim])if?not?self.inference:#?訓練階段將條件label的embedding拼接到圖片上作為encoder的輸入encoder_y?=?self.encoder_y_dense(label_emb)encoder_y?=?tf.reshape(encoder_y,?[-1,?self.img_size,?self.img_size,?1])image?=?tf.concat([encoder_y,?image],?axis=-1)z,?z_mean,?z_log_var?=?self.sample_latent(bs,?image)latent?=?self.decoder_dense(z)#?將條件label的embedding拼接到z上作為decoder的輸入decoder_y?=?self.decoder_y_dense(label_emb)latent?=?tf.concat([latent,?decoder_y],?axis=-1)latent?=?tf.reshape(latent,[-1,?self.decoder_input_size[0],?self.decoder_input_size[0],self.decoder_input_size[1]?*?2])#?通過特征重建圖像reconstruct_img?=?self.decoder(latent,?training)return?reconstruct_img,?z_mean,?z_log_var@tf.functiondef?forward(self,?data,?training):img?=?data["img_data"]label?=?data["label"]bs?=?tf.shape(img)[0]reconstruct_img,?z_mean,?z_log_var?=?self((bs,?img,?label),?training)return?self.compute_loss(reconstruct_img,?z_mean,?z_log_var,?img)def?train_step(self,?data):with?tf.GradientTape()?as?tape:result?=?self.forward(data,?True)trainable_vars?=?self.trainable_variablesgradients?=?tape.gradient(result["total_loss"],?trainable_vars)self.optimizer.apply_gradients(zip(gradients,?trainable_vars))return?result生成 0~9 的圖片效果如下:
從 VAE 的原理可以看到,我們做了假設 ,但是在大多數場景,這個假設過于嚴苛,很難保證數據特征符合基本的正態分布(嚴格意義上也做不到,嚴格分布的話說明特征就是高斯噪聲了),因為這個缺陷,所以基本的 VAE 生成的圖像細節不夠,邊緣偏模糊。
為了解決這些問題,又出現 DDPM(Denoising Diffusion Probabilistic Model),因為 DDPM 相比 GAN,更容易訓練(GAN 需要交替訓練,而且容易出現模式崩塌,可以參考我們以前的文章),此外 DDPM 的多樣性相比 GAN 更好(GAN 因為生成的圖像要“欺騙”過鑒別器,所以生成的圖像和訓練集合的真實圖像類似),所以最近 DDPM 成為最受歡迎的生成模型。
DDPM
DDPM 啟發點來自非平衡熱力學,系統和環境之間有著物質和能量交換,比如在一個盛水的容器中滴入一滴墨水,最終墨水會均勻的擴散到水中,但是如果擴散的每一步足夠小,那么這一步就可逆。
所以主要流程上分兩個階段,前向加噪和反向去噪,原始數據為 ,每一步添加足夠小的高斯噪聲,經過足夠的 step T 后,最終數據 會變成標準的高斯噪聲(下圖的 q),因為前向加噪上是可行的,所以我們假設反向去噪也是可行的,可以逐步的從噪聲中一點點的恢復數據的有用信息(下圖的 p)直到為 ,下邊將詳細介紹兩部分。
1. 前向加噪
假設前向加噪過程每一步添加噪聲的過程符合以下高斯分布,且整個過程滿足馬爾科夫鏈,即以下公式:
根據上文提到的重參數技巧,公式 50 可以寫成(為了方便,寫成標量形式):
其中 ,所以公式 52 可以理解為向原始的數據設中加非常小的高斯噪音,并且隨著t變大加的噪音逐漸變大,為了方便公式推導,令:
因為:
根據正態分布的求和計算公式以及重參數技巧:
令 ,將公式 63 帶入 57 并推導到一般形式,得到如下前向公式:
公式 64 就是正向過程的最終公式,可以看到正向過程是不存在任何網絡參數的,而且對于給定的 t,無需迭代,通過表達式可以直接計算得到 。
2. 反向去噪
反向去噪期望從標準的高斯分布噪聲 逐步的消除噪音,每次只恢復目標數據的一點點,最終生成目標數據 ,假設的反向去噪也是符合高斯分布和馬爾科夫鏈,可以用以下數學公式描述:
因為 中 是依賴 的,所以單純的 是無法計算的,所以我們需要轉而計算 (上圖的粉色路徑),前者有帶學習的參數,我們假設:
接下來的目標是需要寫出 的表達式,主要是利用條件概率和貝葉斯公式(為了簡化都用標量的形式)。
帶入各自的表達式:
得到:
對比正態分布公式:
可以得到我們需要的 的表達式:
接下來我們需要推導下優化的目標,根據前邊公式 10 的推導有以下:
因為:
公式 101 代入公式 96 得到:
對 112 的 繼續推導:
其中 中 為直接計算出來等于常數,所以 為常數;而 為 的 t=1 的特殊表達式,故可以合并到 ,所以從公式 95 可以看出,我們最大化的對數似然 ,等價最小化公式 118,而根據公式 47,兩個正態分布的 KL 散度等于:
如果上述的 KL 離散度最小,我們希望 逼近 ,根據前邊公式93的推導,我們知道:
根據這個公式,對于已知 的情況下,如果能預測出 ,就可以解決我們的問題,啟發我們設計以下目標:
所以 KL 散度(公式 122)變成以下公式:
前邊的公式 130 的常數 在訓練過程可以認為被合并到學習率,所以可以被略掉,所以我們最終的優化目標 Loss 為以下:
所以訓練過程如下:
從公式 66 和 126,以及重參數技巧可以得知:
所以等待訓練完成得到 后,循環執行公式 132 就得到了最終的目標數據 ,過程如下:
經過前邊較多的公式推導,最終得到 DDPM 的訓練和生成過程確非常簡單,從前邊能看到希望網絡 輸入輸出 shape 一致,所以常見的 DDPM 都是用 unet 來實現(下圖,核心是四點:下采樣、上采樣、上下采樣的特征拼接),在代碼上我們做了部分優化。
1. 為了簡化代碼,我們去掉常見實現方式的 self-attention;
2. 一般時間步 t 也會采用 transformer 中基本的 sincos 的 position 編碼,為了簡化編碼,我們的時間編碼直接采用可以學習網絡并只加入 Unet 的編碼階段,解碼階段不加入;
3. 相比前邊的 VAE 代碼,這里的代碼相對復雜,卷積模塊采用 Resnet 的殘差處理方式(經過實驗,前邊 VAE 基本的編碼器和解碼器過于簡單,沒法收斂);
4. 參照官方,用 group norm 代替 batch norm。
class?ConvResidualLayer(tf.keras.layers.Layer):def?__init__(self,?filter_num):super(ConvResidualLayer,?self).__init__()self.conv1?=?tf.keras.layers.Conv2D(filter_num,?kernel_size=1,?padding='same')#?import?tensorflow_addons?as?tfaself.gn1?=?tfa.layers.GroupNormalization(8)self.conv2?=?tf.keras.layers.Conv2D(filter_num,?kernel_size=3,?padding='same')self.gn2?=?tfa.layers.GroupNormalization(8)self.act2?=?tf.keras.activations.swishdef?call(self,?inputs,?training=False,?*args,?**kwargs):residual?=?self.conv1(inputs)x?=?self.gn1(residual)x?=?tf.nn.swish(x)x?=?self.conv2(x)x?=?self.gn2(x)x?=?tf.nn.swish(x)out?=?x?+?residualreturn?out?/?1.44class?SimpleDDPMModel(tf.keras.Model):def?__init__(self,?max_time_step=100):super(SimpleDDPMModel,?self).__init__()#?定義ddpm?前向過程的一些參數self.max_time_step?=?max_time_step#?采用numpy?的float64,避免連乘的精度失準betas?=?np.linspace(1e-4,?0.02,?max_time_step,?dtype=np.float64)alphas?=?1.0?-?betasalphas_bar?=?np.cumprod(alphas,?axis=0)betas_bar?=?1.0?-?alphas_barself.betas,?self.alphas,?self.alphas_bar,?self.betas_bar?=?tuple(map(lambda?x:?tf.constant(x,?tf.float32),[betas,?alphas,?alphas_bar,?betas_bar]))filter_nums?=?[64,?128,?256]self.encoders?=?[tf.keras.Sequential([ConvResidualLayer(num),tf.keras.layers.MaxPool2D(2)])?for?num?in?filter_nums]self.mid_conv?=?ConvResidualLayer(filter_nums[-1])self.decoders?=?[tf.keras.Sequential([tf.keras.layers.Conv2DTranspose(num,?3,?strides=2,?padding="same"),ConvResidualLayer(num),ConvResidualLayer(num),])?for?num?in?reversed(filter_nums)]self.final_conv?=?tf.keras.Sequential([ConvResidualLayer(64),tf.keras.layers.Conv2D(1,?3,?padding="same")])self.img_size?=?32self.time_embeddings?=?[tf.keras.Sequential([tf.keras.layers.Dense(num,?activation=tf.keras.layers.LeakyReLU()),tf.keras.layers.Dense(num)])for?num?in?filter_nums]#?實現公式?64?從原始數據生成噪音圖像def?q_noisy_sample(self,?x_0,?t,?noisy):alpha_bar,?beta_bar?=?self.extract([self.alphas_bar,?self.betas_bar],?t)sqrt_alpha_bar,?sqrt_beta_bar?=?tf.sqrt(alpha_bar),?tf.sqrt(beta_bar)return?sqrt_alpha_bar?*?x_0?+?sqrt_beta_bar?*?noisydef?extract(self,?sources,?t):bs?=?tf.shape(t)[0]targets?=?[tf.gather(source,?t)?for?i,?source?in?enumerate(sources)]return?tuple(map(lambda?x:?tf.reshape(x,?[bs,?1,?1,?1]),?targets))#?實現公式?131,從噪聲數據恢復上一步的數據def?p_real_sample(self,?x_t,?t,?pred_noisy):alpha,?beta,?beta_bar?=?self.extract([self.alphas,?self.betas,?self.betas_bar],?t)noisy?=?tf.random.normal(shape=tf.shape(x_t))#?這里的噪聲系數和beta取值一樣,也可以滿足越靠近0,噪聲越小noisy_weight?=?tf.sqrt(beta)#?當t==0?時,不加入隨機噪聲bs?=?tf.shape(x_t)[0]noisy_mask?=?tf.reshape(1?-?tf.cast(tf.equal(t,?0),?tf.float32),?[bs,?1,?1,?1])noisy_weight?*=?noisy_maskx_t_1?=?(x_t?-?beta?*?pred_noisy?/?tf.sqrt(beta_bar))?/?tf.sqrt(alpha)?+?noisy?*?noisy_weightreturn?x_t_1#?unet?的下采樣def?encoder(self,?noisy_img,?t,?data,?training):xs?=?[]for?idx,?conv?in?enumerate(self.encoders):noisy_img?=?conv(noisy_img)t?=?tf.cast(t,?tf.float32)time_embedding?=?self.time_embeddings[idx](t)time_embedding?=?tf.reshape(time_embedding,?[-1,?1,?1,?tf.shape(time_embedding)[-1]])#?time?embedding?直接相加noisy_img?+=?time_embeddingxs.append(noisy_img)return?xs#?unet的上采樣def?decoder(self,?noisy_img,?xs,?training):xs.reverse()for?idx,?conv?in?enumerate(self.decoders):noisy_img?=?conv(tf.concat([xs[idx],?noisy_img],?axis=-1))return?noisy_img@tf.functiondef?pred_noisy(self,?data,?training):img?=?data["img_data"]bs?=?tf.shape(img)[0]noisy?=?tf.random.normal(shape=tf.shape(img))t?=?data.get("t",?None)#?在訓練階段t為空,隨機生成成tif?t?is?None:t?=?tf.random.uniform(shape=[bs,?1],?minval=0,?maxval=self.max_time_step,?dtype=tf.int32)noisy_img?=?self.q_noisy_sample(img,?t,?noisy)else:noisy_img?=?imgxs?=?self.encoder(noisy_img,?t,?data,?training)x?=?self.mid_conv(xs[-1])x?=?self.decoder(x,?xs,?training)pred_noisy?=?self.final_conv(x)return?{"pred_noisy":?pred_noisy,?"noisy":?noisy,"loss":?tf.reduce_mean(tf.reduce_sum((pred_noisy?-?noisy)?**?2,?axis=(1,?2,?3)),?axis=-1)}#?生成圖片def?call(self,?inputs,?training=None,?mask=None):bs?=?inputs[0]x_t?=?tf.random.normal(shape=[bs,?self.img_size,?self.img_size,?1])for?i?in?reversed(range(0,?self.max_time_step)):t?=?tf.reshape(tf.repeat(i,?bs),?[bs,?1])p?=?self.pred_noisy({"img_data":?x_t,?"t":?t},?False)x_t?=?self.p_real_sample(x_t,?t,?p["pred_noisy"])return?x_tdef?train_step(self,?data):with?tf.GradientTape()?as?tape:result?=?self.pred_noisy(data,?True)trainable_vars?=?self.trainable_variablesgradients?=?tape.gradient(result["loss"],?trainable_vars)self.optimizer.apply_gradients(zip(gradients,?trainable_vars))return?{"loss":?result["loss"]}def?test_step(self,?data):result?=?self.pred_noisy(data,?False)return?{"loss":?result["loss"]}生成的圖片如下:
類似 CVAE,使用 DDPM 的時候,我們依然希望可以通過條件控制生成,如前邊提到的 DALLE-2,Stable Diffusion 都是通過條件(文本 prompt)來控制生成的圖像,為了實現這個目的,就需要采用 Conditional Diffusion Model。
Conditional Diffusion Model
目前最主要使用的 Conditional Diffusion Model 主要有兩種實現方式,Classifier-guidance 和 Classifier-free,從名字也可以看出,前者需要一個分類器模型,后者無需分類器模型,下邊講簡單推導兩種的實現方案,并給出? Classifier-free Diffusion Model 的實現代碼。
1. Classifier-guidance
參考前邊的推導公式在無條件的模型下,我們需要優化;而在控制條件 y 下,我們需要優化的是,可以用貝葉斯進行以下的公式推導:
從以下公式推導可以看出,我們需要一個分類模型,這個分類模型可以對前向過程融入噪音的數據很好的分類,在擴散模型求梯度的階段,融入這個分類模型對當前噪音數據的梯度即可。
2. Classifier-free
通過 classifier-guidance 的公式證明,我們很容易得到以下的公式推導:
取值 0~1 之間,從公式 140 可以看出,只要我們在模型輸入上,采樣性的融入 y 就可以達到目標,所以在前邊的 DDPM 代碼上改動比較簡單,我們對 0~9 這 10 個數字學習一個 embedding 表示,然后采樣性的加入 unet 的 encoder 的階段,代碼如下:
class?SimpleCDDPMModel(SimpleDDPMModel):def?__init__(self,?max_time_step=100,?label_num=10):super(SimpleCDDPMModel,?self).__init__(max_time_step=max_time_step)#?condition?的embedding和time?step的一致self.condition_embedding?=?[tf.keras.Sequential([tf.keras.layers.Embedding(label_num,?num),tf.keras.layers.Dense(num)])for?num?in?self.filter_nums]#?unet?的下采樣def?encoder(self,?noisy_img,?t,?data,?training):xs?=?[]mask?=?tf.random.uniform(shape=(),?minval=0.0,?maxval=1.0,?dtype=tf.float32)for?idx,?conv?in?enumerate(self.encoders):noisy_img?=?conv(noisy_img)t?=?tf.cast(t,?tf.float32)time_embedding?=?self.time_embeddings[idx](t)time_embedding?=?tf.reshape(time_embedding,?[-1,?1,?1,?tf.shape(time_embedding)[-1]])#?time?embedding?直接相加noisy_img?+=?time_embedding#?獲取?condition?的embeddingcondition_embedding?=?self.condition_embedding[idx](data["label"])condition_embedding?=?tf.reshape(condition_embedding,?[-1,?1,?1,?tf.shape(condition_embedding)[-1]])#?訓練階段一定的概率下加入condition,推理階段全部加入if?training:if?mask?<?0.15:condition_embedding?=?tf.zeros_like(condition_embedding)noisy_img?+=?condition_embeddingxs.append(noisy_img)return?xs#?生成圖片def?call(self,?inputs,?training=None,?mask=None):bs?=?inputs[0]label?=?tf.reshape(tf.repeat(inputs[1],?bs),?[-1,?1])x_t?=?tf.random.normal(shape=[bs,?self.img_size,?self.img_size,?1])for?i?in?reversed(range(0,?self.max_time_step)):t?=?tf.reshape(tf.repeat(i,?bs),?[bs,?1])p?=?self.pred_noisy({"img_data":?x_t,?"t":?t,?"label":?label},?False)x_t?=?self.p_real_sample(x_t,?t,?p["pred_noisy"])return?x_t最終生成的圖片如下:
參考文獻
[1] https://www.jarvis73.com/2022/08/08/Diffusion-Model-1/
[2]?https://blog.csdn.net/qihangran5467/article/details/118337892
[3] https://jaketae.github.io/study/vae/
[4] https://pyro.ai/examples/cvae.html
[5] https://lilianweng.github.io/posts/2021-07-11-diffusion-models/
[6] https://spaces.ac.cn/archives/9164
[7] https://zhuanlan.zhihu.com/p/575984592
[8] https://kxz18.github.io/2022/06/19/Diffusion/
[9] https://zhuanlan.zhihu.com/p/502668154
[10]?https://xyfjason.top/2022/09/29/%E4%BB%8EVAE%E5%88%B0DDPM/
[11] https://arxiv.org/pdf/2208.11970.pdf
更多閱讀
#投 稿?通 道#
?讓你的文字被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學術熱點剖析、科研心得或競賽經驗講解等。我們的目的只有一個,讓知識真正流動起來。
📝?稿件基本要求:
? 文章確系個人原創作品,未曾在公開渠道發表,如為其他平臺已發表或待發表的文章,請明確標注?
? 稿件建議以?markdown?格式撰寫,文中配圖以附件形式發送,要求圖片清晰,無版權問題
? PaperWeekly 尊重原作者署名權,并將為每篇被采納的原創首發稿件,提供業內具有競爭力稿酬,具體依據文章閱讀量和文章質量階梯制結算
📬?投稿通道:
? 投稿郵箱:hr@paperweekly.site?
? 來稿請備注即時聯系方式(微信),以便我們在稿件選用的第一時間聯系作者
? 您也可以直接添加小編微信(pwbot02)快速投稿,備注:姓名-投稿
△長按添加PaperWeekly小編
🔍
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
·
·
總結
以上是生活随笔為你收集整理的AIGC基础:从VAE到DDPM原理、代码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 春节高并发抢红包的技术升华综合实战(No
- 下一篇: 单身狗福音:钢铁直男也可以用AI歌曲俘获