
Stable Diffusionを使ったイラスト作成の記録(21) ~ 色混ざりを避ける手法の考察 ~
前回の記事
シリーズ一覧
Layered Diffusion Pipelineを使うためのリンク集
ライブラリの入手先と使用法(英語) : Githubリポジトリ
日本語での使用方法の解説 : Noteの記事
今回は前回までとはテーマを変えました
しばらくの間、画風の調整のテーマを中心にして実験をしてきましたが、今回は視点を変えてみます。
以前、『ネガティブプロンプトとは何か』という記事を書きましたが、これと似たような効果をネガティブプロンプトを使わずに実現することはできるだろうか、というのが今回のテーマになります。
この記事では、"1girl, red hair, blue eye"というプロンプトに対し、"1girl, blue hair, red eye"という色が逆転したネガティブプロンプトを当てることで、色混ざりを抑えるという手法を説明しています。
これをネガティブプロンプトを用いない他の手法で回避できないか、というのが、今回のテーマです。
1)プロンプトで負の重みを使う
LayeredDiffusionPipelineのデフォルトのエンコーディング方式は、ShiftEncodingですが、これはフレーズごとに重みを設定することができ、正の値だけでなく負の値も取ることができます。
そこで、ネガティブプロンプトの代わりに、望ましくない要素に負の重みを付けてプロンプトに加えることで、望ましくない要素を抑制できないかというのがアイデアです。重みが直感的な効果を発揮するなら、負の重みを与えれば要素を取り除くことができるのではないかという考えからです。
今回は、比較対象として、3種の画像を生成しました。
ネガティブプロンプトも負の重みもなし
プロンプト:"1girl, red hair, blue eye"
ネガティブプロンプト:""
ネガティブプロンプトを使用
プロンプト:"1girl, red hair, blue eye, blue hair :-1.0, red eye :-1.0"
ネガティブプロンプト:""
負の重みを使用
プロンプト:"1girl, red hair, blue eye"
ネガティブプロンプト:"blue hair, red eye"
それぞれ、色混ざりが起きている4つの乱数シードを使って、4系統の画像を生成しています。以下が生成画像です。

全ての乱数シードにおいて、ネガティブプロンプトを使った画像は色混ざりが解決しているのに対し、負の重みを使っている画像は解決していないどころか悪化してさえいます。負の重みは、直感的な予想とは異なる効果をもたらすものだということが予想されます。
2)レイヤーを足して特定要素を強化する
次に、負の重みが使えないのなら、逆に欲しい要素を強化することができるのではないかと考えました。しかし、単に重みを増やしても色混ざり問題も一緒に強化されるかもしれませんし、これまでの実験から重みを増やし過ぎると画像が乱れることも分かっています。
そこで、欲しい要素だけに絞った新しいレイヤーを用意し、レイヤー機能で重ね合わせることで、特定の要素だけを強化することを考えました。つまり、次のような2つのレイヤーを重ね合わせます。
基本レイヤー
プロンプト:"1girl, red hair, blue eye"
追加レイヤー
プロンプト:欲しい要素
ここでの欲しい要素には、"red hair"か"blue eye"のどちらかを入れます。それぞれの画像と追加レイヤーを含まない画像を並べて比較した画像がこちらです。

追加レイヤーで指定した要素が反映されていることが分かります。今回は追加レイヤーの重みは一定にしたのですが、これを変化させることで追加要素の影響力を調整することも可能ではないかと思います。
3)プロンプトのEmbeddingを合成する
レイヤーの合成が効果を持つのなら、プロンプトのembeddingの段階で、異なるプロンプトを合成して画像生成しても似たような効果が得られるかもしれないと考えました。プロンプトの合成は、embeddingテンソルを単純に加算平均することで実現しました。
合成したプロンプトは、次の2つになります。
赤い髪プロンプト:"1girl, red hair :重み"
青い目プロンプト:"1girl, blue eye :重み"
それぞれのプロンプトに重みが付けられるようになっていますが、これは1.0と2.0の2種類で実験しています。2.0を試す理由は、加算平均をすることで片方にしかない要素は本来の半分の重みしかなくなってしまうため、それを補うことを考えたためです。
これも同様に、合成しない通常のプロンプトを用いた画像と比較しています。

重み2.0の画像を見ると、完全ではないですが、一定程度の成功が見られるようです。特に、髪色に対する色混ざりは完全になくなっています。
さらに、ここから推測されることとして、色混ざりは同じプロンプト上に複数の色を記述する時に起きて、違うプロンプトに別々に記述したものを合成する場合には起きないことから、テキストエンコーダーの処理の中に色混ざりを起こす原因があり、embeddingや拡散モデルの方には原因がない可能性が高いことが考えられます。
まとめ
プロンプトの負の重みで、要素を取り除くことはできない
プロンプトで使える「重み」は、必ずしも直感に即した効果をもたらすわけではなく、負の重みを使って要素を取り除くことはできませんでした。
レイヤーを足すことで、要素を強調することができる
ネガティブプロンプトが要素を消す効果があるように、追加レイヤーのプロンプトはその要素を強化する効果があることが分かりました。
プロンプトの合成で、要素を分離することができる
異なる要素を別々のプロンプトに記述し、embeddingの加算平均を取ることで、要素間の干渉を抑えることができました。ただし、平均を取ることで個々の要素の影響が減少するため、重みを調整して平均を取った後の重みが減らないようにする必要があります。
また、この結果から、色混ざりの原因が、テキストエンコーダーの処理の中にある可能性が高いことが推測されました。この発見は、今後、要素間の干渉を抑えたテキストエンコーディングの開発につながる可能性があります。
追記
そういえば、Open AIのDALL-Eも色混ざり問題が起きるのです。Stable Diffusionに比べると程度は抑えられているので、"1 girl, red hair, blue eye"のプロンプトだとあまり気になるほどではないですが、"a photo of a brown bird and a blue bear"だと無視できない間違いが起きます。

DALL-Eの論文を読むと、画像生成自体にはCLIPは使われていませんが、最後のベスト画像の選択でCLIPのスコアを使っているようなので、これも、CLIPのテキストエンコーダーの性能によるものかもしれません。
ちなみに、GoogleのImagenの論文に同じようなプロンプトで生成した画像があるのですが、こちらは完全に色の間違いを起こしていません。

ただし、残念ながら、Imagenのデモは公開されていないようなので、論文に乗っている画像しかなく、追試はできていません。
CLIPはOpen AIの技術なので、Googleが使っているとは考えづらいので、これも、CLIPに問題がある可能性の傍証にはなると思います。