西川和久の不定期コラム

自分で撮影したグラビアを使い、Stable Diffusion用美女モデルを作成してみた

 日頃、civtaiなどにあるリアル系美女モデル(Model)と自作顔LoRAで遊んでいる筆者だが、自分で撮影したグラビアが直ぐ用意できるものだけでも数千枚あるので、これでモデル作ったらどうなる!? 的に始めてみた。

MERGEとTRAINED、顔LoRAとファインチューニングの話

 civitaiのModelsを眺めていると、Typeに「CHECKPOINT MERGE」と「CHECKPOINT TRAINED」、2種類あるのに気づく。

TypeにCHECKPOINT MERGE
TypeにCHECKPOINT TRAINED

 前者は文字通り複数のモデルをマージして新しいモデルを作る手法。これは筆者もお気に入りのモデルを組み合わすとどうなるか? 的に試したことがある。簡単なやり方だが、AUTOMATIC1111に「Checkpoint Merger」という項目があるので、これを使う。

 実際どんな動きをしているのか? までは把握していないので、ロジック的な説明はできないのだが、操作はモデルを2つ(以上)選び、Multiplier (M) - set to 0 to get model Aの数値を変更するだけといった感じで簡単だ。

AUTOMATIC1111 / Checkpoint Merger。ここで好みのモデルを選び、各項目を設定、実行すると割と時間もかからずモデルが生成される

 上記は単純マージと呼ばれているものだが、階層マージと呼ばれる、各層に重みを付け、加重/差分マージを行なう高度な方法もある。そしてもうお気づきかと思うが、マージモデルは、画像を学習することはない。

 それ対して後者、TRAINEDは、名前の通り学習、つまり何らのテーマに沿った画像を用意し、それを基準となるモデルに学習させ、新しいモデルを作る手法だ。従って筆者がやりたいのはこちらとなる。

 必要画像枚数が10~15枚程度と少ないものの、顔LoRA(Low-Rank-Adaptation)もざっくり言えばこの1種。基準となるモデルで学習し、学習した部分だけを別ファイルとして保存。必要に応じて使用する。モデル自体をファインチューニング(Fine Tuning)する方法と顔LoRAの違いを簡単にまとめると以下のようになる。

【表】ファインチューニングと顔LoRAの違い
必要枚数learning_rate合計ステップ数
ファインチューニング1万枚(前後?)1e-6~5e-6枚数=1 epoch x ?
顔LoRA10~15枚1e-4枚数x10x20(経験値)

 顔LoRAは、同一人物の顔さえ似ればいいので、枚数は極端に少なく、その分、learning_rateが桁2つ高くなっている。合計ステップ(step)数は10枚で2,000、15枚で3,000。これはどこかに基準が載っているわけではなく、筆者の経験から来た値だ。

 対してファインチューニングは、同一人物ではなく、広く浅く(今回は日本人美女を)学習するため、できるだけ枚数は多く、そして顔LoRAより桁2つ少ないlearning_rateで学習する。いろいろ調べると、1万枚程度を使うとのこと。

 問題は合計ステップ数。合計枚数=1 epochとなり、何epoch繰り返すのかだが、たとえば1万枚だと10回繰り返すだけで10万ステップという、顔LoRAとは桁違いの数字となる。

 これがどの程度が適正か? は、まだ始めたばかりなので不明な部分だったりする。

SDXLリアル系モデルを作るにはどうすればよいのか

 今回はSDXLのモデルをファインチューニングしたい。用意するものは以下の通り

  1. テーマに沿った大量の画像
  2. その1枚1枚にcaptionファイル(内容を説明した英文)
  3. その1枚1枚にtagファイル(よくPromptでみかける 1girl, solo と言ったタグ)
  4. sd-scripts
  5. sd_xl_base_1.0.safetensors
  6. GeForce RTX 3090や4090など、VRAM 24GBのGPU

 1~5は後述するが、問題は6。実際学習すると分かるのだが、sd-scriptsの省VRAMオプションを駆使してもVRAMを約22GB使用する。従って24GB搭載しているRTX 3090、4090クラスでないとローカルPCでは学習できない。もちろんSD 1.5ならもっと少ない容量でOKだが、個人的に興味がないため試していない。

テーマに沿った大量の画像

 今回はすぐ出せる画像で手元に約4,500枚。もちろんまだまだあるのだが、古いものは着脱式HDD数十台に保存しており、さらにそのマウンタを装備するPCがなく(笑)、サクッと出せないので諦めた。

 実は、そもそもこれをやってみようと思った理由なのだが、カメラマンでもない人が万単位の画像を集めるとなると、ネットからダウンロードするしか手がない(この是非ついては今回は触れない。あくまでも技術論としたい)。

 そして人物の場合、ほとんどが肌や顔を修正した画像となる。これで学習したものは、はたしてリアルな人肌? 人の顔? と言う疑問が前からあったのだ。

 手持ちの画像は、筆者が撮影しており、納品先では修正していると思うが、手元のは無修正の写真。つまりリアルな人肌や顔だ。これを使って学習すればどうなるのか? が興味の対象だったりする。実際、顔LoRAも修正したり適当に撮った写真と、ちゃんと撮った無修正の写真では圧倒的な差が出ており、ファインチューニングでも同じでは? ということだ。

 以下、Pythonのプログラムで処理している。実際はGeminiにこんな感じの仕様を提示しコードを出してもらった。

  1. 基準フォルダに画像が入った複数のフォルダごとコピー
    → いきなり1つのフォルダに手動でコピーするとファイル名が重複するため
  2. 長辺1,024px未満は除外
    → 対象は基準フォルダ以下全てのjpg|jpeg|JPG|JPEG
    → SDXLは1,024x1,024だがオプション指定によりトリミングする必要無し
  3. Adobe RGBのものはsRGBへ変換
  4. アスペクト比を維持しつつ、長辺1,024pxへ縮小
  5. 連番のファイル名で出力フォルダへ保存。拡張子は.jpgへ統一

 長くなるのでプログラムは省略するが、分かる人なら簡単に書けるし、分からない人でもGeminiやChat GPTなどに聞くと、似たようなコードが出てくる。

データセットの一部。後述する.captionと、.txtも一緒に入っている

 これで1つのフォルダに長辺1,024pxで連番のファイル名となった画像ファイル(.jpg)が用意できた。

.captionファイル

 いろいろ方法はあるのだが、BLIP-2を使用した。簡単に言えばマルチモーダルLLMの一種で画像からキャプションを生成する。モデルは、Salesforce/blip2-opt-2.7bと、Salesforce/blip2-opt-6.7bの2つあるのだが、後者は容量不足で動かなかった。

 これもPythonのプログラムで処理している。仕様は上記の画像を入れてあるフォルダから、1枚ずつ画像を読み込み、キャプションを生成。ファイル名は同じで拡張子を.captionとして保存する。

 参考にしたのはこのサイト。基本、サイトにあるコードと同じなのだが、フォルダ内にある全画像でループを回し、.captionファイルを保存しているのが違いだ。初回は、モデルを自動的にダウンロードするので時間がかかる(blip2-opt-2.7bで15GBほど)。

 image = Image.open("../data/woman.jpg")

 ↓これを以下の様に変える

# 入力フォルダのパス
input_dir = "/checkpoint/dataset"
output_dir = "/checkpoint/caption"

# サブフォルダも含めて画像ファイルを検索
files = glob.glob(os.path.join(input_dir, "**", "*.jpg"), recursive=True)

count = 1
# 画像ファイル処理
for file in files:
    # ファイル名と拡張子を分割
    filename, ext = os.path.splitext(os.path.basename(file))
    # 画像を読み込み
    image = Image.open(file)
.
. image = Image.open("../data/woman.jpg")以降のコードと同じ
.
    # 出力ファイル名
    output_file = os.path.join(output_dir, filename + ".caption")

    # キャプションをテキストファイルに出力
    with open(output_file, "w") as f:
        f.write(generated_text)

    count += 1

print("処理が完了しました。")
BLIP-2の実行結果。A woman in a bikiniなど、結構あっさりした文章しか出ない

 話は前後するが、先に後述するsd-scripts用の環境を作っておくと良い。と言うのも、CUDAなど、使用する基本的なモジュールは同じだからだ。その後、実行しモジュールがないと、止まった部分だけpip installすれば動くようになる。

.txt(tag)ファイル

 これもいろいろあるが、JoyTagというのが目に止まったのでこれを使用。デモサイトがここにあり、このコードを参考にした。

 キャプションと動きは同じでループの中が変わっただけ。画像を入れてあるフォルダから、1枚ずつ画像を読み込み、tagを生成。ファイル名は同じで拡張子を.txtとして保存する。

MyCkptで生成した画像を使ってのJoyTagの実行結果。プロンプトでよく見かける。余談になるが、この写真、往年の岩佐 真悠子さんにそっくり。撮ったことあるものの、かなり前で着脱式HDDの中のため、今回のデータセットには入ってない。AIの空似だ(笑)

 キャプションにしてもタグにしても、マルチモーダル対応のOpenAI APIなどを使えば可能なのだが、今回の写真のように、女性で肌露出が多い画像を入力すると、利用規約違反で動かないので、あてにしない方がいい(笑)。

sd-scriptsで学習

 さてこれでデータセットに必要な全データが揃ったので、sd-scriptsを実行する環境を用意する。今回のファイル構造は以下の通り。もちろん適当に変えても問題はない。

ファイル構造
checkpoint/
├dataset/
├output/
├config.toml
├meta_clean.json
├sd_xl_base_1.0.safetensors
sd-scripts/
└finetune/

 まずMinicondaでpython 3.10.6の環境を作り切り替える。次にsd-scriptsをgit clone、モジュールをインストールする。この辺りはGitHubにある手順と同じだが、bitsandbytesとtritonを追加でインストールする必要がある。accelerate configの最後はbf16を指定。

conda create -n sds python=3.10.6
conda activate sds

git clone https://github.com/kohya-ss/sd-scripts sd-scripts
cd sd-scripts

pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118
pip install --upgrade -r requirements.txt
pip install xformers==0.0.20
pip install bitsandbytes
pip install .\triton-2.0.0-cp310-cp310-win_amd64.whl
※ https://huggingface.co/r4ziel/xformers_pre_built/resolve/main/triton-2.0.0-cp310-cp310-win_amd64.whl を事前にダウンロード

accelerate config
- This machine
- No distributed training
- NO
- NO
- NO
- all
- bf16

 次にファインチューニングでは、.captionファイルと.txtファイルを1つの.jsonファイルへまとめる必要があり、sd-scriptsのfinetuneフォルダにあるプログラムを利用する。実行する前に、.captionファイルと、.txtファイルを画像のあるdatasetにコピーするのを忘れずに!

.caption から .json変換
python merge_captions_to_metadata.py --full_path '/checkpoint/dataset' meta_cap.json
.txtから .jsonへ変換/追記
python merge_dd_tags_to_metadata.py --full_path '/checkpoint/dataset' --in_json meta_cap.json meta_cap_dd.json
クリーニング(不要なものを削除する)
python clean_captions_and_tags.py meta_cap_dd.json meta_clean.json

 これらを順に実行するとmeta_clean.jsonができているので、これを /checkpointへコピー。config.tomlで指定する

config.toml
[[datasets]]
resolution = [1024, 1024]
batch_size = 1
[[datasets.subsets]]
image_dir = '/checkpoint/dataset'
metadata_file = '/checkpoint/meta_clean.json' # 上記で変換したjson

 これで準備OK! sd-scriptsフォルダに戻り以下のコマンドを実行すれば学習を開始する。

accelerate launch --num_cpu_threads_per_process 1 sdxl_train.py \
--pretrained_model_name_or_path='/checkpoint/sd_xl_base_1.0.safetensors' \
--dataset_config='/checkpoint/config.toml' \
--output_dir='/checkpoint/output' \
--output_name='MyCkpt' \
--save_model_as=safetensors \
--max_train_epochs=100 \
--learning_rate=5e-6 \
--save_every_n_epochs=5 \
--use_8bit_adam \
--xformers \
--mixed_precision='bf16' --full_bf16 \
--gradient_checkpointing \
--enable_bucket --bucket_no_upscale \
--no_half_vae \
--cache_text_encoder_outputs

 sd_xl_base_1.0.safetensorsは、Stableが出しているSDXLのベースモデル。これを使って学習する。というのも、美女は用意した画像に大量に入っているものの、一般的な風景や物などは全く入っておらず、これまで用意するのは大変だからだ(というか無理)。そこは、このモデルを使う、というわけだ。ほかにお気に入りのモデルがあればそれでも良い。

 いろいろパラメータがあるが、意味はこんな感じとなる。

  • --enable_bucket --bucket_no_upscale : 縦位置/横位置異なるサイズ対応。1,024x1,024/1:1にトリミングする必要なし
  • --cache_text_encoder_outputs : テキストエンコーダーをキャッシュ(GPU負荷低減)
  • --mixed_precision='bf16' --full_bf16 : 必ず併用。fp16だとVRAMが足らず動かない
  • --save_every_n_epochs=5 : 5 epochs毎に保存
  • --max_train_epochs=100 : 最大epoch数
  • --learning_rate=5e-6 : 通常1e-6~5e-6。どの辺りが適正値か不明
--enable_bucketの様子・2:3/3:2の640x1,024が3,710枚、1,024x640が795枚。3:4/4:3の768x1,024と1,024x768が若干混じってる

 学習が始まり、かかる時間を見ると、ざっくり4.5K枚1周で1時間(GeForce RTX 3090 + eGPUボックス)。おそらくPCI Express x16接続のGeForce RTX 4090だと倍の速度で処理できる=4.5K枚30分。オプションで指定した--max_train_epochs=100だと、1 epoch = 4,500ステップ×100なので450,000ステップ。100時間=約4日! かかる計算だ。なおこの100が正解なのか、もっと少なくていいのか、もっと多く必要なのかは不明。

 また、learning_rateも5e-6が正解なのだろうか。1e-6/2e-6/3e-6/4e-6どれがいいのかも分からない。順に試すにしても、結果が出るまで時間がかかる。とりあえず真ん中の3e-6を試したが、5e-6より効きが弱い。よって4e-6か5e-6、どちらかとなりそうな感じだろうか。

学習中。VRAM 21.9GB使用中。NVIDIAのドライバは例のVRAMオフロードをオフにしないと処理が激遅になる
epoch数10/20/30のXYZプロット。一番上はsd_xl_base_1.0。これを見る限り30 epochsが良さそうだが、実際使うと20 epochsが調子いい。また、学習ロスも後者の方が若干少ない。10万ステップ辺りが妥当な線か!?
sd_xl_base_1.0.safetensorsで生成。これはこれで悪くはない
MyCkpt.safetensorsで生成(20 epochs版)。肌の質感などが全く異なる。こちらの方がより実写的なポートレートっぽい

 執筆中はまだ30 epochs。この状態でのXYZプロットと、同一設定のsd_xl_base_1.0とMyCkptで生成した画像を掲載した。5~15 epochsだと、体が崩れるケースも多かったのだが、20 epochs以降、それが減った感じだ(なくなったわけではない)。

 今の課題は、顔はともかく体が崩れやすいこと。epoch数なのか? そもそもそlearning_rate=5e-6が高いのか? 3e-6では少しの変化だったので、3e-6か4e-6で20 epochs程度まで回せば分かると思われるが、約20時間……。結果が分かるまで時間がかかり過ぎる(笑)。

 入稿後、SDXLのデフォルト--learning_rate=は4e-7と知り、試したところ10 epochsでは変化は少ないものの、体が崩れる現象はそれほど出ず、顔も良好だった。さらに回すとどうなるか!? これから試される方は、4e-7で始めてほしい。

 また、全ての画像、著作権は筆者だが、モデル用に学習してもいいのか、各所属事務所に確認していない関係上、できたモデルをcivitaiなどで公開することはない。あくまでも筆者専用実験モデルだ。


 以上、sd-scriptsを使ってファインチューニングする方法を解説した。LoRA関連は調べれば山盛り出るのだが、ファインチューニングに関しては部分的に書かれたものはあっても、全てまとまっているのは皆無。主にsd-scriptsのGitHubにある情報だけを頼りに試している。従って見落としなど、間違っている/抜けている部分もあるかもしれないが、その時は何らかの方法でお伝えしたい。