stable diffusion はオープンソースの画像生成プログラムです。
stable diffusion の仕組みは、hugging face のブログで説明があります。下の図は、リンクのブログからの転載です。
ソースコードはこちらからDLして動かすことができます(参考 環境構築の覚書)。
テキストから画像を生成する実行ファイルはscript/txt2img.py です(コードはこれ)。
中を見ると、実行時の引数でいろいろなチューニングをするために、理解するためには少し煩雑です。そこで、引数は固定して、実行できる最小限のシンプルバージョンに整えましたので、ここで覚書的に残します。
以降、jupyter notebook で動かすことを想定しています。
まず、使用するパッケージのインポートをします。
import torch import numpy as np from omegaconf import OmegaConf from PIL import Image from einops import rearrange from pytorch_lightning import seed_everything from ldm.util import instantiate_from_config from ldm.models.diffusion.plms import PLMSSampler from ldm.models.diffusion.ddim import DDIMSampler
次に、中で使用する関数を定義します。オリジナルにはたくさん関数が定義されていますが、シンプルバージョンで必要なものはモデルをロードするための以下の関数のみです。
def load_model_from_config(config, ckpt, verbose=False): print(f"Loading model from {ckpt}") pl_sd = torch.load(ckpt, map_location="cpu") if "global_step" in pl_sd: print(f"Global Step: {pl_sd['global_step']}") sd = pl_sd["state_dict"] model = instantiate_from_config(config.model) m, u = model.load_state_dict(sd, strict=False) if len(m) > 0 and verbose: print("missing keys:") print(m) if len(u) > 0 and verbose: print("unexpected keys:") print(u) model.cuda() model.eval() return model
次は、パラメータの設定です。オリジナルでは実行時のオプションでパラメータを指定できるようになっていますが、ここではoptというクラスを作り、クラス変数にパラメータ値を定義するようにしました。画像サイズは、横512、縦256 としています。
class opt: prompt='Colorful cocktails by the pool' # プロンプト seed=10 # 乱数のシード値 ddim_eta=0.0 # 0.0でサンプリングが決定的になる ddim_steps=50 # ddimサンプル数 n_iterとの違い不明 f=8 # ダウンサンプリングファクター scale=7.5 # unconditional guidance scale C=4 # 潜在変数のチャンネル数 H=256 # 出力画像の縦の長さ W=512 # 出力画像の横の長さ n_samples=1 # 1つのプロンプトから生成する画像の数 n_iter=0 # 意味分からず。デフォルトでは2 ckpt='sd-v1-4.ckpt' # HuggingFaceからDLしたモデルのパス config='configs/stable-diffusion/v1-inference.yaml' # パラメータファイルのパス outdir='outputs/txt2img-samples' # 出力フォルダー precision='autocast' n_rows=0 from_file=None dpm_solver=False fixed_code=False laion400m=False skip_grid=False skip_save=False
次のコードでモデルのロードします。自分のPC(Core-i5-11400F, GeForce RTX 3060Ti, 8GB)では20秒くらいかかりました。
print("モデルのロード----------") config = OmegaConf.load(f"{opt.config}") model = load_model_from_config(config, 'sd-v1-4.ckpt') device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") model = model.to(device)
ここからが画像生成のプロセスです。
まず、文章であるプロンプトを数値行列に変換したtext embeddings (77×768)を作ります。この計算はほとんど時間はかかりません。
print("プロンプトから潜在変数を作成----------") seed_everything(opt.seed) # seed値 c = model.get_learned_conditioning(opt.prompt) # promptからc(c.size=[1,77,768])を生成
ここが画像生成のコア部分です。ガウスノイズの行列からノイズ除去の処理を繰り返して、text embeddings に対応するconditioned latents (潜在変数 4 x 32 x 64)を作ります。自分のPCでは10秒くらいかかりました。
print("潜在変数を条件にしてサンプリング----------") # 30秒程かかる # sampler = DDIMSampler(model) # DDIMを使用する場合 sampler = PLMSSampler(model) # PLMSを使用する場合 shape = [opt.C, opt.H // opt.f, opt.W // opt.f] # shape = [4, 32, 64] batch_size = opt.n_samples uc = model.get_learned_conditioning(batch_size * [""]) start_code = None samples_ddim, _ = sampler.sample(S=opt.ddim_steps, conditioning=c, batch_size=opt.n_samples, shape=shape, verbose=False, unconditional_guidance_scale=opt.scale, unconditional_conditioning=uc, eta=opt.ddim_eta, x_T=start_code) # samples_ddimが潜在変数。samples_ddim.size = [1, 4 ,32, 64]
conditioned latents から、Variational Autoencoder Decoder でoutput image (生成画像 256 x 512 x 3)を作ります。この計算はほとんど時間はかかりません。
print("潜在変数から、画像を作成----------") # 一瞬 x_samples_ddim = model.decode_first_stage(samples_ddim) x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) x_samples_ddim = x_samples_ddim.cpu().permute(0, 2, 3, 1).numpy() # x_samples_ddimが生成された画像 x_samples_ddim.size=(1, 256, 512, 3)
最後、画像を表示します(jupyter notebook で表示することを想定しています)。
print("画像表示----------") # 一瞬 x_checked_image = x_samples_ddim x_checked_image_torch = torch.from_numpy(x_checked_image).permute(0, 3, 1, 2) x_sample = x_checked_image_torch[0] x_sample = 255. * rearrange(x_sample.cpu().numpy(), 'c h w -> h w c') img = Image.fromarray(x_sample.astype(np.uint8)) # 結果の表示 display(img)
「プールサイドのカラフルなカクテル ’Colorful cocktails by the pool’」というプロンプトから生成された画像です。
ここまでのスクリプトをつなげたものはこちら。
import torch import numpy as np from omegaconf import OmegaConf from PIL import Image from einops import rearrange from pytorch_lightning import seed_everything from ldm.util import instantiate_from_config from ldm.models.diffusion.plms import PLMSSampler from ldm.models.diffusion.ddim import DDIMSampler def load_model_from_config(config, ckpt, verbose=False): print(f"Loading model from {ckpt}") pl_sd = torch.load(ckpt, map_location="cpu") if "global_step" in pl_sd: print(f"Global Step: {pl_sd['global_step']}") sd = pl_sd["state_dict"] model = instantiate_from_config(config.model) m, u = model.load_state_dict(sd, strict=False) if len(m) > 0 and verbose: print("missing keys:") print(m) if len(u) > 0 and verbose: print("unexpected keys:") print(u) model.cuda() model.eval() return model class opt: prompt='Colorful cocktails by the pool' # プロンプト seed=10 # 乱数のシード値 ddim_eta=0.0 # 0.0でサンプリングが決定的になる ddim_steps=50 # ddimサンプル数 n_iterとの違い不明 f=8 # ダウンサンプリングファクター scale=7.5 # unconditional guidance scale C=4 # 潜在変数のチャンネル数 H=256 # 出力画像の縦の長さ W=512 # 出力画像の横の長さ n_samples=1 # 1つのプロンプトから生成する画像の数 n_iter=0 # 意味分からず。デフォルトでは2 ckpt='sd-v1-4.ckpt' # HuggingFaceからDLしたモデルのパス config='configs/stable-diffusion/v1-inference.yaml' # パラメータファイルのパス outdir='outputs/txt2img-samples' # 出力フォルダー precision='autocast' n_rows=0 from_file=None dpm_solver=False fixed_code=False laion400m=False skip_grid=False skip_save=False print("モデルのロード----------") config = OmegaConf.load(f"{opt.config}") model = load_model_from_config(config, 'sd-v1-4.ckpt') device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") model = model.to(device) print("プロンプトから潜在変数を作成----------") seed_everything(opt.seed) # seed値 c = model.get_learned_conditioning(opt.prompt) # promptからc(c.size=[1,77,768])を生成 print("潜在変数を条件にしてサンプリング----------") # 30秒程かかる # sampler = DDIMSampler(model) # DDIMを使用する場合 sampler = PLMSSampler(model) # PLMSを使用する場合 shape = [opt.C, opt.H // opt.f, opt.W // opt.f] # shape = [4, 32, 64] batch_size = opt.n_samples uc = model.get_learned_conditioning(batch_size * [""]) start_code = None samples_ddim, _ = sampler.sample(S=opt.ddim_steps, conditioning=c, batch_size=opt.n_samples, shape=shape, verbose=False, unconditional_guidance_scale=opt.scale, unconditional_conditioning=uc, eta=opt.ddim_eta, x_T=start_code) # samples_ddimが潜在変数。samples_ddim.size = [1, 4 ,32, 64] print("潜在変数から、画像を作成----------") # 一瞬 x_samples_ddim = model.decode_first_stage(samples_ddim) x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) x_samples_ddim = x_samples_ddim.cpu().permute(0, 2, 3, 1).numpy() # x_samples_ddimが生成された画像 x_samples_ddim.size=(1, 256, 512, 3) print("画像表示----------") # 一瞬 x_checked_image = x_samples_ddim x_checked_image_torch = torch.from_numpy(x_checked_image).permute(0, 3, 1, 2) x_sample = x_checked_image_torch[0] x_sample = 255. * rearrange(x_sample.cpu().numpy(), 'c h w -> h w c') img = Image.fromarray(x_sample.astype(np.uint8)) # 結果の表示 display(img)
コメントを残す