創薬ちゃん生成チャレンジ参加記(kawaii作成のための指針2)
前置き
こんにちはchiliと申します。
以前ノートでkawaii生成のための指針という記事を書きました。
今回は創薬ちゃん生成チャレンジに参加しましたのでその参加記と前回から新しく知った指針について紹介いたします。
このコンテストのルールは以下の通りです:
- txt2imgで生成した画像で創薬ちゃんを生成する
- 評価指標は「創薬ちゃんっぽさ」として以下の点が考慮される
- 容姿の類似度
- 「創薬」感
- アート性も場合によって考慮
特に髪のメッシュと創薬感をどのように出すかが最大に難しい課題でした。
まず最初に結果的に私がサブミットしたものをお見せします。
こだわりは
- 赤のメッシュも入りつつアニメ感のある全体的に綺麗なイラスト
- 背中から当たる光もちゃんと表現されていてエモい
- 手にガラスの実験器具のようなものを持たせている+生物学の研究室にありそうな装置で創薬感を演出
- 上の影響で手が崩れているがそれも今のtxt2imgの限界という感じが良い(後付け)
- seed値が"0"でseedにお祈りでなく他のパラメータで調整(ただしうまく行ったのはたまたま)
などなどです。
どのようにしてこの絵に至ったかのアプローチを以下のフローで紹介します。
- プロンプトの試行錯誤
- ネガティブプロンプト
- Samplerの見直し
- その他パラメータの調整
1.プロンプトの試行錯誤
このコンテストの最大の難関は髪と創薬感(+衣装)です。まずは髪のメッシュの再現ために学習元の某サイトのタグを検索して上に関するプロンプトを取得して試します。また創薬感に対しても実験室のイメージから作成できないかを確認します。以下のようなプロンプトにしました。
Sampler: PLMS
Seed: 0~4
guidance_scale: 7.5
num_inference_steps: 30
Prompt: full-body, standing, beautiful character portrait of a beautiful cute scientist girl (in a chemistry lab), 1girl, Artists, with black long_hair, bright red streaks, two-tone_hair, streaked_hair, alternate_hairstyle, backlight, atmospheric lighting, anime eyes and iris, blush cheeks, dot nose, vivid color, highly colorful, vivid color, highly colorful, looking_at_viewer, game cg, HQ highres, CANON EOS, 35mm Lens, trending pixiv, fanbox
結果が以下の画像です。
普段は目をはっきりとされるセンテンスや描画を安定させるセンテンスを入れているのですが、今回は目や髪の作成に影響があったためバッサリと削っています。そのため、seedを動かす際の打率を下げる要因になってしまっています。またリアル調なものになっており創薬ちゃんのイメージとは程遠いです。
2.ネガティブプロンプト
リアルすぎるのはCG系の影響が多いためと考え、CG系の綺麗な絵が出る単語のoctane renderをあえてネガティブプロンプトに追加します。また体崩れをなくすためよく使われている"bad anatomy disfigured mutated"を追加しています。
ネガティブプロンプトは最初ベクトル演算によって潜在空間をずらすことを考えていたのですが、AUTOMATIC1111のコードを見るとunconditional_conditioning加えることで実現しており、私もそのように実装しました。
uc = None
if guidance_scale != 1.0:
# uc = self.model.get_learned_conditioning(batch_size * [""]) # オリジナル
uc = self.model.get_learned_conditioning(batch_size * [negative_prompt])
if isinstance(prompts, tuple):
prompts = list(prompts)
c = torch.zeros_like(uc)
for prompt, weight in zip(prompts, weights):
c = torch.add(c, self.model.get_learned_conditioning(prompt), alpha=weight)
shape = [opt.C, height // opt.f, width // opt.f]
samples_ddim, _ = self.sampler.sample(
S=num_inference_steps,
conditioning=c,
batch_size=batch_size,
shape=shape,
verbose=False,
unconditional_guidance_scale=guidance_scale,
unconditional_conditioning=uc,
eta=eta,
x_T=latents
)
結果が以下の画像です。
Sampler: PLMS
Seed: 0~4
guidance_scale: 7.5
num_inference_steps: 30
Prompt: full-body, standing, beautiful character portrait of a beautiful cute scientist girl (in a chemistry lab), 1girl, Artists, with black long_hair, bright red streaks, two-tone_hair, streaked_hair, alternate_hairstyle, backlight, atmospheric lighting, anime eyes and iris, blush cheeks, dot nose, vivid color, highly colorful, vivid color, highly colorful, looking_at_viewer, game cg, HQ highres, CANON EOS, 35mm Lens, trending pixiv, fanbox
negative_prompt: bad anatomy disfigured mutated octane render
だいぶリアル調を抑えられてkawaiiになってきましたが、まだ創薬ちゃんには程遠いです。
3.Samplerの見直し
StableDiffusionではDiffusion modelという徐々にデノイズして画像を生成する手法ですが、その際のデノイズの方法に複数のSamplerがあります。こちらも実装してします。(実際にどのように違うかについてはまだちゃんと理解しておらずコードと論文から勉強中です)
デフォルトのSatableDiffusionのパッケージでは生成できないので、k-diffusionというライブラリをインストールします。
pip install "git+https://github.com/crowsonkb/k-diffusion.git"
KDiffusionSamplerというベースとなるクラスを作成します。
from torch import nn
import k_diffusion as K
class CFGDenoiser(nn.Module):
def __init__(self, model):
super().__init__()
self.inner_model = model
def forward(self, x, sigma, uncond, cond, cond_scale):
x_in = torch.cat([x] * 2)
sigma_in = torch.cat([sigma] * 2)
cond_in = torch.cat([uncond, cond])
uncond, cond = self.inner_model(x_in, sigma_in, cond=cond_in).chunk(2)
return uncond + (cond - uncond) * cond_scale
class KDiffusionSampler:
def __init__(self, m, sampler):
self.model = m
self.model_wrap = K.external.CompVisDenoiser(m)
self.schedule = sampler
def get_sampler_name(self):
return self.schedule
def sample(self, S, conditioning, batch_size, shape, verbose, unconditional_guidance_scale, unconditional_conditioning, eta, x_T):
sigmas = self.model_wrap.get_sigmas(S)
x = x_T * sigmas[0]
model_wrap_cfg = CFGDenoiser(self.model_wrap)
samples_ddim = K.sampling.__dict__[f'sample_{self.schedule}'](model_wrap_cfg, x, sigmas, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': unconditional_guidance_scale}, disable=False)
return samples_ddim, None
Samplerとの対応関係は以下の通りで、切り替えられるようにクラスの中に入れておきます。
if opt.sampler == "PLMS":
self.sampler = PLMSSampler(self.model)
elif opt.sampler == "DDIM":
self.sampler = DDIMSampler(self.model)
elif opt.sampler == "k_dpm_2_a":
self.sampler = KDiffusionSampler(self.model, 'dpm_2_ancestral')
elif opt.sampler == "k_dpm_2":
self.sampler = KDiffusionSampler(self.model, 'k_dpm_2')
elif opt.sampler == "k_euler_a":
self.sampler = KDiffusionSampler(self.model, 'euler_ancestral')
elif opt.sampler == "k_euler":
self.sampler = KDiffusionSampler(self.model, 'euler')
elif opt.sampler == "k_heun":
self.sampler = KDiffusionSampler(self.model, 'heun')
elif opt.sampler == "k_lms":
self.sampler = KDiffusionSampler(self.model, 'lms')
else:
raise Exception("Unknown sampler: " + opt.sampler)
結果は以下の通りです。
Sampler: PLMS(再掲)
Sampler: k_lms
Sampler: DDIM
Sampler: k_euler
Sampler: k_heun
Sampler: k_euler_a
Sampler: k_dpm_2_a
_aつきのものがよさそうなのでk_euler_aを選びました。
4. その他パラメータの調整
デノイズのステップサイズであるnum_inference_stepsと、プロンプトに従う度合いのguidance_scaleを調整して良い画像を選びます。
seed: 0に固定
x軸方向:guidance_scale [5.0, 7.0, 7.5, 8.0, 10.0]
y軸方向:num_inference_steps [30, 40, 50, 60, 70]
ここでもっともよさそうなguidance_scale=8.0, num_inference_steps=50を選択しました。
(実際はもっと泥臭く・幅広い探索をしているのですが今回は説明のため省いています)
prompt: full-body, standing, beautiful character portrait of a beautiful cute scientist girl (in a chemistry lab), 1girl, Artists, with black long_hair, bright red streaks, two-tone_hair, streaked_hair, alternate_hairstyle, backlight, atmospheric lighting, anime eyes and iris, blush cheeks, dot nose, vivid color, highly colorful, vivid color, highly colorful, looking_at_viewer, game cg, HQ highres, CANON EOS, 35mm Lens, trending pixiv, fanbox
negative_prompt: bad anatomy disfigured mutated octane render,
sampler: k_euler_a
guidance_scale: 8.0
num_inference_steps: 50
seed: 0
感想
これまではpromptを選んだらseedを振っていい画像を出てくるのを選ぶアプローチが多かったのですが、今回は制約があるためそのアプローチではうまくいかず、これまで試せていないSamplerや惜しい画像に対してStep数などを調整する良い機会となりました。ただpromptと同様に試せる設計空間が組合せ爆発的に膨大になるため、狙った画像を出すためには実験計画法に基づくようなより最適なパラメータの探索が必要となります。
反省点
今回のコンテストでは色々な試行をしたのですが、その際に以下の点での反省がありました。
-
FrozenCLIPEmbedderWithCustomWordsの不使用
Commandから生成することを好んでいるのでAUTOMATIC1111を使わずそこから機能だけ抜き出して使用していたのですが、AUTOMATIC1111では[],()でワードを強調する機能があるようです。これを使えばもっと望んだ画像を出すことができそうでこちらを実装しておくべきでした。
https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/21086e60a9e4ad6f677ccc7719be651356c18a2e/modules/sd_hijack.py#L272 -
Trinartキャラクターズを使わなかった
852話さんの絵を見ると衣装が非常に正しく作成されていました。他、Trinartキャラクターズであげている人を見ても全体的に再現率やキャラクターの解像度が高かったのです。
(ただ私はプロンプトをいじるだけより上記のようにコードの中身が見られて色々検証できる方が好きなので、Trinartキャラクターを使う選択肢はなかったのです)
https://twitter.com/8co28/status/1571124935313199104 -
文字数オーバー
この時にはCLIPで文字数をカウントする方法を試していなかったので結構な文字数オーバーをしています。","なども省きまだまだプロンプトを練る方法がありそうです。
考察
ネガティブプロンプトについては今回最も効果があり、このアプローチからもっと欲しい画像を作れる可能性があると考えています。ネガティブプロンプトではclip->UNetと通した後、その潜在空間の中でデノイズを調整します。diffusers/pipeline
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
その際、1次元的な変形をしているのですが、
vec0 = (オリジナルプロンプト) + guidance_scale_1 *(オリジナルプロンプト+赤い髪)-(オリジナルプロンプト)
vec1 = (オリジナルプロンプト) + guidance_scale_2 *(オリジナルプロンプト+白衣)-(オリジナルプロンプト)
のようなベクトルを複数用意しておいてguidance_scale_*を調整することで狙った画像を生成する方法を試していました。このベクトル演算自体はtd2skさんがCLIPからの出力を用いて施行されていますが、そちらとは別の結果が出ないかなと考えです。上で述べた[],()の協調とともにコンテスト後半ではこの実装を試していたのですが、今回はタイムオーバーとなりました。うまくできましたらまた記事を書きます。
今後とも、StableDiffusionというコードがいじれるからこそのCode2Imageでのkawaii生成を試行してkawaiiを作成していきたいです!
追記(2022/9/22)
佳作🥉いただきました🥳
最後にこれまで作ってきた中で気に入ったものを5点ほど紹介させてください!気に入ったらいいねいただけると嬉しいです。
Discussion