0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI画像生成ツールを自作して分かった、プロンプト設計の本質

0
Posted at

はじめに

去年から Gazouya(がぞうや) という AI 画像生成サービスを個人開発している。Stable Diffusion 系のモデルをバックエンドにして、ユーザーが日本語で prompt を入力するだけでイラストや写真風の画像を生成できる Web サービスだ。

開発を進めるなかで何度もぶつかった壁がある。「ユーザーが入力した日本語プロンプトを、どうやってモデルに正しく伝えるか」 という問題だ。

たとえば「桜が舞い散る夕暮れの京都の路地、どこか懐かしい雰囲気」という日本語プロンプト。人間なら一発で情景が浮かぶ。でも、これをそのまま Stable Diffusion に渡しても、期待した絵はまず出てこない。

なぜか——というのを、実際に画像生成のパイプラインを自作した経験から、技術的に掘り下げてみる。

画像生成 AI の基本的な仕組み

まず、text-to-image のパイプラインがどうなっているか整理しておく。

大まかには 4 つのステージがある:

1. Tokenizer(トークナイザー)

ユーザーが入力したテキストを、モデルが理解できる数値の列(token)に変換する。ここで使われるのは CLIP というモデルの tokenizer で、Byte-Pair Encoding(BPE)という方式を取っている。要するに、頻出する文字列の組み合わせをあらかじめ辞書化しておいて、入力テキストをその辞書の ID 列に置き換える。

2. Text Encoder(テキストエンコーダー)

token 列をベクトル表現に変換する。具体的には CLIP の text encoder が 77 token × 768 次元の埋め込みベクトルを出力する。このベクトルが「テキストの意味」を表現していて、以降の処理はすべてこのベクトルを頼りに進む。

3. Diffusion Model(拡散モデル)

ランダムノイズから始めて、text encoder の出力を条件として、少しずつノイズを取り除いていく(denoising)。UNet というアーキテクチャが使われていて、各ステップで「このピクセルはノイズか、意味のある信号か」を判定しながら画像を再構成する。一般的には 20〜50 ステップの denoising を行う。

4. VAE Decoder(VAE デコーダー)

拡散モデルが出力するのは「潜在空間」と呼ばれる圧縮された表現(latent representation)。これを VAE(Variational Autoencoder)のデコーダーが実際の画像(512x512 や 768x768 のピクセルデータ)に復元する。

このパイプラインの中で、出力品質に最も影響を与えるのが Tokenizer と Text Encoder の段階だ。ここでテキストの意味がうまくベクトル化できなければ、後段の diffusion model がいくら高性能でも期待した絵は出ない。

プロンプトが画像に変わるまで——具体例で追う

「a red cat sitting on a chair」という英語プロンプトで実際の処理を追ってみる。

まず tokenizer がこれを分割する:

["a", "red", "cat", "sitting", "on", "a", "chair"]
↓ BPE tokenizer
[320, 2678, 3698, 5832, 530, 320, 5868]

この 7 つの token ID が text encoder に渡され、77 token にパディングされた上で、768 次元のベクトルに変換される。このとき、各 token は単独で処理されるのではなく、周囲の token との関係(attention)も考慮される。「red」が「cat」にかかっていること、「on a chair」が場所を示していることを、attention 機構が捉える。

これが英語ならかなり正確に機能する。CLIP の学習データの大半が英語だからだ。

問題はここから。

日本語プロンプトの落とし穴

同じ意味のプロンプトを日本語で入力してみる:「赤い猫が椅子に座っている」

これが tokenizer に渡ると、こうなる:

["赤", "い", "猫", "が", "椅子", "に", "座", "って", "いる"]
↓ BPE tokenizer(CLIP の辞書は英語偏重)
[???]

CLIP の BPE 辞書は圧倒的に英語寄りで、日本語の token はかなり粗い粒度でしか登録されていない。漢字 1 文字単位でバラバラに tokenize されたり、ひらがなと漢字の組み合わせが意図しない単位で区切られたりする。

「赤い猫」は「赤」+「い」+「猫」に分解されるかもしれないし、「赤い」+「猫」になるかもしれない。どちらになるかは BPE のマージルール次第で、日本語話者の直感とはかなりズレる。

さらに深刻なのが、日本語の表現の豊かさがむしろ仇になるケースだ。

「ちょっとだけレトロな雰囲気の喫茶店」という日本語は自然だが、tokenizer は「ちょっと」「だけ」「レトロ」「な」「雰囲気」「の」「喫茶店」とバラす。この断片化された情報から、「ほんの少しのレトロ感」という絶妙なニュアンスを text encoder が再構成できるかというと、かなり難しい。

実際、自分で検証してみて驚いたのが、「赤い猫が椅子に座っている」を日本語のまま SD に入力した場合と、「A red cat sitting on a chair」を入れた場合で、構図の正確さに明確な差が出ること。同じモデル、同じ seed なのに、日本語だと猫と椅子の位置関係が崩れたり、猫の色が赤じゃなくなったりする。

これは CLIP の text encoder が、学習時に見たことのない token の組み合わせに対して適切な埋め込みを生成できないためだ。日本語は学習データに少ないから、token 単位で見れば既知でも、その並び方のパターンが学習されていない。

じゃあどうするか——Gazouya での取り組み

ここまで書いてきた課題に対して、Gazouya では次のようなアプローチを取っている。

日本語プロンプトの前処理パイプライン

Gazouya では大きく分けて 4 つの段階を踏んでいる。

1. 形態素解析による構造化

ユーザーが入力した日本語を、まず形態素解析(MeCab + UniDic)で意味のある単位に分解する。「桜が舞い散る夕暮れの京都の路地」なら、「桜」「舞い散る」「夕暮れ」「京都」「路地」のように、名詞・動詞・修飾関係を特定する。これによって、BPE tokenizer の「漢字 1 文字ずつバラす」問題を回避できる。

2. スタイル指定と被写体の分離

日本語プロンプトには「○○風」「○○のような雰囲気」といったスタイル指定が、被写体の説明と一緒くたに書かれることが多い。これを分離して、スタイルは重み付け調整、被写体は正確な翻訳、という別々の処理をかける。

3. 英語翻訳 + 日本語 token のハイブリッド埋め込み

核となるのはこの処理。被写体や構図の情報は英語に翻訳して CLIP の得意領域で処理させる。一方、日本語特有の美的感覚(「侘び寂び」「萌え」「エモい」など翻訳不可能な概念)は、専用の日本語 embedding で補完する。この 2 つを attention レベルで融合させている。

4. ネガティブプロンプトの自動生成

ユーザーが明示的に書かなくても、生成結果の傾向から「このスタイル指定だと崩れやすいパターン」を学習して、自動的に negative prompt を補う。たとえば「水彩風」と指定されたら、「oil painting, thick brushstrokes, heavy texture」を自動で negative に追加する。

これらの処理は表からは見えない。ユーザーはただ日本語で prompt を入力するだけ。でも裏側ではこれだけのことをやって、ようやく「期待通りの絵」に近づけている。実際の画面で言うと、こういう流れだ。

Gazouyaで日本語プロンプトを入力し、生成画像が出力されるまでの画面

たとえば上の例では「水彩画風の猫が本を読んでいるイラスト」という日本語プロンプトに対して、前処理パイプラインが裏側で走り、出力された画像がこれだ。ユーザーは英語に翻訳する必要も、tokenizer の分割を気にする必要もない。

良いプロンプトを設計するために——開発者が知っておくべきこと

ここまで読んで「結局どうすればいいんだ」と思った人のために、実践的なポイントを 3 つ。

tokenizer のクセを知る

これが一番効く。CLIP の tokenizer が実際にどんな token を出力するかは、Hugging Face の transformers ライブラリで数行で確認できる。

from transformers import CLIPTokenizer

tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
tokens = tokenizer.encode("赤い猫が椅子に座っている")
print(tokenizer.convert_ids_to_tokens(tokens))
# → ['赤', 'い', '猫', 'が', '椅子', 'に', '座', 'って', 'いる']

この結果を見れば、自分のプロンプトが意図した通りに分割されているかどうかが一目でわかる。分割が細かすぎる単語は、英語に置き換えるか、より一般的な表現に変更するという判断ができる。

英語ベースのモデルなら英語で書くのが基本

当たり前のようで見落とされがち。Midjourney も Stable Diffusion も DALL·E も、コアの text encoder は英語前提で学習されている。日本語で細かいニュアンスを伝えたいなら、少なくとも主要な被写体と構図は英語で書いたほうが精度は高い。ただし、これはユーザーに強いるべきことではない。ツール側が吸収すべき課題だ。

スタイル指定は構造化する

「ジブリ風で水彩タッチの、柔らかい光の差す森の中の小屋」みたいなプロンプトは、人間には伝わるが tokenizer は混乱する。スタイル(ジブリ風、水彩タッチ、柔らかい光)と被写体(森の中の小屋)を分けて、それぞれに適切な重み付けをするのが理想だ。AUTOMATIC1111 の WebUI なら (word:1.2) 記法が使えるが、そもそも分離して入力できたほうがユーザー体験としては優れている。

おわりに

AI 画像生成のプロンプト設計は、単なる「単語の選び方」ではない。tokenizer がどうテキストを解釈し、text encoder がどうベクトル化し、それが diffusion model にどう影響するか——このパイプライン全体を理解しているかどうかで、出力の質は大きく変わる。

そして、日本語で AI 画像生成を使うというのは、実はかなり特殊な要求だ。ほとんどのモデルが英語圏で作られている以上、日本語をそのまま放り込んでうまくいかないのは、仕組みを知ればある意味当然でもある。

このギャップをどう埋めるか。少なくとも自分は、Gazouya をそのための実験台にしている。日本語でイメージを伝えられる画像生成——まだ道半ばだけど、手応えはある。


筆者について:Gazouya(がぞうや)という AI 画像生成サービスを個人開発しています。Stable Diffusion / ComfyUI / LoRA まわりを中心に記事を書いています。
Gazouya: https://gazouya.com

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?