深入淺出了解生成模型-6:常用基礎(chǔ)模型與 Adapters等解析

基座擴(kuò)散模型

主要介紹基于Unet以及基于Dit框架的基座擴(kuò)散模型,其中SD迭代版本挺多的(從1.2到3.5)因此本文主要重點介紹SD 1.5以及SDXL兩個基座模型,以及兩者之間的對比差異,除此之外還有許多閉源的擴(kuò)散模型比如說Imagen、DALE等。對于Dit基座模型主要介紹:Hunyuan-DiT、FLUX.1等。對于各類模型評分網(wǎng)站(模型評分仁者見仁智者見智,特別是此類生成模型視覺圖像生成是一個很主觀的過程,同一張圖片不同人視覺感官都是不同的):https://lmarena.ai/leaderboard

SDv1.5 vs SDXL[1]

SDv1.5: https://huggingface.co/stable-diffusion-v1-5/stable-diffusion-v1-5

SDXL:https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0

兩者模型詳細(xì)的模型結(jié)構(gòu):SDv1.5--SDXL模型結(jié)構(gòu)圖,其中具體模型參數(shù)的對比如下:

1、CLIP編碼器區(qū)別:

在SD1.5中選擇的是CLIP-ViT/L(得到的維度為:768)而在SDXL中選擇的是兩個CLIP文本編碼器:OpenCLIP-ViT/G(得到的維度為:1280)以及CLIP-ViT/L(得到維度為:768)在代碼中對于兩個文本通過編碼器處理之后SDXL直接通過cat方式拼接:prompt_embeds = torch.concat(prompt_embeds_list, dim=-1) 也就是說最后得到的維度為:[..,..,1280+768]。最后效果很明顯:SDXL對于文本的理解能力大于SD1.5

2、圖像輸出維度區(qū)別:

再SD1.5中的默認(rèn)輸出是:512x512而再SDXL中的默認(rèn)輸出是:1024x1024,如果希望將SD1.5生成的圖像處理為1024x1024可以直接通過超分算法來進(jìn)行處理,除此之外在SDXL中還會使用一個refiner模型(和Unet的結(jié)構(gòu)相似)來強化base模型(Unet)生成的效果。

3、SDXL論文中的技術(shù)細(xì)節(jié):

1、圖像分辨率優(yōu)化策略。

數(shù)據(jù)集中圖像的尺寸圖像利用率問題(選擇512x512舍棄256x256就會導(dǎo)致圖像大量被舍棄)如果通過超分辨率算法將圖像就行擴(kuò)展會放大偽影,這些偽影可能會泄漏到最終的模型輸出中,例如,導(dǎo)致樣本模糊。(The second method, on the other hand, usually introduces upscaling artifacts which may leak into the final model outputs, causing, for example, blurry samples.)作者做法是:訓(xùn)練階段直接將原始圖像的分辨率

作為一個條件,通過傅里葉特征編碼而后加入到time embedding中,推理階段直接將分辨率作為一個條件就行嵌入,進(jìn)而實現(xiàn):當(dāng)輸入低分辨率條件時,生成的圖像較模糊;在不斷增大分辨率條件時,生成的圖像質(zhì)量不斷提升。

image.png

2、圖像裁剪優(yōu)化策略

直接統(tǒng)一采樣裁剪坐標(biāo)top和cleft(分別指定從左上角沿高度和寬度軸裁剪的像素數(shù)量的整數(shù)),并通過傅里葉特征嵌入將它們作為調(diào)節(jié)參數(shù)輸入模型https://www.naquan.com/,類似于上面描述的尺寸調(diào)節(jié)。第1,2點代碼中的處理方式為:

def _get_add_time_ids(

? ? ? ? self, original_size, crops_coords_top_left, target_size, dtype, text_encoder_projection_dim=None

? ? ):

? ? add_time_ids = list(original_size + crops_coords_top_left + target_size)

? ? passed_add_embed_dim = (

? ? ? ? self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim

? ? )

? ? expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features

? ? ...

? ? add_time_ids = torch.tensor([add_time_ids], dtype=dtype)

? ? return add_time_ids

推薦閱讀:

1、SDv1.5-SDXL-SD3生成效果對比

Imagen

https://imagen.research.google/

https://deepmind.google/models/imagen/

非官方實現(xiàn):https://github.com/lucidrains/imagen-pytorch

類似Github,通過3階段生成:https://github.com/deep-floyd/IF

Imagen[2]論文中主要提出:1、純文本語料庫上預(yù)訓(xùn)練的通用大型語言模型(例如T5、CLIP、BERT等)在編碼圖像合成的文本方面非常有效:在Imagen中增加語言模型的大小比增加圖像擴(kuò)散模型的大小更能提高樣本保真度和Imagetext對齊。

2、通過提高classifier-free guidance weight(

也就是其中的參數(shù)

)可以提高image-text之間的對齊,但會損害圖像逼真度,產(chǎn)生高度飽和不自然的圖像(論文里面給出的分析是:每個時間步中預(yù)測和正式的x都會限定在

這個范圍但是較大的

可能導(dǎo)致超出這個范圍),論文里面做法就是提出 動態(tài)調(diào)整方法:在每個采樣步驟中,我們將s設(shè)置為

中的某個百分位絕對像素值,如果s>1,則我們將

閾值設(shè)置為范圍

,然后除以s。

3、和上面SD模型差異比較大的一點就是,在imagen中直接使用多階段生成策略,模型先生成64x64圖像再去通過超分辨率擴(kuò)散模型去生成256x256以及1024x1024的圖像,在此過程中作者提到使用noise conditioning augmentation(NCA)策略(對輸入的文本編碼后再去添加隨機噪聲)

Dit

https://github.com/facebookresearch/DiT

Dit[3]模型結(jié)構(gòu)上,1、模型輸入,將輸入的image/latent切分為不同patch而后去對不同編碼后的patch上去添加位置編碼(直接使用的sin-cos位置編碼),2、時間步以及條件編碼,對于時間步t以及條件c的編碼而后將兩部分編碼后的內(nèi)容進(jìn)行相加,在TimestepEmbedder上處理方式是:直接通過正弦時間步嵌入方式而后將編碼后的內(nèi)容通過兩層liner處理;在LabelEmbedder處理方式上就比較簡單直接通過nn.Embedding進(jìn)行編碼處理。3、使用Adaptive layer norm(adaLN)block以及adaZero-Block(對有些參數(shù)初始化為0,就和lora中一樣初始化AB為0,為了保證后續(xù)模型訓(xùn)練過程中的穩(wěn)定)

在layernorm中一般歸一化處理方式為:

其中有兩個參數(shù)

是固定的可學(xué)習(xí)參數(shù)(比如說直接通過 nn.Parameter 進(jìn)行創(chuàng)建),在模型初始化時創(chuàng)建,并在訓(xùn)練過程中通過梯度下降優(yōu)化。但是在 adaLN中則是直接通過

通過輸入的條件c進(jìn)行學(xué)習(xí)的,

Hunyuan-DiT

https://huggingface.co/Tencent-Hunyuan/HunyuanDiT

騰訊的Hunyuan-DiT[4]模型整體結(jié)構(gòu)

整體框架不是很復(fù)雜,1、文本編碼上直接通過結(jié)合兩個編碼器:CLIP、T5;2、VAE則是直接使用的SD1.5的;3、引入2維的旋轉(zhuǎn)位置編碼;4、在Dit結(jié)構(gòu)上(圖片VAE壓縮而后去切分成不同patch),使用的是堆疊的注意力模塊(在SD1.5中也是這種結(jié)構(gòu))self-attention+cross-attention(此部分輸入文本)。論文里面做了改進(jìn)措施:1、借鑒之前處理,計算attention之前首先進(jìn)行norm處理(也就是將norm拿到attention前面)。

簡短了解一下模型是如何做數(shù)據(jù)的:

PixArt

https://pixart-alpha.github.io/

華為諾亞方舟實驗室提出的

模型整體框架如下:

相比較Dit模型論文里面主要進(jìn)行的改進(jìn)如下:

1、Cross-Attention layer,在DiT block中加入了一個多頭交叉注意力層,它位于自注意力層(上圖中的Multi-Head Self

-Attention)和前饋層(Pointwise Feedforward)之間,使模型能夠靈活地引入文本嵌入條件。此外,為了利用預(yù)訓(xùn)練權(quán)重,將交叉注意力層中的輸出投影層初始化為零,作為恒等映射,保留了輸入以供后續(xù)層使用。

2、AdaLN-single,在Dit中的adaptive normalization layers(adaLN)中部分參數(shù)(27%)沒有起作用(在文生圖任務(wù)中)將其替換為adaLN-single

不同模型參數(shù)對生成的影響

https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#stable-diffusion-20

參數(shù)guidance_rescale對于生成的影響

引導(dǎo)擴(kuò)散模型(如 Classifier-Free Guidance,CFG)中,用于調(diào)整文本條件對生成圖像的影響強度。它的核心作用是控制模型在生成過程中對文本提示的“服從程度”。公式上,CFG 調(diào)整預(yù)測噪聲的方式如下:

其中:1、

:基于文本條件預(yù)測的噪聲。2、

:無條件(無文本提示)預(yù)測的噪聲。3、guidance_scale:決定條件噪聲相對于無條件噪聲的權(quán)重。得到最后測試結(jié)果如下(參數(shù)分別為[1.0, 3.0, 5.0, 7.5, 10.0, 15.0, 20.0],prompt = "A majestic lion standing on a mountain during golden hour, ultra-realistic, 8k", negative_prompt = "blurry, distorted, low quality"),容易發(fā)現(xiàn)數(shù)值越大文本對于圖像的影響也就越大。

tmp-CFG.png

其中代碼具體操作如下,從代碼也很容易發(fā)現(xiàn)上面計算公式中的 uncond代表的就是我的negative_prompt,也就是說CFG做的就是negative_prompt對生成的影響:

if self.do_classifier_free_guidance:

? ? prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)

? ? add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0)

? ? add_neg_time_ids = add_neg_time_ids.repeat(batch_size * num_images_per_prompt, 1)

? ? add_time_ids = torch.cat([add_neg_time_ids, add_time_ids], dim=0)

prompt_embeds = prompt_embeds.to(device)

Adapters

https://huggingface.co/docs/diffusers/tutorials/using_peft_for_inference

此類方法是在完備的 DF 權(quán)重基礎(chǔ)上,額外添加一個“插件”,保持原有權(quán)重不變。我只需修改這個插件,就可以讓模型生成不同風(fēng)格的圖像??梢岳斫鉃樵谠寄P椭庑略鲆粋€“生成條件”,通過修改這一條件即可靈活控制模型生成各種風(fēng)格或滿足不同需求的圖像。

ControlNet

https://github.com/lllyasviel/ControlNet

建議直接閱讀:https://github.com/lllyasviel/ControlNet/discussions/categories/announcements 來了解更加多細(xì)節(jié)

ControlNet[5]的處理思路就很簡單,再左圖中模型的處理過程就是直接通過:

來生成圖像,但是在ControlNet里面會 將我們最開始的網(wǎng)絡(luò)結(jié)構(gòu)復(fù)制 然后通過在其前后引入一個 zero-convolution 層來“指導(dǎo)”(

)模型的輸出也就是說將上面的生成過程變?yōu)椋?/p>

。通過凍結(jié)最初的模型的權(quán)重保持不變,保留了Stable Diffusion模型原本的能力;與此同時,使用額外數(shù)據(jù)對“可訓(xùn)練”副本進(jìn)行微調(diào),學(xué)習(xí)我們想要添加的條件。因此在最后我們的SD模型中就是如下一個結(jié)構(gòu):

在論文里面作者給出一個實際的測試效果可以很容易理解里面條件c(條件 ??就是提供給模型的顯式結(jié)構(gòu)引導(dǎo)信息,用于在生成過程中精確控制圖像的空間結(jié)構(gòu)或布局,一般來說可以是草圖、分割圖等)到底是一個什么東西,比如說就是直接給出一個“線稿”然后模型來輸出圖像。

補充-1:為什么使用上面這種結(jié)構(gòu)

在github上作者討論了為什么要使用上面這種結(jié)構(gòu)而非直接使用mlp等(作者給出了很多測試圖像),最后總結(jié)就是:這種結(jié)構(gòu)好

補充-2:使用0卷積層會不會導(dǎo)致模型無法優(yōu)化問題?

不會,因為對于神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)大多都是:

計算梯度過程中即使

但是里面的

模型的參數(shù)還是可以被優(yōu)化的

ControlNet代碼操作

Code: https://github.com/shangxiaaabb/ProjectCode/tree/main/code/Python/DFModelCode/training_controlnet

模型權(quán)重:

首先,簡單了解一個ControlNet數(shù)據(jù)集格式,一般來說數(shù)據(jù)主要是三部分組成:1、image(可以理解為生成的圖像);2、condiction_image(可以理解為輸入ControlNet里面的條件

);3、text。比如說以raulc0399/open_pose_controlnet為例

模型加載,一般來說擴(kuò)散模型就只需要加載如下幾個:DDPMScheduler、AutoencoderKL(vae模型)、UNet2DConditionModel(不一定加載條件Unet模型),除此之外在ControlNet中還需要加載一個ControlNetModel。對于ControlNetModel中代碼大致結(jié)構(gòu)為,代碼中通過self.controlnet_down_blocks來存儲ControlNet的下采樣模塊(初始化為0的卷積層)。self.down_blocks用來存儲ControlNet中復(fù)制的Unet的下采樣層。在forward中對于輸入的樣本(sample)首先通過 self.down_blocks逐層處理疊加到 down_block_res_samples中,而后就是直接將得到結(jié)果再去通過 self.controlnet_down_blocks每層進(jìn)行處理,最后返回下采樣的每層結(jié)果以及中間層處理結(jié)果:down_block_res_samples,mid_block_res_sample

class ControlNetModel(ModelMixin, ConfigMixin, FromOriginalModelMixin):

? ? @register_to_config

? ? def __init__(...):

? ? ? ? ...

? ? ? ? self.down_blocks = nn.ModuleList([])

? ? ? ? self.controlnet_down_blocks = nn.ModuleList([])

? ? ? ? # 封裝下采樣過程(對應(yīng)上面模型右側(cè)結(jié)構(gòu))

? ? ? ? controlnet_block = nn.Conv2d(output_channel, output_channel, kernel_size=1)

? ? ? ? controlnet_block = zero_module(controlnet_block)

? ? ? ? self.controlnet_down_blocks.append(controlnet_block)

? ? ? ? for i, down_block_type in enumerate(down_block_types):

? ? ? ? ? ? # down_block_types就是Unet里面下采樣的每一個模塊比如說:CrossAttnDownBlock2D

? ? ? ? ? ? ...

? ? ? ? ? ? down_block = get_down_block(down_block_type) # 通過 get_down_block 獲取uet下采樣的模塊

? ? ? ? ? ? self.down_blocks.append(down_block)

? ? ? ? ? ? for _ in range(layers_per_block):

? ? ? ? ? ? ? ? controlnet_block = nn.Conv2d(output_channel, output_channel, kernel_size=1)

? ? ? ? ? ? ? ? controlnet_block = zero_module(controlnet_block)

? ? ? ? ? ? ? ? self.controlnet_down_blocks.append(controlnet_block)

? ? @classmethod

? ? def from_unet(cls, unet,...):

? ? ? ? ...

? ? ? ? # 通過cls實例化的類本身ControlNetModel

? ? ? ? controlnet = cls(...)

? ? ? ? if load_weights_from_unet:

? ? ? ? ? ? # 將各類權(quán)重加載到 controlnet 中

? ? ? ? ? ? controlnet.conv_in.load_state_dict(unet.conv_in.state_dict())

? ? ? ? ? ? controlnet.time_proj.load_state_dict(unet.time_proj.state_dict())

? ? ? ? ? ? ...

? ? ? ? return controlnet

? ? def forward(...):

? ? ? ? ...

? ? ? ? # 時間編碼

? ? ? ? t_emb = self.time_proj(timesteps)

? ? ? ? emb = self.time_embedding(t_emb, timestep_cond)

? ? ? ? if self.class_embedding is not None:

? ? ? ? ? ? ...

? ? ? ? ? ? class_emb = self.class_embedding(class_labels).to(dtype=self.dtype)

? ? ? ? ? ? emb = emb + class_emb

? ? ? ? # 對條件進(jìn)行編碼

? ? ? ? if self.config.addition_embed_type is not None:

? ? ? ? ? ? if self.config.addition_embed_type == "text":

? ? ? ? ? ? ? ? aug_emb = self.add_embedding(encoder_hidden_states)

? ? ? ? ? ? elif self.config.addition_embed_type == "text_time":

? ? ? ? ? ? ? ? time_ids = added_cond_kwargs.get("time_ids")

? ? ? ? ? ? ? ? time_embeds = self.add_time_proj(time_ids.flatten())

? ? ? ? ? ? ? ? time_embeds = time_embeds.reshape((text_embeds.shape[0], -1))

? ? ? ? ? ? ? ? add_embeds = torch.concat([text_embeds, time_embeds], dim=-1)

? ? ? ? ? ? ? ? add_embeds = add_embeds.to(emb.dtype)

? ? ? ? ? ? ? ? aug_emb = self.add_embedding(add_embeds)

? ? ? ? emb = emb + aug_emb if aug_emb is not None else emb? ? ?

? ? ? ? sample = self.conv_in(sample)

? ? ? ? controlnet_cond = self.controlnet_cond_embedding(controlnet_cond)

? ? ? ? sample = sample + controlnet_cond

? ? ? ? # 下采樣處理

? ? ? ? down_block_res_samples = (sample,)

? ? ? ? for downsample_block in self.down_blocks:

? ? ? ? ? ? if ...

? ? ? ? ? ? ? ? ...

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? sample, res_samples = downsample_block(hidden_states=sample, temb=emb)

? ? ? ? ? ? down_block_res_samples += res_samples

? ? ? ? # 中間層處理

? ? ? ? ...

? ? ? ? # 將輸出后的內(nèi)容去和0卷積進(jìn)行疊加

? ? ? ? controlnet_down_block_res_samples = ()

? ? ? ? for down_block_res_sample, controlnet_block in zip(down_block_res_samples, self.controlnet_down_blocks):

? ? ? ? ? ? down_block_res_sample = controlnet_block(down_block_res_sample)

? ? ? ? ? ? controlnet_down_block_res_samples = controlnet_down_block_res_samples + (down_block_res_sample,)

? ? ? ? ...

? ? ? ? if not return_dict:

? ? ? ? ? ? return (down_block_res_samples, mid_block_res_sample)

? ? ? ? ...

模型訓(xùn)練,訓(xùn)練過程和DF訓(xùn)練差異不大。將圖像通過VAE處理、產(chǎn)生噪聲、時間步、將噪聲添加到(VAE處理之后的)圖像中,而后通過 controlnet得到每層下采樣的結(jié)果以及中間層結(jié)果:down_block_res_samples, mid_block_res_sample = controlnet(...)而后將這兩部分結(jié)果再去通過unet處理

model_pred = unet(

? ? noisy_latents,

? ? timesteps,

? ? encoder_hidden_states=encoder_hidden_states,

? ? down_block_additional_residuals=[

? ? ? ? sample.to(dtype=weight_dtype) for sample in down_block_res_samples

? ? ],

? ? mid_block_additional_residual=mid_block_res_sample.to(dtype=weight_dtype),

? ? return_dict=False,

)[0]

后續(xù)就是計算loss等處理

模型驗證,直接就是使用StableDiffusionControlNetPipeline來處理了。最后隨機測試的部分例子(controlnet微調(diào)效果不是很好):

output.jpg

T2I-Adapter

https://github.com/TencentARC/T2I-Adapter

image.png

T2I[6]的處理思路也比較簡單(T2I-Adap 4 ter Details里面其實就寫的很明白了),對于輸入的條件圖片(比如說邊緣圖像):512x512,首先通過 pixel unshuffle進(jìn)行下采樣將圖像分辨率改為:64x64而后通過一層卷積+兩層殘差連接,輸出得到特征

之后將其與對應(yīng)的encoder結(jié)構(gòu)進(jìn)行相加:

,當(dāng)然T2I也支持多個條件(直接通過加權(quán)組合就行)

DreamBooth

https://huggingface.co/docs/diffusers/v0.34.0/using-diffusers/dreambooth

論文[7]里面主要出發(fā)點就是:1、解決language drif(語言偏離問題):指的是模型通過后訓(xùn)練(微調(diào)等處理之后)模型喪失了對某些語義特征的感知,就比如說擴(kuò)散模型里面,模型通過不斷微調(diào)可能就不知道“狗”是什么從而導(dǎo)致模型生成錯誤。2、高效的生成需要的對象,不會產(chǎn)生:生成錯誤、細(xì)節(jié)丟失問題,比如說下面圖像中的問題:

為了實現(xiàn)圖像的“高效遷移”,作者直接將圖像(比如說我們需要風(fēng)格化的圖片)作為一個特殊的標(biāo)記,也就是論文里面提到的 a [identifier] [class noun](其中class noun為類別比如所狗,identifier就是一個特殊的標(biāo)記),在prompt中加入類別,通過利用預(yù)訓(xùn)練模型中關(guān)于該類別物品的先驗知識,并將先驗知識與特殊標(biāo)記符相關(guān)信息進(jìn)行融合,這樣就可以在不同場景下生成不同姿勢的目標(biāo)物體。就比如下面的 fine-tuning過程通過幾張圖片讓模型學(xué)習(xí)到 特殊的狗,然后再推理階段模型可以利用這個 特殊的狗去生成新的動作。

再論文里面作者設(shè)計如下的Class-specific Prior Preservation Loss(參考stackexchange)[8]:

上面損失函數(shù)中后面一部分就是我們的先驗損失,比如說

就是對 "a dog"進(jìn)行編碼然后計算生成損失。在代碼中:

if args.with_prior_preservation:

? ? model_pred, model_pred_prior = torch.chunk(model_pred, 2, dim=0)

? ? target, target_prior = torch.chunk(target, 2, dim=0)

? ? # Compute instance loss

? ? loss = F.mse_loss(model_pred.float(), target.float(), reduction="mean")

? ? # Compute prior loss

? ? prior_loss = F.mse_loss(model_pred_prior.float(), target_prior.float(), reduction="mean")

? ? # Add the prior loss to the instance loss.

? ? loss = loss + args.prior_loss_weight * prior_loss

else:

? ? loss = F.mse_loss(model_pred.float(), target.float(), reduction="mean")

DreamBooth代碼操作

代碼:https://github.com/shangxiaaabb/ProjectCode/tree/main/code/Python/DFModelCode/training_dreambooth_lora/

權(quán)重:https://www.modelscope.cn/models/bigyellowjie/SDXL-DreamBooth-LOL/files

在介紹DreamBooth代碼之前,簡單回顧DreamBooth原理,我希望我的模型去學(xué)習(xí)一種畫風(fēng)那么我就需要準(zhǔn)備樣本圖片(如3-5)這幾張圖片就是專門的模型需要學(xué)習(xí)的,但是為了防止模型過擬合(模型只學(xué)習(xí)了我的圖片內(nèi)容,但是對一些細(xì)節(jié)丟掉了,比如說我提供的5張油畫,模型就學(xué)會了我的油畫畫風(fēng)但是為了防止模型對更加多的油畫細(xì)節(jié)忘記了,那么我就準(zhǔn)備num_epochs * num_samples 張油畫類型圖片然后通過計算 Class-specific Prior Preservation Loss)需要準(zhǔn)備 類型圖片來計算Class-specific Prior Preservation Loss。代碼處理(SDXL+Lora):

首先是lora處理模型:在基于transformer里面的模型很容易使用lora,比如說下面代碼使用lora包裹模型并且對模型權(quán)重進(jìn)行保存:

from peft import LoraConfig

def get_lora_config(rank, dropout, use_dora, target_modules):

? ? '''lora config'''

? ? base_config = {

? ? ? ? "r": rank,

? ? ? ? "lora_alpha": rank,

? ? ? ? "lora_dropout": dropout,

? ? ? ? "init_lora_weights": "gaussian",

? ? ? ? "target_modules": target_modules,

? ? }

? ? return LoraConfig(**base_config)

# 包裹lora模型權(quán)重

unet_target_modules = ["to_k", "to_q", "to_v", "to_out.0"]

unet_lora_config = get_lora_config(

? ? rank= config.rank,

? ? dropout= config.lora_dropout,

? ? use_dora= config.use_dora,

? ? target_modules= unet_target_modules,

)

unet.add_adapter(unet_lora_config)

一般的話考慮SD模型權(quán)重都比較大,而且我們使用lora微調(diào)模型沒必要對所有的模型權(quán)重進(jìn)行存儲,那么一般都會定義一個hook來告訴模型那些參數(shù)需要保存、加載比如:

def save_model_hook(models, weights, output_dir):

? ? if accelerator.is_main_process:

? ? ? ? unet_lora_layers_to_save = None


? ? ? ? for model in models:

? ? ? ? ? ? if isinstance(model, type(unwrap_model(unet))):

? ? ? ? ? ? ? ? unet_lora_layers_to_save = convert_state_dict_to_diffusers(get_peft_model_state_dict(model))

? ? ? ? ? ? ...

? ? ? ? ? ? weights.pop() # 去掉不需要保存的參數(shù)

? ? ? ? StableDiffusionXLPipeline.save_lora_weights(

? ? ? ? ? ? output_dir,

? ? ? ? ? ? unet_lora_layers= unet_lora_layers_to_save,

? ? ? ? ? ? ...

? ? ? ? )

def load_model_hook(models, input_dir):

? ? unet_ = None

? ? while len(models) > 0:

? ? ? ? model = models.pop()

? ? ? ? if isinstance(model, type(unwrap_model(unet))):

? ? ? ? ? ? unet_ = model

? ? lora_state_dict, network_alphas = StableDiffusionLoraLoaderMixin.lora_state_dict(input_dir)

? ? unet_state_dict = {f"{k.replace('unet.', '')}": v for k, v in lora_state_dict.items() if k.startswith("unet.")}

? ? unet_state_dict = convert_unet_state_dict_to_peft(unet_state_dict)

? ? incompatible_keys = set_peft_model_state_dict(unet_, unet_state_dict, adapter_name="default")

? ? ...

accelerator.register_save_state_pre_hook(save_model_hook)

accelerator.register_load_state_pre_hook(load_model_hook)

這樣一來使用 accelerator.save_state(save_path) 就會先去使用 hook處理參數(shù)然后進(jìn)行保存。

其次模型訓(xùn)練:就是常規(guī)的模型訓(xùn)練(直接在樣本圖片:instance_data_dir以及樣本的prompt:instance_prompt上進(jìn)行微調(diào))然后計算loss即可,如果涉及到Class-specific Prior Preservation Loss(除了上面兩個組合還需要:class_data_dir以及 class_prompt)那么處理過程為(以SDXL為例),不過需要事先了解的是在計算這個loss之前會將兩個數(shù)據(jù)集以及prompt都組合到一起成為一個數(shù)據(jù)集(instance-image-prompt 以及 class-image-prompt之間是匹配的):

# 樣本內(nèi)容編碼

instance_prompt_hidden_states, instance_pooled_prompt_embeds = compute_text_embeddings(config.instance_prompt, text_encoders, tokenizers)

# 類型圖片內(nèi)容編碼

if config.with_prior_preservation:

? ? class_prompt_hidden_states, class_pooled_prompt_embeds = compute_text_embeddings(config.class_prompt, text_encoders, tokenizers)

...

prompt_embeds = instance_prompt_hidden_states

unet_add_text_embeds = instance_pooled_prompt_embeds

if not config.with_prior_preservation:

? ? prompt_embeds = torch.cat([prompt_embeds, class_prompt_hidden_states], dim=0)

? ? unet_add_text_embeds = torch.cat([unet_add_text_embeds, class_pooled_prompt_embeds], dim=0)

...

model_pred = unet(...)

if config.with_prior_preservation:

? ? model_pred, model_pred_prior = torch.chunk(model_pred, 2, dim=0)

? ? target, target_prior = torch.chunk(target, 2, dim=0)

? ? ...

? ? prior_loss = F.mse_loss(model_pred_prior.float(), target_prior.float(), reduction="mean")

...

loss = F.mse_loss(model_pred.float(), target.float(), reduction="mean")

...

loss = loss + config.prior_loss_weight * prior_loss

accelerator.backward(loss)

在這個里面之所以用 chunk是因為在數(shù)據(jù)集構(gòu)件中:

pixel_values = [example["instance_images"] for example in examples]

...? ?

if with_prior_preservation:

? ? pixel_values += [example["class_images"] for example in examples]

pixel_values = torch.stack(pixel_values)

...

那么這樣一來數(shù)據(jù)中一半來自樣本圖片一部分來自類型圖片,在模型處理之后在model_pred就有一部分是樣本圖片的預(yù)測,另外一部分為類型圖片預(yù)測。最后測試的結(jié)果為(prompt: "A photo of Rengar the Pridestalker in a bucket",模型代碼以及權(quán)重下載):

image.png

總結(jié)

對于不同的擴(kuò)散(基座)模型(SD1.5、SDXL、Imagen)等大部分都是采用Unet結(jié)構(gòu),當(dāng)然也有采用Dit的,這兩個模型(SD1.5、SDXL)之間的差異主要在于后者會多一個clip編碼器再文本語義上比前者更加有優(yōu)勢。對于adapter而言,可以直接理解為再SD的基礎(chǔ)上去使用“風(fēng)格插件”,這個插件不去對SD模型進(jìn)行訓(xùn)練(從而實現(xiàn)對參數(shù)的減?。瑢τ贑ontroNet就是直接對Unet的下采樣所有的模塊(前后)都加一個zero-conv而后將結(jié)果再去嵌入到下采用中,而T2I-Adapter則是去對條件進(jìn)行編碼而后嵌入到SD模型(上采用模塊)中。對于deramboth就是直接通過給定的樣本圖片去生“微調(diào)”模型,而后通過設(shè)計的Class-specific Prior Preservation Loss來確保所生成的樣本特里不會發(fā)生過擬合。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容