上一講,我們一起體驗(yàn)了 CLIP 這個(gè)多模態(tài)的模型。在這個(gè)模型里,我們已經(jīng)能夠把一段文本和對(duì)應(yīng)的圖片關(guān)聯(lián)起來了??吹轿谋竞蛨D片的關(guān)聯(lián),想必你也能聯(lián)想到過去半年非?;馃岬摹拔纳鷪D”(Text-To-Image)的應(yīng)用浪潮了。相比于在大語言模型里 OpenAI 的一枝獨(dú)秀。文生圖領(lǐng)域就屬于百花齊放了,OpenAI 陸續(xù)發(fā)表了 DALL-E 和 DALL-E 2,Google 也不甘示弱地發(fā)表了 Imagen,而市場(chǎng)上實(shí)際被用得最多、反饋?zhàn)詈玫挠脩舳水a(chǎn)品是 Midjourney。
不過,在整個(gè)技術(shù)社區(qū)里,最流行的產(chǎn)品則是 Stable Diffusion。因?yàn)樗且粋€(gè)完全開源的產(chǎn)品,我們不僅可以調(diào)用 Stable Diffusion 內(nèi)置的模型來生成圖片,還能夠下載社區(qū)里其他人訓(xùn)練好的模型來生成圖片。我們不僅可以通過文本來生成圖片,還能通過圖片來生成圖片,通過文本來編輯圖片。
那么今天這一講,我們就來看看如何使用 Stable Diffusion,做到上面這些事情。
使用 Stable Diffusion 生成圖片
文生圖
可能你還沒怎么體驗(yàn)過文生圖的應(yīng)用,那我們先用幾行最簡(jiǎn)單的代碼體驗(yàn)一下。在這一講里,我建議一定要用 Colab 或者其他的 GPU 環(huán)境,因?yàn)橛?CPU 來執(zhí)行的話,速度會(huì)慢到讓人無法接受。
安裝依賴包:
%pip install diffusers accelerate transformers
代碼:
from diffusers import DiffusionPipeline
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
pipeline.to("cuda")
image = pipeline("a photograph of an astronaut riding a horse").images[0]
image
輸出結(jié)果:
代碼非常簡(jiǎn)單,只有寥寥幾行。這里,我們使用了 Huggingface 的 Diffusers 庫(kù),通過 DiffusionPipeline 加載了 RunwayML 的 stable-diffusion-v1-5 的模型。然后,指定了這個(gè) Pipeline 使用 CUDA 也就是利用 GPU 來進(jìn)行計(jì)算。最后向這個(gè) Pipeline 輸入了一段文本,通過這段文本我們就生成了一張圖片。
這里,我們畫的是在 Stable Diffusion 里非常經(jīng)典的一張“宇航員在太空騎馬”的圖片。之所以畫這么一張圖片,是為了證明我們并不是通過“搜索”的方式找到一張已經(jīng)存在的圖片。比如,上一講里我們介紹過 CLIP 模型,其實(shí)就可以完成從文本到圖片的搜索功能。而 Stable Diffusion,是真的讓 AI“畫”出來一張新的圖片。畢竟,以前宇航員也從來沒有在太空騎過馬,也不可能有人拍下過這樣的照片。
Stable Diffusion 的基本原理
Stable Diffusion 生成的圖片效果的確不錯(cuò),相信你也很好奇這個(gè)事情的原理是什么。其實(shí),Stable Diffusion 背后不是單獨(dú)的一個(gè)模型,而是由多個(gè)模型組合而成的。整個(gè) Stable Diffusion 文生圖的過程是由這樣三個(gè)核心模塊組成的。
第一個(gè)模塊是一個(gè) Text-Encoder,把我們輸入的文本變成一個(gè)向量。實(shí)際使用的就是我們上一講介紹的 CLIP 模型。因?yàn)?CLIP 模型學(xué)習(xí)的是文本和圖像之間的關(guān)系,所以得到的這個(gè)向量既理解了文本的含義,又能和圖片的信息關(guān)聯(lián)起來。
第二個(gè)是 Generation 模塊,顧名思義是一個(gè)圖片信息生成模塊。這里也有一個(gè)機(jī)器學(xué)習(xí)模型,叫做 UNet,還有一個(gè)調(diào)度器(Scheduler),用來一步步地去除噪聲。這個(gè)模塊的工作流程是先往前面的用 CLIP 模型推理出來的向量里添加很多噪聲,再通過 UNet+Scheduler 逐漸去除噪聲,最后拿到了一個(gè)新的張量。這個(gè)張量可以認(rèn)為是一個(gè)尺寸上縮小了的圖片信息向量,里面隱含了我們要生成的圖片信息。
最后一個(gè)模塊,則是 Decoder 或者叫做解碼器。背后也是一個(gè)機(jī)器學(xué)習(xí)的模型,叫做 VAE。它會(huì)根據(jù)第二步的返回結(jié)果把這個(gè)圖像信息還原成最終的圖片。
這個(gè)過程,可以結(jié)合 Stable Diffusion 相關(guān)論文里的一張模型架構(gòu)圖來看。
這樣聽起來可能有點(diǎn)太理論了,那我們還是看看具體的代碼和圖片生成的過程吧,這樣就比較容易理解圖片是怎么生成的了。
我們先把 DiffusionPipeline 打印出來,看看它內(nèi)部是由哪些部分組成的。
pipeline
輸出結(jié)果:
StableDiffusionPipeline {
"_class_name": "StableDiffusionPipeline",
"_diffusers_version": "0.15.1",
"feature_extractor": [
"transformers",
"CLIPFeatureExtractor"
],
"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"
]
}
這個(gè)對(duì)象里面有 3 部分。
1. Tokenizer 和 Text_Encoder,就是我們上面說的把文本變成向量的 Text Encoder??梢钥吹轿覀冞@里用的模型就是上一講的 CLIP 模型。
2. UNet 和 Scheduler,就是對(duì)文本向量以及輸入的噪聲進(jìn)行噪聲去除的組件,也就是 Generation 模塊。這里用的是 UNet2DConditionModel 模型,還把 PNDMScheduler 用作了去除噪聲的調(diào)度器。
3. VAE,也就是解碼器(Decoder),這里用的是 AutoencoderKL,它會(huì)根據(jù)上面生成的圖片信息最后還原出一張高分辨率的圖片。
剩下的 feature_extractor,可以用來提取圖像特征,如果我們不想文生圖,想要圖生圖,它就會(huì)被用來把我們輸入的圖片的特征提取成為向量。而 safety_checker 則是用來檢查生成內(nèi)容,避免生成具有冒犯性的圖片。
接下來,我們就自己來組合一下這些模型,來把整個(gè)圖片生成的過程給演示出來。首先,我們把上面 Stable Diffusion 1.5 需要的模型組件都加載出來。
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMScheduler
vae = AutoencoderKL.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="vae")
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")
unet = UNet2DConditionModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="unet")
scheduler = PNDMScheduler.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="scheduler")
torch_device = "cuda"
vae.to(torch_device)
text_encoder.to(torch_device)
unet.to(torch_device)
注意,對(duì)應(yīng)的 CLIPTokenizer 和 CLIPTextModel 的名字并不是 stable-diffusion-v1-5,如果使用 Diffusers 庫(kù)的 Pipeline 的話,可以從模型里面對(duì)應(yīng)模塊的 config.json 讀取到它們。
然后,我們把接下來生成圖片的參數(shù)初始化一下,包括文本、對(duì)應(yīng)的圖片分辨率,以及一系列模型中需要使用的超參數(shù)。
import torch
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 = 25 # Number of denoising steps
guidance_scale = 7.5 # Scale for classifier-free guidance
generator = torch.manual_seed(42) # Seed generator to create the inital latent noise
batch_size = len(prompt)
然后,我們把對(duì)應(yīng)的輸入文本變成一個(gè)向量,然后再根據(jù)一個(gè)空字符串生成一個(gè)“無條件”的向量,最后把兩個(gè)向量拼接在一起。我們實(shí)際生成圖片的過程,就是逐漸從這個(gè)無條件的向量向輸入文本表示的向量靠攏的過程。
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]
max_length = text_input.input_ids.shape[-1]
uncond_input = tokenizer([""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt")
uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
然后,我們可以先生成一系列隨機(jī)噪聲。
latents = torch.randn(
(batch_size, unet.in_channels, height // 8, width // 8),
generator=generator,
)
latents = latents.to(torch_device)
latents = latents * scheduler.init_noise_sigma
接下來就是生成圖片的代碼了,我們先定義兩個(gè)函數(shù),它們會(huì)分別顯示 Generation 模塊生成出來的圖片信息,以及 Decoder 模塊還原出來的最終圖片。
import PIL
import torch
import numpy as np
from PIL import Image
from IPython.display import display
def display_denoised_sample(sample, i):
image_processed = sample.cpu().permute(0, 2, 3, 1)
image_processed = (image_processed + 1.0) * 127.5
image_processed = image_processed.numpy().astype(np.uint8)
image_pil = PIL.Image.fromarray(image_processed[0])
display(f"Denoised Sample @ Step {i}")
display(image_pil)
return image_pil
def display_decoded_image(latents, i):
# scale and decode the image latents with vae
latents = 1 / 0.18215 * latents
with torch.no_grad():
image = vae.decode(latents).sample
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]
display(f"Decoded Image @ step {i}")
display(pil_images[0])
return pil_images[0]
最后,我們通過 Diffusion 算法一步一步來生成圖片就好了。我們根據(jù)前面指定的參數(shù),循環(huán)了 25 步,每一步都通過 Scheduler 和 UNet 來進(jìn)行圖片去噪聲的操作。并且每 5 步都把對(duì)應(yīng)去噪后的圖片信息,以及解碼后還原的圖片顯示出來。
from tqdm.auto import tqdm
scheduler.set_timesteps(num_inference_steps)
denoised_images = []
decoded_images = []
for i, t in enumerate(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, timestep=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
if i % 5 == 0:
denoised_image = display_denoised_sample(latents, i)
decoded_image = display_decoded_image(latents, i)
denoised_images.append(denoised_image)
decoded_images.append(decoded_image)
輸出結(jié)果:
Denoised Sample @ Step 0
Decoded Image @ step 0
Denoised Sample @ Step 5
Decoded Image @ step 5
Denoised Sample @ Step 10
Decoded Image @ step 10
Denoised Sample @ Step 15
Decoded Image @ step 15
Denoised Sample @ Step 20
Decoded Image @ step 20
Denoised Sample @ Step 25
Decoded Image @ step 25
運(yùn)行完程序,你就可以看到我們的圖片是如何一步步從完全的噪點(diǎn)還原成一張圖片的了。而且你仔細(xì)觀察,還可以看到 Generation 生成的圖像信息,類似于 Decoder 還原出來的圖像信息的輪廓。這是因?yàn)?U-Net 其實(shí)是一個(gè)圖片語義分割的模型。
而如果我們打印一下生成的圖片的維度,你也可以看到,Generation 生成的圖像信息分辨率只有 64x64,而我們還原出來的圖片分辨率是 512x512。
print(latents.shape)
latents = 1 / 0.18215 * latents
with torch.no_grad():
image = vae.decode(latents).sample
print(image.shape)
輸出結(jié)果:
torch.Size([1, 4, 64, 64])
torch.Size([1, 3, 512, 512])
圖生圖
相信你已經(jīng)理解了這個(gè) Stable Diffusion 生成圖片的過程,以及過程里每個(gè)模塊的工作了。那你應(yīng)該比較容易理解如何通過 Stable Diffusion 實(shí)現(xiàn)圖生圖了,我們下面就來具體看一看。
當(dāng)然,這一次我們就不用自己一步步調(diào)用各個(gè)模塊來實(shí)現(xiàn)圖生圖了。我們可以直接使用 Diffusers 庫(kù)里自帶的 Pipeline。
import torch
from PIL import Image
from io import BytesIO
from diffusers import StableDiffusionImg2ImgPipeline
device = "cuda"
model_id_or_path = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id_or_path, torch_dtype=torch.float16)
pipe = pipe.to(device)
image_file = "./data/sketch-mountains-input.jpg"
init_image = Image.open(image_file).convert("RGB")
init_image = init_image.resize((768, 512))
prompt = "A fantasy landscape, trending on artstation"
images = pipe(prompt=prompt, image=init_image, strength=0.75, guidance_scale=7.5).images
display(init_image)
display(images[0])
輸出結(jié)果:
對(duì)應(yīng)的代碼也非常簡(jiǎn)單,我們把 Pipeline 換成了 StableDiffusionImg2ImgPipeline,此外除了輸入一段文本之外,我們還提供了一張草稿圖。然后,你可以看到對(duì)應(yīng)生成的圖片的輪廓,就類似于我們提供的草稿圖。而圖片的內(nèi)容風(fēng)格,則是按照我們文本提示語的內(nèi)容生成的。
StableDiffusionImg2ImgPipeline 的生成過程,其實(shí)和我們之前拆解的一步步生成圖片的過程是相同的。唯一的一個(gè)區(qū)別是,我們其實(shí)不是從一個(gè)完全隨機(jī)的噪聲開始的,而是把對(duì)應(yīng)的草稿圖,通過 VAE 的編碼器,變成圖像生成信息,又在上面加了隨機(jī)的噪聲。所以,去除噪音的過程中,對(duì)應(yīng)的草稿圖的輪廓就會(huì)逐步出現(xiàn)了。而在一步步生成圖片的過程中,內(nèi)容又會(huì)向我們給出的提示語的內(nèi)容來學(xué)習(xí)。
而如果我們換一下提示語,就能更改生成的具體內(nèi)容。比如我們想換成宮崎駿的風(fēng)格,并且希望后面高聳的不是山,而是城堡,出現(xiàn)的圖片還是相同的輪廓,但是用不同的內(nèi)容。在下面給出了一個(gè)代碼示例,可以自己看一看。
prompt = "ghibli style, a fantasy landscape with castles"
images = pipe(prompt=prompt, image=init_image, strength=0.75, guidance_scale=7.5).images
display(init_image)
display(images[0])
輸出結(jié)果:
更多使用方法
理解了 Stable Diffusion 的基本框架,你可以試一試更多相關(guān)的 Pipeline 的用法。比如,除了引導(dǎo)內(nèi)容生成的提示語,我們還可以設(shè)置一個(gè)負(fù)面的提示語(negative prompt),也就是排除一些內(nèi)容。
prompt = "ghibli style, a fantasy landscape with castles"
negative_prompt = "river"
images = pipe(prompt=prompt, negative_prompt=negative_prompt, image=init_image, strength=0.75, guidance_scale=7.5).images
display(images[0])
輸出結(jié)果:
可以看到,我們希望在圖片里面盡量排除“River”。而新生成的圖片,右邊就沒有了任何類似于河流的內(nèi)容,而中間藍(lán)色的部分也更像一個(gè)排水渠而不是自然的河流。負(fù)面提示語并不會(huì)改變模型的結(jié)構(gòu)。它其實(shí)就是把原先的“無條件”向量,替換成了負(fù)面提示語的向量。這樣,模型就盡可能從負(fù)面的提示語文本內(nèi)容中向我們正面的提示語文本內(nèi)容學(xué)習(xí),也就是盡量遠(yuǎn)離負(fù)面提示語的內(nèi)容。
同樣,我們還可以通過 Stable Diffusion 來提升圖片的分辨率,只不過需要一個(gè)單獨(dú)的模型。這個(gè)模型就是專門在一個(gè)高低分辨率的圖片組合上訓(xùn)練出來的。對(duì)應(yīng)的 UNet 和 VAE 的模型是和原始的 Stable Diffusion 不一樣的。
from diffusers import StableDiffusionUpscalePipeline
# load model and scheduler
model_id = "stabilityai/stable-diffusion-x4-upscaler"
pipeline = StableDiffusionUpscalePipeline.from_pretrained(
model_id, revision="fp16", torch_dtype=torch.float16
)
pipeline = pipeline.to("cuda")
# let's download an image
low_res_img_file = "./data/low_res_cat.png"
low_res_img = Image.open(low_res_img_file).convert("RGB")
low_res_img = low_res_img.resize((128, 128))
prompt = "a white cat"
upscaled_image = pipeline(prompt=prompt, image=low_res_img).images[0]
low_res_img_resized = low_res_img.resize((512, 512))
display(low_res_img_resized)
display(upscaled_image)
輸出結(jié)果:
如果我們打印一下 pipeline,對(duì)應(yīng)的模型的組件還是相同的。
pipeline
輸出結(jié)果:
StableDiffusionUpscalePipeline {
"_class_name": "StableDiffusionUpscalePipeline",
"_diffusers_version": "0.15.1",
"low_res_scheduler": [
"diffusers",
"DDPMScheduler"
],
"max_noise_level": 350,
"scheduler": [
"diffusers",
"DDIMScheduler"
],
"text_encoder": [
"transformers",
"CLIPTextModel"
],
"tokenizer": [
"transformers",
"CLIPTokenizer"
],
"unet": [
"diffusers",
"UNet2DConditionModel"
],
"vae": [
"diffusers",
"AutoencoderKL"
]
}
但是如果去看對(duì)應(yīng)模型的配置文件,可以看到 VAE 和 UNet 里使用的模型都是不一樣的。
使用社區(qū)里的其他模型
在這個(gè)過程中,你可以看到 Stable Diffusion 并不是指某一個(gè)特定的模型,而是指一類模型結(jié)構(gòu)。因?yàn)?Stable Diffusion 是完全開源的,所以你大可以利用自己的數(shù)據(jù)去訓(xùn)練一個(gè)屬于自己的模型。事實(shí)上,市面上開源訓(xùn)練出來的 Stable Diffusion 的模型非常多,也已經(jīng)有了像 CIVITAI 這樣的分享 Stable Diffusion 模型的平臺(tái)。

我們可以去 CIVITAI 的網(wǎng)站,找到我們喜歡的模型。比如我們專門找一個(gè)二次元的模型 counterfeit-V2.5。在對(duì)應(yīng)的模型頁面,我們可以看到它直接就包含了 Huggingface 里面的模型。?
所以我們就可以直接通過 Diffuers 庫(kù)來調(diào)用這個(gè)模型。
pipeline.to("cuda")
prompt = "((masterpiece,best quality)),1girl, solo, animal ears, rabbit, barefoot, knees up, dress, sitting, rabbit ears, short sleeves, looking at viewer, grass, short hair, smile, white hair, puffy sleeves, outdoors, puffy short sleeves, bangs, on ground, full body, animal, white dress, sunlight, brown eyes, dappled sunlight, day, depth of field"
negative_prompt = "EasyNegative, extra fingers,fewer fingers,"
image = pipeline(prompt=prompt, negative_prompt=negative_prompt).images[0]
image
輸出結(jié)果:
當(dāng)然,不是所有 CIVITAI 里的模型都在 Huggingface 上提供了自己的模型版本。默認(rèn) CIVITAI 的模型,往往只是提供了一個(gè)模型權(quán)重文件。你可以使用現(xiàn)在最流行的 Stable-Diffusion-Web-UI 應(yīng)用來使用這個(gè)模型權(quán)重文件。你可以把 Web-UI 在本地部署起來,它會(huì)提供一個(gè)圖形界面讓你不用寫代碼就可以直接調(diào)整各種參數(shù)來生成圖片。

CIVITAI 的 Wiki里面也詳細(xì)提供了在 Stable-Diffusion-Web-UI 里面使用模型的步驟,可以照著這個(gè)步驟多拿幾個(gè)模型試試看。
小結(jié)
這一講,體驗(yàn)了一下 Stable Diffusion 這個(gè)圖片生成的開源模型。我們不僅通過 Diffusers 這個(gè)封裝好的 Python 庫(kù),體驗(yàn)了文生圖、圖生圖、提升圖片分辨率等一系列應(yīng)用,也深入到 Stable Diffusion 的模型內(nèi)部,理解了整個(gè)模型的結(jié)構(gòu),還看到我們是如何一步步從一張全是噪點(diǎn)的圖片,逐漸去除噪聲變成一張可用的圖片的。
在體驗(yàn)了基礎(chǔ)的模型之后,我們也一起嘗試了一下其他愛好者自己生成的模型。這也是下一講我們要介紹的重點(diǎn)內(nèi)容。我們會(huì)了解到如何通過 LoRa 這樣的算法進(jìn)行模型微調(diào),以及如何通過 ControlNet 讓我們生成的圖片更加可控。
思考題
最后,按照慣例還是留一道思考題。除了今天演示的這些應(yīng)用之外,HuggingFace 還提供了很多實(shí)戰(zhàn)場(chǎng)景。比如,就可以通過 StableDiffusionInpaintPipeline,用一個(gè)遮照?qǐng)D片和一段提示語來修改圖片畫面中的某一部分元素。
可以照著官方文檔,體驗(yàn)一下這個(gè)功能,研究一下源代碼,想想這個(gè)功能是如何通過 Stable Diffusion 的模型結(jié)構(gòu)實(shí)現(xiàn)的。文章來源:http://www.zghlxwxcb.cn/news/detail-828258.html
推薦閱讀
這一講里,我們只是簡(jiǎn)單介紹了一下 Stable Diffusion 的模型結(jié)構(gòu)。其實(shí),無論是 DALL-E 2 還是 Imagen,采用的圖片生成方式都是和 Stable Diffusion 類似的。如果想要深入了解一下這些模型的結(jié)構(gòu),可以去看一下 B 站里面“跟李沐學(xué) AI”里面對(duì)于 DALL-E 2 論文的講解。?文章來源地址http://www.zghlxwxcb.cn/news/detail-828258.html
到了這里,關(guān)于24|Stable Diffusion:最熱門的開源AI畫圖工具的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!