博客

人工智能与大模型

扩散和去噪——文本到图像的生成型人工智能

2024.07.19 65分钟阅读

01 扩散的概念

对去噪扩散模型进行训练,以从噪声中提取图案,从而生成理想的图像。训练过程包括显示具有根据噪声调度算法来确定,不同噪声水平图像(或其他数据)的模型示例,旨在预测数据的哪些部分是噪声。如果成功,噪声预测模型将能够从纯噪声中逐渐构建出逼真的图像,在每个时间步长从图像中减去噪声增量。

与本段上边的图像不同,现代扩散模型不会从添加了噪声的图像中预测噪声,至少不会直接预测。相反,它们预测图像潜在空间表示中的噪声。潜在空间表示一组压缩的数字特征中的图像,即变分自动编码器(VAE)的编码模块的输出。这种技巧将“潜在”置于潜在扩散中,大大减少了生成图像的时间和计算要求。正如论文作者所报道的那样,潜在扩散使推理速度至少比直接扩散快2.7倍,训练速度大约快3倍。

从事潜在扩散研究的人经常谈论使用“扩散模型”,但事实上,扩散过程采用了几个模块。如上图所示,文本到图像工作流的扩散管道通常包括文本嵌入模型(及其标记器)、去噪预测/扩散模型和图像解码器。潜在扩散的另一个重要部分是调度器,它决定了如何在一系列“时间步长”内缩放和更新噪声(一系列迭代更新,逐渐从潜在空间中去除噪声)。

02 潜在扩散代码示例

对于我们的大多数示例,我们将使用 CompVis/latet-diffusion-v1-4。文本嵌入由 CLIPTextModel 和 CLIPTokenizer 处理。噪声预测使用 “U-Net”,这是一种图像到图像模型,最初作为生物医学图像应用(特别是分割)的模型而受到关注。为了从去噪的潜在阵列生成图像,流水线使用变分自动编码器(VAE)进行图像解码,将这些阵列转换为图像。

我们将从 HuggingFace 组件构建此管道的版本开始。

# local setup
virtualenv diff_env –python=python3.8
source diff_env/bin/activate
pip install diffusers transformers huggingface-hub
pip install torch --index-url https://download.pytorch.org/whl/cu118

如果您在本地工作,请务必查看 pytorch.org,以确保您的系统版本正确。我们的导入相对简单,下面的代码片段足以完成以下所有演示。

import os
import numpy as np
import torch
from diffusers import StableDiffusionPipeline, AutoPipelineForImage2Image
from diffusers.pipelines.pipeline_utils import numpy_to_pil
from transformers import CLIPTokenizer, CLIPTextModel
from diffusers import AutoencoderKL, UNet2DConditionModel, \
       PNDMScheduler, LMSDiscreteScheduler

from PIL import Image
import matplotlib.pyplot as plt

现在来了解细节。首先定义图像和扩散参数以及提示。

prompt = [" "]

# image settings
height, width = 512, 512

# diffusion settings
number_inference_steps = 64
guidance_scale = 9.0
batch_size = 1

使用您选择的种子初始化伪随机数生成器,以再现结果。

def seed_all(seed):
    torch.manual_seed(seed)
    np.random.seed(seed)

seed_all(193)

现在我们可以初始化文本嵌入模型、自动编码器、U-Net 和时间步长调度器。

tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")
vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", \
        subfolder="vae")
unet = UNet2DConditionModel.from_pretrained("CompVis/stable-diffusion-v1-4",\
        subfolder="unet")
scheduler = PNDMScheduler()
scheduler.set_timesteps(number_inference_steps)

my_device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
vae = vae.to(my_device)
text_encoder = text_encoder.to(my_device)
unet = unet.to(my_device)

将文本提示编码为嵌入需要首先对字符串输入进行标记。标记化将字符替换为与语义单元词汇表对应的整数代码,例如通过字节对编码(BPE)。我们的管道在图像的文本提示旁边嵌入了一个空提示(无文本)。这平衡了所提供的描述和自然出现的图像之间的扩散过程。我们将在本文后面看到如何更改这些组件的相对权重。

prompt = prompt * batch_size
tokens = tokenizer(prompt, padding="max_length",\
max_length=tokenizer.model_max_length, truncation=True,\
        return_tensors="pt")

empty_tokens = tokenizer([""] * batch_size, padding="max_length",\
max_length=tokenizer.model_max_length, truncation=True,\
        return_tensors="pt")


with torch.no_grad():
    text_embeddings = text_encoder(tokens.input_ids.to(my_device))[0]
    max_length = tokens.input_ids.shape[-1]
    notext_embeddings = text_encoder(empty_tokens.input_ids.to(my_device))[0]
    text_embeddings = torch.cat([notext_embeddings, text_embeddings])

我们将潜在空间初始化为随机正态噪声,并根据我们的扩散时间步长调度器对其进行缩放。

latents = torch.randn(batch_size, unet.config.in_channels, \
        height//8, width//8)
latents = (latents * scheduler.init_noise_sigma).to(my_device)

一切准备就绪,我们可以深入到扩散循环本身。我们可以通过在整个过程中定期采样来跟踪图像,这样我们就可以看到噪声是如何逐渐降低的。

images = []
display_every = number_inference_steps // 8

# diffusion loop
for step_idx, timestep in enumerate(scheduler.timesteps):
    with torch.no_grad():
        # concatenate latents, to run null/text prompt in parallel.
        model_in = torch.cat([latents] * 2)
        model_in = scheduler.scale_model_input(model_in,\
                timestep).to(my_device)
        predicted_noise = unet(model_in, timestep, \
                encoder_hidden_states=text_embeddings).sample
        # pnu - empty prompt unconditioned noise prediction
        # pnc - text prompt conditioned noise prediction
        pnu, pnc = predicted_noise.chunk(2)
        # weight noise predictions according to guidance scale
        predicted_noise = pnu + guidance_scale * (pnc - pnu)
        # update the latents
        latents = scheduler.step(predicted_noise, \
                timestep, latents).prev_sample
        # Periodically log images and print progress during diffusion
        if step_idx % display_every == 0\
                or step_idx + 1 == len(scheduler.timesteps):
           image = vae.decode(latents / 0.18215).sample[0]
           image = ((image / 2.) + 0.5).cpu().permute(1,2,0).numpy()
           image = np.clip(image, 0, 1.0)
           images.extend(numpy_to_pil(image))
           print(f"step {step_idx}/{number_inference_steps}: {timestep:.4f}")

在扩散过程结束时,我们对您想要生成的内容进行了很好的渲染。接下来,我们将介绍其他技术以实现更好的控制。由于我们已经制作了扩散管道,我们可以在其余的示例中使用 HuggingFace 的流线型扩散管道。

 

03 控制扩散管道

我们将在本节中使用一组辅助函数:

def seed_all(seed):
    torch.manual_seed(seed)
    np.random.seed(seed)

def grid_show(images, rows=3):
    number_images = len(images)
    height, width = images[0].size
    columns = int(np.ceil(number_images / rows))
    grid = np.zeros((height*rows,width*columns,3))
    for ii, image in enumerate(images):
        grid[ii//columns*height:ii//columns*height+height, \
                ii%columns*width:ii%columns*width+width] = image
        fig, ax = plt.subplots(1,1, figsize=(3*columns, 3*rows))
        ax.imshow(grid / grid.max())
    return grid, fig, ax

def callback_stash_latents(ii, tt, latents):
    # adapted from fastai/diffusion-nbs/stable_diffusion.ipynb
    latents = 1.0 / 0.18215 * latents
    image = pipe.vae.decode(latents).sample[0]
    image = (image / 2. + 0.5).cpu().permute(1,2,0).numpy()
    image = np.clip(image, 0, 1.0)
    images.extend(pipe.numpy_to_pil(image))

my_seed = 193

我们将从扩散模型最著名和最直接的应用开始:从文本提示生成图像,称为文本到图像生成。我们将使用的模型由发表潜在扩散论文的学术实验室发布到野外(HuggingFace 中心)。拥抱面部通过便捷的管道 API 协调潜在扩散等工作流程。我们想根据是否有 GPU 来定义要计算的设备和浮点。

if (1):
    #Run CompVis/stable-diffusion-v1-4 on GPU
    pipe_name = "CompVis/stable-diffusion-v1-4"
    my_dtype = torch.float16
    my_device = torch.device("cuda")
    my_variant = "fp16"
    pipe = StableDiffusionPipeline.from_pretrained(pipe_name,\
    safety_checker=None, variant=my_variant,\
        torch_dtype=my_dtype).to(my_device)
else:
    #Run CompVis/stable-diffusion-v1-4 on CPU
    pipe_name = "CompVis/stable-diffusion-v1-4"
    my_dtype = torch.float32
    my_device = torch.device("cpu")
    pipe = StableDiffusionPipeline.from_pretrained(pipe_name, \
            torch_dtype=my_dtype).to(my_device)

 

  • 引导尺度

如果你使用一个非常不寻常的文本提示(与数据集中的提示非常不同),最终可能会出现在潜在空间中较少被使用的部分。空提示嵌入提供了一种平衡,根据 guidance_scale 将两者结合起来,可以让您在提示的特异性与常见图像特征之间进行权衡。

guidance_images = []
for guidance in [0.25, 0.5, 1.0, 2.0, 4.0, 6.0, 8.0, 10.0, 20.0]:
    seed_all(my_seed)
    my_output = pipe(my_prompt, num_inference_steps=50, \
    num_images_per_prompt=1, guidance_scale=guidance)
    guidance_images.append(my_output.images[0])
    for ii, img in enumerate(my_output.images):
        img.save(f"prompt_{my_seed}_g{int(guidance*2)}_{ii}.jpg")

temp = grid_show(guidance_images, rows=3)
plt.savefig("prompt_guidance.jpg")
plt.show()

由于我们使用9个引导系数生成了提示,因此您可以绘制提示并查看扩散是如何发展的。默认的引导系数为0.75,因此第7张图像将是默认的图像输出。

  • 负面提示

有时潜在的扩散真的“想要”产生一个与你的意图不符的图像。在这些场景中,您可以使用否定提示来推动扩散过程远离不希望的输出。例如,我们可以使用一个负面提示,使我们的火星宇航员扩散输出少一点人性化。

my_prompt = " "
my_negative_prompt = " "

output_x = pipe(my_prompt, num_inference_steps=50, num_images_per_prompt=9, \
        negative_prompt=my_negative_prompt)

temp = grid_show(output_x)
plt.show()

您应该收到提示后的输出,同时避免输出否定提示中描述的内容。

  • 图像变化

从头开始生成文本到图像并不是扩散管道的唯一应用。实际上,扩散非常适合从初始图像开始进行图像修改。我们将使用一个略有不同的管道和经过预训练的模型,该模型针对图像到图像的扩散进行了调整。

pipe_img2img = AutoPipelineForImage2Image.from_pretrained(\

        "runwayml/stable-diffusion-v1-5", safety_checker=None,\

torch_dtype=my_dtype, use_safetensors=True).to(my_device)

这种方法的一个应用是生成主题的变体。概念艺术家可能会使用这种技术来快速迭代不同的想法,以根据最新的研究来说明系外行星。

我们将首先在 TRAPPIST 系统中下载一位公共领域艺术家对行星 1e 的概念(来源:NASA/JPL Caltech)。然后,在缩小尺度以删除细节后,我们将使用扩散管道来制作系外行星 TRAPPIST-1e 的几个不同版本。

url = \
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/TRAPPIST-1e_artist_impression_2018.png/600px-TRAPPIST-1e_artist_impression_2018.png"
img_path = url.split("/")[-1]
if not (os.path.exists("600px-TRAPPIST-1e_artist_impression_2018.png")):
    os.system(f"wget \     '{url}'")
    init_image = Image.open(img_path)

seed_all(my_seed)

trappist_prompt = "Artist's impression of TRAPPIST-1e"\
     "large Earth-like water-world exoplanet with oceans,"\
     "NASA, artist concept, realistic, detailed, intricate"

my_negative_prompt = "cartoon, sketch, orbiting moon"

my_output_trappist1e = pipe_img2img(prompt=trappist_prompt, num_images_per_prompt=9, \
     image=init_image, negative_prompt=my_negative_prompt, guidance_scale=6.0)

grid_show(my_output_trappist1e.images)
plt.show()

通过向模型提供示例初始图像,我们可以生成类似的图像。您还可以使用文本引导的图像到图像管道,通过增加引导、添加负面提示等来更改图像的样式,例如“非现实主义”、“水彩”或“纸素描”。您的里程可能会有所不同,调整提示将是找到您想要创建的正确图像的最简单方法。

 

04 结论

尽管扩散模型和仿人类艺术备受争议,但扩散模型还有其他更具影响力的用途。它已被应用于蛋白质设计和药物开发的蛋白质折叠预测。文本到视频也是一个活跃的研究领域,由几家公司(如 Stability AI、谷歌)提供。扩散也是文本到语音应用的一种新兴方法。

很明显,扩散过程在人工智能的发展以及技术与全球人类环境的互动中起着核心作用。虽然版权、其他知识产权法的复杂性以及对人类艺术和科学的影响在积极和消极方面都是显而易见的。但真正积极的是,人工智能在理解语言和生成图像方面具有前所未有的能力。正是 AlexNet 让计算机分析图像并输出文本,直到现在计算机才能分析文本提示并输出连贯的图像。

相关贴子

敬请登记。

登记
本网站受 reCAPTCHA 保护,适用 Google隐私政策和服务条款。