扩散模型实战(十一):剖析Stable Diffusion Pipeline各个组件

 推荐阅读列表:

 扩散模型实战(一):基本原理介绍

扩散模型实战(二):扩散模型的发展

扩散模型实战(三):扩散模型的应用

扩散模型实战(四):从零构建扩散模型

扩散模型实战(五):采样过程

扩散模型实战(六):Diffusers DDPM初探

扩散模型实战(七):Diffusers蝴蝶图像生成实战

扩散模型实战(八):微调扩散模型

扩散模型实战(九):使用CLIP模型引导和控制扩散模型

扩散模型实战(十):Stable Diffusion文本条件生成图像大模型

       在扩散模型实战(十):Stable Diffusion文本条件生成图像大模型中介绍了如何使用Stable Diffusion Pipeline控制图片生成效果以及大小,本文让我们剖析一下Stable Diffusion Pipeline内容细节。

        Stable Diffusion Pipeline要比之前介绍的DDPMPipeline复杂一些,除了UNet和调度器还有其他组件,让我们来看一下具体包括的组件: 

 

from diffusers import (    StableDiffusionPipeline,     StableDiffusionImg2ImgPipeline,    StableDiffusionInpaintPipeline,     StableDiffusionDepth2ImgPipeline    )

 

# 载入管线model_id = "stabilityai/stable-diffusion-2-1-base"pipe = StableDiffusionPipeline.from_pretrained(model_id).to(device)pipe = StableDiffusionPipeline.from_pretrained(model_id,    revision="fp16",torch_dtype=torch.float16).to(device)

 

# 查看pipe的组件print(list(pipe.components.keys()))

 

# 输出组件['vae','text_encoder','tokenizer','unet','scheduler',  'safety_checker','feature_extractor']

       这些组件的概念之前都介绍过了,下面通过代码分析一下这些组件的细节:

一、可变分自编码器(VAE)

        可变分自编码器(VAE)是一种模型,模型结构如图所示:

图片

       在使用Stable Diffusion生成图片时,首先需要在VAE的“隐空间”中应用扩散过程以生成隐编码,然后在扩散之后对他们解码,得到最终的输出图片,实际上,UNet的输入不是完整的图片,而是经过VAE压缩后的特征,这样可以极大地减少计算资源,代码如下:

 

# 创建取值区间为(-1, 1)的伪数据images = torch.rand(1, 3, 512, 512).to(device) * 2 - 1 print("Input images shape:", images.shape) # 编码到隐空间with torch.no_grad():    latents = 0.18215 * pipe.vae.encode(images).latent_dist.meanprint("Encoded latents shape:", latents.shape) # 再解码回来with torch.no_grad():    decoded_images = pipe.vae.decode(latents / 0.18215).sampleprint("Decoded images shape:", decoded_images.shape)

 

# 输出Input images shape: torch.Size([1, 3, 512, 512])Encoded latents shape: torch.Size([1, 4, 64, 64])Decoded images shape: torch.Size([1, 3, 512, 512])

       在这个示例中,原本512X512像素的图片被压缩成64X64的隐式表示,图片的每个空间维度都被压缩至原来的八分之一,因此设定参数width和height时,需要将它们设置成8的倍数。

PS:VAE解码过程并不完美,图像质量有所损失,但在实际使用中已经足够好了。

二、分词器tokenizer和文本编码器text_encoder

       Prompt文本描述如何控制Stable Diffusion呢?首先需要对Prompt文本描述使用tokenizer进行分词转换为数值表示的ID,然后将这些分词后的ID输入给文本编码器。实际使用户中,可以直接调用_encode_prompt方法来补全或者截断分词后的长度为77获得最终的Prompt文本表示,代码如下:

 

# 手动对提示文字进行分词和编码# 分词input_ids = pipe.tokenizer(["A painting of a flooble"])['input_ids']print("Input ID -> decoded token")for input_id in input_ids[0]:    print(f"{input_id} -> {pipe.tokenizer.decode(input_id)}") # 将分词结果输入CLIP input_ids = torch.tensor(input_ids).to(device)with torch.no_grad():    text_embeddings = pipe.text_encoder(input_ids)['last_hidden_state']print("Text embeddings shape:", text_embeddings.shape)

 

# 输出Input ID -> decoded token49406 -> <|startoftext|>320 -> a3086 -> painting539 -> of320 -> a4062 -> floo1059 -> ble49407 -> <|endoftext|>Text embeddings shape: torch.Size([1, 8, 1024])

获取最终的文本特征

 

text_embeddings = pipe._encode_prompt("A painting of a flooble",    device, 1, False, '')print(text_embeddings.shape)

 

# 输出torch.Size([1, 77, 1024])

可以看到最终的文本从长度8补全到了77。

三、UNet网络

      在扩散模型中,UNet的作用是接收“带噪”的输入并预测噪声,以实现“去噪”,网络结构如下图所示,与前面的示例不同,此次输入的并非是原始图片,而是图片的隐式表示,另外还有文本Prompt描述也作为UNet的输入。

图片

       下面让我们对上述三种输入使用伪输入让模型来了解一下UNet在预测过程中,输入输出形状和大小,代码如下:

 

# 创建伪输入timestep = pipe.scheduler.timesteps[0]latents = torch.randn(1, 4, 64, 64).to(device)text_embeddings = torch.randn(1, 77, 1024).to(device) # 让模型进行预测with torch.no_grad():    unet_output = pipe.unet(latents, timestep, text_embeddings).sampleprint('UNet output shape:', unet_output.shape)

 

# 输出UNet output shape: torch.Size([1, 4, 64, 64])

四、调度器Scheduler

       调度器保持了如何添加噪声的信息,并管理如何基于模型的预测更新“带噪”样本,默认的调度器是PNDMScheduler。

        我们可以观察一下在添加噪声过程中,噪声水平随时间步增加的变化

 

plt.plot(pipe.scheduler.alphas_cumprod, label=r'$\bar{\alpha}$')plt.xlabel('Timestep (high noise to low noise ->)')plt.title('Noise schedule')plt.legend()

图片

     下面看一下使用不同的调度器生成的效果对比,比如使用LMSDiscreteScheduler,代码如下:

 

from diffusers import LMSDiscreteScheduler # 替换原来的调度器pipe.scheduler = LMSDiscreteScheduler.from_config(pipe.scheduler.config) # 输出配置参数print('Scheduler config:', pipe.scheduler) # 使用新的调度器生成图片pipe(prompt="Palette knife painting of an winter cityscape",    height=480, width=480, generator=torch.Generator(device=device).   manual_seed(42)).images[0]

 

# 输出Scheduler config: LMSDiscreteScheduler {  "_class_name": "LMSDiscreteScheduler",  "_diffusers_version": "0.11.1",  "beta_end": 0.012,  "beta_schedule": "scaled_linear",  "beta_start": 0.00085,  "clip_sample": false,  "num_train_timesteps": 1000,  "prediction_type": "epsilon",  "set_alpha_to_one": false,  "skip_prk_steps": true,  "steps_offset": 1,  "trained_betas": null}

        生成的图片如下图所示:

图片

五、复现完整Pipeline

       到目前为止,我们已经分步骤剖析了Pipeline的每个组件,现在我们将组合起来手动实现一个完整的Pipeline,代码如下:

 

guidance_scale = 8num_inference_steps=30prompt = "Beautiful picture of a wave breaking"negative_prompt = "zoomed in, blurry, oversaturated, warped" # 对提示文字进行编码text_embeddings = pipe._encode_prompt(prompt, device, 1, True,    negative_prompt) # 创建随机噪声作为起点latents = torch.randn((1, 4, 64, 64), device=device, generator=generator)latents *= pipe.scheduler.init_noise_sigma # 准备调度器pipe.scheduler.set_timesteps(num_inference_steps, device=device) # 生成过程开始for i, t in enumerate(pipe.scheduler.timesteps):     latent_model_input = torch.cat([latents] * 2)     latent_model_input = pipe.scheduler.scale_model_input(       latent_model_input, t)     with torch.no_grad():      noise_pred = pipe.unet(latent_model_input, t,          encoder_hidden_states=text_embeddings).sample       noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)      noise_pred = noise_pred_uncond + guidance_scale *           (noise_pred_text - noise_pred_uncond)     latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample # 将隐变量映射到图片,效果如图6-14所示with torch.no_grad():    image = pipe.decode_latents(latents.detach()) pipe.numpy_to_pil(image)[0]

         生成的效果,如下图所示:

图片

六、其他Pipeline介绍

       在也提到一些其他Pipeline模型,比如图片到图片风格迁移Img2Img,图片修复Inpainting以及图片深度Depth2Image模型,本小节我们就来探索一下这些模型的具体使用效果。

6.1 Img2Img

       到目前为止,我们的图片仍然是从完全随机的隐变量开始生成的,并且都使用了完整的扩展模型采样循环。Img2Img Pipeline不必从头开始,它首先会对一张已有的图片进行编码,在得到一系列的隐变量后,就在这些隐变量上随机添加噪声,并以此作为起点。

       噪声的数量和“去噪”的步数决定了Img2Img生成的效果,添加少量噪声只会带来微小的变化,添加大量噪声并执行完整的“去噪”过程,可能得到与原始图片完全不同,近在整体结构上相似的图片。

 

# 载入Img2Img管线model_id = "stabilityai/stable-diffusion-2-1-base"img2img_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(    model_id).to(device)

 

result_image = img2img_pipe(    prompt="An oil painting of a man on a bench",    image = init_image, # 输入待编辑图片    strength = 0.6, # 文本提示在设为0时完全不起作用,设为1时作用强度最大).images[0] # 显示结果,如图6-15所示fig, axs = plt.subplots(1, 2, figsize=(12, 5))axs[0].imshow(init_image);axs[0].set_title('Input Image')axs[1].imshow(result_image);axs[1].set_title('Result')

        生成的效果,如下图所示:

图片

6.2 Inpainting

       Inpainting是一个图片修复技术,它可以保留图片一部分内容不变,其他部分生成新的内容,Inpainting UNet网络结构如下图所示:

图片

          下面我们使用一个示例来展示一下效果:

 

pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/ stable-diffusion-inpainting")pipe = pipe.to(device)# 添加提示文字,用于让模型知道补全图像时使用什么内容prompt = "A small robot, high resolution, sitting on a park bench"image = pipe(prompt=prompt, image=init_image,     mask_image=mask_image).images[0] # 查看结果,如图6-17所示fig, axs = plt.subplots(1, 3, figsize=(16, 5))axs[0].imshow(init_image);axs[0].set_title('Input Image')axs[1].imshow(mask_image);axs[1].set_title('Mask')axs[2].imshow(image);axs[2].set_title('Result')

        生成的效果,如下图所示:

图片

       这是个有潜力的模型,如果可以和自动生成掩码的模型结合就会非常强大,比如Huggingface Space上的一个名为CLIPSeg的模型就可以自动生成掩码。

6.3 Depth2Image

       如果想保留图片的整体结构而不保留原有的颜色,比如使用不同的颜色或纹理生成新图片,Img2Img是很难通过“强度”来控制的。而Depth2Img采用深度预测模型来预测一个深度图,这个深度图被输入微调过的UNet以生成图片,我们希望生成的图片既能保留原始图片的深度信息和总体结构,同时又能在相关部分填入全新的内容,代码如下:

 

# 载入Depth2Img管线pipe = StableDiffusionDepth2ImgPipeline.from_pretrained    ("stabilityai/stable-diffusion-2-depth")pipe = pipe.to(device)# 使用提示文字进行图像补全prompt = "An oil painting of a man on a bench"image = pipe(prompt=prompt, image=init_image).images[0] # 查看结果,如图6-18所示fig, axs = plt.subplots(1, 2, figsize=(16, 5))axs[0].imshow(init_image);axs[0].set_title('Input Image')axs[1].imshow(image);axs[1].set_title('Result')

图片

PS:该模型在3D场景下加入纹理非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/208663.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM 之 class文件详解

目录 一. 前言 二. class文件结构 2.1. 文件格式 2.2. 魔数与版本号 2.3. 常量池 2.4. 访问标志 2.5. 类索引、父类索引和接口索引集合 2.6. 字段表集合 2.7. 方法表集合 2.8. 属性表集合 2.8.1. Code 属性表 2.8.2. Exceptions 属性 2.8.3. LineNumberTable 属性…

[Unity+OpenAI TTS] 集成openAI官方提供的语音合成服务,构建海王暖男数字人

1.简述 最近openAI官方发布了很多新功能&#xff0c;其中就包括了最新发布的TTS语音合成服务的api接口。说到这个语音合成接口&#xff0c;大家可能会比较陌生&#xff0c;但是说到chatgpt官方应用上的聊天机器人&#xff0c;那个台湾腔的海王暖男的声音&#xff0c;可能就有印…

Oracle与Redis Enterprise协同,作为企业缓存解决方案

来源&#xff1a;虹科云科技 虹科干货丨Oracle与Redis Enterprise协同&#xff0c;作为企业缓存解决方案 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 单独使用Oracle作为企业缓存数据库时&#xff0c;会出现哪些问题呢&#xff1f;使用Redis Enterprise与Oracle共…

汇编-CALL和RET指令

CALL指令调用一个过程&#xff0c; 使处理器从新的内存位置开始执行。过程使用RET(从过程返回) 指令将处理器转回到该过程被调用的程序点上。 CALL指令的动作&#xff1a; 1.将CALL指令的下一条指令地址压栈(作为子过程返回的地址) 2.将被调过程的地址复制到指令指针寄存器E…

七、通过libfdk_aac编解码器实现aac音频和pcm的编解码

前言 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12 AAC编码是MP3格式的后继产品&#xff0c;通常在相同的比特率下可以获得比MP3更高的声音质量&#xff0c;是iPhone、iPod、iPad、iTunes的标准音频格式。 AAC相较于MP3的改进包含&#xff1a; 更多的采…

Eclipse常用设置-乱码

在用Eclipse进行Java代码开发时&#xff0c;经常会遇到一些问题&#xff0c;记录下来&#xff0c;方便查看。 一、properties文件乱码 常用的配置文件properties里中文的乱码&#xff0c;不利于识别。 处理流程&#xff1a;Window -> Preferences -> General -> Ja…

「快学Docker」监控和日志记录容器的健康和性能

「快学Docker」监控和日志记录容器的健康和性能 1. 容器健康状态监控2. 性能监控3. 日志记录几种采集架构图 4. 监控工具和平台cAdvisor&#xff08;Container Advisor&#xff09;PrometheusGrafana 5. 自动化运维 1. 容器健康状态监控 方法1&#xff1a;需要实时监测容器的运…

机器人算法—ROS TF坐标变换

1.TF基本概念 &#xff08;1&#xff09;什么是TF&#xff1f; TF是Transformations Frames的缩写。在ROS中&#xff0c;是一个工具包&#xff0c;提供了坐标转换等方面的功能。 tf工具包&#xff0c;底层实现采用的是一种树状数据结构&#xff0c;根据时间缓冲并维护多个参考…

2022年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 红框中加入哪个选项积木,不能阻止气球下落? A: B: C: D:

实现极坐标图表QPolarChart的角度轴范围是[0,360]时,0度在水平右侧

目录 参考角度轴范围是[0,360]时&#xff0c;0度在水平右侧.h.cpp 参考 Qt数据可视化(QPolarChart雷达图) 默认QPolarChart的范围是[0,360]时&#xff0c;0度在垂直上方 如官方例子QValueAxis角度轴范围是[-100,100] 角度轴范围是[0,360]时&#xff0c;0度在水平右侧 原理&am…

@Scheduled注解 定时任务讲解

用于在Java Spring框架中定时执行特定任务的注解 Scheduled&#xff0c;它能够指定方法在特定时间间隔或特定时间点执行。默认参数是cron&#xff0c;cron参数被用来定义一个Cron表达式&#xff0c;它代表了任务执行的时间规则 参数如下 Cron 这是是一种时间表达式&#xff…

模拟量采集----测量输入的电流

生活中的模拟量有很多 大多都为电压信号和电流信号 今天讲如何测量输入的电流信号 通过欧姆定律可知 电流测量的测量&#xff1a;是将电流加载在固定阻值的电阻上&#xff0c;来测量这个电阻二端的电压 最后反算出电流的大小 所用的公式是IU/R 我们使用仿真软件来看测量…