HuggingFace Diffusers 0.2 : Stable Diffusion (テキスト-to-画像変換)

HuggingFace Diffusers 0.2 : Stable Diffusion (テキスト-to-画像変換) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/01/2022 (v0.2.4)

* 本ページは、HuggingFace Diffusers の以下のドキュメントを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

クラスキャット 人工知能 研究開発支援サービス

クラスキャット は人工知能・テレワークに関する各種サービスを提供しています。お気軽にご相談ください :

◆ 人工知能とビジネスをテーマに WEB セミナーを定期的に開催しています。スケジュール
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

  • 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
  • sales-info@classcat.com  ;  Web: www.classcat.com  ;   ClassCatJP

 

 

HuggingFace Diffusers 0.2 : Stable Diffusion (テキスト-to-画像変換)

Stable Diffusion は CompVis, Stability AILAION の研究者と技術者により作成されたテキスト-to-画像の潜在拡散モデルです。それは LAION-5B データベースのサブセットの 512×512 画像で訓練されています。このモデルはモデルをテキスト・プロンプト上で条件付けるために凍結された CLIP ViT-L/14 テキストエンコーダを使用します。860M UNet と 123M テキストエンコーダを持つ、このモデルは比較的軽量で少なくとも 10GB VRAM を持つ 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
Mon Aug 22 16:29:29 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 V100-SXM2...  Off  | 00000000:00:04.0 Off |                    0 |
| N/A   39C    P0    29W / 300W |      0MiB / 16160MiB |      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==0.2.4 as well scipy, ftfy and transformers.

!pip install diffusers==0.2.4
!pip install transformers scipy ftfy
!pip install "ipywidgets>=7,<8"

Stable Diffusion 重みをダウンロードまたは使用する前に、モデルライセンスを認める必要もあります。
この記事ではモデルのバージョン v1-4 を使用しますので、、そのカード にアクセスし、ライセンスを読んで、同意するならばチェックボックスをチェックする必要があります。

貴方は 🤗 Hugging Face ハブで登録ユーザである必要があり、コードを動作させるにはアクセストークンを使用する必要があります。アクセストークンの詳細は、ドキュメントの このセクション を参照してください。

google colab は外部ウィジェットを無効にしていますので、それを明示的に有効にする必要があります。notebook_login を使用可能にするため次のセルを実行してください。

from google.colab import output
output.enable_custom_widget_manager()

Now you can login with your user token.

from huggingface_hub import notebook_login

notebook_login()
Login successful
Your token has been saved to /root/.huggingface/token
Authenticated through git-credential store but this isn't the helper defined on your machine.
You might have to re-authenticate when pushing to the Hugging Face Hub. Run the following command in your terminal in case you want to set this credential helper as the default

git config --global credential.helper store

 

Stable Diffusion パイプライン

StableDiffusionPipeline は end-to-end な推論パイプラインで、数行のコードだけでテキストから画像を生成するために使用できます。

最初に、モデルの総てのコンポーネントの事前訓練済み重みをロードします。

model id CompVis/stable-diffusion-v1-4 に加えて、特定のリビジョン, torch_dtype と use_auth_token を from_pretrained メソッドに渡しています。use_auth_token は貴方が実際にモデルのライセンスを認めたかを検証するために必要です。

私たちは総ての free Google Colab が Stable Diffusion を実行できることを保証したいので、重みを半精度 branch fp16 からロードし、torch_dtype=torch.float16 を渡して diffusers に float16 精度を想定することも知らせます。

最高の可能な精度を確実にしたいのであれば、高いメモリ使用量と引き換えに revision="fp16" と torch_dtype=torch.float16 を削除することを確かにしてください。

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, use_auth_token=True)  

Next, let's move the pipeline to GPU to have faster inference.

pipe = pipe.to("cuda")

autocast の使用は推論を高速に実行します、それは半精度を使用するからです。

from torch import autocast

prompt = "a photograph of an astronaut riding a horse"
with autocast("cuda"):
  image = pipe(prompt)["sample"][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")

# or if you're in a google colab you can directly display it with 
image

上のセルを複数回実行すると毎回異なる画像を与えます。決定論的な出力を望むのであればパイプラインにランダムシードを渡すことができます。同じシードを使用するたびに同じ画像結果を得ます。

import torch

generator = torch.Generator("cuda").manual_seed(1024)

with autocast("cuda"):
  image = pipe(prompt, generator=generator)["sample"][0]

image

num_inference_steps 引数を使用して推論ステップ数を変更できます。一般に、使用するステップが多いほど、結果は良くなります。最新のモデルの一つである、Stable Diffusion は比較的小さい数のステップで素晴らしく動作しますので、デフォルトの 50 を使用することを勧めます。より速い結果を望む場合、小さい数を使用できます。

次のセルは前と同じシードを使用していますが、より少ないステップによります。馬の頭やヘルメットのように、幾つかの細部が前の画像よりもリアルでなく上手く輪郭が示されていないことに注意してください :

import torch

generator = torch.Generator("cuda").manual_seed(1024)

with autocast("cuda"):
  image = pipe(prompt, num_inference_steps=15, generator=generator)["sample"][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

with autocast("cuda"):
  images = pipe(prompt)["sample"]

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):
  with autocast("cuda"):
    images = pipe(prompt)["sample"]
  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"
with autocast("cuda"):
  image = pipe(prompt, height=512, width=768)["sample"][0]
image

 

2. Stable Diffusion とは何か

それでは、Stable Diffusion の理論的なパートに進みます 👩‍🎓。

Stable Diffusion は High-Resolution Image Synthesis with Latent Diffusion Models で提案された、Latent Diffusion と呼ばれる拡散モデルの特定のタイプに基づいています。

一般的な拡散モデルは、画像のような関心のあるサンプルに到達するために、ランダムなガウスノイズをステップ毎にノイズ除去するために訓練される機械学習システムです。それらがどのように動作するかの詳細な概要については、この colab を調べてください。

拡散モデルは画像データの生成に対して最先端の結果を獲得することを示しました。しかし拡散モデルの一つの欠点は逆のノイズ除去のプロセスが遅いことです。加えて、これらのモデルはピクセル空間で作用しますので多くのメモリを消費し、これは高解像度の画像を生成するとき理不尽に高価になります。そのため、これらのモデルを訓練したり推論に使用することは困難です。

Latent diffusion は、拡散プロセスを (実際のピクセル空間を使用する代わりに) 低次元の潜在空間に対して適用することにより、メモリと計算の複雑さを軽減することができます。これが標準的な拡散と潜在拡散モデルの主な違いです : 潜在拡散では、モデルは画像の潜在的な (圧縮された) 表現を生成するために訓練されます。

潜在拡散には 3 つの主要なコンポーネントがあります。

 
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 を使用します。

 
latent diffusion は何故高速で効率的か?

latent diffusion モデルの 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×64 のランダムな潜在的画像表現を生成するために使用され、一方でテキストプロンプトは CLIP のテキストエンコーダを使用してサイズ 77×768 のテキスト埋め込みに変換されます。

次に U-Net は、テキスト埋め込みに条件付けされながら、ランダムな潜在画像表現を反復的にノイズ除去します。ノイズ差分である、U-Net の出力はスケジューラ・アルゴリズムを通してノイズ除去された潜在的画像表現を計算するために使用されます。多くの様々なスケジューラ・アルゴリズムがこの計算のために使用できますが、それぞれに良い点と悪い点があります。Stable Diffusion については、以下の一つの使用を勧めます :

スケジューラ・アルゴリズムがどのように機能するかの理論はこのノートブックの範囲外ですが、手短に言えば、それらは前のノイズ表現と予測されたノイズ差分から予測されたノイズ除去された画像表現を計算することを覚えておくべきです。詳細は、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 を使用しますが、他の diffusion モデルは 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", use_auth_token=True)

# 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", use_auth_token=True)

そして事前定義されたスケジューラをロードする代わりに、K-LMS スケジューラをロードします。

from diffusers import LMSDiscreteScheduler

scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", num_train_timesteps=1000)

Next we move the models to the GPU.

from torch import autocast

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
latents.shape

Cool 64×64 is expected. モデルはこの潜在的表現 (純粋なノイズ) を後で 512 × 512 画像に変換します。

次に、スケジューラを選択された num_inference_steps で初期化します。これはノイズ除去プロセスの間に使用される sigmas と正確な時間ステップの値を計算します。

scheduler.set_timesteps(num_inference_steps)

K-LMS スケジューラは latents をその sigma 値で乗算する必要がります。これをここで行いましょう。

latents = latents * scheduler.sigmas[0]

We are ready to write the denoising loop.

from tqdm.auto import tqdm
from torch import autocast

with autocast("cuda"):
  for i, t in tqdm(enumerate(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)
    sigma = scheduler.sigmas[i]
    latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5)

    # 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, i, 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)

そして最後に、画像を 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]

Nice, as we can see the photo has quite a definition 🔥.

 

以上