Diffusers documentation

Treinar um modelo de difusão

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Treinar um modelo de difusão

A geração de imagens incondicional é uma aplicação popular dos modelos de difusão que gera imagens semelhantes às do conjunto de dados usado para o treinamento. Normalmente, os melhores resultados são obtidos ao fazer o ajuste fino de um modelo pré-treinado em um conjunto de dados específico. Você pode encontrar muitos desses checkpoints no Hub, mas, se não encontrar nenhum que lhe agrade, você sempre pode treinar o seu próprio!

Este tutorial ensinará você a treinar um UNet2DModel do zero em um subconjunto do conjunto de dados Smithsonian Butterflies para gerar suas próprias borboletas 🦋.

💡 Este tutorial de treinamento é baseado no notebook Training with 🧨 Diffusers. Para mais detalhes e contexto sobre modelos de difusão, como eles funcionam, confira o notebook!

Antes de começar, verifique se você tem o 🤗 Datasets instalado para carregar e pré-processar conjuntos de dados de imagens, e o 🤗 Accelerate, para simplificar o treinamento em qualquer número de GPUs. O comando a seguir também instalará o TensorBoard para visualizar as métricas de treinamento (você também pode usar o Weights & Biases para acompanhar seu treinamento).

# uncomment to install the necessary libraries in Colab
#!pip install diffusers[training]

Incentivamos você a compartilhar seu modelo com a comunidade. Para isso, será necessário fazer login na sua conta do Hugging Face (crie uma aqui caso ainda não tenha!). Você pode fazer login a partir de um notebook e inserir seu token quando solicitado. Certifique-se de que seu token possui a permissão de escrita.

>>> from huggingface_hub import notebook_login

>>> notebook_login()

Ou faça login a partir do terminal:

hf auth login

Como os checkpoints do modelo são bastante grandes, instale o Git-LFS para versionar esses arquivos grandes:

!sudo apt -qq install git-lfs
!git config --global credential.helper store

Configuração de treinamento

Para facilitar, crie uma classe TrainingConfig contendo os hiperparâmetros de treinamento (sinta-se à vontade para ajustá-los):

>>> from dataclasses import dataclass

>>> @dataclass
... class TrainingConfig:
...     image_size = 128  # the generated image resolution
...     train_batch_size = 16
...     eval_batch_size = 16  # how many images to sample during evaluation
...     num_epochs = 50
...     gradient_accumulation_steps = 1
...     learning_rate = 1e-4
...     lr_warmup_steps = 500
...     save_image_epochs = 10
...     save_model_epochs = 30
...     mixed_precision = "fp16"  # `no` for float32, `fp16` for automatic mixed precision
...     output_dir = "ddpm-butterflies-128"  # the model name locally and on the HF Hub

...     push_to_hub = True  # whether to upload the saved model to the HF Hub
...     hub_model_id = "<your-username>/<my-awesome-model>"  # the name of the repository to create on the HF Hub
...     hub_private_repo = None
...     overwrite_output_dir = True  # overwrite the old model when re-running the notebook
...     seed = 0


>>> config = TrainingConfig()

Carregue o conjunto de dados

Você pode carregar facilmente o conjunto de dados Smithsonian Butterflies com a biblioteca 🤗 Datasets:

>>> from datasets import load_dataset

>>> config.dataset_name = "huggan/smithsonian_butterflies_subset"
>>> dataset = load_dataset(config.dataset_name, split="train")

💡 Você pode encontrar conjuntos de dados adicionais do HugGan Community Event ou usar seu próprio conjunto de dados criando um ImageFolder local. Defina config.dataset_name como o id do repositório do conjunto de dados, se ele for do HugGan Community Event, ou imagefolder se você estiver usando suas próprias imagens.

🤗 O Datasets usa o recurso Image para decodificar automaticamente os dados da imagem e carregá-la como PIL.Image, que podemos visualizar:

>>> import matplotlib.pyplot as plt

>>> fig, axs = plt.subplots(1, 4, figsize=(16, 4))
>>> for i, image in enumerate(dataset[:4]["image"]):
...     axs[i].imshow(image)
...     axs[i].set_axis_off()
>>> fig.show()

As imagens, no entanto, têm todos os tamanhos diferentes, então você precisará pré-processá-las primeiro:

  • Resize altera o tamanho da imagem para o definido em config.image_size.
  • RandomHorizontalFlip aumenta o conjunto de dados espelhando as imagens aleatoriamente.
  • Normalize é importante para reescalar os valores dos pixels para o intervalo [-1, 1], que é o que o modelo espera.
>>> from torchvision import transforms

>>> preprocess = transforms.Compose(
...     [
...         transforms.Resize((config.image_size, config.image_size)),
...         transforms.RandomHorizontalFlip(),
...         transforms.ToTensor(),
...         transforms.Normalize([0.5], [0.5]),
...     ]
... )

Use o método set_transform do 🤗 Datasets para aplicar a função preprocess em tempo real durante o treinamento:

>>> def transform(examples):
...     images = [preprocess(image.convert("RGB")) for image in examples["image"]]
...     return {"images": images}


>>> dataset.set_transform(transform)

Sinta-se à vontade para visualizar as imagens novamente para confirmar que elas foram redimensionadas. Agora você está pronto para envolver o dataset em um DataLoader para o treinamento!

>>> import torch

>>> train_dataloader = torch.utils.data.DataLoader(dataset, batch_size=config.train_batch_size, shuffle=True)

Crie um UNet2DModel

Modelos pré-treinados no 🧨 Diffusers são facilmente criados a partir de sua classe de modelo com os parâmetros desejados. Por exemplo, para criar um UNet2DModel:

>>> from diffusers import UNet2DModel

>>> model = UNet2DModel(
...     sample_size=config.image_size,  # the target image resolution
...     in_channels=3,  # the number of input channels, 3 for RGB images
...     out_channels=3,  # the number of output channels
...     layers_per_block=2,  # how many ResNet layers to use per UNet block
...     block_out_channels=(128, 128, 256, 256, 512, 512),  # the number of output channels for each UNet block
...     down_block_types=(
...         "DownBlock2D",  # a regular ResNet downsampling block
...         "DownBlock2D",
...         "DownBlock2D",
...         "DownBlock2D",
...         "AttnDownBlock2D",  # a ResNet downsampling block with spatial self-attention
...         "DownBlock2D",
...     ),
...     up_block_types=(
...         "UpBlock2D",  # a regular ResNet upsampling block
...         "AttnUpBlock2D",  # a ResNet upsampling block with spatial self-attention
...         "UpBlock2D",
...         "UpBlock2D",
...         "UpBlock2D",
...         "UpBlock2D",
...     ),
... )

Muitas vezes, é uma boa ideia verificar rapidamente se a forma da imagem de exemplo corresponde à forma da saída do modelo:

>>> sample_image = dataset[0]["images"].unsqueeze(0)
>>> print("Input shape:", sample_image.shape)
Input shape: torch.Size([1, 3, 128, 128])

>>> print("Output shape:", model(sample_image, timestep=0).sample.shape)
Output shape: torch.Size([1, 3, 128, 128])

Ótimo! Em seguida, você precisará de um agendador para adicionar algum ruído à imagem.

Criar um agendador

O agendador se comporta de maneira diferente dependendo de você estar usando o modelo para treinamento ou inferência. Durante a inferência, o agendador gera a imagem a partir do ruído. Durante o treinamento, o agendador recebe uma saída do modelo - ou uma amostra - de um ponto específico no processo de difusão e aplica ruído à imagem de acordo com um agendamento de ruído e uma regra de atualização.

Vamos dar uma olhada no DDPMScheduler e usar o método add_noise para adicionar um pouco de ruído aleatório à sample_image de antes:

>>> import torch
>>> from PIL import Image
>>> from diffusers import DDPMScheduler

>>> noise_scheduler = DDPMScheduler(num_train_timesteps=1000)
>>> noise = torch.randn(sample_image.shape)
>>> timesteps = torch.LongTensor([50])
>>> noisy_image = noise_scheduler.add_noise(sample_image, noise, timesteps)

>>> Image.fromarray(((noisy_image.permute(0, 2, 3, 1) + 1.0) * 127.5).type(torch.uint8).numpy()[0])

O objetivo de treinamento do modelo é prever o ruído adicionado à imagem. A perda nesta etapa pode ser calculada por:

>>> import torch.nn.functional as F

>>> noise_pred = model(noisy_image, timesteps).sample
>>> loss = F.mse_loss(noise_pred, noise)

Treinar o modelo

Até aqui, você já tem a maior parte das peças para começar a treinar o modelo e o que falta é juntar tudo.

Primeiro, você precisará de um otimizador e de um agendador de taxa de aprendizado:

>>> from diffusers.optimization import get_cosine_schedule_with_warmup

>>> optimizer = torch.optim.AdamW(model.parameters(), lr=config.learning_rate)
>>> lr_scheduler = get_cosine_schedule_with_warmup(
...     optimizer=optimizer,
...     num_warmup_steps=config.lr_warmup_steps,
...     num_training_steps=(len(train_dataloader) * config.num_epochs),
... )

Em seguida, você precisará de uma maneira de avaliar o modelo. Para a avaliação, você pode usar o DDPMPipeline para gerar um lote de imagens de exemplo e salvá-lo como uma grade:

>>> from diffusers import DDPMPipeline
>>> from diffusers.utils import make_image_grid
>>> import os

>>> def evaluate(config, epoch, pipeline):
...     # Sample some images from random noise (this is the backward diffusion process).
...     # The default pipeline output type is `List[PIL.Image]`
...     images = pipeline(
...         batch_size=config.eval_batch_size,
...         generator=torch.Generator(device='cpu').manual_seed(config.seed), # Use a separate torch generator to avoid rewinding the random state of the main training loop
...     ).images

...     # Make a grid out of the images
...     image_grid = make_image_grid(images, rows=4, cols=4)

...     # Save the images
...     test_dir = os.path.join(config.output_dir, "samples")
...     os.makedirs(test_dir, exist_ok=True)
...     image_grid.save(f"{test_dir}/{epoch:04d}.png")

Agora você pode agrupar todos esses componentes em um loop de treinamento com 🤗 Accelerate para registro fácil no TensorBoard, acúmulo de gradientes e treinamento com precisão mista. Para enviar o modelo para o Hub, escreva uma função para obter o nome do seu repositório e informações e, em seguida, envie-o para o Hub.

💡 O loop de treinamento abaixo pode parecer intimidador e longo, mas valerá a pena mais tarde, quando você iniciar o treinamento em apenas uma linha de código! Se não consegue esperar e quer começar a gerar imagens, sinta-se à vontade para copiar e executar o código abaixo. Você sempre pode voltar e examinar o loop de treinamento com mais detalhes depois, como quando estiver esperando seu modelo terminar o treinamento. 🤗

>>> from accelerate import Accelerator
>>> from huggingface_hub import create_repo, upload_folder
>>> from tqdm.auto import tqdm
>>> from pathlib import Path
>>> import os

>>> def train_loop(config, model, noise_scheduler, optimizer, train_dataloader, lr_scheduler):
...     # Initialize accelerator and tensorboard logging
...     accelerator = Accelerator(
...         mixed_precision=config.mixed_precision,
...         gradient_accumulation_steps=config.gradient_accumulation_steps,
...         log_with="tensorboard",
...         project_dir=os.path.join(config.output_dir, "logs"),
...     )
...     if accelerator.is_main_process:
...         if config.output_dir is not None:
...             os.makedirs(config.output_dir, exist_ok=True)
...         if config.push_to_hub:
...             repo_id = create_repo(
...                 repo_id=config.hub_model_id or Path(config.output_dir).name, exist_ok=True
...             ).repo_id
...         accelerator.init_trackers("train_example")

...     # Prepare everything
...     # There is no specific order to remember, you just need to unpack the
...     # objects in the same order you gave them to the prepare method.
...     model, optimizer, train_dataloader, lr_scheduler = accelerator.prepare(
...         model, optimizer, train_dataloader, lr_scheduler
...     )

...     global_step = 0

...     # Now you train the model
...     for epoch in range(config.num_epochs):
...         progress_bar = tqdm(total=len(train_dataloader), disable=not accelerator.is_local_main_process)
...         progress_bar.set_description(f"Epoch {epoch}")

...         for step, batch in enumerate(train_dataloader):
...             clean_images = batch["images"]
...             # Sample noise to add to the images
...             noise = torch.randn(clean_images.shape, device=clean_images.device)
...             bs = clean_images.shape[0]

...             # Sample a random timestep for each image
...             timesteps = torch.randint(
...                 0, noise_scheduler.config.num_train_timesteps, (bs,), device=clean_images.device,
...                 dtype=torch.int64
...             )

...             # Add noise to the clean images according to the noise magnitude at each timestep
...             # (this is the forward diffusion process)
...             noisy_images = noise_scheduler.add_noise(clean_images, noise, timesteps)

...             with accelerator.accumulate(model):
...                 # Predict the noise residual
...                 noise_pred = model(noisy_images, timesteps, return_dict=False)[0]
...                 loss = F.mse_loss(noise_pred, noise)
...                 accelerator.backward(loss)

...                 if accelerator.sync_gradients:
...                     accelerator.clip_grad_norm_(model.parameters(), 1.0)
...                 optimizer.step()
...                 lr_scheduler.step()
...                 optimizer.zero_grad()

...             progress_bar.update(1)
...             logs = {"loss": loss.detach().item(), "lr": lr_scheduler.get_last_lr()[0], "step": global_step}
...             progress_bar.set_postfix(**logs)
...             accelerator.log(logs, step=global_step)
...             global_step += 1

...         # After each epoch you optionally sample some demo images with evaluate() and save the model
...         if accelerator.is_main_process:
...             pipeline = DDPMPipeline(unet=accelerator.unwrap_model(model), scheduler=noise_scheduler)

...             if (epoch + 1) % config.save_image_epochs == 0 or epoch == config.num_epochs - 1:
...                 evaluate(config, epoch, pipeline)

...             if (epoch + 1) % config.save_model_epochs == 0 or epoch == config.num_epochs - 1:
...                 if config.push_to_hub:
...                     upload_folder(
...                         repo_id=repo_id,
...                         folder_path=config.output_dir,
...                         commit_message=f"Epoch {epoch}",
...                         ignore_patterns=["step_*", "epoch_*"],
...                     )
...                 else:
...                     pipeline.save_pretrained(config.output_dir)

Ufa, foi bastante código! Mas você finalmente está pronto para iniciar o treinamento com a função notebook_launcher do 🤗 Accelerate. Passe para a função o loop de treinamento, todos os argumentos de treinamento e o número de processos (você pode alterar esse valor para o número de GPUs disponíveis para você) a serem usados no treinamento:

>>> from accelerate import notebook_launcher

>>> args = (config, model, noise_scheduler, optimizer, train_dataloader, lr_scheduler)

>>> notebook_launcher(train_loop, args, num_processes=1)

Quando o treinamento estiver concluído, confira as imagens finais 🦋 geradas pelo seu modelo de difusão! 🦋

>>> import glob

>>> sample_images = sorted(glob.glob(f"{config.output_dir}/samples/*.png"))
>>> Image.open(sample_images[-1])

Próximas etapas

A geração de imagens incondicional é um exemplo de tarefa que pode ser treinada. Você pode explorar outras tarefas e técnicas de treinamento visitando a página 🧨 Exemplos de Treinamento do Diffusers. Aqui estão alguns exemplos do que você pode aprender:

  • Textual Inversion, um algoritmo que ensina a um modelo um conceito visual específico e o integra na imagem gerada.
  • DreamBooth, uma técnica para gerar imagens personalizadas de um sujeito a partir de várias imagens de entrada do sujeito.
  • Guia para o ajuste fino de um modelo Stable Diffusion em seu próprio conjunto de dados.
  • Guia para o uso de LoRA, uma técnica de ajuste fino de modelos muito grandes que é eficiente em memória e mais rápida.
Update on GitHub