논문 한 번 읽었다고 SD 프로세스를 이해한 것은 절대 아니다! 직접 코드를 뜯어보고 GPT한테 물어도 보고 하면서 이해하려고 노력해야 조금이나마 내 것이 되는 것 같다..
위 깃헙에서 main 함수 중 루프 돌면서 학습하는 부분만 가져와 봤다.
이해한 내용은 모두 한글로 주석을 달아 두었다.
# 매 epoch마다.. (training step이라고도 하고, 100~1000회정도로 고정)
for epoch in range(first_epoch, args.num_train_epochs):
train_loss = 0.0 # loss 초기화
for step, batch in enumerate(train_dataloader):
with accelerator.accumulate(unet):
# Convert images to latent space (픽셀단위 -> latent level)
latents = vae.encode(batch["pixel_values"].to(weight_dtype)).latent_dist.sample()
latents = latents * vae.config.scaling_factor
# Sample noise that we'll add to the latents
noise = torch.randn_like(latents)
if args.noise_offset:
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
noise += args.noise_offset * torch.randn(
(latents.shape[0], latents.shape[1], 1, 1), device=latents.device
)
if args.input_perturbation:
new_noise = noise + args.input_perturbation * torch.randn_like(noise)
bsz = latents.shape[0]
# Sample a random timestep for each image
# 매 번 다른 timestep을 설정한다
timesteps = torch.randint(0, noise_scheduler.config.num_train_timesteps, (bsz,), device=latents.device)
timesteps = timesteps.long()
# 노이즈 추가
# Add noise to the latents according to the noise magnitude at each timestep
# (this is the forward diffusion process)
if args.input_perturbation:
noisy_latents = noise_scheduler.add_noise(latents, new_noise, timesteps)
else:
noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps)
# Get the text embedding for conditioning
encoder_hidden_states = text_encoder(batch["input_ids"])[0]
# Get the target for loss depending on the prediction type
if args.prediction_type is not None:
# set prediction_type of scheduler if defined
noise_scheduler.register_to_config(prediction_type=args.prediction_type)
if noise_scheduler.config.prediction_type == "epsilon":
target = noise
elif noise_scheduler.config.prediction_type == "v_prediction":
target = noise_scheduler.get_velocity(latents, noise, timesteps)
else:
raise ValueError(f"Unknown prediction type {noise_scheduler.config.prediction_type}")
# unet이 얼마나 노이즈 꼈는지 예측하고
# Predict the noise residual and compute loss
model_pred = unet(noisy_latents, timesteps, encoder_hidden_states).sample
# 예측값과 실제 노이즈(target) 비교, loss 계산
if args.snr_gamma is None:
loss = F.mse_loss(model_pred.float(), target.float(), reduction="mean")
else:
# Compute loss-weights as per Section 3.4 of https://arxiv.org/abs/2303.09556.
# Since we predict the noise instead of x_0, the original formulation is slightly changed.
# This is discussed in Section 4.2 of the same paper.
snr = compute_snr(noise_scheduler, timesteps)
if noise_scheduler.config.prediction_type == "v_prediction":
# Velocity objective requires that we add one to SNR values before we divide by them.
snr = snr + 1
mse_loss_weights = (
torch.stack([snr, args.snr_gamma * torch.ones_like(timesteps)], dim=1).min(dim=1)[0] / snr
)
loss = F.mse_loss(model_pred.float(), target.float(), reduction="none")
loss = loss.mean(dim=list(range(1, len(loss.shape)))) * mse_loss_weights
loss = loss.mean()
# accelerator를 사용하는 경우 process 여러 개에 나눠서 학습하기 때문에
# loss를 모아서 평균내준다 (avg_loss)
# gradient_accumulation으로 작은 batch size를 사용하는 경우에도 평균내준다 (train_loss)
# Gather the losses across all processes for logging (if we use distributed training).
avg_loss = accelerator.gather(loss.repeat(args.train_batch_size)).mean()
train_loss += avg_loss.item() / args.gradient_accumulation_steps
# Backpropagate
accelerator.backward(loss)
if accelerator.sync_gradients:
accelerator.clip_grad_norm_(unet.parameters(), args.max_grad_norm)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
# Checks if the accelerator has performed an optimization step behind the scenes
if accelerator.sync_gradients:
if args.use_ema:
ema_unet.step(unet.parameters())
progress_bar.update(1)
global_step += 1
accelerator.log({"train_loss": train_loss}, step=global_step)
train_loss = 0.0
# checkpoint 만들기 (저장, 로그 찍기)
if global_step % args.checkpointing_steps == 0:
if accelerator.is_main_process:
# _before_ saving state, check if this save would set us over the `checkpoints_total_limit`
if args.checkpoints_total_limit is not None:
checkpoints = os.listdir(args.output_dir)
checkpoints = [d for d in checkpoints if d.startswith("checkpoint")]
checkpoints = sorted(checkpoints, key=lambda x: int(x.split("-")[1]))
# before we save the new checkpoint, we need to have at _most_ `checkpoints_total_limit - 1` checkpoints
if len(checkpoints) >= args.checkpoints_total_limit:
num_to_remove = len(checkpoints) - args.checkpoints_total_limit + 1
removing_checkpoints = checkpoints[0:num_to_remove]
logger.info(
f"{len(checkpoints)} checkpoints already exist, removing {len(removing_checkpoints)} checkpoints"
)
logger.info(f"removing checkpoints: {', '.join(removing_checkpoints)}")
for removing_checkpoint in removing_checkpoints:
removing_checkpoint = os.path.join(args.output_dir, removing_checkpoint)
shutil.rmtree(removing_checkpoint)
save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")
accelerator.save_state(save_path)
logger.info(f"Saved state to {save_path}")
logs = {"step_loss": loss.detach().item(), "lr": lr_scheduler.get_last_lr()[0]}
progress_bar.set_postfix(**logs)
if global_step >= args.max_train_steps:
break
if accelerator.is_main_process:
if args.validation_prompts is not None and epoch % args.validation_epochs == 0:
if args.use_ema:
# Store the UNet parameters temporarily and load the EMA parameters to perform inference.
ema_unet.store(unet.parameters())
ema_unet.copy_to(unet.parameters())
log_validation(
vae,
text_encoder,
tokenizer,
unet,
args,
accelerator,
weight_dtype,
global_step,
)
if args.use_ema:
# Switch back to the original UNet parameters.
ema_unet.restore(unet.parameters())
간단하게 적자면, 이미지 X장을 저장하고 싶다면, training step Y번의 루프를 돌면서 training 해준다.
각 y마다, 이미지 X장에 대해서, 노이즈를 추가해준다. 이 때 각 이미지에 추가되는 노이즈의 크기가 다르다. timestep이라는 랜덤 정수들을 이미지 개수만큼 뽑아서 노이즈의 크기를 조절해주기 때문.
이제 이 노이즈를 모델이 예측하도록 한다. 그리고 실제로 이미지에 추가된 노이즈랑 비교하고, loss를 계산해서 모델을 업데이트해준다. 이 과정을 Y번 반복한다.
그러니까 timestep은 위 이미지에서 Z_T가 얼마나 많은 노이즈가 꼈는지를 조절하는 값이고
training step은 이 noise 씌우기 - noise 예측하기의 과정을 몇 번 해야 하느냐의 값이다.
'Computer > ML·DL·NLP' 카테고리의 다른 글
torchvision.transforms.Compose (0) | 2024.01.26 |
---|---|
[PyTorch] .grad.zero_() zero_grad() 차이점 (0) | 2024.01.16 |
[KoBigBird] Basemodel, configuration (0) | 2022.12.16 |
[ML] train, valid, test batch size 조정하기, 그 영향 (1) | 2022.12.06 |
[PCA] 공분산 행렬, PCA (0) | 2022.09.20 |
댓글