終於最後一天了…這份notbook讓我們把他看完。
其實這課程更重視原理,只是他想辦法用白話的方式講
所以要上這門課,請配合服用其他基礎課程,如果沒有的話,會有很多地方聽不懂或是上不下去。
廢話不多說,繼續把這份notebook看完。
Textual Inversion
這翻譯成文本反轉
_Diagram from the textual inversion blog post
當我們使用模型生成圖像時,通常會依賴於事先定義好的token嵌入,例如 cat 或 dog 這樣的詞,但是如果我們能夠基於特定的圖「學習」新的token嵌入呢?這就是文本反轉的核心思想。
具體流程大致如下:
從案例圖像開始:一開始需要有一組代表特定概念或主題的圖像。
將圖像輸入模型:將這些圖像輸入到一個預先訓練好的模型中。這個模型是一個能夠生成嵌入的模型。
生成新的嵌入:模型將基於輸入的示例圖像生成新的token嵌入。這些嵌入將捕捉到示例圖像中的特定概念或主題。
使用新的嵌入生成圖像:使用這些新學到的嵌入作為輸入,生成新的圖像或完成其他任務。
這種方法的優勢在於可以捕捉到更加複雜或細緻的概念,這些概念可能不在原始模型的詞彙表中,或者原始嵌入不能充分表達。透過文本反轉可以更靈活地探索和實驗,尋找或創建特定需求的嵌入。
在先前的筆記中,我們研究了如何使用特定的嵌入來影響Stable Diffusion模型生成的圖像。通常情況下,這些嵌入是從文本編碼器中得到的,並用於指導模型生成具有特定語義的圖像。
但現在我們手頭上有一個預先學習的特殊嵌入(birb-style),代表了某種特殊的繪畫風格。我們的目標是使用這個特殊的嵌入來替換原始提示中某個token的嵌入,然後觀察其對生成圖像的影響。
所以,主要的步驟是:
載入預先學習的特殊嵌入:
birb_embed = torch.load(r'C:\codespace\fastai\stable diffusion\learned_embeds.bin')
birb_embed.keys(), birb_embed['<birb-style>'].shape
使用這個嵌入替換原始句子中的某個token的嵌入:
prompt = 'A mouse in the style of puppy'
# Tokenize
text_input = tokenizer(prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
input_ids = text_input.input_ids.to(torch_device)
# Get token embeddings
token_embeddings = token_emb_layer(input_ids)
# The new embedding - our special birb word
replacement_token_embedding = birb_embed['<birb-style>'].to(torch_device)
# Insert this into the token embeddings
token_embeddings[0, torch.where(input_ids[0]==6829)] = replacement_token_embedding.to(torch_device)
們首先將提示句子tokenize。接著,從模型中取得這些tokens的嵌入。然後,我們選擇了birb-style這個特殊的嵌入來替換原始嵌入。在這個例子中,我們選擇替換'puppy'這個token的嵌入。
使用修改後的嵌入生成圖像:
# Combine with pos embs
input_embeddings = token_embeddings + position_embeddings
# Feed through to get final output embs
modified_output_embeddings = get_output_embeds(input_embeddings)
# And generate an image with this:
generate_with_embs(modified_output_embeds)
我們組合token嵌入和位置嵌入,形成模型的完整輸入。然後將這些組合後的嵌入輸入模型,得到輸出嵌入。最後就可以使用這些嵌入來生成圖像。
接下來我們來看Messing with Embeddings
在先前的筆記中,我們探索了如何通過替換單詞的token嵌入來影響模型生成的圖像。但除了這種替換之外,還有其他的方法可以嘗試。例如通過平均兩個不同提示的嵌入來創建一個「奇美拉」會怎麼樣呢?
我們使用奇美拉(chimera) 來描述我們通過組合兩個不同提示的嵌入來創建的新嵌入。
嵌入兩個提示:
text_input1 = tokenizer(["A mouse"], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
text_input2 = tokenizer(["A leopard"], padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
我們選擇了兩個提示:“A mouse”和“A leopard”。我們使用tokenizer將這兩個提示轉換為token格式。
獲取兩個提示的嵌入:
with torch.no_grad():
text_embeddings1 = text_encoder(text_input1.input_ids.to(torch_device))[0]
text_embeddings2 = text_encoder(text_input2.input_ids.to(torch_device))[0]
混合兩個嵌入:
mix_factor = 0.35
# 兩個提示嵌入的加權平均。
mixed_embeddings = (text_embeddings1*mix_factor + \
text_embeddings2*(1-mix_factor))
生成圖像:
generate_with_embs(mixed_embeddings)
接下來我們看UNET and CFG
剛剛我們深入探索了如何使用和操作模型的嵌入來生成圖像。現在要深入瞭解實際的擴散模型。這通常是一個UNET模型,它接受雜訊的潛在變量(x)並預測雜訊。我們使用一個條件模型,它還接受時間步長(t)和我們的文本嵌入(又稱為 encoder_hidden_states)作為條件。將所有這些都輸入到模型中如下所示:
noise_pred = unet(latents, t, encoder_hidden_states=text_embeddings)["sample"]
這看起來可能有點複雜
條件: 我們的模型是條件的,這意味著它不僅僅基於輸入的雜訊潛在變量來做出預測,還會考慮其他資訊。在這裡,這些資訊是時間步長(t)和文本嵌入。
接下來,讓我們看一下如何實際使用這個模型:
預備調度器:
set_timesteps(scheduler, num_inference_steps)
調度器確定在推論過程中的具體時間步長。
設定時間步長和雜訊值:
t = scheduler.timesteps[0]
sigma = scheduler.sigmas[0]
創建一個雜訊潛在變量:
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
這是模型的初始狀態,它是一個隨機noise。
獲取文本嵌入:
text_input = tokenizer(['A macaw'], 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]
通過UNET預測雜訊殘差:
with torch.no_grad():
noise_pred = unet(latents, t, encoder_hidden_states=text_embeddings)["sample"]
這是核心步驟,我們將雜訊潛在變量、時間步長和文本嵌入輸入到UNET中,得到雜訊的預測。
最後可以看到,無論我們的輸入的形狀是什麼,UNET都會返回相同形狀的輸出。這是因為它預測每個潛在變量的雜訊,所以輸出的形狀與輸入相同。