見出し画像

Stable Diffusionを使ったイラスト作成の記録(9) ~ ネガティブレイヤーをさらに分析 ~

前回の記事

シリーズ一覧

Layered Diffusion Pipelineを使うためのリンク集

今回やること

前回、ネガティブレイヤーを導入した際に、論旨から外れるために検討から外した要素について、拾い上げて再検討を行います。注目するのは次の3点です。

  • 通常レイヤーのcfg_scaleの、ネガティブレイヤーへの干渉の可能性

  • 通常レイヤーのcfg_scaleを上げた時の影響とネガティブレイヤーの影響の比較

  • 通常レイヤーのネガティブプロンプトを、ネガティブレイヤーの代わりに使った時の効果

通常レイヤーのcfg_scaleの干渉

レイヤーのcfg_scaleのデフォルト値は4.0です。前回のスクリプトからレイヤー定義の部分を抜き出すと次のようになっていますが、最初の2つのレイヤーはcfg_scale=4.0が省略されています。

        Layer(
            prompt=("high school, school ground, school building, cherry tree, "
                    "blue sky"),
            negative_prompt="1girl",
        ),
        Layer(
            prompt="1girl, solo, high school, school uniform",
            negative_prompt="",
            mask_by="schoolgirl_mask.png",
        ),
        Layer(
            prompt=negative_quality_tags,
            cfg_scale=1.0,
            mask_by=neg_scale,
            strength=neg_after,
        ),

私の以前の記事にネガティブプロンプトを使ったCFGの潜在変数の計算が述べられていますが、これをネガティブレイヤーを含んだ式にすると、次のようになります。

P: 通常レイヤーのプロンプトから導いた潜在変数
N: 通常レイヤーのネガティブプロンプトから導いた潜在変数
NL: ネガティブレイヤーのプロンプトから導いた潜在変数
cfg_scale: 通常レイヤーのCFGスケール
neg_scale: ネガティブレイヤーのスケール(mask_byパラメータ)

新しい潜在変数
  = (1 - neg_scale) * (cfg_scale * P + (1 - cfg_scale) * N)
    + neg_scale * NL
  = cfg_scale * (1 - neg_scale) * P
    + (1 - cfg_scale) * (1 - neg_scale) * N
    + neg_scale * NL

ネガティブレイヤーを含む潜在変数の計算式

ここで通常レイヤーのプロンプトから導いた潜在変数Pの係数を見ると、"cfg_scale * (1 - neg_scale)"となっていて、通常レイヤーのCFGスケールとネガティブレイヤーのスケールの積となっています。

cfg_scale > 1 かつ (1 - neg_scale) > 1 であることを考えると、この積がそれなりに大きな値となる可能性があります。例えば cfg_scale=4.0, neg_scale=-3.0 であれば、係数は16.0となります。

Stable Diffusion v2のドキュメントに掲載されているグラフによれば、CFGスケールは4.0あたりが最適と考えられます。

出典: https://huggingface.co/stabilityai/stable-diffusion-2

これは2つの潜在変数を合成するときの議論で、ネガティブレイヤーを含む3つの潜在変数を合成する時にそのまま適用できるかは不明であるため、検証が必要です。

ただし、私の環境では大規模な評価を行うのは無理なので、いつものように数点のサンプルを観察することとします。

テスト設計

前回の記事で選んだ乱数シードとネガティブレイヤーパラメータの組み合わせに対して、通常レイヤーのcfg_scaleをデフォルトの4.0の半分にし、次のネガティブレイヤーのパラメータを試します。

  • ネガティブレイヤーなし

  • 前回のネガティブレイヤーのパラメータ

  • (1 - neg_scale)が2倍になるように前回のパラメータを調整

そして、この設定での生成画像を、デフォルトのcfg_scaleでネガティブレイヤーなしとネガティブレイヤーあり(前回のパラメータ)の画像と比較します。

生成画像

上段がデフォルトのcfg_scaleで、下段が半分のcfg_scaleの画像です。

seed=1334630829
seed=1124067389
seed=129970857
seed=2895285493
seed=3545805225
seed=1578222291

まず分かることは、cfg_scaleを半分にすると、生成画像に大きな影響があり、クォリティが劣化します。クォリティの劣化はネガティブレイヤーの適用で回復します。

cfg_scaleを半分にして、ネガティブレイヤーのスケールを2倍にすると、元画像と同程度にくっきりとした画像が生成されているところから、当初の予測通り、この2つのパラメータには乗算的な効果がある可能性が示唆されます。

ただし、心配されたように、cfg_scaleが過剰であるという可能性は、上記の結果からは認められませんでした。

通常レイヤーのcfg_scale単体での影響

cfg_scaleを半分にすると生成画像のクォリティが下がるということは、cfg_scaleを増やすと生成画像のクォリティが上がる可能性があるということです。このことは、上に挙げたStable Diffusion v2のドキュメントのグラフから上限があることが示唆されていますが、4.0が最適であるかは手元では検証していません。

ネガティブレイヤーの追加がCFGスケールの増強と同等の意味を持つならば、クォリティの向上はネガティブプロンプトの効果ではなく、CFGスケールの実質的な増強によるものである可能性があります。

なので、通常レイヤーのcfg_scale単体の効果を、ネガティブレイヤーの影響を除いて検証しておく必要があります。

テスト設計

前回の記事で選んだ乱数シードとネガティブレイヤーパラメータの組み合わせに対して、通常レイヤーのcfg_scaleにデフォルトの4.0よりも大きな数字を適用して比較します。テストするcfg_scaleは6.0, 8.0, 1.0とします。

生成画像

各列が左からcfg_scale=4.0, 6.0, 8.0, 10.0に対応します。

cfg_scaleの変化

cfg_scaleが増えるにつれ、画像の描写、特に人物の描写がくっきりしていくのが見て取れます。これはネガティブレイヤーを導入した時にも見られた効果です。

しかし、ネガティブレイヤーを導入した時とは異なり、変化はすでに存在する要素を強調する方向に留まり、新たな質的変化を起こすようなものではないことも見て取れます。

このことから、cfg_scaleとネガティブレイヤーは部分的には重なりがあるものの、異なる効果を求めて使用されるべきものだということが再確認されました。

通常レイヤーのネガティブプロンプトの活用

前回のスクリプトでは、画像クォリティの向上のためのネガティブプロンプトをネガティブレイヤーとして組み込みましたが、代わりに通常レイヤーのネガティブプロンプトとして追加することも可能です。

その場合、通常のネガティブプロンプトではネガティブレイヤーで行ったような画像生成の途中から適用するということができませんが、Layered Diffusion Pipelineでは、プロンプトを途中で変更する機能があるので、それを使って実装します。

注)この機能は、レイヤー実装前からある比較的古い機能であるため、パラメータの使用法がGeneralized Strengthとの一貫性に欠けていて、今後、大きな変更または削除が起きる可能性があります。

スクリプト

使用するスクリプトは以下のものになります。プロンプトとその適用開始時点をリストで与えることで、途中でプロンプトを変更できるようになります。

# スクリプト(9-1)
seed, img_strength = 123456, strg(0.75, level=0.5)
cfg_scale, neg_after = 4.0, 0.8
images = pipe(
    initialize=ByImage(
        image="schoolgirl.png",
        strength=img_strength,
    ),
    iterate=[
        Layer(
            prompt=("high school, school ground, school building, cherry tree, "
                    "blue sky"),
            negative_prompt=(
                ([(0.0, "1girl")] if neg_after < 1.0 else [])
                + [(1.0 - neg_after, "1girl, " + negative_quality_tags)]
            ),
            cfg_scale=cfg_scale,
        ),
        Layer(
            prompt="1girl, solo, high school, school uniform",
            negative_prompt=[(1.0 - neg_after, negative_quality_tags)],
            mask_by="schoolgirl2_mask.png",
            cfg_scale=cfg_scale,
          ),
    ],
    num_steps=30,
    size=(512, 512),
    rand_seed=seed,
)

テスト設計

negative_quality_tagsとneg_afterは、前回の記事で選んだパラメータをそのまま使います。cfg_scaleは4.0, 6.0, 8.0を使用します。

その上で、前回の記事で同じneg_afterに対して生成した画像と比較します。比較対象となる画像のneg_scaleは-2, -4, -6です。

生成画像

最上段はネガティブレイヤーもネガティブプロンプトもなしの画像です。2段目はネガティブレイヤーを用いた画像で、3段目は通常レイヤーのネガティブプロンプトを用いた画像です。

seed=1334630829
seed=1124067389
seed=129970857
seed=2895285493
seed=3545805225
seed=1578222291

このアプローチの特徴は、前節で確認したcfg_scale単体を増やした時の影響がネガティブレイヤーの導入で確認された影響とミックスされているように見えるところです。

これは、実際にスクリプト上でも、cfg_scaleとネガティブプロンプトが分離されていないため、画像生成の結果と矛盾していません。

まとめ

通常レイヤーのcfg_scaleとネガティブレイヤーのスケール(mask_byパラメータ)は乗算的な影響を画像生成プロセスに与えています。

通常レイヤーのCFGとネガティブレイヤーはそれぞれ異なる効果を狙って使用されるため、2つのスケールの間にある相互関係は必ずしも望ましいものではありません。

現状のネガティブレイヤーの実装は、2つの機能を一定程度分離することに成功していますが、完全な分離ではないことに注意が必要です。

この記事が気に入ったらサポートをしてみませんか?