Skip to content

Commit 0d96e10

Browse files
authored
Merge pull request kohya-ss#1339 from kohya-ss/alpha-masked-loss
Alpha masked loss
2 parents ffce3b5 + fc85496 commit 0d96e10

15 files changed

+313
-40
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ The majority of scripts is licensed under ASL 2.0 (including codes from Diffuser
161161
- Example: `--network_args "loraplus_unet_lr_ratio=16" "loraplus_text_encoder_lr_ratio=4"` or `--network_args "loraplus_lr_ratio=16" "loraplus_text_encoder_lr_ratio=4"` etc.
162162
- `network_module` `networks.lora` and `networks.dylora` are available.
163163

164+
- The feature to use the transparency (alpha channel) of the image as a mask in the loss calculation has been added. PR [#1223](https://github.com/kohya-ss/sd-scripts/pull/1223) Thanks to u-haru!
165+
- The transparent part is ignored during training. Specify the `--alpha_mask` option in the training script or specify `alpha_mask = true` in the dataset configuration file.
166+
- See [About masked loss](./docs/masked_loss_README.md) for details.
167+
164168
- LoRA training in SDXL now supports block-wise learning rates and block-wise dim (rank). PR [#1331](https://github.com/kohya-ss/sd-scripts/pull/1331)
165169
- Specify the learning rate and dim (rank) for each block.
166170
- See [Block-wise learning rates in LoRA](./docs/train_network_README-ja.md#階層別学習率) for details (Japanese only).
@@ -214,6 +218,10 @@ https://github.com/kohya-ss/sd-scripts/pull/1290) Thanks to frodo821!
214218
- 例:`--network_args "loraplus_unet_lr_ratio=16" "loraplus_text_encoder_lr_ratio=4"` または `--network_args "loraplus_lr_ratio=16" "loraplus_text_encoder_lr_ratio=4"` など
215219
- `network_module``networks.lora` および `networks.dylora` で使用可能です。
216220

221+
- 画像の透明度(アルファチャネル)をロス計算時のマスクとして使用する機能が追加されました。PR [#1223](https://github.com/kohya-ss/sd-scripts/pull/1223) u-haru 氏に感謝します。
222+
- 透明部分が学習時に無視されるようになります。学習スクリプトに `--alpha_mask` オプションを指定するか、データセット設定ファイルに `alpha_mask = true` を指定してください。
223+
- 詳細は [マスクロスについて](./docs/masked_loss_README-ja.md) をご覧ください。
224+
217225
- SDXL の LoRA で階層別学習率、階層別 dim (rank) をサポートしました。PR [#1331](https://github.com/kohya-ss/sd-scripts/pull/1331)
218226
- ブロックごとに学習率および dim (rank) を指定することができます。
219227
- 詳細は [LoRA の階層別学習率](./docs/train_network_README-ja.md#階層別学習率) をご覧ください。

docs/masked_loss_README-ja.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
## マスクロスについて
2+
3+
マスクロスは、入力画像のマスクで指定された部分だけ損失計算することで、画像の一部分だけを学習することができる機能です。
4+
たとえばキャラクタを学習したい場合、キャラクタ部分だけをマスクして学習することで、背景を無視して学習することができます。
5+
6+
マスクロスのマスクには、二種類の指定方法があります。
7+
8+
- マスク画像を用いる方法
9+
- 透明度(アルファチャネル)を使用する方法
10+
11+
なお、サンプルは [ずんずんPJイラスト/3Dデータ](https://zunko.jp/con_illust.html) の「AI画像モデル用学習データ」を使用しています。
12+
13+
### マスク画像を用いる方法
14+
15+
学習画像それぞれに対応するマスク画像を用意する方法です。学習画像と同じファイル名のマスク画像を用意し、それを学習画像と別のディレクトリに保存します。
16+
17+
- 学習画像
18+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/607c5116-5f62-47de-8b66-9c4a597f0441)
19+
- マスク画像
20+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/53e9b0f8-a4bf-49ed-882d-4026f84e8450)
21+
22+
```.toml
23+
[[datasets.subsets]]
24+
image_dir = "/path/to/a_zundamon"
25+
caption_extension = ".txt"
26+
conditioning_data_dir = "/path/to/a_zundamon_mask"
27+
num_repeats = 8
28+
```
29+
30+
マスク画像は、学習画像と同じサイズで、学習する部分を白、無視する部分を黒で描画します。グレースケールにも対応しています(127 ならロス重みが 0.5 になります)。なお、正確にはマスク画像の R チャネルが用いられます。
31+
32+
DreamBooth 方式の dataset で、`conditioning_data_dir` で指定したディレクトリにマスク画像を保存してください。ControlNet のデータセットと同じですので、詳細は [ControlNet-LLLite](train_lllite_README-ja.md#データセットの準備) を参照してください。
33+
34+
### 透明度(アルファチャネル)を使用する方法
35+
36+
学習画像の透明度(アルファチャネル)がマスクとして使用されます。透明度が 0 の部分は無視され、255 の部分は学習されます。半透明の場合は、その透明度に応じてロス重みが変化します(127 ならおおむね 0.5)。
37+
38+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/0baa129b-446a-4aac-b98c-7208efb0e75e)
39+
40+
※それぞれの画像は透過PNG
41+
42+
学習時のスクリプトのオプションに `--alpha_mask` を指定するか、dataset の設定ファイルの subset で、`alpha_mask` を指定してください。たとえば、以下のようになります。
43+
44+
```toml
45+
[[datasets.subsets]]
46+
image_dir = "/path/to/image/dir"
47+
caption_extension = ".txt"
48+
num_repeats = 8
49+
alpha_mask = true
50+
```
51+
52+
## 学習時の注意事項
53+
54+
- 現時点では DreamBooth 方式の dataset のみ対応しています。
55+
- マスクは latents のサイズ、つまり 1/8 に縮小されてから適用されます。そのため、細かい部分(たとえばアホ毛やイヤリングなど)はうまく学習できない可能性があります。マスクをわずかに拡張するなどの工夫が必要かもしれません。
56+
- マスクロスを用いる場合、学習対象外の部分をキャプションに含める必要はないかもしれません。(要検証)
57+
- `alpha_mask` の場合、マスクの有無を切り替えると latents キャッシュが自動的に再生成されます。

docs/masked_loss_README.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
## Masked Loss
2+
3+
Masked loss is a feature that allows you to train only part of an image by calculating the loss only for the part specified by the mask of the input image. For example, if you want to train a character, you can train only the character part by masking it, ignoring the background.
4+
5+
There are two ways to specify the mask for masked loss.
6+
7+
- Using a mask image
8+
- Using transparency (alpha channel) of the image
9+
10+
The sample uses the "AI image model training data" from [ZunZunPJ Illustration/3D Data](https://zunko.jp/con_illust.html).
11+
12+
### Using a mask image
13+
14+
This is a method of preparing a mask image corresponding to each training image. Prepare a mask image with the same file name as the training image and save it in a different directory from the training image.
15+
16+
- Training image
17+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/607c5116-5f62-47de-8b66-9c4a597f0441)
18+
- Mask image
19+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/53e9b0f8-a4bf-49ed-882d-4026f84e8450)
20+
21+
```.toml
22+
[[datasets.subsets]]
23+
image_dir = "/path/to/a_zundamon"
24+
caption_extension = ".txt"
25+
conditioning_data_dir = "/path/to/a_zundamon_mask"
26+
num_repeats = 8
27+
```
28+
29+
The mask image is the same size as the training image, with the part to be trained drawn in white and the part to be ignored in black. It also supports grayscale (127 gives a loss weight of 0.5). The R channel of the mask image is used currently.
30+
31+
Use the dataset in the DreamBooth method, and save the mask image in the directory specified by `conditioning_data_dir`. It is the same as the ControlNet dataset, so please refer to [ControlNet-LLLite](train_lllite_README.md#Preparing-the-dataset) for details.
32+
33+
### Using transparency (alpha channel) of the image
34+
35+
The transparency (alpha channel) of the training image is used as a mask. The part with transparency 0 is ignored, the part with transparency 255 is trained. For semi-transparent parts, the loss weight changes according to the transparency (127 gives a weight of about 0.5).
36+
37+
![image](https://github.com/kohya-ss/sd-scripts/assets/52813779/0baa129b-446a-4aac-b98c-7208efb0e75e)
38+
39+
※Each image is a transparent PNG
40+
41+
Specify `--alpha_mask` in the training script options or specify `alpha_mask` in the subset of the dataset configuration file. For example, it will look like this.
42+
43+
```toml
44+
[[datasets.subsets]]
45+
image_dir = "/path/to/image/dir"
46+
caption_extension = ".txt"
47+
num_repeats = 8
48+
alpha_mask = true
49+
```
50+
51+
## Notes on training
52+
53+
- At the moment, only the dataset in the DreamBooth method is supported.
54+
- The mask is applied after the size is reduced to 1/8, which is the size of the latents. Therefore, fine details (such as ahoge or earrings) may not be learned well. Some dilations of the mask may be necessary.
55+
- If using masked loss, it may not be necessary to include parts that are not to be trained in the caption. (To be verified)
56+
- In the case of `alpha_mask`, the latents cache is automatically regenerated when the enable/disable state of the mask is switched.

docs/train_network_README-ja.md

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ accelerate launch --num_cpu_threads_per_process 1 train_network.py
102102
* Text Encoderに関連するLoRAモジュールに、通常の学習率(--learning_rateオプションで指定)とは異なる学習率を使う時に指定します。Text Encoderのほうを若干低めの学習率(5e-5など)にしたほうが良い、という話もあるようです。
103103
* `--network_args`
104104
* 複数の引数を指定できます。後述します。
105+
* `--alpha_mask`
106+
* 画像のアルファ値をマスクとして使用します。透過画像を学習する際に使用します。[PR #1223](https://github.com/kohya-ss/sd-scripts/pull/1223)
105107

106108
`--network_train_unet_only``--network_train_text_encoder_only` の両方とも未指定時(デフォルト)はText EncoderとU-Netの両方のLoRAモジュールを有効にします。
107109

docs/train_network_README-zh.md

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ LoRA的模型将会被保存在通过`--output_dir`选项指定的文件夹中
101101
* 当在Text Encoder相关的LoRA模块中使用与常规学习率(由`--learning_rate`选项指定)不同的学习率时,应指定此选项。可能最好将Text Encoder的学习率稍微降低(例如5e-5)。
102102
* `--network_args`
103103
* 可以指定多个参数。将在下面详细说明。
104+
* `--alpha_mask`
105+
* 使用图像的 Alpha 值作为遮罩。这在学习透明图像时使用。[PR #1223](https://github.com/kohya-ss/sd-scripts/pull/1223)
104106

105107
当未指定`--network_train_unet_only``--network_train_text_encoder_only`时(默认情况),将启用Text Encoder和U-Net的两个LoRA模块。
106108

finetune/prepare_buckets_latents.py

+27-6
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111

1212
import torch
1313
from library.device_utils import init_ipex, get_preferred_device
14+
1415
init_ipex()
1516

1617
from torchvision import transforms
1718

1819
import library.model_util as model_util
1920
import library.train_util as train_util
2021
from library.utils import setup_logging
22+
2123
setup_logging()
2224
import logging
25+
2326
logger = logging.getLogger(__name__)
2427

2528
DEVICE = get_preferred_device()
@@ -89,7 +92,9 @@ def main(args):
8992

9093
# bucketのサイズを計算する
9194
max_reso = tuple([int(t) for t in args.max_resolution.split(",")])
92-
assert len(max_reso) == 2, f"illegal resolution (not 'width,height') / 画像サイズに誤りがあります。'幅,高さ'で指定してください: {args.max_resolution}"
95+
assert (
96+
len(max_reso) == 2
97+
), f"illegal resolution (not 'width,height') / 画像サイズに誤りがあります。'幅,高さ'で指定してください: {args.max_resolution}"
9398

9499
bucket_manager = train_util.BucketManager(
95100
args.bucket_no_upscale, max_reso, args.min_bucket_reso, args.max_bucket_reso, args.bucket_reso_steps
@@ -107,7 +112,7 @@ def main(args):
107112
def process_batch(is_last):
108113
for bucket in bucket_manager.buckets:
109114
if (is_last and len(bucket) > 0) or len(bucket) >= args.batch_size:
110-
train_util.cache_batch_latents(vae, True, bucket, args.flip_aug, False)
115+
train_util.cache_batch_latents(vae, True, bucket, args.flip_aug, args.alpha_mask, False)
111116
bucket.clear()
112117

113118
# 読み込みの高速化のためにDataLoaderを使うオプション
@@ -208,7 +213,9 @@ def setup_parser() -> argparse.ArgumentParser:
208213
parser.add_argument("in_json", type=str, help="metadata file to input / 読み込むメタデータファイル")
209214
parser.add_argument("out_json", type=str, help="metadata file to output / メタデータファイル書き出し先")
210215
parser.add_argument("model_name_or_path", type=str, help="model name or path to encode latents / latentを取得するためのモデル")
211-
parser.add_argument("--v2", action="store_true", help="not used (for backward compatibility) / 使用されません(互換性のため残してあります)")
216+
parser.add_argument(
217+
"--v2", action="store_true", help="not used (for backward compatibility) / 使用されません(互換性のため残してあります)"
218+
)
212219
parser.add_argument("--batch_size", type=int, default=1, help="batch size in inference / 推論時のバッチサイズ")
213220
parser.add_argument(
214221
"--max_data_loader_n_workers",
@@ -231,18 +238,32 @@ def setup_parser() -> argparse.ArgumentParser:
231238
help="steps of resolution for buckets, divisible by 8 is recommended / bucketの解像度の単位、8で割り切れる値を推奨します",
232239
)
233240
parser.add_argument(
234-
"--bucket_no_upscale", action="store_true", help="make bucket for each image without upscaling / 画像を拡大せずbucketを作成します"
241+
"--bucket_no_upscale",
242+
action="store_true",
243+
help="make bucket for each image without upscaling / 画像を拡大せずbucketを作成します",
235244
)
236245
parser.add_argument(
237-
"--mixed_precision", type=str, default="no", choices=["no", "fp16", "bf16"], help="use mixed precision / 混合精度を使う場合、その精度"
246+
"--mixed_precision",
247+
type=str,
248+
default="no",
249+
choices=["no", "fp16", "bf16"],
250+
help="use mixed precision / 混合精度を使う場合、その精度",
238251
)
239252
parser.add_argument(
240253
"--full_path",
241254
action="store_true",
242255
help="use full path as image-key in metadata (supports multiple directories) / メタデータで画像キーをフルパスにする(複数の学習画像ディレクトリに対応)",
243256
)
244257
parser.add_argument(
245-
"--flip_aug", action="store_true", help="flip augmentation, save latents for flipped images / 左右反転した画像もlatentを取得、保存する"
258+
"--flip_aug",
259+
action="store_true",
260+
help="flip augmentation, save latents for flipped images / 左右反転した画像もlatentを取得、保存する",
261+
)
262+
parser.add_argument(
263+
"--alpha_mask",
264+
type=str,
265+
default="",
266+
help="save alpha mask for images for loss calculation / 損失計算用に画像のアルファマスクを保存する",
246267
)
247268
parser.add_argument(
248269
"--skip_existing",

library/config_util.py

+5
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,13 @@ class DreamBoothSubsetParams(BaseSubsetParams):
8686
class_tokens: Optional[str] = None
8787
caption_extension: str = ".caption"
8888
cache_info: bool = False
89+
alpha_mask: bool = False
8990

9091

9192
@dataclass
9293
class FineTuningSubsetParams(BaseSubsetParams):
9394
metadata_file: Optional[str] = None
95+
alpha_mask: bool = False
9496

9597

9698
@dataclass
@@ -213,11 +215,13 @@ def __validate_and_convert_scalar_or_twodim(klass, value: Union[float, Sequence]
213215
DB_SUBSET_DISTINCT_SCHEMA = {
214216
Required("image_dir"): str,
215217
"is_reg": bool,
218+
"alpha_mask": bool,
216219
}
217220
# FT means FineTuning
218221
FT_SUBSET_DISTINCT_SCHEMA = {
219222
Required("metadata_file"): str,
220223
"image_dir": str,
224+
"alpha_mask": bool,
221225
}
222226
CN_SUBSET_ASCENDABLE_SCHEMA = {
223227
"caption_extension": str,
@@ -538,6 +542,7 @@ def generate_dataset_group_by_blueprint(dataset_group_blueprint: DatasetGroupBlu
538542
random_crop: {subset.random_crop}
539543
token_warmup_min: {subset.token_warmup_min},
540544
token_warmup_step: {subset.token_warmup_step},
545+
alpha_mask: {subset.alpha_mask},
541546
"""
542547
),
543548
" ",

library/custom_train_functions.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -480,12 +480,20 @@ def apply_noise_offset(latents, noise, noise_offset, adaptive_noise_scale):
480480

481481

482482
def apply_masked_loss(loss, batch):
483-
# mask image is -1 to 1. we need to convert it to 0 to 1
484-
mask_image = batch["conditioning_images"].to(dtype=loss.dtype)[:, 0].unsqueeze(1) # use R channel
483+
if "conditioning_images" in batch:
484+
# conditioning image is -1 to 1. we need to convert it to 0 to 1
485+
mask_image = batch["conditioning_images"].to(dtype=loss.dtype)[:, 0].unsqueeze(1) # use R channel
486+
mask_image = mask_image / 2 + 0.5
487+
# print(f"conditioning_image: {mask_image.shape}")
488+
elif "alpha_masks" in batch and batch["alpha_masks"] is not None:
489+
# alpha mask is 0 to 1
490+
mask_image = batch["alpha_masks"].to(dtype=loss.dtype).unsqueeze(1) # add channel dimension
491+
# print(f"mask_image: {mask_image.shape}, {mask_image.mean()}")
492+
else:
493+
return loss
485494

486495
# resize to the same size as the loss
487496
mask_image = torch.nn.functional.interpolate(mask_image, size=loss.shape[2:], mode="area")
488-
mask_image = mask_image / 2 + 0.5
489497
loss = loss * mask_image
490498
return loss
491499

0 commit comments

Comments
 (0)