HuggingFace Diffusers 0.12 : ノートブック : Stable Diffusion 🎨 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 02/22/2023 (v0.12.1)
* 本ページは、HuggingFace Diffusers の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Web: www.classcat.com ; ClassCatJP
HuggingFace Diffusers 0.12 : ノートブック : Stable Diffusion 🎨
Stable Diffusion は CompVis, Stability AI と LAION の研究者と技術者により作成されたテキスト-to-画像変換の潜在的拡散モデルです。それは LAION-5B データベースのサブセットの 512×512 画像で訓練されています。このモデルはモデルをテキスト・プロンプト上で条件付けるために凍結された CLIP ViT-L/14 テキストエンコーダを使用します。860M UNet と 123M テキストエンコーダを持つ、このモデルは比較的軽量で多くのコンシューマ向け GPU 上で動作します。詳細は モデルカード を見てください。
この Colab ノートブックは Stable Diffusion を 🤗 Hugging Face 🧨 Diffusers ライブラリ で使用する方法を紹介します。
Let’s get started!
1. StableDiffusionPipeline の使用方法
Stable Diffusion がどのように機能するかの理論的な側面に深入りする前に、let’s try it out a bit 🤗
このセクションでは、テキスト-to-画像変換の推論を数行のコードだけで実行できる方法を紹介します。
セットアップ
最初に、このノートブックを実行するために GPU ランタイムを使用していることを確認してください、そうすれば推論は遥かに高速です。次のコマンドが失敗した場合、上の「ランタイム」メニューを使用して「ランタイムのタイプを変更」を選択してください。
!nvidia-smi
Fri Dec 9 16:32:59 2022 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |===============================+======================+======================| | 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 | | N/A 72C P0 30W / 70W | 0MiB / 15109MiB | 0% Default | | | | N/A | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=============================================================================| | No running processes found | +-----------------------------------------------------------------------------+
Next, you should install diffusers as well scipy, ftfy and transformers. accelerate は遥かに高速なロードを実現するために使用されます。
!pip install diffusers==0.11.1
!pip install transformers scipy ftfy accelerate
Stable Diffusion パイプライン
StableDiffusionPipeline は end-to-end な推論パイプラインで、数行のコードだけでテキストから画像を生成するために使用できます。
最初に、モデルの総てのコンポーネントの事前訓練済み重みをロードします。このノートブックでは Stable Diffusion version 1.4 (CompVis/stable-diffusion-v1-4) を使用しますが、貴方が試すことのできる他のバリアントもあります :
- runwayml/stable-diffusion-v1-5
- stabilityai/stable-diffusion-2-1-base
- stabilityai/stable-diffusion-2-1 このバージョンは、他が 512×512 で動作する一方で、768×768 の解像度を持つ画像を生成できます。
モデル id CompVis/stable-diffusion-v1-4 に加えて、特定のリビジョン と torch_dtype を from_pretrained メソッドに渡しています。
私たちは総てのフリーな Google Colab が Stable Diffusion を実行できることを保証したいので、重みを半精度ブランチ fp16 からロードしていて、そして torch_dtype=torch.float16 を渡して diffusers に float16 精度を想定することも知らせます。
最高の可能な精度を確実にしたいのであれば、高いメモリ使用量と引き換えに torch_dtype=torch.float16 を削除することを確実にしてください。
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", torch_dtype=torch.float16)
Next, let’s move the pipeline to GPU to have faster inference.
pipe = pipe.to("cuda")
And we are ready to generate images:
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 either save it such as:
image.save(f"astronaut_rides_horse.png")
# or if you're in a google colab you can directly display it with
image
(訳注: 以下の画像は訳者による実験結果)
上のセルを複数回実行すると毎回異なる画像を与えます。決定論的な出力を望むのであればパイプラインにランダムシードを渡すことができます。同じシードを使用するたびに同じ画像結果を得ます。
import torch
generator = torch.Generator("cuda").manual_seed(1024)
image = pipe(prompt, generator=generator).images[0]
image
num_inference_steps 引数を使用して推論ステップ数を変更できます。一般に、使用するステップが多いほど、結果は良くなります。最新のモデルの一つである、Stable Diffusion は比較的小さいステップ数で素晴らしく動作しますので、デフォルトの 50 を使用することを勧めます。より速い結果を望む場合、小さい数を使用できます。
次のセルは前と同じシードを使用していますが、より少ないステップを使用しています。馬の頭やヘルメットのように、幾つかの細部が前の画像よりもリアルでなく上手く輪郭が示されていないことに注意してください :
import torch
generator = torch.Generator("cuda").manual_seed(1024)
image = pipe(prompt, num_inference_steps=15, generator=generator).images[0]
image
パイプライン呼び出しの他のパラメータは guidance_scale です。それは、この場合テキストである条件シグナルと全体的なサンプル品質への忠実性 (= adherence) を増加する方法です。簡単に言えば、分類器フリーなガイダンスはプロンプトにより良く適合するように生成を強制します。7 や 8.5 のような数値は良い結果を与えますが、非常に大きな数字を使用する場合、画像は良く見えるかもしれませんが、多様性は少なくなります。
このパラメータの技術的詳細についてはこのノートブックの 最後のセクション で学習できます。
同じプロンプトに対して複数の画像を生成するためには、何回か繰り返される同じプロンプトを持つリストを単純に使用します。前に使用した文字列の代わりにリストをパイプラインに送ります。
最初に画像のグリッドを表示するヘルパー関数を書きましょう。image_grid 関数を作成するには次のセルを実行するだけです, or disclose the code if you are interested in how it’s done.
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
And here’s how to generate a grid of n × m images.
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
非正方形画像の生成
Stable Diffusion はデフォルトでは 512 × 512 ピクセルの画像を生成します。 しかし height と width 引数を使用してデフォルトをオーバーライドすることは非常に簡単ですので、ポートレートやランドスケープの比率で矩形画像を作成できます。
これらは良い画像サイズを選択するための幾つかの推奨です :
- height と width は両者とも 8 の倍数であることを確認してください。
- 512 より下になると低い品質の画像という結果になるかもしれません。
- 両方の方向で 512 を越えると画像領域を繰り返します (大域的な一貫性が失われます)。
- 非正方形画像を作成するベストな方法は 1 つの次元で 512 を、そして他の次元ではそれよりも大きい値を使用することです。
prompt = "a photograph of an astronaut riding a horse"
image = pipe(prompt, height=512, width=768).images[0]
image
2. Stable Diffusion とは何か
それでは、Stable Diffusion の理論的なパートに進みます 👩🎓。
Stable Diffusion は High-Resolution Image Synthesis with Latent Diffusion Models で提案された、Latent Diffusion (潜在的拡散) と呼ばれる拡散モデルの特定のタイプに基づいています。
一般的な拡散モデルは、画像のような関心のあるサンプルに到達するために、ランダムなガウスノイズをステップ毎にノイズ除去するように訓練される機械学習システムです。それらがどのように動作するかの詳細な概要については、この colab を調べてください。
拡散モデルは画像データの生成に対して最先端の結果を獲得できることを示しました。しかし拡散モデルの一つの欠点は逆のノイズ除去の過程が遅いことです。加えて、これらのモデルはピクセル空間で作用しますので多くのメモリを消費し、これは高解像度な画像を生成するとき理不尽に高価になります。そのため、これらのモデルを訓練したり推論に使用することは困難です。
Latent diffusion は、拡散過程を (実際のピクセル空間を使用する代わりに) 低次元の潜在的空間に対して適用することにより、メモリと計算の複雑さを軽減することができます。これが標準的な拡散モデルと潜在的拡散モデルの主な違いです : 潜在的拡散では、モデルは画像の潜在的な (圧縮された) 表現を生成するために訓練されます。
潜在的拡散には 3 つの主要なコンポーネントがあります。
- オートエンコーダ (VAE)
- U-Net
- テキストエンコーダ, e.g. CLIP のテキストエンコーダ
1. オートエンコーダ (VAE)
VAE モデルは 2 つのパート、エンコーダとデコーダを持ちます。エンコーダは画像を低次元潜在的表現に変換するために使用され、これは U-Net モデルへの入力として機能します。逆に、デコーダは潜在的表現を画像に変換し戻します。
潜在的拡散の訓練の間、エンコーダは (各ステップで徐々に多くなるノイズを適用する) 順方向の拡散過程に対して画像の潜在的表現 (latents) を得るために使用されます。推論の間、逆の拡散過程により生成された、ノイズ除去された latents は VAE デコーダを使用して画像に変換し戻されます。これから分かるように、推論の間は VAE デコーダだけを必要とします。
2. U-Net
U-Net はエンコーダ部とデコーダ部を持ち、両者とも ResNet ブロックから構成されます。エンコーダは画像表現を低次元な画像表現に圧縮して、デコーダは低解像度な画像表現を (おそらくはノイズが少ない) 元のより高解像度な画像表現にデコードし戻します。より具体的には、U-Net 出力はノイズ残差を予測します、これは予測されたノイズ除去された画像表現を計算するために使用できます。
ダウンサンプリングの間に重要な情報を失うことを防ぐために、エンコーダのダウンサンプリング ResNet とデコーダのアップサンプリング ResNet 間にショートカット接続が通常は追加されます。更に、stable diffusion U-Net は cross-attention 層を通してその出力をテキスト埋め込み上で条件付けることができます。cross-attention 層は通常は ResNet ブロック間に U-Net のエンコーダ部とデコーダ部の両方に追加されます。
3. テキストエンコーダ
テキストエンコーダは例えば “An astronout riding a horse” のような入力プロンプトを U-Net により理解可能な埋め込み空間内に変換することを担当します。それは通常は、入力トークンのシークエンスを潜在的なテキスト埋め込みのシークエンスにマップする、単純な transformer ベースのエンコーダです。
Imagen にインスパイアされて、Stable Diffusion は訓練の間にはテキストエンコーダを 訓練しないで、単純に CLIP の既に訓練済みのテキストエンコーダ, CLIPTextModel を利用します。
潜在的拡散は何故高速で効率的か?
潜在的拡散モデルの U-Net は低次元空間で作用するので、ピクセル空間の拡散モデルと比べてそれはメモリと計算要件を大幅に削減します。例えば、Stable Diffusion で使用されるオートエンコーダは 8 の縮小因子 (= reduction factor) を持ちます。これは、shape (3, 512, 512) の画像が潜在的空間では (3, 64, 64) になることを意味し、8 × 8 = 64 倍少ないメモリを必要とするだけです。
これが 512 × 512 画像を素早く生成できる理由です、16GB Colab GPU でさえも!
推論時の Stable Diffusion
すべてを一つにまとめて、論理的なフローを図示してモデルが推論でどのように動作するか詳しく見ましょう。
stable diffusion モデルは入力として潜在的シードとテキストプロンプトを受け取ります。そして潜在的シードはサイズ $64 \times 64$ のランダムな潜在的画像表現を生成するために使用され、一方でテキストプロンプトは CLIP のテキストエンコーダを使用してサイズ $77 \times 768$ のテキスト埋め込みに変換されます。
次に U-Net は、テキスト埋め込みに条件付けされながら、ランダムな潜在画像表現を反復的にノイズ除去します。ノイズ差分である、U-Net の出力はスケジューラ・アルゴリズムを通してノイズ除去された潜在的画像表現を計算するために使用されます。多くの様々なスケジューラ・アルゴリズムがこの計算のために使用できますが、それぞれに良い点と悪い点があります。Stable Diffusion については、以下の一つの使用を勧めます :
- PNDM スケジューラ (used by default)
- K-LMS スケジューラ
- Heun 離散スケジューラ
- DPM Solver Multistep スケジューラ。このスケジューラはより少ないステップで素晴らしい品質を実現できます。You can try with 25 instead of the default 50!
スケジューラ・アルゴリズムがどのように機能するかの理論はこのノートブックの範囲外ですが、手短に言えば、それらは前のノイズ表現と予測されたノイズ差分から予測されたノイズ除去された画像表現を計算することを覚えておくべきです。詳細は、Elucidating the Design Space of Diffusion-Based Generative Models を調べることを勧めます。
ノイズ除去プロセスはより良い潜在的画像表現を段階的に取得するためにおよそ 50 回繰り返されます。完了すれば、潜在的画像表現は変分オートエンコーダのデコーダ部によりデコードされます。
Latent and Stable Diffusion へのこの簡潔なイントロダクションの後は、🤗 Hugging Face Diffusers をどのように高度に利用するかを見ましょう!
3. diffusers で独自の推論パイプラインを書く方法
最後に、diffusers でカスタム diffusers パイプラインを作成できる方法を示します。これは、システムのある機能に少し深入りしたり特定のコンポーネントに潜在的に切り替えるために、非常に有用な場合があります。
このセクションでは、Stable Diffusion を異なるスケジューラ、つまり (この PR で追加された) Katherine Crowson の K-LMS スケジューラで使用する方法を実演します。
StableDiffusionPipeline をそれを私たち自身でどのように書けたかを見るために段階的に進んでいきましょう。
関連する個々のモデルをロードすることから始めます。
import torch
torch_device = "cuda" if torch.cuda.is_available() else "cpu"
事前訓練済みモデル は完全な diffusion パイプラインをセットアップするために必要な総てのコンポーネントを含みます。それらは以下のフォルダにストアされています :
- text_encoder : Stable Diffusion は CLIP を使用します が、他の拡散モデルは BERT のような他のエンコーダを使用するかもしれません。
- tokenizer : text_encoder モデルにより使用されたものと一致していなければなりません。
- scheduler : 訓練の間に画像にノイズを徐々に追加するために使用されるスケジューリング・アルゴリズムです。
- unet : 入力の潜在的表現を生成するために使用されるモデル。
- vae : 潜在的表現をリアル画像にデコードするために使用するオートエンコーダ・モジュール。
from_pretrained への subfolder 引数を使用し、それらがセーブされているフォルダを参照してコンポーネントをロードできます。
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.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="scheduler")
Next we move the models to the GPU.
vae = vae.to(torch_device)
text_encoder = text_encoder.to(torch_device)
unet = unet.to(torch_device)
次に画像を生成するために使用するパラメータを定義します。
guidance_scale は Imagen 論文 の式 (2) のガイダンス重み w への類推として定義されていることに注意してください。guidance_scale == 1 は分類器フリーなガイダンスを行わないことに対応します。ここではそれを前に行われたように 7.5 に設定します。
前のサンプルとは違い、より明瞭になった画像を得るために 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 を取得します。これらの埋め込みは UNet モデルを条件付けるために使用されます。
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) と同じ shape を持つ必要があります。
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]
分類器フリーなガイダンスに対して、2 つのフォワードパスを行なう必要があります。一つは条件付き入力 (text_embeddings) により、もう一つは条件なし埋め込み (uncond_embeddings) によります。実際には、2 つのフォワードパスを実行することを避けるために両者を単一のバッチに連結することができます。
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)
latents.shape
torch.Size([1, 4, 64, 64])
Cool $64 \times 64$ is expected. モデルはこの潜在的表現 (純粋なノイズ) を後で 512 × 512 画像に変換します。
次に、スケジューラを選択された num_inference_steps で初期化します。これはノイズ除去過程の間に使用される sigmas と正確な時間ステップの値を計算します。
scheduler.set_timesteps(num_inference_steps)
K-LMS スケジューラは latents をその sigma 値で乗算する必要があります。これをここで行いましょう。
latents = latents * scheduler.init_noise_sigma
We are ready to write the denoising loop.
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
そして生成された latents を画像にデコードし戻すために 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]
Now you have all the pieces to build your own pipelines or use diffusers components as you like 🔥.
以上