Stable Diffusion 是一種文本到圖像的潛在擴散模型,由來自 CompVis、Stability AI 和 LAION 的研究人員和工程師建立。 它使用來自 LAION-5B 資料庫子集的 512x512 圖像進行訓練。 穩定擴散,生成人臉,也可以在自己的機器上運作,如下圖所示:
推薦:将 NSDT場景編輯器 加入你的3D開發工具鍊。
如果你足夠聰明和有創意,你可以建立一系列圖像,然後形成視訊。 例如,Xander Steenbrugge 使用穩定擴散和圖 1 所示的輸入提示建立了令人驚歎的 Voyage through Time 視訊:
以下是他用來創作這幅創意作品的提示和種子:
在本文中,我們将首先介紹什麼是Stable Diffusion并讨論其主要組成部分。 然後我們将使用穩定擴散以三種不同的方式建立圖像,從簡單到複雜。
1、穩定擴散模型
擴散模型是機器學習模型,經過訓練可以逐漸對随機高斯噪聲進行去噪以獲得感興趣的樣本,例如圖像。
擴散模型有一個主要的缺點,因為去噪過程的時間和記憶體消耗都非常昂貴。 這會使程序變慢并消耗大量記憶體。 這樣做的主要原因是它們在像素空間中運作,這變得非常昂貴,尤其是在生成高分辨率圖像時。
引入穩定擴散來解決這個問題,因為它依賴于潛在擴散。 潛在擴散通過在較低次元的潛在空間上應用擴散過程而不是使用實際像素空間來減少記憶體和計算成本。
1.1 潛在擴散的組成
潛在擴散包含三個主要組成部分:
- 變分自編碼器 (VAE)
變分自編碼器 (VAE) 由兩個主要部分組成:編碼器和解碼器。 編碼器會将圖像轉換為低維潛在表示,該表示将作為下一個元件 U_Net 的輸入。 解碼器将做相反的事情,将潛在表示轉換回圖像。
編碼器用于在潛在擴散訓練期間為前向擴散過程擷取輸入圖像的潛在表示(latent)。 在推理過程中,VAE 解碼器會将潛在的轉換回圖像。
- U-Net
U-Net 也由編碼器和解碼器部分組成,兩者都由 ResNet 塊組成。 編碼器将圖像表示壓縮為較低分辨率的圖像,解碼器将較低分辨率解碼回較高分辨率的圖像。
為了防止 U-Net 在下采樣時丢失重要資訊,通常在編碼器的下采樣 ResNet 和解碼器的上采樣 ResNet 之間添加快捷連接配接。
此外,穩定擴散 U-Net 能夠通過交叉注意層調節其在文本嵌入上的輸出。 交叉注意層被添加到 U-Net 的編碼器和解碼器部分,通常在 ResNet 塊之間。
- 文本編碼器
文本編碼器會将輸入提示(例如,“A Pikachu fine dining with view to the Effiel tower”)轉換為 U-Net 可以了解的嵌入空間。 這将是一個簡單的基于轉換器的編碼器,它将标記序列映射到潛在文本嵌入序列。
重要的是使用一個好的提示符以獲得預期的輸出。 這就是為什麼現在正在流行即時工程的主題。 提示工程是找到某些詞的行為,這些詞可以觸發模型産生具有某些屬性的輸出。
1.2 為什麼潛擴散快速高效
latent diffusion 之是以快速高效,是因為 latent diffusion 的 U-Net 在低維空間上運作。 與像素空間擴散相比,這減少了記憶體和計算複雜性。 例如,Stable Diffusion 中使用的自動編碼器的縮減系數為 8。這意味着形狀為 (3, 512, 512 ) 的圖像在潛在空間中變為 (4, 64, 64 ),這需要的記憶體減少 64 倍。
1.3 推理中的穩定擴散
首先,穩定擴散模型将潛在種子和文本提示作為輸入。 然後使用潛在種子生成大小為 64×64 的随機潛在圖像表示,而文本提示通過 CLIP 的文本編碼器轉換為大小為 77×768 的文本嵌入。
接下來,U-Net 在以文本嵌入為條件的同時疊代地對随機潛在圖像表示進行去噪。 U-Net 的輸出是噪聲殘差,用于通過排程程式算法計算去噪的潛在圖像表示。 排程器算法根據先前的噪聲表示和預測的噪聲殘差計算預測的去噪圖像表示。
許多不同的排程程式算法可用于此計算,每個算法都有其優點和缺點。 對于穩定擴散,建議使用以下之一:
- PNDM 排程程式(預設使用)
- DDIM排程器
- K-LMS排程程式
去噪過程重複大約 50 次以逐漸檢索更好的潛在圖像表示。 完成後,潛在圖像表示由變分自編碼器的解碼器部分解碼。
2、使用HuggingFace Space
HuggingFace Space提供了一個非常簡單的 API 來使用穩定擴散生成圖像。 在下圖中,你可以看到我使用了“Astronauts riding a horse”,可以在下圖中看到輸出:
有一些可用的進階選項可用于更改生成圖像的品質,如下圖所示:
有四個選項可供使用:
- images:這個控制圖檔的數量,最多4張圖檔。
- Steps:此選項選擇你想要的擴散過程的步驟數。 步驟越多,生成的圖像品質就越好。 如果你想要高品質,你可以選擇可用的最大步數,即 50。如果你需要更快的結果,那麼可以考慮減少步數。
- Guidance Scale:指導比例是生成的圖像與你的輸入提示的接近程度與輸入的多樣性之間的權衡。 它的典型值約為 7.5。 比例增加得越多,圖像的品質就越高,但輸出的多樣性就會降低。
- Seed:種子使你能夠控制生成樣本的多樣性
3、使用Diffusers開發包
第二種方法是使用 Hugging Face 生成的 Diffusers 庫并在 google Colab 上運作它。 diffuser 是 Hugging Face 生成的一個庫,它包含了目前可用的大部分穩定的擴散模型。
第一步是打開google collab,然後按connect。 之後,要檢查它是否連接配接到GPU,可以從資源按鈕中檢查它,如下圖所示:
另一個選項是從 Runtime 菜單中選擇 change run-time type,然後你應該會發現硬體加速器被選擇為 GPU:
首先,讓我們確定使用 GPU 運作時來使用下面的代碼運作此筆記本,以便推理速度更快。 如果以下指令失敗,請使用運作時菜單并選擇更改運作時類型,如上圖所示:
!nvidia-smi
如果它正在工作并被檢測到,你将收到類似的消息:
接下來,應該安裝diffusers、 scipy、ftfy 和 transformer:
!pip install diffusers==0.4.0
!pip install transformers scipy ftfy
!pip install "ipywidgets>=7,<8"
還需要通過勾選此處的複選框來接受示範許可。 你必須在 hugging face 上注冊并獲得通路令牌才能使用這些模型。
由于 google collab 已經禁用了外部小部件,我們需要啟用它。 為此,請運作以下代碼以使用 notebook_login :
from google.colab import output
output.enable_custom_widget_manager()
現在可以使用從你的帳戶獲得的通路令牌登入到你的Huggingface帳戶:
from huggingface_hub import notebook_login
notebook_login()
接下來,我們将從擴散器庫中加載 StableDiffusionPipeline。 StableDiffusionPipeline 是一個端到端推理管道,可用于從文本生成圖像。
我們将加載預訓練模型的權重。 模型 ID 将是 CompVis/stable-diffusion-v1–4,我們還将對函數使用特定類型的修訂版和 torch_dtype。 我們将設定 revision = “fp16” 以從半精度分支加載權重,并設定 torch_dtype = “torch.float16” 以告知擴散器期望權重為 float 16 精度。
像這樣設定變量以便能夠在免費版的 google CoLab 上運作模型非常重要。
import torch
from diffusers import StableDiffusionPipeline
# make sure you're logged in with `huggingface-cli login`
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", revision="fp16", torch_dtype=torch.float16)
現在讓我們将管道移動到 GPU 以進行更快的推理:
pipe = pipe.to("cuda")
現在是生成圖檔的時候了。 我們将編寫一個提示并将其提供給管道并列印輸出。 這裡的輸入提示是一張宇航員騎馬的照片:
prompt = "a photograph of an astronaut riding a horse"
image = pipe(prompt).images[0] # image here is in [PIL format](https://pillow.readthedocs.io/en/stable/)
# Now to display an image you can do either save it such as:
image.save(f"astronaut_rides_horse.png")
我們看一下輸出:
每次你運作上面的代碼,你都會得到不同的圖像。 要每次都獲得相同的結果,可以将随機種子傳遞給管道,如下面的代碼所示:
import torch
generator = torch.Generator("cuda").manual_seed(1024)
image = pipe(prompt, generator=generator).images[0]
image
還可以使用 num_inference_steps 參數更改推理步驟的數量。 一般來說,推理步驟越多,生成的圖像品質越高,但生成結果的時間也會越長。 如果想要更快的結果,你可以使用更少的步驟。
以下單元格使用與之前相同的種子,但步驟更少:
import torch
generator = torch.Generator("cuda").manual_seed(1024)
image = pipe(prompt, num_inference_steps=15, generator=generator).images[0]
image
請注意一些細節,例如馬的頭部或頭盔,比上一張圖像中的定義更少:
管道調用中的另一個參數是指導比例。 這是一種提高對條件信号的依從性的方法,在這種情況下,條件信号是文本以及整體樣本品質。
簡單來說,無分類器指導迫使生成更好地比對提示。 像 7 或 8.5 這樣的數字給出了很好的結果。 如果您使用非常大的數字,圖像可能看起來不錯,但多樣性會降低。
要為同一個提示生成多個圖像,我們隻需使用一個包含重複多次相同提示的清單。 我們會将清單而不是我們之前使用的字元串發送到管道。
讓我們首先編寫一個輔助函數來顯示圖像網格。 隻需運作以下單元格即可建立 image_grid 函數:
from PIL import Image
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
現在,我們可以在運作帶有 3 個提示清單的管道後生成網格圖像。
num_images = 3
prompt = ["a photograph of an astronaut riding a horse"] * num_images
images = pipe(prompt).images
grid = image_grid(images, rows=1, cols=3)
grid
結果如下:
我們還可以生成 n*m 圖像的網格:
num_cols = 3
num_rows = 4
prompt = ["a photograph of an astronaut riding a horse"] * num_cols
all_images = []
for i in range(num_rows):
images = pipe(prompt).images
all_images.extend(images)
grid = image_grid(all_images, rows=num_rows, cols=num_cols)
grid
結果如下:
穩定擴散生成的圖像的預設大小為 512*512 像素。 但是,使用高度和寬度參數更改生成圖像的高度和寬度非常容易。 以下是選擇合适圖像尺寸的一些提示:
- 将高度和寬度參數都選擇為 8 的倍數。
- 在較低品質下将任何高度和寬度設定為小于 512。
- 将兩個方向設定為大于 512 将導緻列出全局一緻性并導緻準備圖像區域。
- 最好選擇的值是一個方向為 512,而另一個方向大于 512。
prompt = "a photograph of an astronaut riding a horse"
image = pipe(prompt, height=512, width=768).images[0]
image
結果如下:
4、用擴散器建立你自己的管道
最後,是時候使用擴散器建立自定義擴散管道了。 我們将示範如何将 Stable Diffusion 與不同的排程程式一起使用,即 Katherine Crowson 的 K-LMS 排程程式。
讓我們逐漸了解 StableDiffusionPipeline,看看我們如何自己編寫它。 我們将從加載涉及的各個模型開始:
import torch
torch_device = "cuda" if torch.cuda.is_available() else "cpu"
預訓練擴散模型包括建立完整擴散管道所需的所有元件。 它們存儲在以下檔案夾中:
- text_encoder:Stable Diffusion使用CLIP,但其他diffusion模型可能使用其他編碼器如BERT。
- tokenizer:它必須與 text_encoder 模型使用的比對。
- scheduler:用于在訓練期間逐漸向圖像添加噪聲的排程算法。
- U-Net:用于生成輸入的潛在表示的模型。
- VAE:變分自編碼器子產品,我們将使用它來将潛在表示解碼為真實圖像。
我們可以通過引用儲存元件的檔案夾來加載元件,使用 from_pretrained 的子檔案夾參數。
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMScheduler
# 1. Load the autoencoder model which will be used to decode the latents into image space.
vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae")
# 2. Load the tokenizer and text encoder to tokenize and encode the text.
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")
# 3. The UNet model for generating the latents.
unet = UNet2DConditionModel.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="unet")
現在我們不加載預定義的排程程式,而是加載 K-LMS 排程程式。
from diffusers import LMSDiscreteScheduler
scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", num_train_timesteps=1000)
接下來,我們将模型移至 GPU。
vae = vae.to(torch_device)
text_encoder = text_encoder.to(torch_device)
unet = unet.to(torch_device)
我們現在定義将用于生成圖像的參數。 請注意,與前面的示例相比,我們設定 num_inference_steps = 100 以獲得更清晰的圖像。
prompt = ["a photograph of an astronaut riding a horse"]
height = 512 # default height of Stable Diffusion
width = 512 # default width of Stable Diffusion
num_inference_steps = 100 # Number of denoising steps
guidance_scale = 7.5 # Scale for classifier-free guidance
generator = torch.manual_seed(32) # Seed generator to create the inital latent noise
batch_size = 1
接下來,我們擷取提示的 text_embeddings。 這些嵌入将用于調節 U-Net 模型。
text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
with torch.no_grad():
text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
我們還将獲得用于無分類器指導的無條件文本嵌入,它們隻是填充标記(空文本)的嵌入。 它們需要與條件 text_embeddings(batch_size 和 seq_length)具有相同的形狀:
max_length = text_input.input_ids.shape[-1]
uncond_input = tokenizer(
[""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
)
with torch.no_grad():
uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
對于無分類器指導,我們需要進行兩次前向傳遞。 第一個是條件輸入 (text_embeddings),第二個是無條件嵌入 (uncond_embeddings)。 是以,我們會将兩者連接配接成一個批次,以避免進行兩次前向傳遞:
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
讓我們生成初始随機噪聲:
latents = torch.randn(
(batch_size, unet.in_channels, height // 8, width // 8),
generator=generator,
)
latents = latents.to(torch_device)
生成的 latent 的形狀是 64 * 64。之後模型會将這個 latent representation(純噪聲)轉換為 512 * 512 的圖像。
現在我們将使用標明的 num_inference_steps 初始化排程程式。 這将計算将在去噪過程中使用的西格瑪和确切的步長值:
scheduler.set_timesteps(num_inference_steps)
K-LMS 排程器需要将潛伏量乘以它的西格瑪值。 讓我們在這裡這樣做:
latents = latents * scheduler.init_noise_sigma
最後,我們現在準備編寫去噪循環:
from tqdm.auto import tqdm
from torch import autocast
for t in tqdm(scheduler.timesteps):
# expand the latents if we are doing classifier-free guidance to avoid doing two forward passes.
latent_model_input = torch.cat([latents] * 2)
latent_model_input = scheduler.scale_model_input(latent_model_input, t)
# predict the noise residual
with torch.no_grad():
noise_pred = 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 = scheduler.step(noise_pred, t, latents).prev_sample
我們現在可以使用 vae 将生成的潛在解碼回圖像:
# scale and decode the image latents with vae
latents = 1 / 0.18215 * latents
with torch.no_grad():
image = vae.decode(latents).sample
最後,讓我們将圖像轉換為 PIL,以便我們可以顯示或儲存它。
image = (image / 2 + 0.5).clamp(0, 1)
image = image.detach().cpu().permute(0, 2, 3, 1).numpy()
images = (image * 255).round().astype("uint8")
pil_images = [Image.fromarray(image) for image in images]
pil_images[0]
原文連結:穩定擴散模型應用指南 — BimAnt