扩散课程文档

DreamBooth 黑客马拉松 🏆

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始使用

Open In Colab

DreamBooth 黑客马拉松 🏆

欢迎参加 DreamBooth 黑客马拉松!在本项赛事中,您将通过对少量您自己的图像进行微调来个性化 Stable Diffusion 模型。为此,我们将使用一种称为DreamBooth的技术,该技术允许将一个主体(例如您的宠物或喜欢的菜肴)植入模型的输出域,以便它可以通过提示中的唯一标识符进行合成。

让我们开始吧!

先决条件

在深入研究此笔记本之前,您应该阅读

  • 第 3 单元自述文件,其中包含对 Stable Diffusion 的深入探讨
  • DreamBooth 博文,以了解此技术的可能性
  • Hugging Face 博文,介绍使用 DreamBooth 微调 Stable Diffusion 的最佳实践

🚨 注意:此笔记本中的代码至少需要 14GB 的 GPU 显存,并且是 🤗 Diffusers 中提供的官方训练脚本的简化版本。它为大多数应用程序生成不错的模型,但如果您至少有 24GB 的显存可用,我们建议您尝试使用高级功能(如类别保留损失)和微调文本编码器。查看 🤗 Diffusers 文档以了解更多详细信息。

什么是 DreamBooth?

DreamBooth 是一种使用专门的微调形式教授 Stable Diffusion 新概念的技术。如果您在 Twitter 或 Reddit 上,您可能已经看到人们使用此技术创建(通常很有趣)的自己的头像。例如,以下是Andrej Karpathy作为牛仔的样子(您可能需要运行单元格才能看到输出)

%%html
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Stableboost auto-suggests a few hundred prompts by default but you can generate additional variations for any one prompt that seems to be giving fun/interesting results, or adjust it in any way: <a href="https://t.co/qWmadiXftP">pic.twitter.com/qWmadiXftP</a></p>&mdash; Andrej Karpathy (@karpathy) <a href="https://twitter.com/karpathy/status/1600578187141840896?ref_src=twsrc%5Etfw">December 7, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

DreamBooth 的工作原理如下

  • 收集大约 10-20 张主体的输入图像(例如,您的狗)并定义一个唯一标识符 [V] 来指代该主体。此标识符通常是某个虚构的单词,例如 flffydog,它在推理时植入不同的文本提示中,以将主体置于不同的上下文中。
  • 通过提供图像以及包含唯一标识符和类别名称(在本例中为“dog”)的文本提示(例如“一张 [V] 狗的照片”)来微调扩散模型。
  • (可选)应用特殊的特定类别先验保留损失,该损失利用模型对类别的语义先验,并通过在文本提示中注入类别名称来鼓励它生成属于主体类别的多样化实例。在实践中,此步骤实际上仅在人脸上需要,并且可以在我们将在本次黑客马拉松中探索的主题中跳过。

DreamBooth 技术的概述如下面的图像所示

DreamBooth 可以做什么?

除了将您的主体放置在有趣的位置之外,DreamBooth 还可以用于文本引导视图合成,其中主体从不同的视角观看,如下面的示例所示

DreamBooth 还可以用于修改主体的属性,例如颜色或混合动物物种!

现在我们已经了解了 DreamBooth 可以做的一些很酷的事情,让我们开始训练我们自己的模型吧!

步骤 1:设置

如果您在 Google Colab 或 Kaggle 上运行此笔记本,请运行以下单元格以安装所需的库

%pip install -qqU diffusers transformers bitsandbytes accelerate ftfy datasets

如果您在 Kaggle 上运行,则需要安装最新的 PyTorch 版本才能与 🤗 Accelerate 一起使用

# Uncomment and run if using Kaggle's notebooks. You may need to restart the notebook afterwards
# %pip install -U torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu116

为了能够将您的模型推送到 Hub 并使其出现在DreamBooth 排行榜上,您需要执行更多步骤。首先,您必须从您的 Hugging Face 帐户中创建一个具有写入访问权限访问令牌,然后执行以下单元格并输入您的令牌

from huggingface_hub import notebook_login

notebook_login()

最后一步是安装 Git LFS

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

步骤 2:选择主题

本次竞赛包含 5 个主题,每个主题将收集属于以下类别的模型

  • 动物 🐨:使用此主题生成您宠物或喜欢的动物在卫城闲逛、游泳或在太空中飞行的图像。
  • 科学 🔬:使用此主题生成关于星系、蛋白质或自然和医学科学任何领域的酷炫合成图像。
  • 食物 🍔:使用此主题对您最喜欢的菜肴或菜系进行 Stable Diffusion 微调。
  • 风景 🏔:使用此主题生成您最喜欢的山脉、湖泊或花园的美丽风景。
  • 万能卡 🔥:使用此主题尽情发挥创意,为任何您选择的类别创建 Stable Diffusion 模型!

我们将为每个主题点赞数最高的 3 个模型颁发奖品,并且鼓励您提交尽可能多的模型!运行下面的单元格以创建一个下拉小部件,您可以在其中选择要提交的主题

import ipywidgets as widgets

theme = "animal"
drop_down = widgets.Dropdown(
    options=["animal", "science", "food", "landscape", "wildcard"],
    description="Pick a theme",
    disabled=False,
)


def dropdown_handler(change):
    global theme
    theme = change.new


drop_down.observe(dropdown_handler, names="value")
display(drop_down)
>>> print(f"You've selected the {theme} theme!")
You've selected the animal theme!

步骤 3:创建图像数据集并将其上传到 Hub

选择主题后,下一步是为该主题创建图像数据集并将其上传到 Hugging Face Hub

  • 您需要大约10-20 张您希望植入模型的主题图像。这些可以是您拍摄的照片或从 Unsplash 等平台下载的照片。或者,您可以查看 Hugging Face Hub 上的任何图像数据集以获取灵感。
  • 为了获得最佳效果,我们建议使用来自不同角度和视角的主题图像。

将图像收集到文件夹中后,您可以使用 UI 拖放图像将其上传到 Hub。有关更多详细信息,请参阅本指南,或观看以下视频

>>> from IPython.display import YouTubeVideo

>>> YouTubeVideo("HaN6qCr_Afc")

或者,您可以使用 🤗 Datasets 的 imagefolder 功能在本地加载数据集,然后将其推送到 Hub

from datasets import load_dataset

dataset = load_dataset("imagefolder", data_dir="your_folder_of_images")
# Push to Hub
dataset.push_to_hub("dreambooth-hackathon-images")
dataset = dataset['train']

创建数据集后,您可以使用以下 load_dataset() 函数下载它

from datasets import load_dataset

dataset_id = "lewtun/corgi"  # CHANGE THIS TO YOUR {hub_username}/{dataset_id}
dataset = load_dataset(dataset_id, split="train")
dataset

现在我们有了数据集,让我们定义一个辅助函数来查看一些图像

>>> from PIL import Image


>>> def image_grid(imgs, rows, cols):
...     assert len(imgs) == rows * cols
...     w, h = imgs[0].size
...     grid = Image.new("RGB", size=(cols * w, rows * h))
...     grid_w, grid_h = grid.size
...     for i, img in enumerate(imgs):
...         grid.paste(img, box=(i % cols * w, i // cols * h))
...     return grid


>>> num_samples = 4
>>> image_grid(dataset["image"][:num_samples], rows=1, cols=num_samples)

如果看起来不错,您可以继续下一步 - 为使用 DreamBooth 进行训练创建 PyTorch 数据集。

步骤 3:创建训练数据集

要为我们的图像创建训练集,我们需要一些组件

  • 一个实例提示,用于在训练开始时为模型提供引导。在大多数情况下,使用“一张[标识符][类别名词]的照片”效果很好,例如,对于我们可爱的柯基犬图片,可以使用“一张ccorgi狗的照片”。
    • 注意:建议您选择一个独特的/虚构的词语(如ccorgi)来描述您的主题。这将确保模型词汇表中的常用词不会被覆盖。
  • 一个分词器,将实例提示转换为可馈送到 Stable Diffusion 文本编码器的输入 ID。
  • 一组图像变换,特别是将图像调整为通用形状,并将像素值归一化为通用均值和标准分布。

考虑到这一点,让我们首先定义实例提示

>>> name_of_your_concept = "ccorgi"  # CHANGE THIS ACCORDING TO YOUR SUBJECT
>>> type_of_thing = "dog"  # CHANGE THIS ACCORDING TO YOUR SUBJECT
>>> instance_prompt = f"a photo of {name_of_your_concept} {type_of_thing}"
>>> print(f"Instance prompt: {instance_prompt}")
Instance prompt: a photo of ccorgi dog

接下来,我们需要创建一个实现 __len____getitem__ dunder 方法的 PyTorch Dataset 对象

from torch.utils.data import Dataset
from torchvision import transforms


class DreamBoothDataset(Dataset):
    def __init__(self, dataset, instance_prompt, tokenizer, size=512):
        self.dataset = dataset
        self.instance_prompt = instance_prompt
        self.tokenizer = tokenizer
        self.size = size
        self.transforms = transforms.Compose(
            [
                transforms.Resize(size),
                transforms.CenterCrop(size),
                transforms.ToTensor(),
                transforms.Normalize([0.5], [0.5]),
            ]
        )

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, index):
        example = {}
        image = self.dataset[index]["image"]
        example["instance_images"] = self.transforms(image)
        example["instance_prompt_ids"] = self.tokenizer(
            self.instance_prompt,
            padding="do_not_pad",
            truncation=True,
            max_length=self.tokenizer.model_max_length,
        ).input_ids
        return example

很好,现在让我们通过加载与原始 Stable Diffusion 模型的文本编码器关联的 CLIP 分词器,然后创建训练数据集来检查这是否有效

from transformers import CLIPTokenizer

# The Stable Diffusion checkpoint we'll fine-tune
model_id = "CompVis/stable-diffusion-v1-4"
tokenizer = CLIPTokenizer.from_pretrained(
    model_id,
    subfolder="tokenizer",
)

train_dataset = DreamBoothDataset(dataset, instance_prompt, tokenizer)
train_dataset[0]

步骤 4:定义数据整理器

现在我们有了训练数据集,接下来我们需要定义一个数据整理器。数据整理器是一个函数,它收集一批数据中的元素并应用一些逻辑来形成一个我们可以提供给模型的单个张量。如果您想了解更多信息,可以查看 Hugging Face 课程中的这段视频Hugging Face 课程

>>> YouTubeVideo("-RPeakdlHYo")

对于 DreamBooth,我们的数据整理器需要为模型提供来自分词器的输入 ID 和来自图像的像素值作为堆叠张量。下面的函数完成了这项工作

import torch


def collate_fn(examples):
    input_ids = [example["instance_prompt_ids"] for example in examples]
    pixel_values = [example["instance_images"] for example in examples]
    pixel_values = torch.stack(pixel_values)
    pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()

    input_ids = tokenizer.pad({"input_ids": input_ids}, padding=True, return_tensors="pt").input_ids

    batch = {
        "input_ids": input_ids,
        "pixel_values": pixel_values,
    }
    return batch

步骤 5:加载 Stable Diffusion 管道的组件

我们几乎准备好了所有用于训练的部件!正如您在关于 Stable Diffusion 的第 3 单元笔记本中看到的那样,管道由几个模型组成

  • 一个文本编码器,将提示转换为文本嵌入。这里我们使用 CLIP,因为它是在训练 Stable Diffusion v1-4 时使用的编码器。
  • 一个 VAE 或变分自动编码器,将图像转换为压缩表示(即潜在变量),并在推理时对其进行解压缩。
  • 一个 UNet,对 VAE 的潜在变量应用去噪操作。

我们可以使用 🤗 Diffusers 和 🤗 Transformers 库加载所有这些组件,如下所示

from diffusers import AutoencoderKL, UNet2DConditionModel
from transformers import CLIPFeatureExtractor, CLIPTextModel

text_encoder = CLIPTextModel.from_pretrained(model_id, subfolder="text_encoder")
vae = AutoencoderKL.from_pretrained(model_id, subfolder="vae")
unet = UNet2DConditionModel.from_pretrained(model_id, subfolder="unet")
feature_extractor = CLIPFeatureExtractor.from_pretrained("openai/clip-vit-base-patch32")

步骤 6:微调模型

现在到了有趣的部分 - 使用 DreamBooth 训练我们的模型!如 Hugging Face 的博客文章Hugging Face 博客文章所示,最需要调整的超参数是学习率和训练步数。

通常,使用较低的学习率可以获得更好的结果,但需要增加训练步数。以下值是一个良好的起点,但您可能需要根据您的数据集进行调整

learning_rate = 2e-06
max_train_steps = 400

接下来,让我们将其他需要的超参数包装到一个 Namespace 对象中,以便更轻松地配置训练运行

from argparse import Namespace

args = Namespace(
    pretrained_model_name_or_path=model_id,
    resolution=512,  # Reduce this if you want to save some memory
    train_dataset=train_dataset,
    instance_prompt=instance_prompt,
    learning_rate=learning_rate,
    max_train_steps=max_train_steps,
    train_batch_size=1,
    gradient_accumulation_steps=1,  # Increase this if you want to lower memory usage
    max_grad_norm=1.0,
    gradient_checkpointing=True,  # Set this to True to lower the memory usage
    use_8bit_adam=True,  # Use 8bit optimizer from bitsandbytes
    seed=3434554,
    sample_batch_size=2,
    output_dir="my-dreambooth",  # Where to save the pipeline
)

最后一步是定义一个 training_function() 函数,该函数包装训练逻辑,并可以传递给 🤗 Accelerate 以处理 1 个或多个 GPU 上的训练。如果这是您第一次使用 🤗 Accelerate,请查看这段视频以快速了解它可以做什么

>>> YouTubeVideo("s7dy8QRgjJ0")

细节应该与我们在第 1 单元和第 2 单元从头开始训练自己的扩散模型时看到的细节相似

import math

import torch.nn.functional as F
from accelerate import Accelerator
from accelerate.utils import set_seed
from diffusers import DDPMScheduler, PNDMScheduler, StableDiffusionPipeline
from diffusers.pipelines.stable_diffusion import StableDiffusionSafetyChecker
from torch.utils.data import DataLoader
from tqdm.auto import tqdm


def training_function(text_encoder, vae, unet):

    accelerator = Accelerator(
        gradient_accumulation_steps=args.gradient_accumulation_steps,
    )

    set_seed(args.seed)

    if args.gradient_checkpointing:
        unet.enable_gradient_checkpointing()

    # Use 8-bit Adam for lower memory usage or to fine-tune the model in 16GB GPUs
    if args.use_8bit_adam:
        import bitsandbytes as bnb

        optimizer_class = bnb.optim.AdamW8bit
    else:
        optimizer_class = torch.optim.AdamW

    optimizer = optimizer_class(
        unet.parameters(),  # Only optimize unet
        lr=args.learning_rate,
    )

    noise_scheduler = DDPMScheduler(
        beta_start=0.00085,
        beta_end=0.012,
        beta_schedule="scaled_linear",
        num_train_timesteps=1000,
    )

    train_dataloader = DataLoader(
        args.train_dataset,
        batch_size=args.train_batch_size,
        shuffle=True,
        collate_fn=collate_fn,
    )

    unet, optimizer, train_dataloader = accelerator.prepare(unet, optimizer, train_dataloader)

    # Move text_encode and vae to gpu
    text_encoder.to(accelerator.device)
    vae.to(accelerator.device)

    # We need to recalculate our total training steps as the size of the training dataloader may have changed
    num_update_steps_per_epoch = math.ceil(len(train_dataloader) / args.gradient_accumulation_steps)
    num_train_epochs = math.ceil(args.max_train_steps / num_update_steps_per_epoch)

    # Train!
    total_batch_size = args.train_batch_size * accelerator.num_processes * args.gradient_accumulation_steps
    # Only show the progress bar once on each machine
    progress_bar = tqdm(range(args.max_train_steps), disable=not accelerator.is_local_main_process)
    progress_bar.set_description("Steps")
    global_step = 0

    for epoch in range(num_train_epochs):
        unet.train()
        for step, batch in enumerate(train_dataloader):
            with accelerator.accumulate(unet):
                # Convert images to latent space
                with torch.no_grad():
                    latents = vae.encode(batch["pixel_values"]).latent_dist.sample()
                    latents = latents * 0.18215

                # Sample noise that we'll add to the latents
                noise = torch.randn(latents.shape).to(latents.device)
                bsz = latents.shape[0]
                # Sample a random timestep for each image
                timesteps = torch.randint(
                    0,
                    noise_scheduler.config.num_train_timesteps,
                    (bsz,),
                    device=latents.device,
                ).long()

                # Add noise to the latents according to the noise magnitude at each timestep
                # (this is the forward diffusion process)
                noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps)

                # Get the text embedding for conditioning
                with torch.no_grad():
                    encoder_hidden_states = text_encoder(batch["input_ids"])[0]

                # Predict the noise residual
                noise_pred = unet(noisy_latents, timesteps, encoder_hidden_states).sample
                loss = F.mse_loss(noise_pred, noise, reduction="none").mean([1, 2, 3]).mean()

                accelerator.backward(loss)
                if accelerator.sync_gradients:
                    accelerator.clip_grad_norm_(unet.parameters(), args.max_grad_norm)
                optimizer.step()
                optimizer.zero_grad()

            # Checks if the accelerator has performed an optimization step behind the scenes
            if accelerator.sync_gradients:
                progress_bar.update(1)
                global_step += 1

            logs = {"loss": loss.detach().item()}
            progress_bar.set_postfix(**logs)

            if global_step >= args.max_train_steps:
                break

        accelerator.wait_for_everyone()

    # Create the pipeline using the trained modules and save it
    if accelerator.is_main_process:
        print(f"Loading pipeline and saving to {args.output_dir}...")
        scheduler = PNDMScheduler(
            beta_start=0.00085,
            beta_end=0.012,
            beta_schedule="scaled_linear",
            skip_prk_steps=True,
            steps_offset=1,
        )
        pipeline = StableDiffusionPipeline(
            text_encoder=text_encoder,
            vae=vae,
            unet=accelerator.unwrap_model(unet),
            tokenizer=tokenizer,
            scheduler=scheduler,
            safety_checker=StableDiffusionSafetyChecker.from_pretrained("CompVis/stable-diffusion-safety-checker"),
            feature_extractor=feature_extractor,
        )
        pipeline.save_pretrained(args.output_dir)

现在我们已经定义了函数,让我们开始训练吧!根据数据集的大小和 GPU 类型,运行此操作可能需要 5 分钟到 1 小时的时间

>>> from accelerate import notebook_launcher

>>> num_of_gpus = 1  # CHANGE THIS TO MATCH THE NUMBER OF GPUS YOU HAVE
>>> notebook_launcher(training_function, args=(text_encoder, vae, unet), num_processes=num_of_gpus)
Launching training on one GPU.

如果您在单个 GPU 上运行,则可以通过将以下代码复制到新单元格并运行它来释放下一部分的一些内存。对于多 GPU 机器,🤗 Accelerate 不允许任何单元格直接使用 torch.cuda 访问 GPU,因此我们不建议在这些情况下使用此技巧

with torch.no_grad():
    torch.cuda.empty_cache()

步骤 7:运行推理并检查生成结果

现在我们已经训练了模型,让我们用它生成一些图像看看效果如何!首先,我们将从保存模型的输出目录加载管道。

pipe = StableDiffusionPipeline.from_pretrained(
    args.output_dir,
    torch_dtype=torch.float16,
).to("cuda")

接下来,让我们生成一些图像。prompt 变量稍后将用于在 Hugging Face Hub 小部件上设置默认值,因此请尝试找到一个合适的提示。您可能还想尝试使用 CLIP Interrogator 创建复杂的提示。

>>> # Pick a funny prompt here and it will be used as the widget's default
>>> # when we push to the Hub in the next section
>>> prompt = f"a photo of {name_of_your_concept} {type_of_thing} in the Acropolis"

>>> # Tune the guidance to control how closely the generations follow the prompt
>>> # Values between 7-11 usually work best
>>> guidance_scale = 7

>>> num_cols = 2
>>> all_images = []
>>> for _ in range(num_cols):
...     images = pipe(prompt, guidance_scale=guidance_scale).images
...     all_images.extend(images)

>>> image_grid(all_images, 1, num_cols)

步骤 8:将您的模型推送到 Hub

如果您对模型感到满意,最后一步是将其推送到 Hub 并将其在 DreamBooth 排行榜 上查看!

首先,您需要为您的模型仓库定义一个名称。默认情况下,我们使用唯一的标识符和类名,但如果您愿意,可以随时更改。

# Create a name for your model on the Hub. No spaces allowed.
model_name = f"{name_of_your_concept}-{type_of_thing}"

接下来,添加关于您训练的模型类型或您想分享的任何其他信息的简短描述。

# Describe the theme and model you've trained
description = f"""
This is a Stable Diffusion model fine-tuned on `{type_of_thing}` images for the {theme} theme.
"""

最后,运行下面的单元格,在 Hub 上创建一个仓库,并将所有文件(包括一个漂亮的模型卡片)推送到仓库。

>>> # Code to upload a pipeline saved locally to the hub
>>> from huggingface_hub import HfApi, ModelCard, create_repo, get_full_repo_name

>>> # Set up repo and upload files
>>> hub_model_id = get_full_repo_name(model_name)
>>> create_repo(hub_model_id)
>>> api = HfApi()
>>> api.upload_folder(folder_path=args.output_dir, path_in_repo="", repo_id=hub_model_id)

>>> content = f"""
... ---
... license: creativeml-openrail-m
... tags:
... - pytorch
... - diffusers
... - stable-diffusion
... - text-to-image
... - diffusion-models-class
... - dreambooth-hackathon
... - {theme}
... widget:
... - text: {prompt}
... ---

... # DreamBooth model for the {name_of_your_concept} concept trained by {api.whoami()["name"]} on the {dataset_id} dataset.

... This is a Stable Diffusion model fine-tuned on the {name_of_your_concept} concept with DreamBooth. It can be used by modifying the `instance_prompt`: **{instance_prompt}**

... This model was created as part of the DreamBooth Hackathon 🔥. Visit the [organisation page](https://huggingface.co/dreambooth-hackathon) for instructions on how to take part!

... ## Description

... {description}

... ## Usage

... ```python
... from diffusers import StableDiffusionPipeline

... pipeline = StableDiffusionPipeline.from_pretrained('{hub_model_id}')
... image = pipeline().images[0]
... image
... ```
... """

>>> card = ModelCard(content)
>>> hub_url = card.push_to_hub(hub_model_id)
>>> print(f"Upload successful! Model can be found here: {hub_url}")
>>> print(
...     f"View your submission on the public leaderboard here: https://huggingface.co/spaces/dreambooth-hackathon/leaderboard"
... )
Upload successful! Model can be found here: https://huggingface.co/lewtun/test-dogs/blob/main/README.md
View your submission on the public leaderboard here: https://huggingface.co/spaces/dreambooth-hackathon/leaderboard

步骤 9:庆祝 🥳

恭喜您,您已经训练了您的第一个 DreamBooth 模型!您可以为比赛训练任意数量的模型 - 重要的是 **获得最多点赞的模型将获得奖品**,所以不要忘记广泛分享您的作品以获得最多的投票!