生成AIをGoogle Colaboratoryで簡単に 【Part6 画像生成AI Stable Diffusion XL編】
はじめに
今回は、初心者向けにGoogle Colaboratoryで、簡単に生成AIを使えるようにする環境を作ります。
(音声対話システムを作成するための個人的な忘備録の意味合いが強いです)
Part6の今回は、画像生成AIを使えるようにします。
今回利用する画像生成AIはStable Diffusion XLになります。
なお、今回Part6ですが、Part1~Part5の内容を読まなくても、わかるように記載しています。
画像生成AIということで、直接的には音声対話システムとは無関係な技術のように見えますが、AIの発話に合わせて適切な画像を表示するみたいなことに使えるかなと思い、勉強しています。
Stable Diffusion XLとは
Stable Diffusion XL(SDXL)とは、Stability AI社が開発した画像生成AIの一つです。
2024年7月時点では、Stable Diffusion 3という最新モデルがリリースされておりますが、それまでは最も高性能なモデルとして使われていたモデルになります。
Stable Diffusion 3は別のPartの記事に譲るとして、今回はStable Diffusion XLに関して扱っていこうと思います。
またもう一つ、Stable Diffusionを利用する場合、主流な使い方は、WebUIを利用するパターンだと思います。しかしながらGoogle ColabはWebUIの利用がNG(利用規約的に)のため、これまでと同様にPythonスクリプトの中で画像を生成していきます。
(Diffuserというライブラリを利用します)
成果物
下記のリポジトリをご覧ください。
解説
下記の通り、解説を行います。
まずは上記のリポジトリをcloneしてください。
git clone https://github.com/personabb/colab_AI_sample.git
その後、cloneしたフォルダ「colab_AI_sample」をマイドライブの適当な場所においてください。
ディレクトリ構造
Google Driveのディレクトリ構造は下記を想定します。
MyDrive/
└ colab_AI_sample/
└ colab_SDXL_sample/
├ configs/
| └ config.ini
├ outputs/
├ module/
| └ module_sd.py
└ StableDiffusionXL_sample.ipynb
-
colab_AI_sample
フォルダは適当です。なんでも良いです。1階層である必要はなく下記のように複数階層になっていても良いです。MyDrive/hogehoge/spamspam/hogespam/colab_AI_sample
-
outputs
フォルダには、生成後の画像が格納されます。最初は空です。- 連続して生成を行う場合、過去の生成内容を上書きするため、ダウンロードするか、名前を変えておくことをオススメします。
使い方解説
StableDiffusionXL_sample.ipynb
をGoogle Colabratoryアプリで開いてください。
ファイルを右クリックすると「アプリで開く」という項目が表示されるため、そこからGoogle Colabratoryアプリを選択してください。
もし、ない場合は、「アプリを追加」からアプリストアに行き、「Google Colabratory」で検索してインストールをしてください。
Google Colabratoryアプリで開いたら、StableDiffusionXL_sample.ipynb
のメモを参考にして、一番上のセルから順番に実行していけば、問題なく最後まで動作して、音声文字起こしをすることができると思います。
コード解説
主に、重要なStableDiffusionXL_sample.ipynb
とmodule/module_sd.py
について解説します。
StableDiffusionXL_sample.ipynb
該当のコードは下記になります。
下記に1セルずつ解説します。
1セル目
#SDXL で必要なモジュールのインストール
!pip install diffusers
!pip install transformers
!pip install scikit-learn
!pip install ftfy
!pip install accelerate
!pip install invisible_watermark
!pip install safetensors
ここでは、必要なモジュールをインストールしています。
Google colabではpytorchなどの基本的な深層学習パッケージなどは、すでにインストール済みなため上記だけインストールすれば問題ありません。
2セル目
#Google Driveのフォルダをマウント(認証入る)
from google.colab import drive
drive.mount('/content/drive')
# カレントディレクトリを本ファイルが存在するディレクトリに変更する。
import glob
import os
pwd = os.path.dirname(glob.glob('/content/drive/MyDrive/**/colab_SDXL_sample/StableDiffusionXL_sample.ipynb', recursive=True)[0])
print(pwd)
%cd $pwd
!pwd
ここでは、Googleドライブの中身をマウントしています。
マウントすることで、Googleドライブの中に入っているファイルを読み込んだり、書き込んだりすることが可能になります。
マウントをする際は、Colabから、マウントの許可を行う必要があります。
ポップアップが表示されるため、指示に従い、マウントの許可を行なってください。
また、続けて、カレントディレクトリを/
から/content/drive/MyDrive/**/colab_SDXL_sample
に変更しています。
(**
はワイルドカードです。任意のディレクトリ(複数)が入ります)
カレントディレクトリは必ずしも変更する必要はないですが、カレントディレクトリを変更することで、これ以降のフォルダ指定が楽になります
3セル目
#モジュールをimportする
from module.module_sd import SDXL
module/module_sd.py
のSDXL
クラスをモジュールとしてインポートします。
この中身の詳細は後の章で解説します。
4セル目
#モデルの設定を行う。
config_text = """
[SDXL]
device = auto
n_steps=50
high_noise_frac=0.8
seed=42
vae_model_path = stabilityai/sdxl-vae
base_model_path = stabilityai/stable-diffusion-xl-base-1.0
refiner_model_path = stabilityai/stable-diffusion-xl-refiner-1.0
use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2
cfg_scale = 7.0
width = 1024
height = 1024
output_type = latent
aesthetic_score = 6
negative_aesthetic_score = 2.5
"""
with open("configs/config.ini", "w", encoding="utf-8") as f:
f.write(config_text)
このセルでは、設定ファイルconfigs/config.ini
の中身をconfig_text
の内容で上書きしています。
SDXLは上記の設定に併せて動作をします。
基本的にはこの設定ファイルを変更する必要はありません。
WebUIで主流に使われている設定をこちらでも採用しております。
強いて言えば、下記の部分でモデルを指定していますが、そちらを変更することでモデルを変更できます。ただし、WebUIでは使えても、DiffuserというPython上で実行するライブラリでは使えないモデルもかなり多くあるため、注意してください(1敗)
vae_model_path = stabilityai/sdxl-vae
base_model_path = stabilityai/stable-diffusion-xl-base-1.0
refiner_model_path = stabilityai/stable-diffusion-xl-refiner-1.0
また、生成される画像の解像度は下記で指定しています。
変更することで、縦長の画像にしたり、横長の画像にしたりできますが、大きく変更すると画像が崩れる可能性があるため、注意してください。
width = 1024 #横幅
height = 1024 #縦幅
最後に、本プログラムではseed
を42に固定しているため、同じpromptを入れて、同じ設定の場合、実行のたびに毎回同じ画像が生成されます。
一方で、このseedの値をランダムに変更することで、生成される画像はランダムに変化します。
5セル目
#読み上げるプロンプトを設定する。
main_prompt = """
1 girl ,Yellowish-white hair ,short hair ,red small ribbon,red eyes,red hat ,school uniform ,solo ,smile ,upper body ,Anime ,Japanese,best quality,high quality,ultra highres,ultra quality
"""
negative_prompt="""
Easy negatvie, (worst quality:2),(low quality:2),(normal quality:2), lowers, normal quality,((monochrome)),((grayscale)),skin spots, acnes, skin blemishes, age spot, nsfw, ugly face, fat, missing fingers,missing limbs, extra fingers, extra arms, extra legs,extra limbs, watermark, error, text, blurry, jpeg artifacts, cropped, bad anatomy, bad hand, big eyes
"""
ここで、生成する画像をプロンプトで指定します。
main_prompt
には、生成したい画像の特徴を入力してください。
例えば、人数、性別、髪の毛や服装の特徴など
negative_prompt
には、避けたい画像の特徴を入力してください。
例えば、低い質の画像や指の本数が多いなどの崩壊している画像の特徴など
そうすることで、main_prompt
に近く、negative_prompt
から遠い画像が生成されます。
6セル目
sd = SDXL()
for i in range(3):
image = sd.generate_image(main_prompt, negative_prompt)
image.save("./outputs/SDXL_result_{}.png".format(i))
ここで、画像を生成しています。
for文を使い、同じpromptで3枚の画像を生成しています。
画像はoutputs
フォルダに保存されますが、実行のたびに同じ名前で保存されるので、過去に保存した画像は上書きされるようになりますので注意してください。
実行すると下記のような画像が生成されます。(seed値という乱数表の値を変更しない限り、実行のたびに必ず同じ画像が生成されます。seedは設定ファイルで変更できます。)
module/module_sd.py
続いて、StableDiffusionXL_sample.ipynb
から読み込まれるモジュールの中身を説明します。
下記にコード全文を示します。
コード全文
from diffusers import DiffusionPipeline, AutoencoderKL
import torch
from diffusers.schedulers import DPMSolverMultistepScheduler
import os
import configparser
# ファイルの存在チェック用モジュール
import errno
class SDXLconfig:
def __init__(self, config_ini_path = './configs/config.ini'):
# iniファイルの読み込み
self.config_ini = configparser.ConfigParser()
# 指定したiniファイルが存在しない場合、エラー発生
if not os.path.exists(config_ini_path):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini_path)
self.config_ini.read(config_ini_path, encoding='utf-8')
SDXL_items = self.config_ini.items('SDXL')
self.SDXL_config_dict = dict(SDXL_items)
class SDXL:
def __init__(self,device = None, config_ini_path = './configs/config.ini'):
SDXL_config = SDXLconfig(config_ini_path = config_ini_path)
config_dict = SDXL_config.SDXL_config_dict
if device is not None:
self.device = device
else:
device = config_dict["device"]
self.device = "cuda" if torch.cuda.is_available() else "cpu"
if device != "auto":
self.device = device
self.n_steps = int(config_dict["n_steps"])
self.high_noise_frac = float(config_dict["high_noise_frac"])
self.seed = int(config_dict["seed"])
self.generator = torch.Generator(device=self.device).manual_seed(self.seed)
self.vae_model_path = config_dict["vae_model_path"]
self.VAE_FLAG = True
if self.vae_model_path == "None":
self.vae_model_path = None
self.VAE_FLAG = False
self.base_model_path = config_dict["base_model_path"]
self.REFINER_FLAG = True
self.refiner_model_path = config_dict["refiner_model_path"]
if self.refiner_model_path == "None":
self.refiner_model_path = None
self.REFINER_FLAG = False
self.use_karras_sigmas = config_dict["use_karras_sigmas"]
if self.use_karras_sigmas == "True":
self.use_karras_sigmas = True
else:
self.use_karras_sigmas = False
self.scheduler_algorithm_type = config_dict["scheduler_algorithm_type"]
if config_dict["solver_order"] != "None":
self.solver_order = int(config_dict["solver_order"])
else:
self.solver_order = None
self.cfg_scale = float(config_dict["cfg_scale"])
self.width = int(config_dict["width"])
self.height = int(config_dict["height"])
self.output_type = config_dict["output_type"]
self.aesthetic_score = float(config_dict["aesthetic_score"])
self.negative_aesthetic_score = float(config_dict["negative_aesthetic_score"])
self.base , self.refiner = self.preprepare_model()
def preprepare_model(self):
if self.VAE_FLAG:
vae = AutoencoderKL.from_pretrained(
self.vae_model_path,
torch_dtype=torch.float16)
base = DiffusionPipeline.from_pretrained(
self.base_model_path,
vae=vae,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
base.to(self.device)
if self.REFINER_FLAG:
refiner = DiffusionPipeline.from_pretrained(
self.refiner_model_path,
text_encoder_2=base.text_encoder_2,
vae=vae,
requires_aesthetics_score=True,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
refiner.enable_model_cpu_offload()
else:
refiner = None
else:
base = DiffusionPipeline.from_pretrained(
self.base_model_path,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
base.to(self.device)
if self.REFINER_FLAG:
refiner = DiffusionPipeline.from_pretrained(
self.refiner_model_path,
text_encoder_2=base.text_encoder_2,
requires_aesthetics_score=True,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
refiner.enable_model_cpu_offload()
else:
refiner = None
if self.solver_order is not None:
base.scheduler = DPMSolverMultistepScheduler.from_config(
base.scheduler.config,
use_karras_sigmas=self.use_karras_sigmas,
Algorithm_type =self.scheduler_algorithm_type,
solver_order=self.solver_order,
)
return base, refiner
else:
base.scheduler = DPMSolverMultistepScheduler.from_config(
base.scheduler.config,
use_karras_sigmas=self.use_karras_sigmas,
Algorithm_type =self.scheduler_algorithm_type,
)
return base, refiner
def generate_image(self, prompt, neg_prompt,seed = None):
if seed is not None:
self.generator = torch.Generator(device=self.device).manual_seed(seed)
image = self.base(
prompt=prompt,
negative_prompt=neg_prompt,
cfg_scale=self.cfg_scale,
num_inference_steps=self.n_steps,
denoising_end=self.high_noise_frac,
output_type=self.output_type,
width = self.width,
height = self.height,
generator=self.generator
).images[0]
if self.REFINER_FLAG:
image = self.refiner(
prompt=prompt,
negative_prompt=neg_prompt,
cfg_scale=self.cfg_scale,
aesthetic_score = self.aesthetic_score,
negative_aesthetic_score = self.negative_aesthetic_score,
num_inference_steps=self.n_steps,
denoising_start=self.high_noise_frac,
image=image[None, :]
).images[0]
return image
if __name__ == "__main__":
sd = SDXL()
prompt = "1 girl ,pink hair ,long hair ,blue ribbon ,train interior ,school uniform ,solo ,smile ,upper body"
#neg_prompt = "extra limbs ,NSFW ,text ,signature ,bad anatomy ,((worth quality ,low quality)) ,normal quality ,bad face ,bad hand ,missing fingers ,missing limbs ,extra fingers ,extra limbs"
neg_prompt = "Easy negatvie, (worst quality:2),(low quality:2),(normal quality:2), lowers, normal quality,((monochrome)),((grayscale)),skin spots, acnes, skin blemishes, age spot, nsfw, ugly face, fat, missing fingers,missing limbs, extra fingers, extra arms, extra legs,extra limbs, watermark, text, error, blurry, jpeg artifacts, cropped, bad anatomy, bad hand, big eyes"
for i in range(3):
image = sd.generate_image(prompt, neg_prompt)
image.save("result_{}.png".format(i))
では一つ一つ解説していきます。
SDXLconfigクラス
class SDXLconfig:
def __init__(self, config_ini_path = './configs/config.ini'):
# iniファイルの読み込み
self.config_ini = configparser.ConfigParser()
# 指定したiniファイルが存在しない場合、エラー発生
if not os.path.exists(config_ini_path):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini_path)
self.config_ini.read(config_ini_path, encoding='utf-8')
SDXL_items = self.config_ini.items('SDXL')
self.SDXL_config_dict = dict(SDXL_items)
ここではconfig_ini_path = './configs/config.ini'
で指定されている設定ファイルをSDXL_config_dict
として読み込んでいます。
辞書型で読み込んでいるため、設定ファイルの中身をpythonの辞書として読み込むことが可能になります。
SDXLクラスのinitメソッド
class SDXL:
def __init__(self,device = None, config_ini_path = './configs/config.ini'):
SDXL_config = SDXLconfig(config_ini_path = config_ini_path)
config_dict = SDXL_config.SDXL_config_dict
if device is not None:
self.device = device
else:
device = config_dict["device"]
self.device = "cuda" if torch.cuda.is_available() else "cpu"
if device != "auto":
self.device = device
self.n_steps = int(config_dict["n_steps"])
self.high_noise_frac = float(config_dict["high_noise_frac"])
self.seed = int(config_dict["seed"])
self.generator = torch.Generator(device=self.device).manual_seed(self.seed)
self.vae_model_path = config_dict["vae_model_path"]
self.VAE_FLAG = True
if self.vae_model_path == "None":
self.vae_model_path = None
self.VAE_FLAG = False
self.base_model_path = config_dict["base_model_path"]
self.REFINER_FLAG = True
self.refiner_model_path = config_dict["refiner_model_path"]
if self.refiner_model_path == "None":
self.refiner_model_path = None
self.REFINER_FLAG = False
self.use_karras_sigmas = config_dict["use_karras_sigmas"]
if self.use_karras_sigmas == "True":
self.use_karras_sigmas = True
else:
self.use_karras_sigmas = False
self.scheduler_algorithm_type = config_dict["scheduler_algorithm_type"]
if config_dict["solver_order"] != "None":
self.solver_order = int(config_dict["solver_order"])
else:
self.solver_order = None
self.cfg_scale = float(config_dict["cfg_scale"])
self.width = int(config_dict["width"])
self.height = int(config_dict["height"])
self.output_type = config_dict["output_type"]
self.aesthetic_score = float(config_dict["aesthetic_score"])
self.negative_aesthetic_score = float(config_dict["negative_aesthetic_score"])
self.base , self.refiner = self.preprepare_model()
まず、設定ファイルの内容をconfig_dict
に格納しています。これは辞書型のため、config_dict["device"]
のような形で設定ファイルの内容を文字列として取得することができます。
あくまで、すべての文字を文字列として取得するため、int型やbool型にしたい場合は、適宜型変更をする必要があることに注意してください。
続いて下記の順番で処理を行います。
- モデルを動作させる
device
を指定する - 設定ファイルの各種設定を取得する
- モデルを定義する。
- 設定ファイルに合わせて、適切なモデルを定義する
-
self.preprepare_model()
メソッドで定義する
SDXLクラスのpreprepare_modelメソッド
class SDXL:
・・・
def preprepare_model(self):
if self.VAE_FLAG:
vae = AutoencoderKL.from_pretrained(
self.vae_model_path,
torch_dtype=torch.float16)
base = DiffusionPipeline.from_pretrained(
self.base_model_path,
vae=vae,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
base.to(self.device)
if self.REFINER_FLAG:
refiner = DiffusionPipeline.from_pretrained(
self.refiner_model_path,
text_encoder_2=base.text_encoder_2,
vae=vae,
requires_aesthetics_score=True,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
refiner.enable_model_cpu_offload()
else:
refiner = None
else:
base = DiffusionPipeline.from_pretrained(
self.base_model_path,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
base.to(self.device)
if self.REFINER_FLAG:
refiner = DiffusionPipeline.from_pretrained(
self.refiner_model_path,
text_encoder_2=base.text_encoder_2,
requires_aesthetics_score=True,
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True
)
refiner.enable_model_cpu_offload()
else:
refiner = None
if self.solver_order is not None:
base.scheduler = DPMSolverMultistepScheduler.from_config(
base.scheduler.config,
use_karras_sigmas=self.use_karras_sigmas,
Algorithm_type =self.scheduler_algorithm_type,
solver_order=self.solver_order,
)
return base, refiner
else:
base.scheduler = DPMSolverMultistepScheduler.from_config(
base.scheduler.config,
use_karras_sigmas=self.use_karras_sigmas,
Algorithm_type =self.scheduler_algorithm_type,
)
return base, refiner
このメソッドはinit
メソッドで呼ばれて、モデルを定義して読み込むメソッドになります。
SDXLでは、base
モデルとrefiner
モデルとvae
モデルの3つのモデルがある。
base
モデルが低解像度で大まかに画像を生成し、refiner
モデルが生成された低解像度画像を高解像度にしつつ、細かい詳細部分をより綺麗にする役割を担っています。またvae
モデルは、適切なものを設定することで、より綺麗な画像を生成することができます。
今回の設定ファイルでは、3つすべてのモデルを利用することになっていますが、設定ファイル次第では、3つのモデルのうち、2つや1つしか使わない設定にすることも可能なように、このメソッドは設計しています。
(モデルを変更した場合に、vae
モデルを使うことを推奨していないモデルやrefiner
モデルを使うことを推奨していないモデルがあるため、それにも対応できるようにしています。)
SDXLクラスのgenerate_imageメソッド
class SDXL:
・・・
def generate_image(self, prompt, neg_prompt,seed = None):
if seed is not None:
self.generator = torch.Generator(device=self.device).manual_seed(seed)
image = self.base(
prompt=prompt,
negative_prompt=neg_prompt,
cfg_scale=self.cfg_scale,
num_inference_steps=self.n_steps,
denoising_end=self.high_noise_frac,
output_type=self.output_type,
width = self.width,
height = self.height,
generator=self.generator
).images[0]
if self.REFINER_FLAG:
image = self.refiner(
prompt=prompt,
negative_prompt=neg_prompt,
cfg_scale=self.cfg_scale,
aesthetic_score = self.aesthetic_score,
negative_aesthetic_score = self.negative_aesthetic_score,
num_inference_steps=self.n_steps,
denoising_start=self.high_noise_frac,
image=image[None, :]
).images[0]
return image
このメソッドは、ここまでで読み込んだモデルと設定を利用して、実際に画像を生成するメソッドです。
本メソッドの引数にてseed
を指定すると、設定ファイルを上書きして、seedを指定できます。
したがって、ここで乱数を指定することで、ランダムな画像を生成できるようになります。
また、引数でseed
を指定した場合は、
self.generator = torch.Generator(device=self.device).manual_seed(seed)
が呼ばれます。
一方で、引数で指定しない場合は、設定ファイルのseed
が使用されて、同様のgenerator
がinit
メソッドで作成されます。
したがって、生成のたびに一つ大きなseedに変更されて適用される(42→43→44)ため、生成のたびに画像を変化させることができます。
本メソッドの通り、refiner
モデルを利用する場合は、base
モデル通過後、refiner
モデルを通って画像が生成される形になります。
まとめ
今回は、初心者向けにGoogle Colaboratoryで、簡単に生成AIを使えるようにする環境を作りました。
Part6の今回は、生成AIの一つ、画像生成AIのStable Diffusion XLモデルを使えるようにしました。
画像生成AIを利用することで、音声対話システムで音声対話を行いながら、その対話に合わせた画像を生成することができるため、よりエンターテイメント性を高めることができます。
実際に、音声対話システムに組み込んでみたので、そちらに関しては後日記事にする予定です
(下記の対話システムに組み込んでいます)
しかしながら、Stable Diffusion XLモデルは高性能で、生成される画像の品質は非常に高いですが、画像を生成するまでにかかる時間が非常に長いため、リアルタイムの音声対話システムで利用するのは難しいという課題があります。
したがって次回は、より高速に動作する画像生成AIであるSDXL-turboを取り扱いたいと思います。
Discussion