
Stable Diffusionを使ったイラスト作成の記録(10) ~ ネガティブレイヤーの分析(解決編) ~
前回の記事
シリーズ一覧
Layered Diffusion Pipelineを使うためのリンク集
ライブラリの入手先と使用法(英語) : Githubリポジトリ
日本語での使用方法の解説 : Noteの記事
残る疑問
前回の分析で、ネガティブレイヤーの効果は2つの効果の合成であることが分かりました。その効果とは、以下の2つです。
通常レイヤーのCFGスケールを後から増加させる
ネガティブプロンプトを後から追加する
しかし、前回はこの2つがどのように影響し合ってネガティブレイヤーの画像生成に作用しているのかが完全には理解できていませんでした。今回は、その疑問をさらに追及します。
ネガティブレイヤーの効果の概念図
ネガティブレイヤーを使わない場合と使う場合での、ネガティブプロンプトとその係数が画像生成プロセスの進展とともにどう変化するかを図示したものが以下のものです。下の計算式によって得られた係数を、ネガティブレイヤーが途中から適用されることを考慮して図示しました。

P: 通常レイヤーのプロンプトから導いた潜在変数
N: 通常レイヤーのネガティブプロンプトから導いた潜在変数
NL: ネガティブレイヤーのプロンプト(=ネガティブ画質タグ)から導いた潜在変数
cfg_scale: 通常レイヤーのCFGスケール
neg_scale: ネガティブレイヤーのスケール(mask_byパラメータ)
新しい潜在変数
= cfg_scale * (1 - neg_scale) * P
+ (1 - cfg_scale) * (1 - neg_scale) * N
+ neg_scale * NL
cfg_scale = 4
neg_scale = -3 とおくと
新しい潜在変数
= 16 * P - 12 * N - 3 * NL
実験的機能の実装
今回の分析を行ううえで、2つの実験的な機能を実装しました。この機能は実験的であるため、まだコードをgithubにアップロードしていませんが、ここに簡単な説明をします。
可変CFGスケール
1つ目の機能は、レイヤーのCFGスケールを可変にすることです。Generalized Strengthと同じような方法で、画像生成プロセスの進捗に合わせて、CFGスケールを変更できるようにします。この機能によって、次の図のようなネガティブプロンプトの変化を表現できるようになります。

ゼロ和レイヤー
通常レイヤーやネガティブレイヤーはCFGスケールに乗算的な影響を与えるので、レイヤーの効果を直感的に把握しづらいという問題があるので、代わりに加算的な影響を与えるレイヤーを作りました。
P: 通常レイヤーのプロンプトから導いた潜在変数
N: 通常レイヤーのネガティブプロンプトから導いた潜在変数
cfg_scale: CFGスケール
[通常レイヤー]
新しい潜在変数 = cfg_scale * P - (cfg_scale - 1) * N
[ゼロ和レイヤー]
新しい潜在変数 = cfg_scale * P - cfg_scale * N
このようにゼロ和レイヤーを使うことで、通常レイヤーとゼロ和レイヤーの重ね合わせは、単純な加算として表現できるようになります。
通常レイヤーのCFGスケールを可変に
最初に行った実験は、ネガティブレイヤーで変化したCFGスケールだけを可変CFGスケールを使って実現し、ネガティブプロンプトはそのままにして比較しました。
生成画像は、下の図のように真ん中に可変CFGスケールを利用した画像を並べています。

生成画像

前回、cfg_scale単体での影響を調べた時とは違い、ネガティブレイヤーを使用した画像と構図上非常に類似した画像が生成されていることが確認できます。
このことから、ネガティブレイヤーの構図に対する影響の大部分は、CFGスケールの変化によるものであることが確認できました。
ネガティブプロンプトを使う(1)
ネガティブレイヤーのネガティブプロンプトによる画像生成への影響をさらに分析するため、2つの系統のアプローチでネガティブ画質タグをネガティブプロンプトに含めました。
1つ目の系統は、CFGスケールを増加させたタイミングからネガティブ画質タグを含める方法です。ネガティブ画質タグに対して与えられたCFGスケールは、4と12です。
2つ目の系統は、画像生成の最初からネガティブ画質タグを含める方法です。この場合、画像生成の最初の部分で本来のネガティブプロンプトが失われないように、"1girl"とネガティブ画質タグを文字列結合してネガティブプロンプトとして利用しています。CFGスケールは、3と15です。
比較画像では、上段に参考画像を配置し、2段目に1つ目の系統を、3段目に2つ目の系統の生成画像を配置しています。下に概念図を表示します。ネガティブレイヤーを使った画像には赤枠で目印が付けられています。

上手でのネガティブレイヤーのCFGスケールは固定値となっていますが、実際には画像によって-2と-4と異なります。また、ネガティブレイヤーを使用した場合の全体の実効CFGスケールは前述の計算式に従ってパラメータによって変わります。各画像のパラメータは生成画像の上部に記述されています。
生成画像






概念図から想像できる通り、1つ目の系統(中段)の画像がネガティブレイヤーの画像に近い画像が生成できています。
それだけでなく、生成された画像のクォリティの面でも、ネガティブレイヤーなしの画像(左列)の画像との連続性の面でも、よい画像が生成されているように見えます。
問題点
1つ目の系統(中段)の手法は、ネガティブレイヤーの2つの要素を適切に切り分けることができていますが、レイヤーの構成が複雑になるという問題点があります。
以下は使用したスクリプトの一部です。
cfg_later = 16.0
neg_cfg_later = 4.0
neg_after = 0.7
def VariableCfgScale(initial_scale, final_scale, after):
def callable_cfg_scale(remaining):
if remaining > after:
return initial_scale
else:
return final_scale
return callable_cfg_scale
images = pipe(
iterate=[
Layer(
prompt=("high school, school ground, school building, cherry tree, "
"blue sky"),
negative_prompt="1girl",
cfg_scale=VariableCfgScale(4.0, cfg_later - neg_cfg_later, neg_after),
),
Layer(
prompt=("high school, school ground, school building, cherry tree, "
"blue sky"),
negative_prompt=negative_quality_tags,
cfg_scale=VariableCfgScale(0.0, neg_cfg_later, neg_after),
is_zero_sum=True,
),
Layer(
prompt="1girl, solo, high school, school uniform",
negative_prompt="",
mask_by="schoolgirl2_mask.png",
cfg_scale=VariableCfgScale(4.0, cfg_later - neg_cfg_later, neg_after),
),
Layer(
prompt="1girl, solo, high school, school uniform",
negative_prompt=negative_quality_tags,
mask_by="schoolgirl2_mask.png",
cfg_scale=VariableCfgScale(0.0, neg_cfg_later, neg_after),
is_zero_sum=True,
),
],
...
2つ目と4つ目のレイヤーがネガティブ画質タグを追加するレイヤーですが、ほとんど同じものを2回書いている上に、全体を通して、cfg_scaleの設定がややこしいです。
ネガティブプロンプトを使う(2)
先の問題点を解決する方法を探るため、さらに何種類か、ネガティブ画質タグを含める方法を検討しました。以下がここで試したパターンの概念図です。

下2つの段は、ゼロ和レイヤーのプロンプトに、通常レイヤーのネガティブプロンプトを設定することで、係数を打ち消し合う効果を狙ったものです。
生成画像






残念ながら、どの手法も、乱数シードによってうまく作用する場合と失敗する場合があり、もう一つ決め手に欠けているように思われます。
まとめ
可変CFGスケールは有用
今回導入した可変CFGスケールは、ネガティブレイヤーのCFGスケールに対する効果だけを切り出す機能として効果的であることが分かりました。
しかし、現時点の実装は暫定的で、利用しやすいインターフェースについて、今後、検討する必要があります。
ネガティブプロンプトに対する機能はさらに検討が必要
ネガティブレイヤーのネガティブ画質タグの効果だけを切り出す手法は見つかったものの、その手法を簡潔に記述することが現状ではできません。
ネガティブ画質タグの簡潔な記述法は、今後の検討課題となります。