參考:HuggingFace
參考:https://jalammar.github.io/illustrated-stable-diffusion/
一、什么是 Stable Diffusion
Stable Diffusion 這個(gè)模型架構(gòu)是由 Stability AI 公司推于2022年8月由 CompVis、Stability AI 和 LAION 的研究人員在 Latent Diffusion Model 的基礎(chǔ)上創(chuàng)建并推出的。
其原型是(Latent Diffusion Model),一般的擴(kuò)散模型都需要直接在像素空間訓(xùn)練和運(yùn)行,訓(xùn)練可能需要上百個(gè) GPUs,在測(cè)試推理的時(shí)候也需要很多硬件支持。所以就有了 LDM 這個(gè)模型的提出,通過平衡【降低復(fù)雜度】和【保持圖像細(xì)節(jié)】,能在保持保真度的同時(shí)實(shí)現(xiàn)模型的加速。
Stable Diffusion 是什么:
- Stable Diffusion 是一種基于擴(kuò)散過程的圖像生成模型,可以生成高質(zhì)量、高分辨率的圖像
- 有較強(qiáng)的穩(wěn)定性和可控性,可以生成具有多樣化效果且良好視覺效果的圖片
Stable Diffusion 和 Midjourney 的對(duì)比:
- 開源情況:Stable Diffusion 開源,Midjourney 閉源
- 質(zhì)量情況:Midjourney 生成的圖片的質(zhì)量更高
- 可控情況:Stable Diffusion 生成圖片的可控性更高
當(dāng)圖片尺寸變大時(shí),需要的計(jì)算能力也隨之增加。這種現(xiàn)象在自注意力機(jī)制(self-attention)這種操作的影響下尤為突出,因?yàn)椴僮鲾?shù)隨著輸入量的增大呈平方地增加。一個(gè) 128px 的正方形圖片有著四倍于 64px 正方形圖片的像素?cái)?shù)量,所以在自注意力層就需要16倍的內(nèi)存和計(jì)算量。這是高分辨率圖片生成任務(wù)的普遍問題。
隱式擴(kuò)散致力于克服這一難題,它使用一個(gè)獨(dú)立的模型 Variational Auto-Encoder(VAE)壓縮圖片到一個(gè)更小的空間維度。這背后的原理是,圖片通場(chǎng)都包含了大量的冗余信息。因此,我們可以訓(xùn)練一個(gè) VAE,并通過大量足夠的圖片數(shù)據(jù)訓(xùn)練,使得它可以將圖片映射到一個(gè)較小的隱式空間,并將這個(gè)較小的特征映射回原圖片。SD 模型中的 VAE 接收一個(gè)三通道圖片輸入,生成出一個(gè)四通道的隱式表征,同時(shí)每一個(gè)空間維度上都減少為原來的八分之一。比如,一個(gè) 512px 的正方形圖片將會(huì)被壓縮到一個(gè) 4×64×64 的隱式表征上。
通過在隱式表征上(而不是完整圖像上)進(jìn)行擴(kuò)散過程,我們可以使用更少內(nèi)存、減少 UNet 層數(shù)、加速生成。同時(shí)我們?nèi)阅馨呀Y(jié)果輸入 VAE 的解碼器,解碼得到高分辨率圖片。這一創(chuàng)新點(diǎn)極大地降低了訓(xùn)練和推理成本。
這里再回顧一下基礎(chǔ)模型 LDM:
- 輸入 Image 為 x x x,輸出生成的 Image 為 x ~ \tilde{x} x~
- 先看上面向右傳播的這行, ? \epsilon ? 為編碼器,將輸入圖片編碼到潛在空間,然后進(jìn)行前向擴(kuò)散加噪,得到 z T z_T zT?。這里的編碼器使用的 VAE
- 然后看最右側(cè)的黑線框,展示的不同的輸入,包括語義圖、文本、圖像等等,經(jīng)過 domain-space 的編碼器 τ θ \tau_{\theta} τθ? 的處理后,輸入去噪網(wǎng)絡(luò)
- 最后看下面這行逆向擴(kuò)散的這行,在去噪網(wǎng)絡(luò)中,級(jí)聯(lián)了多個(gè) U-Net 網(wǎng)絡(luò),具體數(shù)量取決于 T T T 的值,在每個(gè)時(shí)刻都使用 U-Net 的結(jié)構(gòu),U-Net 的每個(gè)中間層的輸入都是 τ θ ( y ) \tau_{\theta}(y) τθ?(y) 和 z T z_T zT?。D 是從潛在空間又解碼到圖像空間,最后得到了生成的圖片
Stable Diffusion模型中使用變分自編碼器(VAE)作為編碼器的原因主要有以下幾點(diǎn):
-
生成模型:VAE是一種生成模型,能夠?qū)W習(xí)數(shù)據(jù)的潛在表示,并且可以從這個(gè)潛在空間中抽樣來生成新的數(shù)據(jù)。這與Stable Diffusion的目標(biāo),即學(xué)習(xí)復(fù)雜數(shù)據(jù)分布并從中抽樣,非常契合。
-
變分推理:VAE利用變分推理進(jìn)行參數(shù)估計(jì)和優(yōu)化。這使得它能夠有效地處理大規(guī)模和高維度的數(shù)據(jù),這對(duì)于許多實(shí)際應(yīng)用(如圖像和文本)來說是非常重要的。
-
損失函數(shù):VAE具有結(jié)構(gòu)化的損失函數(shù),包括重構(gòu)損失和KL散度。這使得我們可以明確地控制重構(gòu)質(zhì)量與潛在表示之間的平衡。
-
魯棒性:由于其隨機(jī)性質(zhì),VAE通常比確定性方法更魯棒,在面對(duì)噪聲或缺失值時(shí)表現(xiàn)更好。
-
連續(xù)潛在空間:VAE假設(shè)一個(gè)連續(xù)的潛在空間,并試圖將輸入映射到此空間上。連續(xù)性使得我們可以通過插值和外推等操作來探索未知區(qū)域,并可能幫助改善生成結(jié)果的質(zhì)量。
VAE 是什么:
是一種生成模型,它使用深度學(xué)習(xí)技術(shù)來提供數(shù)據(jù)的緊湊連續(xù)潛在表示。VAE由編碼器和解碼器兩部分組成。
-
編碼器:編碼器將輸入數(shù)據(jù)(例如圖像)映射到一個(gè)潛在空間。每個(gè)輸入都被映射為該空間中的一個(gè)點(diǎn),或者更準(zhǔn)確地說,是一個(gè)分布。這個(gè)分布通常假設(shè)為高斯分布,由均值和方差定義。
-
解碼器:解碼器則執(zhí)行相反的操作,它從潛在空間中取樣,并將這些樣本映射回原始數(shù)據(jù)空間(例如生成圖像)。解碼過程也是隨機(jī)的,在給定潛在變量的情況下,輸出是原始數(shù)據(jù)空間上的一個(gè)條件分布。
VAE通過最大化下界(ELBO, Evidence Lower BOund)進(jìn)行訓(xùn)練。ELBO包含兩部分:
- 重構(gòu)損失:測(cè)量解碼后的輸出與原始輸入之間的差異。
- KL散度:測(cè)量編碼后得到的高斯分布與標(biāo)準(zhǔn)正態(tài)分布之間的差異。
通過優(yōu)化這兩項(xiàng)內(nèi)容使得模型能夠有效地學(xué)習(xí)出數(shù)據(jù)背后復(fù)雜且有用的特征,并且保證了潛在空間具有良好結(jié)構(gòu)性質(zhì)以便于采樣和推理。
總結(jié)一句話就是, VAE 是一種利用深度神經(jīng)網(wǎng)絡(luò)進(jìn)行參數(shù)化并利用變分推理進(jìn)行訓(xùn)練以學(xué)習(xí)復(fù)雜數(shù)據(jù)集隱含結(jié)構(gòu)并能從中生成新樣本 的生成模型。
二、Diffusers 庫
Diffusers 庫如何使用:
git clone https://github.com/huggingface/diffusers.git
#可以使用 diffusers 這個(gè) image 起容器
nvcr.io/nvidia/pytorch:22.08-py3
# 然后在容器中安裝,為了更方便看內(nèi)部,我使用了 -e . 的安裝方式,每次調(diào)用庫都會(huì)進(jìn)入我自己的 diffusers 庫
cd diffusers
pip install -e .
Diffusers 庫是什么:
- diffusers 是 Hugging Face 推出的一個(gè)擴(kuò)散模型庫,它提供了簡(jiǎn)單方便的推理、訓(xùn)練 pipeline,同時(shí)擁有一個(gè)模型和數(shù)據(jù)社區(qū),代碼可以像 torchhub 一樣直接從指定的倉庫去調(diào)用別人上傳的數(shù)據(jù)集和 pretrain checkpoint
- diffusers 庫包括了 SOTA 的擴(kuò)散模型的管道,只需幾行代碼就可以在推理中運(yùn)行,生成想要的圖像
- 可使用不同的噪聲調(diào)度器,用于平衡不同的擴(kuò)散速度和輸出質(zhì)量。
- 有很多預(yù)訓(xùn)練好的模型,可以作為構(gòu)建模塊使用,并與調(diào)度器結(jié)合,以創(chuàng)建自己的端到端擴(kuò)散系統(tǒng)。
擴(kuò)散模型的主要步驟:
- 獲取一批數(shù)據(jù)
- 添加隨機(jī)噪聲
- 將數(shù)據(jù)輸入模型
- 將模型預(yù)測(cè)與干凈圖像進(jìn)行比較,以計(jì)算loss
- 更新模型的參數(shù)
一個(gè)簡(jiǎn)單的示例如下:
# Dataloader (you can mess with batch size)
batch_size = 128
train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# How many runs through the data should we do?
n_epochs = 3
# Create the network
net = BasicUNet()
net.to(device)
# Our loss finction
loss_fn = nn.MSELoss()
# The optimizer
opt = torch.optim.Adam(net.parameters(), lr=1e-3)
# Keeping a record of the losses for later viewing
losses = []
# The training loop
for epoch in range(n_epochs):
for x, y in train_dataloader:
# Get some data and prepare the corrupted version
x = x.to(device) # Data on the GPU
noise_amount = torch.rand(x.shape[0]).to(device) # Pick random noise amounts
noisy_x = corrupt(x, noise_amount) # Create our noisy x
# Get the model prediction
pred = net(noisy_x)
# Calculate the loss
loss = loss_fn(pred, x) # How close is the output to the true 'clean' x?
# Backprop and update the params:
opt.zero_grad()
loss.backward()
opt.step()
# Store the loss for later
losses.append(loss.item())
# Print our the average of the loss values for this epoch:
avg_loss = sum(losses[-len(train_dataloader):])/len(train_dataloader)
print(f'Finished epoch {epoch}. Average loss for this epoch: {avg_loss:05f}')
# View the loss curve
plt.plot(losses)
plt.ylim(0, 0.1);
?? Diffusers 的核心 API 被分為三個(gè)主要部分:
- 管線: 從高層出發(fā)設(shè)計(jì)的多種類函數(shù),旨在以易部署的方式,能夠做到快速通過主流預(yù)訓(xùn)練好的擴(kuò)散模型來生成樣本。
- 模型: 訓(xùn)練新的擴(kuò)散模型時(shí)用到的主流網(wǎng)絡(luò)架構(gòu),e.g. UNet.
- 管理器 (or 調(diào)度器): 在推理中使用多種不同的技巧來從噪聲中生成圖像,同時(shí)也生成在訓(xùn)練中所需的帶噪圖像。這兩個(gè)步驟都是由 調(diào)度器(scheduler) 來處理的。
利用使用 DDPM 的調(diào)度器:
from diffusers import DDPMScheduler
noise_scheduler = DDPMScheduler(num_train_timesteps=1000)
# 可以使用 noise_scheduler.add_noise 功能來添加不同程度的噪聲
timesteps = torch.linspace(0, 999, 8).long().to(device)
noise = torch.randn_like(xb)
noisy_xb = noise_scheduler.add_noise(xb, noise, timesteps)
print("Noisy X shape", noisy_xb.shape)
show_images(noisy_xb).resize((8 * 64, 64), resample=Image.NEAREST)
擴(kuò)散模型的 UNet:
大多數(shù)擴(kuò)散模型使用的模型結(jié)構(gòu)都是一些 [U-net] 的變種
簡(jiǎn)單來說,一個(gè)U-net模型大致會(huì)有以下三個(gè)特征:
- 輸入模型中的圖片會(huì)經(jīng)過幾個(gè)由 ResNetLayer 構(gòu)成的層,其中每層都使圖片的尺寸減半。
- 在這之后,同樣數(shù)量的上采樣層會(huì)將圖片的尺寸恢復(fù)到原始規(guī)模。
- 殘差連接模塊會(huì)將特征圖分辨率相同的上采樣層和下采樣層連接起來。
U-net模型一個(gè)關(guān)鍵特征是輸出圖片的尺寸與輸入圖片相同,而這正是我們?cè)跀U(kuò)散模型中所需要的。
Diffusers 為我們提供了一個(gè)易用的 UNet2DModel 類,用來在 PyTorch 中創(chuàng)建我們所需要的結(jié)構(gòu)。
我們來使用 U-net 為我們生成目標(biāo)大小的圖片吧。 注意這里 down_block_types 對(duì)應(yīng)下采樣模塊 (上圖中綠色部分), 而 up_block_types 對(duì)應(yīng)上采樣模塊 (上圖中紅色部分):
from diffusers import UNet2DModel
# Create a model
model = UNet2DModel(
sample_size=image_size, # the target image resolution
in_channels=3, # the number of input channels, 3 for RGB images
out_channels=3, # the number of output channels
layers_per_block=2, # how many ResNet layers to use per UNet block
block_out_channels=(64, 128, 128, 256), # More channels -> more parameters
down_block_types=(
"DownBlock2D", # a regular ResNet downsampling block
"DownBlock2D",
"AttnDownBlock2D", # a ResNet downsampling block with spatial self-attention
"AttnDownBlock2D",
),
up_block_types=(
"AttnUpBlock2D",
"AttnUpBlock2D", # a ResNet upsampling block with spatial self-attention
"UpBlock2D",
"UpBlock2D", # a regular ResNet upsampling block
),
)
model.to(device);
訓(xùn)練擴(kuò)散模型:
下面是PyTorch中的一個(gè)典型的迭代優(yōu)化循環(huán)過程的步驟,我們?cè)谄渲兄鹋╞atch)的輸入數(shù)據(jù),并使用優(yōu)化器一步步更新模型的參數(shù) - 在這個(gè)樣例中我們使用學(xué)習(xí)率為 0.0004 的 AdamW 優(yōu)化器。
對(duì)于每一批的數(shù)據(jù),我們會(huì):
- 隨機(jī)取樣幾個(gè)迭代周期
- 對(duì)數(shù)據(jù)進(jìn)行相應(yīng)的噪聲處理
- 把帶噪數(shù)據(jù)輸入模型
- 使用 MSE 作為損失函數(shù)來比較目標(biāo)結(jié)果與模型預(yù)測(cè)結(jié)果,在這個(gè)樣例中,即是比較真實(shí)噪聲和模型預(yù)測(cè)的噪聲之間的差距。
- 通過loss.backward ()與optimizer.step ()來更新模型參數(shù)
在這個(gè)過程中我們需要記錄下每一步中的損失函數(shù)的值,用來后續(xù)繪制損失的曲線圖。
# Set the noise scheduler
noise_scheduler = DDPMScheduler(
num_train_timesteps=1000, beta_schedule="squaredcos_cap_v2"
)
# Training loop
optimizer = torch.optim.AdamW(model.parameters(), lr=4e-4)
losses = []
for epoch in range(30):
for step, batch in enumerate(train_dataloader):
clean_images = batch["images"].to(device)
# Sample noise to add to the images
noise = torch.randn(clean_images.shape).to(clean_images.device)
bs = clean_images.shape[0]
# Sample a random timestep for each image
timesteps = torch.randint(
0, noise_scheduler.num_train_timesteps, (bs,), device=clean_images.device
).long()
# Add noise to the clean images according to the noise magnitude at each timestep
noisy_images = noise_scheduler.add_noise(clean_images, noise, timesteps)
# Get the model prediction
noise_pred = model(noisy_images, timesteps, return_dict=False)[0]
# Calculate the loss
loss = F.mse_loss(noise_pred, noise)
loss.backward(loss)
losses.append(loss.item())
# Update the model parameters with the optimizer
optimizer.step()
optimizer.zero_grad()
if (epoch + 1) % 5 == 0:
loss_last_epoch = sum(losses[-len(train_dataloader) :]) / len(train_dataloader)
print(f"Epoch:{epoch+1}, loss: {loss_last_epoch}")
模型訓(xùn)練好之后如何生成圖像:
方法一:建立一個(gè)管道
from diffusers import DDPMPipeline
image_pipe = DDPMPipeline(unet=model, scheduler=noise_scheduler)
pipeline_output = image_pipe()
pipeline_output.images[0]
# 將模型保存
image_pipe.save_pretrained("my_pipeline")
# ls my_pipeline
# model_index.json scheduler unet
方法二:寫一個(gè)采樣循環(huán)
從完全隨機(jī)的噪聲圖像開始,從最大噪聲往最小噪聲方向運(yùn)行調(diào)度器,根據(jù)模型的預(yù)測(cè)每一步去除少量噪聲:
# Random starting point (8 random images):
sample = torch.randn(8, 3, 32, 32).to(device)
for i, t in enumerate(noise_scheduler.timesteps):
# Get model pred
with torch.no_grad():
residual = model(sample, t).sample
# Update sample with step
sample = noise_scheduler.step(residual, t, sample).prev_sample
show_images(sample)
三、微調(diào)、引導(dǎo)、條件生成
3.1 微調(diào)
從頭訓(xùn)練一個(gè)擴(kuò)散模型耗費(fèi)的時(shí)間相當(dāng)長(zhǎng)!尤其是當(dāng)你使用高分辨率圖片時(shí),從頭訓(xùn)練模型所需的時(shí)間和數(shù)據(jù)量可能多得不切實(shí)際。幸運(yùn)的是,我們還有個(gè)解決方法:從一個(gè)已經(jīng)被訓(xùn)練過的模型去開始訓(xùn)練!這樣,我們從一個(gè)已經(jīng)學(xué)過如何去噪的模型開始,希望能相比于隨機(jī)初始化的模型能有一個(gè)更好的起始點(diǎn)。一般而言,當(dāng)你的新數(shù)據(jù)和原有模型的原始訓(xùn)練數(shù)據(jù)多多少少有點(diǎn)相似的時(shí)候,微調(diào)效果會(huì)最好(比如你想生成卡通人臉,那你用于微調(diào)的模型最好是個(gè)在人臉數(shù)據(jù)上訓(xùn)練過的模型)。
3.2 引導(dǎo)
無條件模型一般沒有對(duì)生成能內(nèi)容的掌控。我們可以訓(xùn)練一個(gè)條件模型(更過內(nèi)容將會(huì)在下節(jié)講述),接收額外輸入,以此來操控生成過程。但我們?nèi)绾问褂靡粋€(gè)已有的無條件模型去做這件事呢?我們可以用引導(dǎo)這一方法:生成過程中每一步的模型預(yù)測(cè)都將會(huì)被一些引導(dǎo)函數(shù)所評(píng)估,并加以修改,以此讓最終的生成結(jié)果符合我們所想。
這個(gè)引導(dǎo)函數(shù)可以是任何函數(shù),這讓我們有了很大的設(shè)計(jì)空間。在筆記本中,我們從一個(gè)簡(jiǎn)單的例子(控制顏色,如上圖所示)開始,到使用一個(gè)叫CLIP的預(yù)訓(xùn)練模型,讓生成的結(jié)果基于文字描述。
3.3 條件生成
引導(dǎo)能讓我們從一個(gè)無條件擴(kuò)散模型中多少得到些額外的收益,但如果我們?cè)谟?xùn)練過程中就有一些額外的信息(比如圖像類別或文字描述)可以輸入到模型里,我們可以把這些信息輸入模型,讓模型使用這些信息去做預(yù)測(cè)。由此我們就創(chuàng)建了一個(gè)條件模型,我們可以在推理階段通過輸入什么信息作為條件來控制模型生成什么。相關(guān)的筆記本中就展示了一個(gè)例子:一個(gè)類別條件的模型,可以根據(jù)類別標(biāo)簽生成對(duì)應(yīng)的圖像。
有很多種方法可以把條件信息輸入到模型種,比如:
- 把條件信息作為額外的通道輸入給 UNet。這種情況下一般條件信息都和圖片有著相同的形狀,比如條件信息是圖像分割的掩模(mask)、深度圖或模糊版的圖像(針對(duì)圖像修復(fù)、超分辨率任務(wù)的模型)
- 把條件信息做成一個(gè)嵌入(embedding),然后把它映射到和模型其中一個(gè)或多個(gè)中間層輸出的通道數(shù)一樣,再把這個(gè)嵌入加到中間層輸出上。這一般是以時(shí)間步(timestep)為條件時(shí)的做法。比如,你可以把時(shí)間步的嵌入映射到特定通道數(shù),然后加到模型的每一個(gè)殘差網(wǎng)絡(luò)模塊的輸出上。這種方法在你有一個(gè)向量形式的條件時(shí)很有用,比如 CLIP 的圖像嵌入。比如能修改輸入圖片的Stable Diffusion模型。
- 添加有交叉注意力機(jī)制的網(wǎng)絡(luò)層(cross-attention)。這在當(dāng)條件是某種形式的文字時(shí)最有效 —— 比如文字被一個(gè) transformer 模型映射成了一串 embedding,那么UNet中有交叉注意力機(jī)制的網(wǎng)絡(luò)層就會(huì)被用來把這些信息合并到去噪路徑中。
CLIP 引導(dǎo)
引導(dǎo)生成的圖片向某種顏色傾斜確實(shí)讓我們多少對(duì)生成有所控制,但如果我們能僅僅打幾行字描述一下就得到我們想要的圖片呢?
基本的方法是:
- 給文字提示語做嵌入(embedding),為 CLIP 獲取一個(gè) 512 維的 embedding
- 對(duì)于擴(kuò)散模型的生成過程的每一步:
- 做出多個(gè)不同版本的預(yù)測(cè)出來的去噪圖片(不同的變種可以提供一個(gè)更干凈的損失信號(hào))
- 對(duì)每一個(gè)預(yù)測(cè)出的去噪圖片,用 CLIP 給圖片做嵌入(embedding),并將這個(gè)嵌入和文字的嵌入做對(duì)比(用一種叫 Great Circle Distance Squared 的度量方法)
- 計(jì)算這個(gè)損失對(duì)于當(dāng)前帶噪的 x 的梯度,并在用調(diào)度器(scheduler)更新它之前用這個(gè)梯度去修改 x
# @markdown load a CLIP model and define the loss function
import open_clip
clip_model, _, preprocess = open_clip.create_model_and_transforms(
"ViT-B-32", pretrained="openai"
)
clip_model.to(device)
# Transforms to resize and augment an image + normalize to match CLIP's training data
tfms = torchvision.transforms.Compose(
[
torchvision.transforms.RandomResizedCrop(224), # Random CROP each time
torchvision.transforms.RandomAffine(
5
), # One possible random augmentation: skews the image
torchvision.transforms.RandomHorizontalFlip(), # You can add additional augmentations if you like
torchvision.transforms.Normalize(
mean=(0.48145466, 0.4578275, 0.40821073),
std=(0.26862954, 0.26130258, 0.27577711),
),
]
)
# And define a loss function that takes an image, embeds it and compares with
# the text features of the prompt
def clip_loss(image, text_features):
image_features = clip_model.encode_image(
tfms(image)
) # Note: applies the above transforms
input_normed = torch.nn.functional.normalize(image_features.unsqueeze(1), dim=2)
embed_normed = torch.nn.functional.normalize(text_features.unsqueeze(0), dim=2)
dists = (
input_normed.sub(embed_normed).norm(dim=2).div(2).arcsin().pow(2).mul(2)
) # Squared Great Circle Distance
return dists.mean()
# @markdown applying guidance using CLIP
prompt = "Red Rose (still life), red flower painting" # @param
# Explore changing this
guidance_scale = 8 # @param
n_cuts = 4 # @param
# More steps -> more time for the guidance to have an effect
scheduler.set_timesteps(50)
# We embed a prompt with CLIP as our target
text = open_clip.tokenize([prompt]).to(device)
with torch.no_grad(), torch.cuda.amp.autocast():
text_features = clip_model.encode_text(text)
x = torch.randn(4, 3, 256, 256).to(
device
) # RAM usage is high, you may want only 1 image at a time
for i, t in tqdm(enumerate(scheduler.timesteps)):
model_input = scheduler.scale_model_input(x, t)
# predict the noise residual
with torch.no_grad():
noise_pred = image_pipe.unet(model_input, t)["sample"]
cond_grad = 0
for cut in range(n_cuts):
# Set requires grad on x
x = x.detach().requires_grad_()
# Get the predicted x0:
x0 = scheduler.step(noise_pred, t, x).pred_original_sample
# Calculate loss
loss = clip_loss(x0, text_features) * guidance_scale
# Get gradient (scale by n_cuts since we want the average)
cond_grad -= torch.autograd.grad(loss, x)[0] / n_cuts
if i % 25 == 0:
print("Step:", i, ", Guidance loss:", loss.item())
# Modify x based on this gradient
alpha_bar = scheduler.alphas_cumprod[i]
x = (
x.detach() + cond_grad * alpha_bar.sqrt()
) # Note the additional scaling factor here!
# Now step with scheduler
x = scheduler.step(noise_pred, t, x).prev_sample
grid = torchvision.utils.make_grid(x.detach(), nrow=4)
im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
Image.fromarray(np.array(im * 255).astype(np.uint8))
gradio 示例:
```python
import gradio as gr
from PIL import Image, ImageColor
# The function that does the hard work
def generate(color, guidance_loss_scale):
target_color = ImageColor.getcolor(color, "RGB") # Target color as RGB
target_color = [a / 255 for a in target_color] # Rescale from (0, 255) to (0, 1)
x = torch.randn(1, 3, 256, 256).to(device)
for i, t in tqdm(enumerate(scheduler.timesteps)):
model_input = scheduler.scale_model_input(x, t)
with torch.no_grad():
noise_pred = image_pipe.unet(model_input, t)["sample"]
x = x.detach().requires_grad_()
x0 = scheduler.step(noise_pred, t, x).pred_original_sample
loss = color_loss(x0, target_color) * guidance_loss_scale
cond_grad = -torch.autograd.grad(loss, x)[0]
x = x.detach() + cond_grad
x = scheduler.step(noise_pred, t, x).prev_sample
grid = torchvision.utils.make_grid(x, nrow=4)
im = grid.permute(1, 2, 0).cpu().clip(-1, 1) * 0.5 + 0.5
im = Image.fromarray(np.array(im * 255).astype(np.uint8))
im.save("test.jpeg")
return im
# See the gradio docs for the types of inputs and outputs available
inputs = [
gr.ColorPicker(label="color", value="55FFAA"), # Add any inputs you need here
gr.Slider(label="guidance_scale", minimum=0, maximum=30, value=3),
]
outputs = gr.Image(label="result")
# And the minimal interface
demo = gr.Interface(
fn=generate,
inputs=inputs,
outputs=outputs,
examples=[
["#BB2266", 3],
["#44CCAA", 5], # You can provide some example inputs to get people started
],
)
demo.launch(debug=True) # debug=True allows you to see errors and output in Colab
四、Stable Diffusion
4.1 以文本為條件生成
為了達(dá)成這一目的,我們首先需要為文本創(chuàng)建一個(gè)數(shù)值的表示形式,用來獲取文字描述的相關(guān)信息。為此,SD 利用了一個(gè)名為 CLIP 的預(yù)訓(xùn)練 transformer 模型。CLIP 的文本編碼器可以將文字描述轉(zhuǎn)化為特征向量形式,這個(gè)特征向量可以用來和圖片的特征向量對(duì)比相似度。所以這個(gè)模型非常適合用來從文字描述來為圖像創(chuàng)建有用的表征信息。一個(gè)輸入文字提示會(huì)首先被分詞(tokenize,基于一個(gè)很大的詞匯庫把句中的詞語或短語轉(zhuǎn)化為一個(gè)個(gè)的token),然后被輸入進(jìn) CLIP 的文字編碼器,為每個(gè)token產(chǎn)出一個(gè) 768 維(針對(duì) SD 1.X版本)或1024維(針對(duì)SD 2.X版本)的向量。為了使得輸入格式一致,文本提示總是被補(bǔ)全或截?cái)嗟胶?77 個(gè) token 的長(zhǎng)度,所以每個(gè)文字提示最終作為生成條件的表示形式是一個(gè)形狀為 77×1024 的張量。
那我們?nèi)绾螌?shí)際地將這些條件信息輸入到 UNet 里讓它預(yù)測(cè)使用呢?答案是使用交叉注意力機(jī)制(cross-attention)。交叉注意力層從頭到尾貫穿了 UNet 結(jié)構(gòu)。UNet 中的每個(gè)空間位置都可以“注意”文字條件中不同的token,以此從文字提示中獲取到了不同位置的相互關(guān)聯(lián)信息。上面的圖表就展示了文字條件信息(以及基于時(shí)間周期 time-step 的條件)是如何在不同的位置點(diǎn)輸入的。可以看到,UNet 的每一層都有機(jī)會(huì)去利用這些條件信息!
4.2 無分類器的引導(dǎo)
然而很多時(shí)候,即使我們付出了很多努力盡可能讓文本成為生成的條件,但模型仍然會(huì)在預(yù)測(cè)時(shí)大量地基于帶噪輸入圖片,而不是文字。在某種程度上,這其實(shí)是可以解釋得通的:很多說明文字和與之關(guān)聯(lián)的圖片相關(guān)性很弱,所以模型就學(xué)著不去過度依賴文字描述!可是這并不是我們期望的效果。如果模型不遵從文本提示,那么我們很可能得到與我們描述根本不相關(guān)的圖片。
為了解決這一問題,我們使用了一個(gè)小技巧,叫做無分類器的引導(dǎo)(Classifie-free Guidance,CGF)。在訓(xùn)練時(shí),我們時(shí)不時(shí)把文字條件置空,強(qiáng)迫模型去學(xué)著在無文字信息的情況下對(duì)圖片去噪(無條件生成)。在推理階段,我們分別做兩個(gè)預(yù)測(cè):一個(gè)有文字條件,一個(gè)沒有。我們可以用這兩者的差異來建立一個(gè)最終結(jié)合版的預(yù)測(cè),讓最終結(jié)果在文本條件預(yù)測(cè)所指明的方向上依據(jù)一個(gè)縮放系數(shù)(即引導(dǎo)尺度)去“走得更遠(yuǎn)”,希望最終生成一個(gè)更好地匹配文字提示的結(jié)果。上圖就展示了在同一個(gè)文本提示下使用不同引導(dǎo)尺度得到的不同結(jié)果??梢钥吹?,更高的引導(dǎo)尺度能讓生成的圖片更接近文字描述。
4.3 其它類型的條件生成:超分辨率、圖像修補(bǔ)、深度圖到圖像的轉(zhuǎn)換
我們也可以創(chuàng)建各種接收不同生成條件的 Stable Diffusion 模型。比如深度圖到圖像轉(zhuǎn)換模型使用深度信息作為生成條件。在推理階段,可以輸入一個(gè)目標(biāo)圖片的深度圖,以此來讓模型生成一個(gè)有相似全局結(jié)構(gòu)的圖片。
用相似的方式,我們也可以輸入一個(gè)低分辨率圖片作為條件,讓模型生成對(duì)應(yīng)的高分辨率圖片(正如Stable Diffusion Upscaler一樣)。此外,我們還可以輸入一個(gè)掩膜(mask),讓模型知道圖像相應(yīng)的區(qū)域需要讓模型用in-painting 的方式重新生成一下:掩膜外的區(qū)域要和原圖片保持一致,掩膜內(nèi)的區(qū)域要生成出新的內(nèi)容。
4.4 使用 DreamBooth 微調(diào)
DreamBooth 可以用來微調(diào)文字到圖像的生成模型,教它一些新的概念,比如某一特定物體或某種特定風(fēng)格。這一技術(shù)一開始是為 Google 的 Imagen Model 開發(fā)的,但被很快應(yīng)用于 stable diffusion 中。效果十分驚艷,但該技術(shù)也對(duì)各種設(shè)置十分敏感。
五、使用 Diffusers 庫來窺探 Stable Diffusion 內(nèi)部
參考文件:https://github.com/fastai/diffusion-nbs/blob/master/Stable%20Diffusion%20Deep%20Dive.ipynb
參考視頻:https://www.youtube.com/watch?app=desktop&v=0_BBRNYInx8
Diffusers 的關(guān)鍵模塊(以 stable diffusion 為例):
- 潛在空間編碼器: VAE,即變分自編碼器,把圖像編碼到特征,進(jìn)行生成過程后再把特征解碼到圖像
- 擴(kuò)散模型:UNet
- 文本 token 化:Tokenizer,把輸入的文本按照字典編碼為對(duì)應(yīng)的 token
- 文本編碼器:Text-Encoder,把 tokens 編碼為一串向量,用來控制擴(kuò)散模型的生成
- 采樣方式定義:Scheduler
- NSFW 是指 “Not Safe For Work” 或者"Not Suitable For Work",主要作用是用于控制算法不希望在畫面中出現(xiàn)不安全或令人不適的元素。Safety_checker 和 Feature_extractor 都是 NSFW 的一部分。
- 模型組織方式:model_index.json
環(huán)境準(zhǔn)備:
!pip install -Uq diffusers ftfy accelerate
# Installing transformers from source for now since we need the latest version for Depth2Img:
!pip install -Uq git+https://github.com/huggingface/transformers
import torch
import requests
from PIL import Image
from io import BytesIO
from matplotlib import pyplot as plt
# We'll be exploring a number of pipelines today!
from diffusers import (
StableDiffusionPipeline,
StableDiffusionImg2ImgPipeline,
StableDiffusionInpaintPipeline,
StableDiffusionDepth2ImgPipeline
)
# We'll use a couple of demo images later in the notebook
def download_image(url):
response = requests.get(url)
return Image.open(BytesIO(response.content)).convert("RGB")
# Download images for inpainting example
img_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo.png"
mask_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo_mask.png"
init_image = download_image(img_url).resize((512, 512))
mask_image = download_image(mask_url).resize((512, 512))
# Set device
device = (
"mps"
if torch.backends.mps.is_available()
else "cuda"
if torch.cuda.is_available()
else "cpu"
)
HuggingFace 模型路徑
5.1 Stable Diffusion Pipeline
print(list(pipeline.components.keys())) # List components
['vae', 'text_encoder', 'tokenizer', 'unet', 'scheduler', 'safety_checker', 'feature_extractor']
1、VAE
可變分自編碼器(VAE)是一種模型,它可以將輸入圖像進(jìn)行編碼,得到一個(gè)“壓縮過”的信息,再把這個(gè)“隱式的”壓縮信息給解碼出來,得到某種接近輸入的輸出。當(dāng)我們使用 Stable Diffusion 生成圖片時(shí),我們先在VAE的“隱空間”應(yīng)用擴(kuò)散過程生成隱編碼,然后在結(jié)尾對(duì)它們解碼來得到最終結(jié)果圖片。這樣UNet的輸入就不是完整的圖片、而是縮小版的特征,可以極大地減少計(jì)算資源的使用。
下面代碼就使用了 VAE,把輸入圖片編碼成隱式表示形式,再對(duì)它解碼:
# Create some fake data (a random image, range (-1, 1))
images = torch.rand(1, 3, 512, 512).to(device) * 2 - 1
print("Input images shape:", images.shape)
# Encode to latent space
with torch.no_grad():
latents = 0.18215 * pipe.vae.encode(images).latent_dist.mean
print("Encoded latents shape:", latents.shape)
# Decode again
with torch.no_grad():
decoded_images = pipe.vae.decode(latents / 0.18215).sample
print("Decoded images shape:", decoded_images.shape)
Input images shape: torch.Size([1, 3, 512, 512])
Encoded latents shape: torch.Size([1, 4, 64, 64])
Decoded images shape: torch.Size([1, 3, 512, 512])
在這個(gè)例子中,原本 512x512 尺寸的圖片被壓縮到 64x64的隱式表示形式(有四個(gè)通道)中。每一空間維度上都被壓縮到了原有的八分之一,這也是上文中為什么我們?cè)O(shè)定 width
和 height
時(shí)需要它們是 8 的倍數(shù)。
使用這些信息量充裕的 4x64x64 隱編碼可比使用 512px 大小的圖片要高效多了,可以讓我們的擴(kuò)散模型更快,并使用更少的資源來訓(xùn)練和使用。VAE的解碼過程并不是完美的,但即使損失了一點(diǎn)點(diǎn)質(zhì)量,總的來說也足夠好了。
注意:上面的代碼例子包含了一個(gè)值為 0.18215 的縮放因子,以此來適配stable diffusion訓(xùn)練時(shí)的處理流程。
2、分詞器 (Tokenizer) 和文本編碼器 (Text Encoder)
文本編碼器的作用是將輸入的字符串(文本提示)轉(zhuǎn)化成數(shù)值表示形式,這樣才能輸入進(jìn) UNet 作為條件。文本首先要被管線中的分詞器(tokenizer)轉(zhuǎn)換成一系列的分詞(token)。文本編碼器有大約五萬分詞的詞匯量 —— 它可以將一個(gè)長(zhǎng)句子分解為一個(gè)個(gè)的文本最小單元,這些最小單元被稱為 token,在英文中,一般一個(gè)單詞就可以作為一個(gè)分詞,而在漢語中,一般一個(gè)或多個(gè)漢字組成的詞語才是一個(gè)分詞。這些分詞的詞嵌入(embedding)會(huì)被送入文本編碼器模型中 —— 文本編碼器是一個(gè)transformer模型,被訓(xùn)練作為 CLIP 這個(gè)模型的文本編碼器。在這里,這個(gè)預(yù)訓(xùn)練過的 transformer 模型已經(jīng)有了足夠好的文本表示能力,能夠很好地把文本描述映射到特征空間。
我們這里通過對(duì)一個(gè)文本描述進(jìn)行編碼,驗(yàn)證一下這個(gè)過程。首先,我們手動(dòng)進(jìn)行分詞,并將它輸入到文本編碼器中,再使用管線的 _encode_prompt
方法,調(diào)用一下這個(gè)編碼過程,該過程包括補(bǔ)全或截?cái)喾衷~串的長(zhǎng)度,使得分詞串的長(zhǎng)度等于最大長(zhǎng)度 77 :
# Tokenizing and encoding an example prompt manualy:
# Tokenize
input_ids = pipe.tokenizer(["A painting of a flooble"])['input_ids']
print("Input ID -> decoded token")
for input_id in input_ids[0]:
print(f"{input_id} -> {pipe.tokenizer.decode(input_id)}")
# Feed through CLIP text encoder
input_ids = torch.tensor(input_ids).to(device)
with torch.no_grad():
text_embeddings = pipe.text_encoder(input_ids)['last_hidden_state']
print("Text embeddings shape:", text_embeddings.shape)
Input ID -> decoded token
49406 -> <|startoftext|>
320 -> a
3086 -> painting
539 -> of
320 -> a
4062 -> floo
1059 -> ble
49407 -> <|endoftext|>
Text embeddings shape: torch.Size([1, 8, 1024])
# Get the final text embeddings using the pipeline's _encode_prompt function:
text_embeddings = pipe._encode_prompt("A painting of a flooble", device, 1, False, '')
text_embeddings.shape
torch.Size([1, 77, 1024])
這些文本嵌入(text embedding),也即文本編碼器中最后一個(gè)transformer模塊的“隱狀態(tài)(hidden state)”,將會(huì)被送入 UNet 中作為 forward
函數(shù)的一個(gè)額外輸入
3、UNet
在擴(kuò)散模型中,UNet 模型接收一個(gè)帶噪的輸入,并預(yù)測(cè)噪聲,以此實(shí)現(xiàn)去噪。但與以往例子不同的是,這里的輸入并不是圖片了,而是圖片的隱式表示形式(latent representation)。此外,除了把用于暗示帶噪程度的 timestep 輸入進(jìn) UNet 作為條件外,這里模型也把文字提示(prompt)的文本嵌入(text embeddings)也作為輸入。這里我們用假數(shù)據(jù)試著讓它預(yù)測(cè)一下,請(qǐng)注意這一過程中各個(gè)輸入輸出的形狀大?。?/p>
# Dummy inputs:
timestep = pipe.scheduler.timesteps[0]
latents = torch.randn(1, 4, 64, 64).to(device)
text_embeddings = torch.randn(1, 77, 1024).to(device)
# Model prediction:
with torch.no_grad():
unet_output = pipe.unet(latents, timestep, text_embeddings).sample
print('UNet output shape:', unet_output.shape) # Same shape as the input latents
UNet output shape: torch.Size([1, 4, 64, 64])
4、調(diào)度器(Scheduler)
調(diào)度器保存了如何加噪這一信息,管理著如何基于模型的預(yù)測(cè)更新帶噪樣本。默認(rèn)的調(diào)度器是 PNDMScheduler
調(diào)度器,但你也可以用其它的(比如 LMSDiscreteScheduler
調(diào)度器),只要它們用相同的配置初始化。
我們可以畫出圖像來觀察如何隨著 timestep 添加噪聲,看看噪聲水平(基于 α ˉ \bar{\alpha} αˉ這個(gè)參數(shù))是怎樣變動(dòng)的:
plt.plot(pipe.scheduler.alphas_cumprod, label=r'$\bar{\alpha}$')
plt.xlabel('Timestep (high noise to low noise ->)');
plt.title('Noise schedule');plt.legend();
如果你想嘗試不同的調(diào)度器,可以參考下面代碼修改:
from diffusers import LMSDiscreteScheduler
# Replace the scheduler
pipe.scheduler = LMSDiscreteScheduler.from_config(pipe.scheduler.config)
# Print the config
print('Scheduler config:', pipe.scheduler)
# Generate an image with this new scheduler
pipe(prompt="Palette knife painting of an winter cityscape", height=480, width=480,
generator=torch.Generator(device=device).manual_seed(42)).images[0]
Scheduler config: LMSDiscreteScheduler {
"_class_name": "LMSDiscreteScheduler",
"_diffusers_version": "0.11.1",
"beta_end": 0.012,
"beta_schedule": "scaled_linear",
"beta_start": 0.00085,
"clip_sample": false,
"num_train_timesteps": 1000,
"prediction_type": "epsilon",
"set_alpha_to_one": false,
"skip_prk_steps": true,
"steps_offset": 1,
"trained_betas": null
}
5、自己做一個(gè)采樣循環(huán)
現(xiàn)在已經(jīng)逐個(gè)了解這些組成部分了,我們可以把它們拼裝到一起,來復(fù)現(xiàn)一下整個(gè)管線的功能:
guidance_scale = 8 #@param
num_inference_steps=30 #@param
prompt = "Beautiful picture of a wave breaking" #@param
negative_prompt = "zoomed in, blurry, oversaturated, warped" #@param
# Encode the prompt
text_embeddings = pipe._encode_prompt(prompt, device, 1, True, negative_prompt)
# Create our random starting point
latents = torch.randn((1, 4, 64, 64), device=device, generator=generator)
latents *= pipe.scheduler.init_noise_sigma
# Prepare the scheduler
pipe.scheduler.set_timesteps(num_inference_steps, device=device)
# Loop through the sampling timesteps
for i, t in enumerate(pipe.scheduler.timesteps):
# expand the latents if we are doing classifier free guidance
latent_model_input = torch.cat([latents] * 2)
# Apply any scaling required by the scheduler
latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
# predict the noise residual with the unet
with torch.no_grad():
noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
# perform guidance
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
# compute the previous noisy sample x_t -> x_t-1
latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample
# Decode the resulting latents into an image
with torch.no_grad():
image = pipe.decode_latents(latents.detach())
# View
pipe.numpy_to_pil(image)[0]
5.2 文本到圖像
下面這是一個(gè)使用 Diffusers 庫來實(shí)現(xiàn) stable diffusion 推理的代碼,可以針對(duì)輸入的 text 來生成一個(gè)名為 output.jpg 的圖片:
from diffusers import DiffusionPipeline
import torch
# from_pretrained 函數(shù)根據(jù) model_index.json 文件 new 了一個(gè) StableDiffusionPipeline。這個(gè)函數(shù)會(huì)挨個(gè)掃描預(yù)訓(xùn)練的模型文件夾中的七個(gè)文件夾,缺少任意一個(gè)都會(huì)報(bào)錯(cuò)。
# variant="fp16" 表示使用 fp16 精度
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", variant="fp16", torch_dtype=torch.float16)
pipeline.to("cuda")
pipeline("An image of a pretty girl in cartoon").images[0].save('output.jpg')
這里的 DiffusionPipeline.from_pretrained
就是會(huì)從 https://huggingface.co/runwayml/stable-diffusion-v1-5
中下載模型,我這里下載的都是 fp16 的,可以手動(dòng)下載,放到自己路徑下,然后修改 DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
這個(gè)路徑為自己的路徑即可
這是我生成的一個(gè)卡通形式的女孩:
這里管線還能設(shè)置許多參數(shù),具體如下:
# Load the pipeline
model_id = "stabilityai/stable-diffusion-2-1-base"
pipe = StableDiffusionPipeline.from_pretrained(model_id).to(device)
# Set up a generator for reproducibility
generator = torch.Generator(device=device).manual_seed(42)
# Run the pipeline, showing some of the available arguments
pipe_output = pipeline(
prompt="Palette knife painting of an autumn cityscape", # What to generate
negative_prompt="Oversaturated, blurry, low quality", # What NOT to generate
height=480, width=640, # Specify the image size
guidance_scale=8, # How strongly to follow the prompt
num_inference_steps=35, # How many steps to take
generator=generator # Fixed random seed
)
# View the resulting image:
pipe_output.images[0]
主要的要調(diào)節(jié)參數(shù)介紹:
-
width
和height
指定了生成圖片的尺寸。它們必須是可被 8 整除的數(shù)字,只有這樣我們的可變分自編碼器(VAE)才能正常工作(原因后面會(huì)將)。 - 步數(shù)
num_inference_steps
也會(huì)影響生成的質(zhì)量。默認(rèn)設(shè)成 50 就行,但你也可以試試設(shè)成 20,看看設(shè)置小了后效果會(huì)如何。 - 使用
negative_prompt
來強(qiáng)調(diào)不希望生成的內(nèi)容,一般會(huì)在無分類器引導(dǎo)(classifier-free guidance)的情況下用到,這種添加額外控制的方式特別管用。列出一些不想要的特性對(duì)生成更好結(jié)果很有幫助。 -
guidance_scale
這個(gè)參數(shù)決定了無分類器引導(dǎo)(CFG)的影響強(qiáng)度有多大。增大這個(gè)值會(huì)使得生成的內(nèi)容更接近文字提示;但這個(gè)值如果過大,可能會(huì)使得結(jié)果變得過飽和、不好看。
還可以看看使用不同的無分類引導(dǎo)強(qiáng)度的效果:
#@markdown comparing guidance scales:
cfg_scales = [1.1, 8, 12] #@param
prompt = "A collie with a pink hat" #@param
fig, axs = plt.subplots(1, len(cfg_scales), figsize=(16, 5))
for i, ax in enumerate(axs):
im = pipe(prompt, height=480, width=480,
guidance_scale=cfg_scales[i], num_inference_steps=35,
generator=torch.Generator(device=device).manual_seed(42)).images[0]
ax.imshow(im); ax.set_title(f'CFG Scale {cfg_scales[i]}');
# model_index.json
{
"_class_name": "StableDiffusionPipeline",
"_diffusers_version": "0.6.0",
"feature_extractor": [
"transformers",
"CLIPImageProcessor"
],
"safety_checker": [
"stable_diffusion",
"StableDiffusionSafetyChecker"
],
"scheduler": [
"diffusers",
"PNDMScheduler"
],
"text_encoder": [
"transformers",
"CLIPTextModel"
],
"tokenizer": [
"transformers",
"CLIPTokenizer"
],
"unet": [
"diffusers",
"UNet2DConditionModel"
],
"vae": [
"diffusers",
"AutoencoderKL"
]
}
diffusers 庫是使用 pipeline 來組織每個(gè)模型的,pipeline 位于 diffusers/src/diffusers/pipelines/pipeline_utils.py
使用 .from_pretrained()
就可以直接從 huggingface 獲取預(yù)訓(xùn)練好的模型
@classmethod
@validate_hf_hub_args
def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.PathLike]], **kwargs):
r"""
Instantiate a PyTorch diffusion pipeline from pretrained pipeline weights.
The pipeline is set in evaluation mode (`model.eval()`) by default.
If you get the error message below, you need to finetune the weights for your downstream task:
```
Some weights of UNet2DConditionModel were not initialized from the model checkpoint at runwayml/stable-diffusion-v1-5 and are newly initialized because the shapes did not match:
- conv_in.weight: found shape torch.Size([320, 4, 3, 3]) in the checkpoint and torch.Size([320, 9, 3, 3]) in the model instantiated
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
```
Parameters:
pretrained_model_name_or_path (`str` or `os.PathLike`, *optional*):
torch_dtype (`str` or `torch.dtype`, *optional*):
force_download (`bool`, *optional*, defaults to `False`):
Whether or not to force the (re-)download of the model weights and configuration files, overriding the
cached versions if they exist.
cache_dir (`Union[str, os.PathLike]`, *optional*):
Path to a directory where a downloaded pretrained model configuration is cached if the standard cache
is not used.
resume_download (`bool`, *optional*, defaults to `False`):
Whether or not to resume downloading the model weights and configuration files. If set to `False`, any
incompletely downloaded files are deleted.
proxies (`Dict[str, str]`, *optional*):
A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128',
'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request.
output_loading_info(`bool`, *optional*, defaults to `False`):
Whether or not to also return a dictionary containing missing keys, unexpected keys and error messages.
local_files_only (`bool`, *optional*, defaults to `False`):
Whether to only load local model weights and configuration files or not. If set to `True`, the model
won't be downloaded from the Hub.
token (`str` or *bool*, *optional*):
The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from
`diffusers-cli login` (stored in `~/.huggingface`) is used.
revision (`str`, *optional*, defaults to `"main"`):
The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier
allowed by Git.
custom_revision (`str`, *optional*, defaults to `"main"`):
The specific model version to use. It can be a branch name, a tag name, or a commit id similar to
`revision` when loading a custom pipeline from the Hub. It can be a ?? Diffusers version when loading a
custom pipeline from GitHub, otherwise it defaults to `"main"` when loading from the Hub.
mirror (`str`, *optional*):
Mirror source to resolve accessibility issues if you’re downloading a model in China. We do not
guarantee the timeliness or safety of the source, and you should refer to the mirror site for more
information.
device_map (`str` or `Dict[str, Union[int, str, torch.device]]`, *optional*):
A map that specifies where each submodule should go. It doesn’t need to be defined for each
parameter/buffer name; once a given module name is inside, every submodule of it will be sent to the
same device.
Set `device_map="auto"` to have ?? Accelerate automatically compute the most optimized `device_map`. For
more information about each option see [designing a device
map](https://hf.co/docs/accelerate/main/en/usage_guides/big_modeling#designing-a-device-map).
max_memory (`Dict`, *optional*):
A dictionary device identifier for the maximum memory. Will default to the maximum memory available for
each GPU and the available CPU RAM if unset.
offload_folder (`str` or `os.PathLike`, *optional*):
The path to offload weights if device_map contains the value `"disk"`.
offload_state_dict (`bool`, *optional*):
If `True`, temporarily offloads the CPU state dict to the hard drive to avoid running out of CPU RAM if
the weight of the CPU state dict + the biggest shard of the checkpoint does not fit. Defaults to `True`
when there is some disk offload.
low_cpu_mem_usage (`bool`, *optional*, defaults to `True` if torch version >= 1.9.0 else `False`):
Speed up model loading only loading the pretrained weights and not initializing the weights. This also
tries to not use more than 1x model size in CPU memory (including peak memory) while loading the model.
Only supported for PyTorch >= 1.9.0. If you are using an older version of PyTorch, setting this
argument to `True` will raise an error.
use_safetensors (`bool`, *optional*, defaults to `None`):
If set to `None`, the safetensors weights are downloaded if they're available **and** if the
safetensors library is installed. If set to `True`, the model is forcibly loaded from safetensors
weights. If set to `False`, safetensors weights are not loaded.
use_onnx (`bool`, *optional*, defaults to `None`):
If set to `True`, ONNX weights will always be downloaded if present. If set to `False`, ONNX weights
will never be downloaded. By default `use_onnx` defaults to the `_is_onnx` class attribute which is
`False` for non-ONNX pipelines and `True` for ONNX pipelines. ONNX weights include both files ending
with `.onnx` and `.pb`.
kwargs (remaining dictionary of keyword arguments, *optional*):
Can be used to overwrite load and saveable variables (the pipeline components of the specific pipeline
class). The overwritten components are passed directly to the pipelines `__init__` method. See example
below for more information.
variant (`str`, *optional*):
Load weights from a specified variant filename such as `"fp16"` or `"ema"`. This is ignored when
loading `from_flax`.
<Tip>
To use private or [gated](https://huggingface.co/docs/hub/models-gated#gated-models) models, log-in with
`huggingface-cli login`.
</Tip>
Examples:
```py
>>> from diffusers import DiffusionPipeline
>>> # Download pipeline from huggingface.co and cache.
>>> pipeline = DiffusionPipeline.from_pretrained("CompVis/ldm-text2im-large-256")
>>> # Download pipeline that requires an authorization token
>>> # For more information on access tokens, please refer to this section
>>> # of the documentation](https://huggingface.co/docs/hub/security-tokens)
>>> pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
>>> # Use a different scheduler
>>> from diffusers import LMSDiscreteScheduler
>>> scheduler = LMSDiscreteScheduler.from_config(pipeline.scheduler.config)
>>> pipeline.scheduler = scheduler
```
"""
經(jīng)過 from_pretrained()
后得到的 pipeline 如下:
StableDiffusionPipeline {
"_class_name": "StableDiffusionPipeline",
"_diffusers_version": "0.25.0.dev0",
"_name_or_path": "./stable-diffusion-v1-5",
"feature_extractor": [
"transformers",
"CLIPImageProcessor"
],
"image_encoder": [
null,
null
],
"requires_safety_checker": true,
"safety_checker": [
"stable_diffusion",
"StableDiffusionSafetyChecker"
],
"scheduler": [
"diffusers",
"PNDMScheduler"
],
"text_encoder": [
"transformers",
"CLIPTextModel"
],
"tokenizer": [
"transformers",
"CLIPTokenizer"
],
"unet": [
"diffusers",
"UNet2DConditionModel"
],
"vae": [
"diffusers",
"AutoencoderKL"
]
}
stable diffusion 類的位置 diffusers/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py
# stablediffusion 的 __init__() 函數(shù)如下:
def __init__(
self,
vae: AutoencoderKL,
text_encoder: CLIPTextModel,
tokenizer: CLIPTokenizer,
unet: UNet2DConditionModel,
scheduler: KarrasDiffusionSchedulers,
safety_checker: StableDiffusionSafetyChecker,
feature_extractor: CLIPImageProcessor,
image_encoder: CLIPVisionModelWithProjection = None,
requires_safety_checker: bool = True,
):
5.3 圖像到圖像
直到現(xiàn)在,我們生成的圖片還都是完全從隨機(jī)的隱變量來開始生成的,而且也都使用了完整的擴(kuò)散模型采樣循環(huán)。但如果使用 Img2Img 管線,我們其實(shí)不必從頭開始。Img2Img 這個(gè)管線首先將一張已有的圖片進(jìn)行編碼,編碼成一系列的隱變量,然后在這些隱變量上隨機(jī)加噪聲,以這些作為起始點(diǎn)。噪聲加多大量、去噪需要多少步數(shù)(timestep)決定了這個(gè) img2img 過程的“強(qiáng)度”。只加一點(diǎn)點(diǎn)噪聲(強(qiáng)度低)只會(huì)帶來微小改變,而加入大量噪聲并跑完完整去噪過程又會(huì)讓生成出幾乎完全不像原始圖片,僅在整體結(jié)構(gòu)上還有點(diǎn)相似。
這個(gè)管線無需什么特殊模型,只要模型的 ID 和我們的文字到圖像模型一樣就行,沒有新的需要下載的文件。
# Loading an Img2Img pipeline
model_id = "stabilityai/stable-diffusion-2-1-base"
img2img_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id).to(device)
這里是使用該管線的代碼:
# Apply Img2Img
result_image = img2img_pipe(
prompt="An oil painting of a man on a bench",
image = init_image, # The starting image
strength = 0.6, # 0 for no change, 1.0 for max strength
).images[0]
# View the result
fig, axs = plt.subplots(1, 2, figsize=(12, 5))
axs[0].imshow(init_image);axs[0].set_title('Input Image')
axs[1].imshow(result_image);axs[1].set_title('Result');
5.4 In-painting
果我們想在一張圖中保留一部分不變而在其它部分生成新東西,那該怎么辦呢?這種技術(shù)叫 inpainting 。雖然我們可以用 StableDiffusionInpaintPipelineLegacy
管線來實(shí)現(xiàn),但其實(shí)有更簡(jiǎn)單的選擇。這里的 Stable Diffusion 模型接收一個(gè)掩模(mask)作為額外條件性輸入。這個(gè)掩模圖片需要和輸入圖片尺寸一致,白色區(qū)域表示要被替換的部分,黑色區(qū)域表示要保留的部分。以下代碼就展示了我們?nèi)绾屋d入這個(gè)管線并如何應(yīng)用到前面載入的示例圖片和掩模上:
# Load the inpainting pipeline (requires a suitable inpainting model)
pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting")
pipe = pipe.to(device)
# Inpaint with a prompt for what we want the result to look like
prompt = "A small robot, high resolution, sitting on a park bench"
image = pipe(prompt=prompt, image=init_image, mask_image=mask_image).images[0]
# View the result
fig, axs = plt.subplots(1, 3, figsize=(16, 5))
axs[0].imshow(init_image);axs[0].set_title('Input Image')
axs[1].imshow(mask_image);axs[1].set_title('Mask')
axs[2].imshow(image);axs[2].set_title('Result');
當(dāng)和其它可以自動(dòng)生成掩模的模型結(jié)合起來的時(shí)候,這個(gè)模型將會(huì)相當(dāng)強(qiáng)大。比如這個(gè)示例space就用了一個(gè)名為 CLIPSeg 的模型,它可以根據(jù)文字描述自動(dòng)地用掩模去掉一個(gè)物體。
5.5 Depth2Image
Img2Img 已經(jīng)很厲害了,但有時(shí)我們還想借用原始圖片的整體結(jié)構(gòu),但使用不同的顏色或紋理來生成新圖片。保留圖片整體結(jié)構(gòu)但卻不保留原有顏色,是很難通過調(diào)節(jié) Img2Img 的“強(qiáng)度”來實(shí)現(xiàn)的。
所以這里就需要另一個(gè)微調(diào)的模型了!這個(gè)模型需要輸入額外的深度信息作為生成條件。相關(guān)管線使用了一個(gè)深度預(yù)測(cè)模型來預(yù)測(cè)出一個(gè)深度圖,然后這個(gè)深度圖會(huì)被輸入微調(diào)過的 UNet 中用以生成圖片。我們這里希望生成的圖片能夠保留原始圖片的深度信息和總體結(jié)構(gòu),同時(shí)又在相關(guān)部分填入全新的內(nèi)容。
# Load the Depth2Img pipeline (requires a suitable model)
pipe = StableDiffusionDepth2ImgPipeline.from_pretrained("stabilityai/stable-diffusion-2-depth")
pipe = pipe.to(device)
# Inpaint with a prompt for what we want the result to look like
prompt = "An oil painting of a man on a bench"
image = pipe(prompt=prompt, image=init_image).images[0]
# View the result
fig, axs = plt.subplots(1, 2, figsize=(16, 5))
axs[0].imshow(init_image);axs[0].set_title('Input Image')
axs[1].imshow(image);axs[1].set_title('Result');
讓我們和 Img2Img 的例子做個(gè)對(duì)比 —— 這里生成結(jié)果有著更豐富的顏色變化,但圖片的整體結(jié)構(gòu)仍然忠于原始圖片。這里的例子其實(shí)并不夠理想,因?yàn)闉榱似ヅ渖瞎返男螤?,這里生成的人有著怪異的身體構(gòu)造。但在某些場(chǎng)景下,這個(gè)技術(shù)還是相當(dāng)有用的,比如這個(gè)例子中的“殺手級(jí)應(yīng)用軟件”(查看推特),它利用深度模型去給一個(gè) 3D 場(chǎng)景加入紋理!
5.6 ControlNet
經(jīng)過了之前的章節(jié)學(xué)習(xí)之后,相信你已經(jīng)能夠熟練的使用prompt來描述并生成一幅精美的畫作了。通常來說,Prompt的準(zhǔn)確性越高、描述的越豐富,生成的畫作也會(huì)越符合你心目中的樣子。
然而你也許也注意到了一件事:無論我們?cè)僭趺淳?xì)的使用prompt來指導(dǎo)Stable diffusion模型,也無法描述清楚人物四肢的角度、背景中物體的位置、每一縷光線照射的角度等等。文字的能力是有極限的!
為了成為一個(gè)更加優(yōu)秀的AI畫手,我們需要突破文字提示(Text conditioning),找到一種能夠通過圖像特征來為擴(kuò)散模型的生成過程提供更加精細(xì)的控制的方式,也就是:圖像提示(Image Conditioning)
幸運(yùn)的是,我們恰好已經(jīng)有了這樣的工具:ControlNet
ControlNet是一種能夠嵌入任意已經(jīng)訓(xùn)練好的擴(kuò)散模型中,為這些模型提供更多控制條件的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)。其基本結(jié)構(gòu)如下圖所示:
如圖所示,ControlNet的基本結(jié)構(gòu)由一個(gè)對(duì)應(yīng)的原本擴(kuò)散模型的block和兩個(gè)“零卷積”層組成。在之后的訓(xùn)練過程中,我們會(huì)“鎖死”原本網(wǎng)絡(luò)的權(quán)重,只更新ControlNet結(jié)構(gòu)中的網(wǎng)路“副本”和零卷積層的權(quán)重。
這些可訓(xùn)練的神經(jīng)網(wǎng)絡(luò)“副本”將學(xué)會(huì)如何讓模型按照新的控制條件來生成結(jié)果,而被“鎖死”的網(wǎng)絡(luò)原本則會(huì)保留原先網(wǎng)絡(luò)已經(jīng)學(xué)會(huì)的所有知識(shí)。
這樣,即使用來訓(xùn)練ControlNet的訓(xùn)練集規(guī)模較小,被“鎖死”的網(wǎng)絡(luò)原本的權(quán)重也能確保擴(kuò)散模型本身的生成效果不受影響。
在ControlNet中的這些“零卷積層”則是一些weight和bias權(quán)重都被初始化為0的 1×1 卷積層。在訓(xùn)練剛開始的時(shí)候,無論新添加的控制條件是什么,這些“零卷積”層都只會(huì)輸出0,因此ControlNet將不會(huì)對(duì)擴(kuò)散模型的生成結(jié)果造成任何影響。
而隨著訓(xùn)練過程的深入,ControlNet部分將學(xué)會(huì)逐漸調(diào)整擴(kuò)散模型原本的生成過程,使得生成的圖像逐漸向新添加的控制條件靠近。
你也許會(huì)問,如果一個(gè)卷積層的所有參數(shù)都為0,輸出結(jié)果也為0,那么它怎么才能正常的進(jìn)行權(quán)重的迭代過程呢?為了回答這個(gè)問題,我們給出一個(gè)簡(jiǎn)單的數(shù)學(xué)推導(dǎo)過程:
假設(shè)我們有一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)層:
y = w x + b y=wx+b y=wx+b
那么我們知道:
? y / ? w = x , ? y / ? x = w , ? y / ? b = 1 \partial y/\partial w=x, \partial y/\partial x=w, \partial y/\partial b=1 ?y/?w=x,?y/?x=w,?y/?b=1
假設(shè)其中的權(quán)重w為0,輸入x不為0,那么我們就有:
? y / ? w ≠ 0 , ? y / ? x = 0 , ? y / ? b ≠ 0 \partial y/\partial w \neq 0, \partial y/\partial x=0, \partial y/\partial b\neq 0 ?y/?w=0,?y/?x=0,?y/?b=0
這意味著只要輸入x不為0,梯度下降的迭代過程就能正常的更新權(quán)重w,使其不再為0。那么我們將會(huì)得到:
? y / ? x ≠ 0 \partial y/\partial x\neq 0 ?y/?x=0
也就是說,經(jīng)過若干次迭代步驟以后,這些零卷積將會(huì)逐漸變成一個(gè)有著正常權(quán)重的普通卷積層。
將上面所描述的ControlNet Block堆疊十四次以后,我們就能獲得了一個(gè)完整的、能夠用來對(duì)穩(wěn)定擴(kuò)散模型添加新的控制的條件的ControlNet:
仔細(xì)研究過這個(gè)結(jié)構(gòu)以后,你就會(huì)發(fā)現(xiàn),Controlnet實(shí)際上使用了訓(xùn)練完成的穩(wěn)定擴(kuò)散模型的encoder模塊,來作為自己的主干網(wǎng)絡(luò)。而這樣的一個(gè)穩(wěn)定而又強(qiáng)力的主干網(wǎng)絡(luò),則保證了ControlNet能夠?qū)W到更多不同的控制圖像生成的方法。
訓(xùn)練一個(gè)附加在某個(gè)穩(wěn)定擴(kuò)散模型上的ControlNet的過程大致如下:
-
收集你所想要的附加控制條件的數(shù)據(jù)集和對(duì)應(yīng)prompt。假如你想訓(xùn)練一個(gè)通過人體關(guān)鍵點(diǎn)來對(duì)擴(kuò)散模型生成的人體進(jìn)行姿態(tài)控制的ControlNet,你需要先收集一批人物的圖片,并標(biāo)注好這些圖片的prompt以及對(duì)應(yīng)的人體的關(guān)鍵點(diǎn)的位置。
-
將prompt輸入被“鎖死”的穩(wěn)定擴(kuò)散模型,將標(biāo)注好的圖像控制條件(例如人體關(guān)鍵點(diǎn)的標(biāo)注結(jié)果)輸入ControlNet模型,然后按照穩(wěn)定擴(kuò)散模型的訓(xùn)練過程迭代ControlNet模型的權(quán)重。
-
訓(xùn)練過程中會(huì)隨機(jī)的將50%的文字提示替換為空白字符串,這樣能夠“強(qiáng)迫”網(wǎng)絡(luò)更多的從圖像控制條件中學(xué)會(huì)更多的語義信息。
-
訓(xùn)練結(jié)束以后,我們就可以使用該ControlNet對(duì)應(yīng)的圖像控制條件(例如輸入人體骨骼關(guān)鍵點(diǎn)),來控制擴(kuò)散模型生成符合條件的圖像了。
請(qǐng)注意,因?yàn)樵谠撚?xùn)練過程中原本的擴(kuò)散模型的權(quán)重不會(huì)產(chǎn)生任何梯度(我們鎖死了這一部分!),所以即使我們添加了十四個(gè)ControlNet blocks,整個(gè)訓(xùn)練過程也不會(huì)需要比訓(xùn)練原先擴(kuò)散模型更多的顯存。
我們?cè)诒菊鹿?jié)將展示一些已經(jīng)訓(xùn)練好的ControlNet的示例,這些圖片都來自ControlNet的github repo:https://github.com/lllyasviel/ControlNet。
你可以在HuggingFace上找到這些由ControlNet的原作者提供的模型:https://huggingface.co/lllyasviel/ControlNet。
以下圖片中左上角的圖像為ControlNet的額外控制條件的輸入圖像,右側(cè)的圖像則為給定條件下穩(wěn)定擴(kuò)散模型的生成結(jié)果。
5.6.1 ControlNet 與 Canny Edge
Prompt:“bird”
5.6.2 ControlNet 與 M-LSD Lines
M-LSD Lines 是另外一種輕量化邊緣檢測(cè)算法,比較擅長(zhǎng)提取圖像中的直線線條。訓(xùn)練在M-LSD Lines上的ControlNet很適合生成室內(nèi)環(huán)境的圖片。
Prompt: “room”
5.6.3 ControlNet 與 HED Boundary
Soft HED Boundary 能夠保存輸入圖片更多的細(xì)節(jié),訓(xùn)練在HED Boundary上的ControlNet很適合用來重新上色和風(fēng)格重構(gòu)。
Prompt: “oil painting of handsome old man, masterpiece”
5.6.4 ControlNet 與 涂鴉畫
ControlNet 的強(qiáng)大能力甚至不使用任何真實(shí)圖片提取的信息也能生成高質(zhì)量的結(jié)果。訓(xùn)練在涂鴉數(shù)據(jù)上的ControlNet能讓穩(wěn)定擴(kuò)散模型學(xué)會(huì)如何將兒童涂鴉轉(zhuǎn)繪成高質(zhì)量的圖片。
Prompt: “turtle”
5.6.5 ControlNet 與 人體關(guān)鍵點(diǎn)
訓(xùn)練在人體關(guān)鍵點(diǎn)數(shù)據(jù)上的ControlNet,能讓擴(kuò)散模型學(xué)會(huì)生成指定姿態(tài)的人體。
Prompt: “Chief in the kitchen”
5.6.6 ControlNet 與 語義分割
語義分割模型是一種提取圖像中各個(gè)區(qū)域語義信息的一種模型,常用來對(duì)圖像中人體、物體、背景的區(qū)域進(jìn)行劃分。訓(xùn)練在語義分割數(shù)據(jù)上的ControlNet適合讓穩(wěn)定擴(kuò)散模型來生成特定結(jié)構(gòu)的場(chǎng)景圖。
Prompt: “House”
5.6.7 實(shí)戰(zhàn)
!pip install -q diffusers==0.14.0 transformers xformers git+https://github.com/huggingface/accelerate.git
!pip install -q opencv-contrib-python
!pip install -q controlnet_aux
from diffusers import StableDiffusionControlNetPipeline
from diffusers.utils import load_image
image = load_image(
"https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png"
)
image
# 提取 canny 邊緣
import cv2
from PIL import Image
import numpy as np
image = np.array(image)
low_threshold = 100
high_threshold = 200
image = cv2.Canny(image, low_threshold, high_threshold)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
canny_image
# 構(gòu)建管道
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
import torch
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
)
# 選擇調(diào)度器,UniPCMultistepScheduler。這個(gè)調(diào)度器能夠顯著加快模型的推理速度,只需要迭代20次就能達(dá)到與之前的默認(rèn)調(diào)度器迭代50次相同的效果!
from diffusers import UniPCMultistepScheduler
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
# 實(shí)現(xiàn)
def image_grid(imgs, rows, cols):
assert len(imgs) == rows * cols
w, h = imgs[0].size
grid = Image.new("RGB", size=(cols * w, rows * h))
grid_w, grid_h = grid.size
for i, img in enumerate(imgs):
grid.paste(img, box=(i % cols * w, i // cols * h))
return grid
prompt = ", best quality, extremely detailed"
prompt = [t + prompt for t in ["Sandra Oh", "Kim Kardashian", "rihanna", "taylor swift"]]
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(len(prompt))]
output = pipe(
prompt,
canny_image,
negative_prompt=["monochrome, lowres, bad anatomy, worst quality, low quality"] * len(prompt),
generator=generator,
num_inference_steps=20,
)
image_grid(output.images, 2, 2)
還可以使用姿態(tài)作為輸入控制圖片生成相同姿態(tài)的不同人物
urls = "yoga1.jpeg", "yoga2.jpeg", "yoga3.jpeg", "yoga4.jpeg"
imgs = [
load_image("https://hf.co/datasets/YiYiXu/controlnet-testing/resolve/main/" + url)
for url in urls
]
image_grid(imgs, 2, 2)
# 提取瑜伽姿勢(shì)
from controlnet_aux import OpenposeDetector
model = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
poses = [model(img) for img in imgs]
image_grid(poses, 2, 2)
controlnet = ControlNetModel.from_pretrained( "fusing/stable-diffusion-v1-5-controlnet-openpose", torch_dtype=torch.float16 )
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
controlnet=controlnet,
torch_dtype=torch.float16,
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
pipe.enable_xformers_memory_efficient_attention()
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(4)]
prompt = "super-hero character, best quality, extremely detailed"
output = pipe(
[prompt] * 4,
poses,
negative_prompt=["monochrome, lowres, bad anatomy, worst quality, low quality"] * 4,
generator=generator,
num_inference_steps=20,
)
image_grid(output.images, 2, 2)
文章來源:http://www.zghlxwxcb.cn/news/detail-793689.html
參考:文章來源地址http://www.zghlxwxcb.cn/news/detail-793689.html
- lllyasviel/sd-controlnet-depth:https://huggingface.co/lllyasviel/sd-controlnet-depth
- lllyasviel/sd-controlnet-hed:https://huggingface.co/lllyasviel/sd-controlnet-hed
- lllyasviel/sd-controlnet-normal:https://huggingface.co/lllyasviel/sd-controlnet-normal
- lllyasviel/sd-controlnet-scribble:https://huggingface.co/lllyasviel/sd-controlnet-scribble
- lllyasviel/sd-controlnet-seg:https://huggingface.co/lllyasviel/sd-controlnet-scribble
- lllyasviel/sd-controlnet-openpose:https://huggingface.co/lllyasviel/sd-controlnet-openpose
- lllyasviel/sd-controlnet-mlsd:https://huggingface.co/lllyasviel/sd-controlnet-mlsd
- lllyasviel/sd-controlnet-mlsd:https://huggingface.co/lllyasviel/sd-controlnet-canny
到了這里,關(guān)于【擴(kuò)散模型】11、Stable Diffusion | 使用 Diffusers 庫來看看 Stable Diffusion 的結(jié)構(gòu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!