CoolFace
Apppublic

Allex21/LT

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
train_util.py5733 linesDownload Raw Back to sd-scripts
1# common functions for training2 3import argparse4import ast5import asyncio6import datetime7import importlib8import json9import logging10import pathlib11import re12import shutil13import time14from typing import (15    Dict,16    List,17    NamedTuple,18    Optional,19    Sequence,20    Tuple,21    Union,22)23from accelerate import Accelerator, InitProcessGroupKwargs, DistributedDataParallelKwargs, PartialState24import glob25import math26import os27import random28import hashlib29import subprocess30from io import BytesIO31import toml32 33from tqdm import tqdm34 35import torch36from library.device_utils import init_ipex, clean_memory_on_device37 38init_ipex()39 40from torch.nn.parallel import DistributedDataParallel as DDP41from torch.optim import Optimizer42from torchvision import transforms43from transformers import CLIPTokenizer, CLIPTextModel, CLIPTextModelWithProjection44import transformers45from diffusers.optimization import (46    SchedulerType as DiffusersSchedulerType,47    TYPE_TO_SCHEDULER_FUNCTION as DIFFUSERS_TYPE_TO_SCHEDULER_FUNCTION,48)49from transformers.optimization import SchedulerType, TYPE_TO_SCHEDULER_FUNCTION50from diffusers import (51    StableDiffusionPipeline,52    DDPMScheduler,53    EulerAncestralDiscreteScheduler,54    DPMSolverMultistepScheduler,55    DPMSolverSinglestepScheduler,56    LMSDiscreteScheduler,57    PNDMScheduler,58    DDIMScheduler,59    EulerDiscreteScheduler,60    HeunDiscreteScheduler,61    KDPM2DiscreteScheduler,62    KDPM2AncestralDiscreteScheduler,63    AutoencoderKL,64)65from library import custom_train_functions66from library.original_unet import UNet2DConditionModel67from huggingface_hub import hf_hub_download68import numpy as np69from PIL import Image70import imagesize71import cv272import safetensors.torch73from library.lpw_stable_diffusion import StableDiffusionLongPromptWeightingPipeline74import library.model_util as model_util75import library.huggingface_util as huggingface_util76import library.sai_model_spec as sai_model_spec77import library.deepspeed_utils as deepspeed_utils78from library.utils import setup_logging, pil_resize79 80setup_logging()81import logging82 83logger = logging.getLogger(__name__)84# from library.attention_processors import FlashAttnProcessor85# from library.hypernetwork import replace_attentions_for_hypernetwork86from library.original_unet import UNet2DConditionModel87 88# Tokenizer: checkpointから読み込むのではなくあらかじめ提供されているものを使う89TOKENIZER_PATH = "openai/clip-vit-large-patch14"90V2_STABLE_DIFFUSION_PATH = "stabilityai/stable-diffusion-2"  # ここからtokenizerだけ使う v2とv2.1はtokenizer仕様は同じ91 92HIGH_VRAM = False93 94# checkpointファイル名95EPOCH_STATE_NAME = "{}-{:06d}-state"96EPOCH_FILE_NAME = "{}-{:06d}"97EPOCH_DIFFUSERS_DIR_NAME = "{}-{:06d}"98LAST_STATE_NAME = "{}-state"99DEFAULT_EPOCH_NAME = "epoch"100DEFAULT_LAST_OUTPUT_NAME = "last"101 102DEFAULT_STEP_NAME = "at"103STEP_STATE_NAME = "{}-step{:08d}-state"104STEP_FILE_NAME = "{}-step{:08d}"105STEP_DIFFUSERS_DIR_NAME = "{}-step{:08d}"106 107# region dataset108 109IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".PNG", ".JPG", ".JPEG", ".WEBP", ".BMP"]110 111try:112    import pillow_avif113 114    IMAGE_EXTENSIONS.extend([".avif", ".AVIF"])115except:116    pass117 118# JPEG-XL on Linux119try:120    from jxlpy import JXLImagePlugin121 122    IMAGE_EXTENSIONS.extend([".jxl", ".JXL"])123except:124    pass125 126# JPEG-XL on Windows127try:128    import pillow_jxl129 130    IMAGE_EXTENSIONS.extend([".jxl", ".JXL"])131except:132    pass133 134IMAGE_TRANSFORMS = transforms.Compose(135    [136        transforms.ToTensor(),137        transforms.Normalize([0.5], [0.5]),138    ]139)140 141TEXT_ENCODER_OUTPUTS_CACHE_SUFFIX = "_te_outputs.npz"142 143 144class ImageInfo:145    def __init__(self, image_key: str, num_repeats: int, caption: str, is_reg: bool, absolute_path: str) -> None:146        self.image_key: str = image_key147        self.num_repeats: int = num_repeats148        self.caption: str = caption149        self.is_reg: bool = is_reg150        self.absolute_path: str = absolute_path151        self.image_size: Tuple[int, int] = None152        self.resized_size: Tuple[int, int] = None153        self.bucket_reso: Tuple[int, int] = None154        self.latents: torch.Tensor = None155        self.latents_flipped: torch.Tensor = None156        self.latents_npz: str = None157        self.latents_original_size: Tuple[int, int] = None  # original image size, not latents size158        self.latents_crop_ltrb: Tuple[int, int] = None  # crop left top right bottom in original pixel size, not latents size159        self.cond_img_path: str = None160        self.image: Optional[Image.Image] = None  # optional, original PIL Image161        # SDXL, optional162        self.text_encoder_outputs_npz: Optional[str] = None163        self.text_encoder_outputs1: Optional[torch.Tensor] = None164        self.text_encoder_outputs2: Optional[torch.Tensor] = None165        self.text_encoder_pool2: Optional[torch.Tensor] = None166        self.alpha_mask: Optional[torch.Tensor] = None  # alpha mask can be flipped in runtime167 168 169class BucketManager:170    def __init__(self, no_upscale, max_reso, min_size, max_size, reso_steps) -> None:171        if max_size is not None:172            if max_reso is not None:173                assert max_size >= max_reso[0], "the max_size should be larger than the width of max_reso"174                assert max_size >= max_reso[1], "the max_size should be larger than the height of max_reso"175            if min_size is not None:176                assert max_size >= min_size, "the max_size should be larger than the min_size"177 178        self.no_upscale = no_upscale179        if max_reso is None:180            self.max_reso = None181            self.max_area = None182        else:183            self.max_reso = max_reso184            self.max_area = max_reso[0] * max_reso[1]185        self.min_size = min_size186        self.max_size = max_size187        self.reso_steps = reso_steps188 189        self.resos = []190        self.reso_to_id = {}191        self.buckets = []  # 前処理時は (image_key, image, original size, crop left/top)、学習時は image_key192 193    def add_image(self, reso, image_or_info):194        bucket_id = self.reso_to_id[reso]195        self.buckets[bucket_id].append(image_or_info)196 197    def shuffle(self):198        for bucket in self.buckets:199            random.shuffle(bucket)200 201    def sort(self):202        # 解像度順にソートする(表示時、メタデータ格納時の見栄えをよくするためだけ)。bucketsも入れ替えてreso_to_idも振り直す203        sorted_resos = self.resos.copy()204        sorted_resos.sort()205 206        sorted_buckets = []207        sorted_reso_to_id = {}208        for i, reso in enumerate(sorted_resos):209            bucket_id = self.reso_to_id[reso]210            sorted_buckets.append(self.buckets[bucket_id])211            sorted_reso_to_id[reso] = i212 213        self.resos = sorted_resos214        self.buckets = sorted_buckets215        self.reso_to_id = sorted_reso_to_id216 217    def make_buckets(self):218        resos = model_util.make_bucket_resolutions(self.max_reso, self.min_size, self.max_size, self.reso_steps)219        self.set_predefined_resos(resos)220 221    def set_predefined_resos(self, resos):222        # 規定サイズから選ぶ場合の解像度、aspect ratioの情報を格納しておく223        self.predefined_resos = resos.copy()224        self.predefined_resos_set = set(resos)225        self.predefined_aspect_ratios = np.array([w / h for w, h in resos])226 227    def add_if_new_reso(self, reso):228        if reso not in self.reso_to_id:229            bucket_id = len(self.resos)230            self.reso_to_id[reso] = bucket_id231            self.resos.append(reso)232            self.buckets.append([])233            # logger.info(reso, bucket_id, len(self.buckets))234 235    def round_to_steps(self, x):236        x = int(x + 0.5)237        return x - x % self.reso_steps238 239    def select_bucket(self, image_width, image_height):240        aspect_ratio = image_width / image_height241        if not self.no_upscale:242            # 拡大および縮小を行う243            # 同じaspect ratioがあるかもしれないので(fine tuningで、no_upscale=Trueで前処理した場合)、解像度が同じものを優先する244            reso = (image_width, image_height)245            if reso in self.predefined_resos_set:246                pass247            else:248                ar_errors = self.predefined_aspect_ratios - aspect_ratio249                predefined_bucket_id = np.abs(ar_errors).argmin()  # 当該解像度以外でaspect ratio errorが最も少ないもの250                reso = self.predefined_resos[predefined_bucket_id]251 252            ar_reso = reso[0] / reso[1]253            if aspect_ratio > ar_reso:  # 横が長い→縦を合わせる254                scale = reso[1] / image_height255            else:256                scale = reso[0] / image_width257 258            resized_size = (int(image_width * scale + 0.5), int(image_height * scale + 0.5))259            # logger.info(f"use predef, {image_width}, {image_height}, {reso}, {resized_size}")260        else:261            # 縮小のみを行う262            if image_width * image_height > self.max_area:263                # 画像が大きすぎるのでアスペクト比を保ったまま縮小することを前提にbucketを決める264                resized_width = math.sqrt(self.max_area * aspect_ratio)265                resized_height = self.max_area / resized_width266                assert abs(resized_width / resized_height - aspect_ratio) < 1e-2, "aspect is illegal"267 268                # リサイズ後の短辺または長辺をreso_steps単位にする:aspect ratioの差が少ないほうを選ぶ269                # 元のbucketingと同じロジック270                b_width_rounded = self.round_to_steps(resized_width)271                b_height_in_wr = self.round_to_steps(b_width_rounded / aspect_ratio)272                ar_width_rounded = b_width_rounded / b_height_in_wr273 274                b_height_rounded = self.round_to_steps(resized_height)275                b_width_in_hr = self.round_to_steps(b_height_rounded * aspect_ratio)276                ar_height_rounded = b_width_in_hr / b_height_rounded277 278                # logger.info(b_width_rounded, b_height_in_wr, ar_width_rounded)279                # logger.info(b_width_in_hr, b_height_rounded, ar_height_rounded)280 281                if abs(ar_width_rounded - aspect_ratio) < abs(ar_height_rounded - aspect_ratio):282                    resized_size = (b_width_rounded, int(b_width_rounded / aspect_ratio + 0.5))283                else:284                    resized_size = (int(b_height_rounded * aspect_ratio + 0.5), b_height_rounded)285                # logger.info(resized_size)286            else:287                resized_size = (image_width, image_height)  # リサイズは不要288 289            # 画像のサイズ未満をbucketのサイズとする(paddingせずにcroppingする)290            bucket_width = resized_size[0] - resized_size[0] % self.reso_steps291            bucket_height = resized_size[1] - resized_size[1] % self.reso_steps292            # logger.info(f"use arbitrary {image_width}, {image_height}, {resized_size}, {bucket_width}, {bucket_height}")293 294            reso = (bucket_width, bucket_height)295 296        self.add_if_new_reso(reso)297 298        ar_error = (reso[0] / reso[1]) - aspect_ratio299        return reso, resized_size, ar_error300 301    @staticmethod302    def get_crop_ltrb(bucket_reso: Tuple[int, int], image_size: Tuple[int, int]):303        # Stability AIの前処理に合わせてcrop left/topを計算する。crop rightはflipのaugmentationのために求める304        # Calculate crop left/top according to the preprocessing of Stability AI. Crop right is calculated for flip augmentation.305 306        bucket_ar = bucket_reso[0] / bucket_reso[1]307        image_ar = image_size[0] / image_size[1]308        if bucket_ar > image_ar:309            # bucketのほうが横長→縦を合わせる310            resized_width = bucket_reso[1] * image_ar311            resized_height = bucket_reso[1]312        else:313            resized_width = bucket_reso[0]314            resized_height = bucket_reso[0] / image_ar315        crop_left = (bucket_reso[0] - resized_width) // 2316        crop_top = (bucket_reso[1] - resized_height) // 2317        crop_right = crop_left + resized_width318        crop_bottom = crop_top + resized_height319        return crop_left, crop_top, crop_right, crop_bottom320 321 322class BucketBatchIndex(NamedTuple):323    bucket_index: int324    bucket_batch_size: int325    batch_index: int326 327 328class AugHelper:329    # albumentationsへの依存をなくしたがとりあえず同じinterfaceを持たせる330 331    def __init__(self):332        pass333 334    def color_aug(self, image: np.ndarray):335        # self.color_aug_method = albu.OneOf(336        #     [337        #         albu.HueSaturationValue(8, 0, 0, p=0.5),338        #         albu.RandomGamma((95, 105), p=0.5),339        #     ],340        #     p=0.33,341        # )342        hue_shift_limit = 8343 344        # remove dependency to albumentations345        if random.random() <= 0.33:346            if random.random() > 0.5:347                # hue shift348                hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)349                hue_shift = random.uniform(-hue_shift_limit, hue_shift_limit)350                if hue_shift < 0:351                    hue_shift = 180 + hue_shift352                hsv_img[:, :, 0] = (hsv_img[:, :, 0] + hue_shift) % 180353                image = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR)354            else:355                # random gamma356                gamma = random.uniform(0.95, 1.05)357                image = np.clip(image**gamma, 0, 255).astype(np.uint8)358 359        return {"image": image}360 361    def get_augmentor(self, use_color_aug: bool):  # -> Optional[Callable[[np.ndarray], Dict[str, np.ndarray]]]:362        return self.color_aug if use_color_aug else None363 364 365class BaseSubset:366    def __init__(367        self,368        image_dir: Optional[str],369        alpha_mask: Optional[bool],370        num_repeats: int,371        shuffle_caption: bool,372        caption_separator: str,373        keep_tokens: int,374        keep_tokens_separator: str,375        secondary_separator: Optional[str],376        enable_wildcard: bool,377        color_aug: bool,378        flip_aug: bool,379        face_crop_aug_range: Optional[Tuple[float, float]],380        random_crop: bool,381        caption_dropout_rate: float,382        caption_dropout_every_n_epochs: int,383        caption_tag_dropout_rate: float,384        caption_prefix: Optional[str],385        caption_suffix: Optional[str],386        token_warmup_min: int,387        token_warmup_step: Union[float, int],388    ) -> None:389        self.image_dir = image_dir390        self.alpha_mask = alpha_mask if alpha_mask is not None else False391        self.num_repeats = num_repeats392        self.shuffle_caption = shuffle_caption393        self.caption_separator = caption_separator394        self.keep_tokens = keep_tokens395        self.keep_tokens_separator = keep_tokens_separator396        self.secondary_separator = secondary_separator397        self.enable_wildcard = enable_wildcard398        self.color_aug = color_aug399        self.flip_aug = flip_aug400        self.face_crop_aug_range = face_crop_aug_range401        self.random_crop = random_crop402        self.caption_dropout_rate = caption_dropout_rate403        self.caption_dropout_every_n_epochs = caption_dropout_every_n_epochs404        self.caption_tag_dropout_rate = caption_tag_dropout_rate405        self.caption_prefix = caption_prefix406        self.caption_suffix = caption_suffix407 408        self.token_warmup_min = token_warmup_min  # step=0におけるタグの数409        self.token_warmup_step = token_warmup_step  # N(N<1ならN*max_train_steps)ステップ目でタグの数が最大になる410 411        self.img_count = 0412 413 414class DreamBoothSubset(BaseSubset):415    def __init__(416        self,417        image_dir: str,418        is_reg: bool,419        class_tokens: Optional[str],420        caption_extension: str,421        cache_info: bool,422        alpha_mask: bool,423        num_repeats,424        shuffle_caption,425        caption_separator: str,426        keep_tokens,427        keep_tokens_separator,428        secondary_separator,429        enable_wildcard,430        color_aug,431        flip_aug,432        face_crop_aug_range,433        random_crop,434        caption_dropout_rate,435        caption_dropout_every_n_epochs,436        caption_tag_dropout_rate,437        caption_prefix,438        caption_suffix,439        token_warmup_min,440        token_warmup_step,441    ) -> None:442        assert image_dir is not None, "image_dir must be specified / image_dirは指定が必須です"443 444        super().__init__(445            image_dir,446            alpha_mask,447            num_repeats,448            shuffle_caption,449            caption_separator,450            keep_tokens,451            keep_tokens_separator,452            secondary_separator,453            enable_wildcard,454            color_aug,455            flip_aug,456            face_crop_aug_range,457            random_crop,458            caption_dropout_rate,459            caption_dropout_every_n_epochs,460            caption_tag_dropout_rate,461            caption_prefix,462            caption_suffix,463            token_warmup_min,464            token_warmup_step,465        )466 467        self.is_reg = is_reg468        self.class_tokens = class_tokens469        self.caption_extension = caption_extension470        if self.caption_extension and not self.caption_extension.startswith("."):471            self.caption_extension = "." + self.caption_extension472        self.cache_info = cache_info473 474    def __eq__(self, other) -> bool:475        if not isinstance(other, DreamBoothSubset):476            return NotImplemented477        return self.image_dir == other.image_dir478 479 480class FineTuningSubset(BaseSubset):481    def __init__(482        self,483        image_dir,484        metadata_file: str,485        alpha_mask: bool,486        num_repeats,487        shuffle_caption,488        caption_separator,489        keep_tokens,490        keep_tokens_separator,491        secondary_separator,492        enable_wildcard,493        color_aug,494        flip_aug,495        face_crop_aug_range,496        random_crop,497        caption_dropout_rate,498        caption_dropout_every_n_epochs,499        caption_tag_dropout_rate,500        caption_prefix,501        caption_suffix,502        token_warmup_min,503        token_warmup_step,504    ) -> None:505        assert metadata_file is not None, "metadata_file must be specified / metadata_fileは指定が必須です"506 507        super().__init__(508            image_dir,509            alpha_mask,510            num_repeats,511            shuffle_caption,512            caption_separator,513            keep_tokens,514            keep_tokens_separator,515            secondary_separator,516            enable_wildcard,517            color_aug,518            flip_aug,519            face_crop_aug_range,520            random_crop,521            caption_dropout_rate,522            caption_dropout_every_n_epochs,523            caption_tag_dropout_rate,524            caption_prefix,525            caption_suffix,526            token_warmup_min,527            token_warmup_step,528        )529 530        self.metadata_file = metadata_file531 532    def __eq__(self, other) -> bool:533        if not isinstance(other, FineTuningSubset):534            return NotImplemented535        return self.metadata_file == other.metadata_file536 537 538class ControlNetSubset(BaseSubset):539    def __init__(540        self,541        image_dir: str,542        conditioning_data_dir: str,543        caption_extension: str,544        cache_info: bool,545        num_repeats,546        shuffle_caption,547        caption_separator,548        keep_tokens,549        keep_tokens_separator,550        secondary_separator,551        enable_wildcard,552        color_aug,553        flip_aug,554        face_crop_aug_range,555        random_crop,556        caption_dropout_rate,557        caption_dropout_every_n_epochs,558        caption_tag_dropout_rate,559        caption_prefix,560        caption_suffix,561        token_warmup_min,562        token_warmup_step,563    ) -> None:564        assert image_dir is not None, "image_dir must be specified / image_dirは指定が必須です"565 566        super().__init__(567            image_dir,568            False,  # alpha_mask569            num_repeats,570            shuffle_caption,571            caption_separator,572            keep_tokens,573            keep_tokens_separator,574            secondary_separator,575            enable_wildcard,576            color_aug,577            flip_aug,578            face_crop_aug_range,579            random_crop,580            caption_dropout_rate,581            caption_dropout_every_n_epochs,582            caption_tag_dropout_rate,583            caption_prefix,584            caption_suffix,585            token_warmup_min,586            token_warmup_step,587        )588 589        self.conditioning_data_dir = conditioning_data_dir590        self.caption_extension = caption_extension591        if self.caption_extension and not self.caption_extension.startswith("."):592            self.caption_extension = "." + self.caption_extension593        self.cache_info = cache_info594 595    def __eq__(self, other) -> bool:596        if not isinstance(other, ControlNetSubset):597            return NotImplemented598        return self.image_dir == other.image_dir and self.conditioning_data_dir == other.conditioning_data_dir599 600 601class BaseDataset(torch.utils.data.Dataset):602    def __init__(603        self,604        tokenizer: Union[CLIPTokenizer, List[CLIPTokenizer]],605        max_token_length: int,606        resolution: Optional[Tuple[int, int]],607        network_multiplier: float,608        debug_dataset: bool,609    ) -> None:610        super().__init__()611 612        self.tokenizers = tokenizer if isinstance(tokenizer, list) else [tokenizer]613 614        self.max_token_length = max_token_length615        # width/height is used when enable_bucket==False616        self.width, self.height = (None, None) if resolution is None else resolution617        self.network_multiplier = network_multiplier618        self.debug_dataset = debug_dataset619 620        self.subsets: List[Union[DreamBoothSubset, FineTuningSubset]] = []621 622        self.token_padding_disabled = False623        self.tag_frequency = {}624        self.XTI_layers = None625        self.token_strings = None626 627        self.enable_bucket = False628        self.bucket_manager: BucketManager = None  # not initialized629        self.min_bucket_reso = None630        self.max_bucket_reso = None631        self.bucket_reso_steps = None632        self.bucket_no_upscale = None633        self.bucket_info = None  # for metadata634 635        self.tokenizer_max_length = self.tokenizers[0].model_max_length if max_token_length is None else max_token_length + 2636 637        self.current_epoch: int = 0  # インスタンスがepochごとに新しく作られるようなので外側から渡さないとダメ638 639        self.current_step: int = 0640        self.max_train_steps: int = 0641        self.seed: int = 0642 643        # augmentation644        self.aug_helper = AugHelper()645 646        self.image_transforms = IMAGE_TRANSFORMS647 648        self.image_data: Dict[str, ImageInfo] = {}649        self.image_to_subset: Dict[str, Union[DreamBoothSubset, FineTuningSubset]] = {}650 651        self.replacements = {}652 653        # caching654        self.caching_mode = None  # None, 'latents', 'text'655 656    def adjust_min_max_bucket_reso_by_steps(657        self, resolution: Tuple[int, int], min_bucket_reso: int, max_bucket_reso: int, bucket_reso_steps: int658    ) -> Tuple[int, int]:659        # make min/max bucket reso to be multiple of bucket_reso_steps660        if min_bucket_reso % bucket_reso_steps != 0:661            adjusted_min_bucket_reso = min_bucket_reso - min_bucket_reso % bucket_reso_steps662            logger.warning(663                f"min_bucket_reso is adjusted to be multiple of bucket_reso_steps"664                f" / min_bucket_resoがbucket_reso_stepsの倍数になるように調整されました: {min_bucket_reso} -> {adjusted_min_bucket_reso}"665            )666            min_bucket_reso = adjusted_min_bucket_reso667        if max_bucket_reso % bucket_reso_steps != 0:668            adjusted_max_bucket_reso = max_bucket_reso + bucket_reso_steps - max_bucket_reso % bucket_reso_steps669            logger.warning(670                f"max_bucket_reso is adjusted to be multiple of bucket_reso_steps"671                f" / max_bucket_resoがbucket_reso_stepsの倍数になるように調整されました: {max_bucket_reso} -> {adjusted_max_bucket_reso}"672            )673            max_bucket_reso = adjusted_max_bucket_reso674 675        assert (676            min(resolution) >= min_bucket_reso677        ), f"min_bucket_reso must be equal or less than resolution / min_bucket_resoは最小解像度より大きくできません。解像度を大きくするかmin_bucket_resoを小さくしてください"678        assert (679            max(resolution) <= max_bucket_reso680        ), f"max_bucket_reso must be equal or greater than resolution / max_bucket_resoは最大解像度より小さくできません。解像度を小さくするかmin_bucket_resoを大きくしてください"681 682        return min_bucket_reso, max_bucket_reso683 684    def set_seed(self, seed):685        self.seed = seed686 687    def set_caching_mode(self, mode):688        self.caching_mode = mode689 690    def set_current_epoch(self, epoch):691        if not self.current_epoch == epoch:  # epochが切り替わったらバケツをシャッフルする692            if epoch > self.current_epoch:693                logger.info("epoch is incremented. current_epoch: {}, epoch: {}".format(self.current_epoch, epoch))694                num_epochs = epoch - self.current_epoch695                for _ in range(num_epochs):696                    self.current_epoch += 1697                    self.shuffle_buckets()698                # self.current_epoch seem to be set to 0 again in the next epoch. it may be caused by skipped_dataloader?699            else:700                logger.warning("epoch is not incremented. current_epoch: {}, epoch: {}".format(self.current_epoch, epoch))701                self.current_epoch = epoch702 703    def set_current_step(self, step):704        self.current_step = step705 706    def set_max_train_steps(self, max_train_steps):707        self.max_train_steps = max_train_steps708 709    def set_tag_frequency(self, dir_name, captions):710        frequency_for_dir = self.tag_frequency.get(dir_name, {})711        self.tag_frequency[dir_name] = frequency_for_dir712        for caption in captions:713            for tag in caption.split(","):714                tag = tag.strip()715                if tag:716                    tag = tag.lower()717                    frequency = frequency_for_dir.get(tag, 0)718                    frequency_for_dir[tag] = frequency + 1719 720    def disable_token_padding(self):721        self.token_padding_disabled = True722 723    def enable_XTI(self, layers=None, token_strings=None):724        self.XTI_layers = layers725        self.token_strings = token_strings726 727    def add_replacement(self, str_from, str_to):728        self.replacements[str_from] = str_to729 730    def process_caption(self, subset: BaseSubset, caption):731        # caption に prefix/suffix を付ける732        if subset.caption_prefix:733            caption = subset.caption_prefix + " " + caption734        if subset.caption_suffix:735            caption = caption + " " + subset.caption_suffix736 737        # dropoutの決定:tag dropがこのメソッド内にあるのでここで行うのが良い738        is_drop_out = subset.caption_dropout_rate > 0 and random.random() < subset.caption_dropout_rate739        is_drop_out = (740            is_drop_out741            or subset.caption_dropout_every_n_epochs > 0742            and self.current_epoch % subset.caption_dropout_every_n_epochs == 0743        )744 745        if is_drop_out:746            caption = ""747        else:748            # process wildcards749            if subset.enable_wildcard:750                # if caption is multiline, random choice one line751                if "\n" in caption:752                    caption = random.choice(caption.split("\n"))753 754                # wildcard is like '{aaa|bbb|ccc...}'755                # escape the curly braces like {{ or }}756                replacer1 = "⦅"757                replacer2 = "⦆"758                while replacer1 in caption or replacer2 in caption:759                    replacer1 += "⦅"760                    replacer2 += "⦆"761 762                caption = caption.replace("{{", replacer1).replace("}}", replacer2)763 764                # replace the wildcard765                def replace_wildcard(match):766                    return random.choice(match.group(1).split("|"))767 768                caption = re.sub(r"\{([^}]+)\}", replace_wildcard, caption)769 770                # unescape the curly braces771                caption = caption.replace(replacer1, "{").replace(replacer2, "}")772            else:773                # if caption is multiline, use the first line774                caption = caption.split("\n")[0]775 776            if subset.shuffle_caption or subset.token_warmup_step > 0 or subset.caption_tag_dropout_rate > 0:777                fixed_tokens = []778                flex_tokens = []779                fixed_suffix_tokens = []780                if (781                    hasattr(subset, "keep_tokens_separator")782                    and subset.keep_tokens_separator783                    and subset.keep_tokens_separator in caption784                ):785                    fixed_part, flex_part = caption.split(subset.keep_tokens_separator, 1)786                    if subset.keep_tokens_separator in flex_part:787                        flex_part, fixed_suffix_part = flex_part.split(subset.keep_tokens_separator, 1)788                        fixed_suffix_tokens = [t.strip() for t in fixed_suffix_part.split(subset.caption_separator) if t.strip()]789 790                    fixed_tokens = [t.strip() for t in fixed_part.split(subset.caption_separator) if t.strip()]791                    flex_tokens = [t.strip() for t in flex_part.split(subset.caption_separator) if t.strip()]792                else:793                    tokens = [t.strip() for t in caption.strip().split(subset.caption_separator)]794                    flex_tokens = tokens[:]795                    if subset.keep_tokens > 0:796                        fixed_tokens = flex_tokens[: subset.keep_tokens]797                        flex_tokens = tokens[subset.keep_tokens :]798 799                if subset.token_warmup_step < 1:  # 初回に上書きする800                    subset.token_warmup_step = math.floor(subset.token_warmup_step * self.max_train_steps)801                if subset.token_warmup_step and self.current_step < subset.token_warmup_step:802                    tokens_len = (803                        math.floor(804                            (self.current_step) * ((len(flex_tokens) - subset.token_warmup_min) / (subset.token_warmup_step))805                        )806                        + subset.token_warmup_min807                    )808                    flex_tokens = flex_tokens[:tokens_len]809 810                def dropout_tags(tokens):811                    if subset.caption_tag_dropout_rate <= 0:812                        return tokens813                    l = []814                    for token in tokens:815                        if random.random() >= subset.caption_tag_dropout_rate:816                            l.append(token)817                    return l818 819                if subset.shuffle_caption:820                    random.shuffle(flex_tokens)821 822                flex_tokens = dropout_tags(flex_tokens)823 824                caption = ", ".join(fixed_tokens + flex_tokens + fixed_suffix_tokens)825 826            # process secondary separator827            if subset.secondary_separator:828                caption = caption.replace(subset.secondary_separator, subset.caption_separator)829 830            # textual inversion対応831            for str_from, str_to in self.replacements.items():832                if str_from == "":833                    # replace all834                    if type(str_to) == list:835                        caption = random.choice(str_to)836                    else:837                        caption = str_to838                else:839                    caption = caption.replace(str_from, str_to)840 841        return caption842 843    def get_input_ids(self, caption, tokenizer=None):844        if tokenizer is None:845            tokenizer = self.tokenizers[0]846 847        input_ids = tokenizer(848            caption, padding="max_length", truncation=True, max_length=self.tokenizer_max_length, return_tensors="pt"849        ).input_ids850 851        if self.tokenizer_max_length > tokenizer.model_max_length:852            input_ids = input_ids.squeeze(0)853            iids_list = []854            if tokenizer.pad_token_id == tokenizer.eos_token_id:855                # v1856                # 77以上の時は "<BOS> .... <EOS> <EOS> <EOS>" でトータル227とかになっているので、"<BOS>...<EOS>"の三連に変換する857                # 1111氏のやつは , で区切る、とかしているようだが とりあえず単純に858                for i in range(859                    1, self.tokenizer_max_length - tokenizer.model_max_length + 2, tokenizer.model_max_length - 2860                ):  # (1, 152, 75)861                    ids_chunk = (862                        input_ids[0].unsqueeze(0),863                        input_ids[i : i + tokenizer.model_max_length - 2],864                        input_ids[-1].unsqueeze(0),865                    )866                    ids_chunk = torch.cat(ids_chunk)867                    iids_list.append(ids_chunk)868            else:869                # v2 or SDXL870                # 77以上の時は "<BOS> .... <EOS> <PAD> <PAD>..." でトータル227とかになっているので、"<BOS>...<EOS> <PAD> <PAD> ..."の三連に変換する871                for i in range(1, self.tokenizer_max_length - tokenizer.model_max_length + 2, tokenizer.model_max_length - 2):872                    ids_chunk = (873                        input_ids[0].unsqueeze(0),  # BOS874                        input_ids[i : i + tokenizer.model_max_length - 2],875                        input_ids[-1].unsqueeze(0),876                    )  # PAD or EOS877                    ids_chunk = torch.cat(ids_chunk)878 879                    # 末尾が <EOS> <PAD> または <PAD> <PAD> の場合は、何もしなくてよい880                    # 末尾が x <PAD/EOS> の場合は末尾を <EOS> に変える(x <EOS> なら結果的に変化なし)881                    if ids_chunk[-2] != tokenizer.eos_token_id and ids_chunk[-2] != tokenizer.pad_token_id:882                        ids_chunk[-1] = tokenizer.eos_token_id883                    # 先頭が <BOS> <PAD> ... の場合は <BOS> <EOS> <PAD> ... に変える884                    if ids_chunk[1] == tokenizer.pad_token_id:885                        ids_chunk[1] = tokenizer.eos_token_id886 887                    iids_list.append(ids_chunk)888 889            input_ids = torch.stack(iids_list)  # 3,77890        return input_ids891 892    def register_image(self, info: ImageInfo, subset: BaseSubset):893        self.image_data[info.image_key] = info894        self.image_to_subset[info.image_key] = subset895 896    def make_buckets(self):897        """898        bucketingを行わない場合も呼び出し必須(ひとつだけbucketを作る)899        min_size and max_size are ignored when enable_bucket is False900        """901        logger.info("loading image sizes.")902        for info in tqdm(self.image_data.values()):903            if info.image_size is None:904                info.image_size = self.get_image_size(info.absolute_path)905 906        if self.enable_bucket:907            logger.info("make buckets")908        else:909            logger.info("prepare dataset")910 911        # bucketを作成し、画像をbucketに振り分ける912        if self.enable_bucket:913            if self.bucket_manager is None:  # fine tuningの場合でmetadataに定義がある場合は、すでに初期化済み914                self.bucket_manager = BucketManager(915                    self.bucket_no_upscale,916                    (self.width, self.height),917                    self.min_bucket_reso,918                    self.max_bucket_reso,919                    self.bucket_reso_steps,920                )921                if not self.bucket_no_upscale:922                    self.bucket_manager.make_buckets()923                else:924                    logger.warning(925                        "min_bucket_reso and max_bucket_reso are ignored if bucket_no_upscale is set, because bucket reso is defined by image size automatically / bucket_no_upscaleが指定された場合は、bucketの解像度は画像サイズから自動計算されるため、min_bucket_resoとmax_bucket_resoは無視されます"926                    )927 928            img_ar_errors = []929            for image_info in self.image_data.values():930                image_width, image_height = image_info.image_size931                image_info.bucket_reso, image_info.resized_size, ar_error = self.bucket_manager.select_bucket(932                    image_width, image_height933                )934 935                # logger.info(image_info.image_key, image_info.bucket_reso)936                img_ar_errors.append(abs(ar_error))937 938            self.bucket_manager.sort()939        else:940            self.bucket_manager = BucketManager(False, (self.width, self.height), None, None, None)941            self.bucket_manager.set_predefined_resos([(self.width, self.height)])  # ひとつの固定サイズbucketのみ942            for image_info in self.image_data.values():943                image_width, image_height = image_info.image_size944                image_info.bucket_reso, image_info.resized_size, _ = self.bucket_manager.select_bucket(image_width, image_height)945 946        for image_info in self.image_data.values():947            for _ in range(image_info.num_repeats):948                self.bucket_manager.add_image(image_info.bucket_reso, image_info.image_key)949 950        # bucket情報を表示、格納する951        if self.enable_bucket:952            self.bucket_info = {"buckets": {}}953            logger.info("number of images (including repeats) / 各bucketの画像枚数(繰り返し回数を含む)")954            for i, (reso, bucket) in enumerate(zip(self.bucket_manager.resos, self.bucket_manager.buckets)):955                count = len(bucket)956                if count > 0:957                    self.bucket_info["buckets"][i] = {"resolution": reso, "count": len(bucket)}958                    logger.info(f"bucket {i}: resolution {reso}, count: {len(bucket)}")959 960            if len(img_ar_errors) == 0:961                mean_img_ar_error = 0  # avoid NaN962            else:963                img_ar_errors = np.array(img_ar_errors)964                mean_img_ar_error = np.mean(np.abs(img_ar_errors))965            self.bucket_info["mean_img_ar_error"] = mean_img_ar_error966            logger.info(f"mean ar error (without repeats): {mean_img_ar_error}")967 968        # データ参照用indexを作る。このindexはdatasetのshuffleに用いられる969        self.buckets_indices: List[BucketBatchIndex] = []970        for bucket_index, bucket in enumerate(self.bucket_manager.buckets):971            batch_count = int(math.ceil(len(bucket) / self.batch_size))972            for batch_index in range(batch_count):973                self.buckets_indices.append(BucketBatchIndex(bucket_index, self.batch_size, batch_index))974 975            # ↓以下はbucketごとのbatch件数があまりにも増えて混乱を招くので元に戻す976            #  学習時はステップ数がランダムなので、同一画像が同一batch内にあってもそれほど悪影響はないであろう、と考えられる977            #978            # # bucketが細分化されることにより、ひとつのbucketに一種類の画像のみというケースが増え、つまりそれは979            # # ひとつのbatchが同じ画像で占められることになるので、さすがに良くないであろう980            # # そのためバッチサイズを画像種類までに制限する981            # # ただそれでも同一画像が同一バッチに含まれる可能性はあるので、繰り返し回数が少ないほうがshuffleの品質は良くなることは間違いない?982            # # TO DO 正則化画像をepochまたがりで利用する仕組み983            # num_of_image_types = len(set(bucket))984            # bucket_batch_size = min(self.batch_size, num_of_image_types)985            # batch_count = int(math.ceil(len(bucket) / bucket_batch_size))986            # # logger.info(bucket_index, num_of_image_types, bucket_batch_size, batch_count)987            # for batch_index in range(batch_count):988            #   self.buckets_indices.append(BucketBatchIndex(bucket_index, bucket_batch_size, batch_index))989            # ↑ここまで990 991        self.shuffle_buckets()992        self._length = len(self.buckets_indices)993 994    def shuffle_buckets(self):995        # set random seed for this epoch996        random.seed(self.seed + self.current_epoch)997 998        random.shuffle(self.buckets_indices)999        self.bucket_manager.shuffle()1000 1001    def verify_bucket_reso_steps(self, min_steps: int):1002        assert self.bucket_reso_steps is None or self.bucket_reso_steps % min_steps == 0, (1003            f"bucket_reso_steps is {self.bucket_reso_steps}. it must be divisible by {min_steps}.\n"1004            + f"bucket_reso_stepsが{self.bucket_reso_steps}です。{min_steps}で割り切れる必要があります"1005        )1006 1007    def is_latent_cacheable(self):1008        return all([not subset.color_aug and not subset.random_crop for subset in self.subsets])1009 1010    def is_text_encoder_output_cacheable(self):1011        return all(1012            [1013                not (1014                    subset.caption_dropout_rate > 01015                    or subset.shuffle_caption1016                    or subset.token_warmup_step > 01017                    or subset.caption_tag_dropout_rate > 01018                )1019                for subset in self.subsets1020            ]1021        )1022 1023    def cache_latents(self, vae, vae_batch_size=1, cache_to_disk=False, is_main_process=True):1024        # マルチGPUには対応していないので、そちらはtools/cache_latents.pyを使うこと1025        logger.info("caching latents.")1026 1027        image_infos = list(self.image_data.values())1028 1029        # sort by resolution1030        image_infos.sort(key=lambda info: info.bucket_reso[0] * info.bucket_reso[1])1031 1032        # split by resolution and some conditions1033        class Condition:1034            def __init__(self, reso, flip_aug, alpha_mask, random_crop):1035                self.reso = reso1036                self.flip_aug = flip_aug1037                self.alpha_mask = alpha_mask1038                self.random_crop = random_crop1039 1040            def __eq__(self, other):1041                return (1042                    self.reso == other.reso1043                    and self.flip_aug == other.flip_aug1044                    and self.alpha_mask == other.alpha_mask1045                    and self.random_crop == other.random_crop1046                )1047 1048        batches: List[Tuple[Condition, List[ImageInfo]]] = []1049        batch: List[ImageInfo] = []1050        current_condition = None1051 1052        logger.info("checking cache validity...")1053        for info in tqdm(image_infos):1054            subset = self.image_to_subset[info.image_key]1055 1056            if info.latents_npz is not None:  # fine tuning dataset1057                continue1058 1059            # check disk cache exists and size of latents1060            if cache_to_disk:1061                info.latents_npz = os.path.splitext(info.absolute_path)[0] + ".npz"1062                if not is_main_process:  # store to info only1063                    continue1064 1065                cache_available = is_disk_cached_latents_is_expected(1066                    info.bucket_reso, info.latents_npz, subset.flip_aug, subset.alpha_mask1067                )1068 1069                if cache_available:  # do not add to batch1070                    continue1071 1072            # if batch is not empty and condition is changed, flush the batch. Note that current_condition is not None if batch is not empty1073            condition = Condition(info.bucket_reso, subset.flip_aug, subset.alpha_mask, subset.random_crop)1074            if len(batch) > 0 and current_condition != condition:1075                batches.append((current_condition, batch))1076                batch = []1077 1078            batch.append(info)1079            current_condition = condition1080 1081            # if number of data in batch is enough, flush the batch1082            if len(batch) >= vae_batch_size:1083                batches.append((current_condition, batch))1084                batch = []1085                current_condition = None1086 1087        if len(batch) > 0:1088            batches.append((current_condition, batch))1089 1090        if cache_to_disk and not is_main_process:  # if cache to disk, don't cache latents in non-main process, set to info only1091            return1092 1093        # iterate batches: batch doesn't have image, image will be loaded in cache_batch_latents and discarded1094        logger.info("caching latents...")1095        for condition, batch in tqdm(batches, smoothing=1, total=len(batches)):1096            cache_batch_latents(vae, cache_to_disk, batch, condition.flip_aug, condition.alpha_mask, condition.random_crop)1097 1098    # weight_dtypeを指定するとText Encoderそのもの、およひ出力がweight_dtypeになる1099    # SDXLでのみ有効だが、datasetのメソッドとする必要があるので、sdxl_train_util.pyではなくこちらに実装する1100    # SD1/2に対応するにはv2のフラグを持つ必要があるので後回し1101    def cache_text_encoder_outputs(1102        self, tokenizers, text_encoders, device, weight_dtype, cache_to_disk=False, is_main_process=True1103    ):1104        assert len(tokenizers) == 2, "only support SDXL"1105 1106        # latentsのキャッシュと同様に、ディスクへのキャッシュに対応する1107        # またマルチGPUには対応していないので、そちらはtools/cache_latents.pyを使うこと1108        logger.info("caching text encoder outputs.")1109        image_infos = list(self.image_data.values())1110 1111        logger.info("checking cache existence...")1112        image_infos_to_cache = []1113        for info in tqdm(image_infos):1114            # subset = self.image_to_subset[info.image_key]1115            if cache_to_disk:1116                te_out_npz = os.path.splitext(info.absolute_path)[0] + TEXT_ENCODER_OUTPUTS_CACHE_SUFFIX1117                info.text_encoder_outputs_npz = te_out_npz1118 1119                if not is_main_process:  # store to info only1120                    continue1121 1122                if os.path.exists(te_out_npz):1123                    continue1124 1125            image_infos_to_cache.append(info)1126 1127        if cache_to_disk and not is_main_process:  # if cache to disk, don't cache latents in non-main process, set to info only1128            return1129 1130        # prepare tokenizers and text encoders1131        for text_encoder in text_encoders:1132            text_encoder.to(device)1133            if weight_dtype is not None:1134                text_encoder.to(dtype=weight_dtype)1135 1136        # create batch1137        batch = []1138        batches = []1139        for info in image_infos_to_cache:1140            input_ids1 = self.get_input_ids(info.caption, tokenizers[0])1141            input_ids2 = self.get_input_ids(info.caption, tokenizers[1])1142            batch.append((info, input_ids1, input_ids2))1143 1144            if len(batch) >= self.batch_size:1145                batches.append(batch)1146                batch = []1147 1148        if len(batch) > 0:1149            batches.append(batch)1150 1151        # iterate batches: call text encoder and cache outputs for memory or disk1152        logger.info("caching text encoder outputs...")1153        for batch in tqdm(batches):1154            infos, input_ids1, input_ids2 = zip(*batch)1155            input_ids1 = torch.stack(input_ids1, dim=0)1156            input_ids2 = torch.stack(input_ids2, dim=0)1157            cache_batch_text_encoder_outputs(1158                infos, tokenizers, text_encoders, self.max_token_length, cache_to_disk, input_ids1, input_ids2, weight_dtype1159            )1160 1161    def get_image_size(self, image_path):1162        return imagesize.get(image_path)1163 1164    def load_image_with_face_info(self, subset: BaseSubset, image_path: str, alpha_mask=False):1165        img = load_image(image_path, alpha_mask)1166 1167        face_cx = face_cy = face_w = face_h = 01168        if subset.face_crop_aug_range is not None:1169            tokens = os.path.splitext(os.path.basename(image_path))[0].split("_")1170            if len(tokens) >= 5:1171                face_cx = int(tokens[-4])1172                face_cy = int(tokens[-3])1173                face_w = int(tokens[-2])1174                face_h = int(tokens[-1])1175 1176        return img, face_cx, face_cy, face_w, face_h1177 1178    # いい感じに切り出す1179    def crop_target(self, subset: BaseSubset, image, face_cx, face_cy, face_w, face_h):1180        height, width = image.shape[0:2]1181        if height == self.height and width == self.width:1182            return image1183 1184        # 画像サイズはsizeより大きいのでリサイズする1185        face_size = max(face_w, face_h)1186        size = min(self.height, self.width)  # 短いほう1187        min_scale = max(self.height / height, self.width / width)  # 画像がモデル入力サイズぴったりになる倍率(最小の倍率)1188        min_scale = min(1.0, max(min_scale, size / (face_size * subset.face_crop_aug_range[1])))  # 指定した顔最小サイズ1189        max_scale = min(1.0, max(min_scale, size / (face_size * subset.face_crop_aug_range[0])))  # 指定した顔最大サイズ1190        if min_scale >= max_scale:  # range指定がmin==max1191            scale = min_scale1192        else:1193            scale = random.uniform(min_scale, max_scale)1194 1195        nh = int(height * scale + 0.5)1196        nw = int(width * scale + 0.5)1197        assert nh >= self.height and nw >= self.width, f"internal error. small scale {scale}, {width}*{height}"1198        image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_AREA)1199        face_cx = int(face_cx * scale + 0.5)1200        face_cy = int(face_cy * scale + 0.5)

Showing the first 1,200 of 5733 lines. Download the file for the rest.