扩散模型 DDPM
扩散模型
标准的扩散模型分为两个主要过程:正向过程
(扩散)和反向过程
(去噪、还原和生成目标)。
- 正向扩散阶段,逐渐引入噪声,直到图像变成完全随机的噪声。
- 再通过反向过程,使用一系列的
马尔科夫链
进行去噪,得到最终清晰的图像数据。
新出现的扩散模型
(Denoising Diffusion Probabilistic Model,DDPM
),整体原理上与 VAE
更加接近。
- X0 是输入样本,如一张原始图片,通过 T 步前向过程(Forward process)采样变换,最后生成了噪声图像 XT ,理解为隐变量 z。这个过程通过马尔科夫链实现。
随机过程中一个定理
- 符合马尔科夫链状态转移的模型,当状态转移到一定次数时,模型状态最终收敛于一个平稳分布。
- 等效于溶质在溶液中溶解的过程,随着溶解过程的进行,溶质(噪声)最终会整体分布到溶液(样本)中。类似 VAE 中的 encoder。而逆向过程(Reverse process)可以理解为 decoder。通过 T 步来还原到原始样本。
什么是扩散模型
扩散模型灵感来自非平衡热力学。通过定义了一个扩散步骤的马尔可夫链
,以缓慢地将随机噪声添加到数据中,然后学习反转扩散过程以从噪声中构建所需的数据样本。
- 发布DALL·E的15个月后,OpenAI在今年春天带了续作DALL·E 2,以其更加惊艳的效果和丰富的可玩性迅速占领了各大AI社区的头条。近年来,随着生成对抗网络(GAN)、变分自编码器(VAE)、扩散模型(Diffusion models)的出现,深度学习已向世人展现其强大的图像生成能力;加上GPT-3、BERT等NLP模型的成功,人类正逐步打破文本和图像的信息界限。
- DALL·E 2中,只需输入简单的文本(prompt),它就可以生成多张1024*1024的高清图像。这些图像甚至可以将不合常理的语义表示,以超现实主义的形式创造出天马行空的视觉效果,例如图1中“写实风格的骑马的宇航员(An astronaut riding a horse in a photorealistic style)”。
【2022-8-31】苏剑林的生成扩散模型漫谈
- 生成模型中,VAE、GAN“如雷贯耳”,还有一些比较小众的选择,如flow模型、VQ-VAE等,颇有人气,尤其是VQ-VAE及其变体VQ-GAN,近期已经逐渐发展到“图像的Tokenizer”的地位,用来直接调用NLP的各种预训练方法。
- 除此之外,还有一个本来更小众的选择——
扩散模型
(Diffusion Models)——正在生成模型领域“异军突起”,当前最先进的两个文本生成图像—— OpenAI 的DALL·E 2
和 Google的Imagen
,都是基于扩散模型
来完成的。
生成扩散模型的大火,始于2020年所提出的DDPM(Denoising Diffusion Probabilistic Model),虽然也用了“扩散模型”这个名字,但事实上除了采样过程的形式有一定的相似之外,DDPM与传统基于朗之万
方程采样的扩散模型完全不一样,一个新的起点、新的篇章。
【2024-2-13】深入理解3D扩散模型
扩散过程具有向图像添加噪声的正向过程和从图像中去除噪声的反向过程。
噪声图像
= a ⋅噪声较小的图像
+ b ⋅噪声
噪声较小的图像
= (噪声图像
- b⋅噪声
)/a- ab是常数, 所有
图像
=(噪声图像
- b’·噪声
)/a’
主要步骤
- 从纯噪声开始作为噪声图像
- 使用模型预测噪声,将图像推向噪声较少的图像
- 进行上述计算以获得噪声较少的图像
什么是扩散模型?
扩散是发生在粉红色图像信息生成器组件内部的过程。 该组件的输入为用于表示输入文本信息的 token 嵌入,和一个起始的随机噪声图像信息张量,生成一个信息张量,图像解码器使用该信息张量绘制最终图像。
- 这个过程以多步形式进行。每步添加更多的相关信息。为了直观地理解整个过程,将随机潜层张量(latent)传递给视觉解码器,看它是否转换为随机视觉噪声。
- 扩散过程有多步,每步操作一个输入潜层张量,并生成一个新的潜层张量。新的张量更好地集成了输入文本和视觉信息,其中视觉信息来自模型训练集中的图像。
详见原文:illustrated-stable-diffusion
扩散模型概览
【2023-4-5】扩散模型(Diffusion Model)首篇综述
- Diffusion Models: A Comprehensive Survey of Methods and Applications
- 加州大学&Google Research的Ming-Hsuan Yang、斯坦福大学(OpenAI)的Yang Song(Score SDE一作)、北京大学崔斌实验室以及CMU、UCLA、蒙特利尔Mila研究院等众研究团队,首次对现有的扩散生成模型(diffusion model)进行了全面的总结分析,从diffusion model算法细化分类、和其他五大生成模型的关联以及在七大领域中的应用等方面展开,最后提出了diffusion model的现有limitation和未来的发展方向。
扩散模型
(diffusion models)是深度生成模型中新的SOTA。其他的五种生成模型 GAN,VAE,Autoregressive model, Normalizing flow, Energy-based model。
- 扩散模型在图片生成任务中超越了原SOTA:GAN,并且在诸多应用领域都有出色的表现,如计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化等。
- 此外,扩散模型与其他研究领域有着密切的联系,如稳健学习、表示学习、强化学习。然而,原始的扩散模型也有缺点,它的采样速度慢,通常需要数千个评估步骤才能抽取一个样本;它的最大似然估计无法和基于似然的模型相比;它泛化到各种数据类型的能力较差。如今很多研究已经从实际应用的角度解决上述限制做出了许多努力,或从理论角度对模型能力进行了分析。
【2024-5-27】 MIT 助理教授 Song Han 的 100多页 DDPM 介绍 ppt
语言模型也可以
【2023-10-13】资讯, 图像、视频生成上,语言模型首次击败扩散模型,tokenizer是关键
大型语言模型(LLM 或 LM)一开始是用来生成语言的,但随着时间的推移,已经能够生成多种模态的内容,并在音频、语音、代码生成、医疗应用、机器人学等领域开始占据主导地位。
当然,LM 也能生成图像和视频。
- 图像像素会被视觉 tokenizer 映射为一系列离散 token。
- 这些 token 被送入 LM transformer,就像词汇一样被用于生成建模。
尽管 LM 在视觉生成方面取得了显著进步,但 LM 的表现仍然不如扩散模型。
- 例如,在图像生成的金标基准 — ImageNet 数据集上进行评估时,最佳语言模型的表现比扩散模型差了 48% 之多(以 256ˆ256 分辨率生成图像时,FID 为 3.41 对 1.79)。
为什么语言模型在视觉生成方面落后于扩散模型?
- 谷歌、CMU 的研究表明: tokenizer 是关键。缺乏一个良好的视觉表示,类似于我们的自然语言系统,以有效地建模视觉世界。
- 论文链接:paper
在相同的训练数据、可比模型大小和训练预算条件下,利用良好的视觉 tokenizer,掩码语言模型在图像和视频基准的生成保真度和效率方面都超过了 SOTA 扩散模型。这是语言模型在标志性的 ImageNet 基准上击败扩散模型的首个证据。
目的不是断言语言模型是否优于其他模型,而是促进 LLM 视觉 tokenization 方法的探索。
- LLM 与其他模型(如扩散模型)的根本区别在于,LLM 使用离散的潜在格式,即从可视化 tokenizer 获得的 token。
这项研究表明,这些离散的视觉 token 的价值不应该被忽视,因为存在以下优势:
- 1、与 LLM 的兼容性。token 表示的主要优点是与语言 token 共享相同的形式,可直接利用社区多年来为开发 LLM 所做的优化,包括更快的训练和推理速度、模型基础设施的进步、扩展模型的方法以及 GPU/TPU 优化等创新。通过相同的 token 空间统一视觉和语言可以为真正的多模态 LLM 奠定基础,后者可以在我们的视觉环境中理解、生成和推理。
- 2、压缩表示。离散 token 可以为视频压缩提供一个新视角。可视化 token 可以作为一种新的视频压缩格式,以减少数据在互联网传输过程中占用的磁盘存储和带宽。与压缩的 RGB 像素不同,这些 token 可以直接输入生成模型,绕过传统的解压缩和潜在编码步骤。这可以加快生成视频应用的处理速度,在边缘计算情况下尤其有益。
- 3、视觉理解优势。研究表明,离散 token 在自监督表示学习中作为预训练目标是有价值的,如 BEiT 和 BEVT 中所讨论的那样。此外,研究发现,使用 token 作为模型输入提高了鲁棒性和泛化性。
研究者提出了一个名为 MAGVIT-v2
的视频 tokenizer,旨在将视频(和图像)映射为紧凑的离散 token。
该模型建立在 VQ-VAE 框架内的 SOTA 视频 tokenizer——MAGVIT 基础上。基于此,研究者提出了两种新技术:
- 1)一种新颖的无查找(lookup-free)量化方法,使得大量词汇的学习成为可能,以提高语言模型的生成质量;
- 2)通过广泛的实证分析,他们确定了对 MAGVIT 的修改方案,不仅提高了生成质量,而且还允许使用共享词汇表对图像和视频进行 token 化。
实验结果表明,新模型在三个关键领域优于先前表现最好的视频 tokenizer——MAGVIT。
- 首先,新模型显著提高了 MAGVIT 的生成质量,在常见的图像和视频基准上刷新了 SOTA。
- 其次,用户研究表明,其压缩质量超过了 MAGVIT 和当前的视频压缩标准 HEVC。
- 此外,它与下一代视频编解码器 VVC 相当。
- 最后,研究者表明,与 MAGVIT 相比,他们的新 token 在两个设置和三个数据集的视频理解任务中表现更强。
论文
- 【2022-9-20】扩散模型大全
- hugginface 扩散模型包:diffusers,colab笔记
- demo: stable-diffusion
经典论文
- 《Deep Unsupervised Learning using Nonequilibrium Thermodynamics》 2015年 扩散模型起源
- 《Denoising Diffusion Probabilistic Models》 2020年 扩散模型兴起, 对应pytorch实现
- 《Improved Denoising Diffusion Probabilistic Models》 2021年 第二篇论文的改进, 对应pytorch实现
技术文章
- The recent rise of diffusion-based models 可以了解到扩散模型近年比较经典的应用
- Introduction to Diffusion Models for Machine Learning 从中可以了解到一个实现扩散模型的库denoising_diffusion_pytorch,博客中有使用案例
- What are Diffusion Models? 也是扩散模型的一个理论介绍博客,推导挺详细的
- Diffusion Models as a kind of VAE 探究了VAE和扩散模型的联系
- The Annotated Diffusion Model 扩散模型理论和代码实现,代码我进行理解加了注释与理论对应,方便大家理解
- An introduction to Diffusion Probabilistic Models 也是一个介绍性博客,公式也很工整
模型
模型下载
SD模型的主体结构如下图所示,主要包括三个模型:
autoencoder
:encoder将图像压缩到latent空间,而decoder将latent解码为图像;CLIP text encoder
:提取输入text的text embeddings,通过cross attention方式送入扩散模型的UNet中作为condition;- SD采用CLIP text encoder来对输入text提取text embeddings,具体的是采用目前OpenAI所开源的最大CLIP模型:clip-vit-large-patch14,这个CLIP的text encoder是一个transformer模型(只有encoder模块):层数为12,特征维度为768,模型参数大小是123M。
UNet
:扩散模型的主体,用来实现文本引导下的latent生成。- SD的扩散模型是一个860M的UNet
- encoder部分包括3个CrossAttnDownBlock2D模块和1个DownBlock2D模块,而decoder部分包括1个UpBlock2D模块和3个CrossAttnUpBlock2D模块,中间还有一个UNetMidBlock2DCrossAttn模块。
- encoder和decoder两个部分是完全对应的,中间存在skip connection。
- 注意3个CrossAttnDownBlock2D模块最后均有一个2x的downsample操作,而DownBlock2D模块是不包含下采样的。
模型结构
对于SD模型
- autoencoder 模型参数大小为84M
- CLIP text encoder 模型大小为123M
- 而 UNet 参数大小为 860M
所以 SD 模型总参数量约为 1B。
扩散模型原理
扩散模型(Diffusion models)定义了正向和逆向两个过程
- 正向过程: 或扩散过程, 从真实数据分布采样,逐步向样本添加高斯噪声,生成噪声样本序列,加噪过程可用方差参数控制,当时,可近似等同于一个高斯分布。
标准的扩散模型
(diffusion models)涉及到图像变换(添加高斯噪声)和图像反转。但是扩散模型的生成并不强烈依赖于图像降解的选择。通过实验证明了基于完全确定性的降解(例如模糊、masking 等),也可以轻松训练一个扩散生成模型。
这个工作成功质疑了社区对扩散模型的理解:并非依赖于梯度郎之万动力学(gradient Langevin dynamics)或变分推理(variational inference)。
DDPM
叫“渐变模型”更准确,扩散模型这一名字反而容易造成理解上的误解,传统扩散模型的能量模型、得分匹配、朗之万
方程等概念,其实跟DDPM及其后续变体都没什么关系。
- DDPM 数学框架其实在ICML2015 论文《Deep Unsupervised Learning using Nonequilibrium Thermodynamics》就已经完成了,但DDPM是首次将它在高分辨率图像生成上调试出来了,从而引导出了后面的火热。由此可见,一个模型的诞生和流行,往往还需要时间和机遇
Stable Diffusion
【2023-4-10】图解Stable Diffusion
- jalammar illustrated-stable-diffusion
Stable Diffusion(简称SD)是AI绘画领域的一个核心模型,能够进行文生图(txt2img)和图生图(img2img)等图像生成任务。
- 与 Midjourney 不同的是,Stable Diffusion 是一个完全开源的项目(模型、代码、训练数据、论文、生态等全部开源),这使得其能快速构建强大繁荣的上下游生态(AI绘画社区、基于SD的自训练AI绘画模型、丰富的辅助AI绘画工具与插件等),并且吸引了越来越多的AI绘画爱好者加入其中,与AI行业从业者一起推动AIGC领域的发展与普惠。
对Stable Diffusion模型的全维度各个细节做一个深入浅出的分析与总结(SD模型结构解析、SD模型经典应用场景介绍、SD模型性能优化、SD模型从0到1保姆级训练教程,SD模型不同AI绘画框架从0到1推理运行保姆级教程、最新SD模型资源汇总分享、SD相关配套工具使用等
【2024-5-18】深入浅出完整解析Stable Diffusion(SD)核心基础知识
Stable Diffusion 发布是AI 绘画领域的一个里程碑事件。它的出现使得普通人也能使用高性能的图像生成模型。
- 生成的图像效果极佳,速度还很快,对硬件资源的要求相对较低。
Stable Diffusion 用法
- 文本生成图像 text2image
- 修改图像(此时输入为文本+图像)
扩散模型组件
Stable Diffusion 是由多个组件和模型组成的系统, 而非一个整体的模型。
文本理解
(text-understanding)组件: 捕捉文本中的意图,将文本信息转换为模型能够理解的数值表示。- 文本编码器是一种特殊的 Transformer 语言模型(CLIP 模型的文本编码器)。 获取输入文本并输出代表文本中每个单词/token 的数值表示(每个 token 由一个向量表示)
图像生成器
(Image Generator),也由多个组件组成。由以下两个阶段组成:图像信息生成器
(Image Information Creator): Stable Diffusion 成功的秘诀,是性能和效率高于之前工作的原因。运行多步来生成图像信息。步数就是 Stable Diffusion 界面或库中的steps 参数,通常设为 50 或 100。图像信息生成器完全在图像信息空间(或者称为潜层空间 latent space)中进行工作. “扩散(diffusion)”描述的就是该组件的行为。该组件通过一步一步地对信息进行处理,从而得到最终的高质量图像(由接下来的图像解码器组件生成)。图像解码器
(Image Decoder): 根据图像信息生成器生成的信息画出图像。不同于多步运行的信息生成器,图像解码器仅运行一次,来生成最终的像素级图像。
Stable Diffusion 三个主要组件,各自由不同的神经网络组成:
ClipText
用于文本编码- 输入:文本
- 输出:77 个 token 嵌入向量,每个向量 768 维
UNet
+Scheduler
用于在潜层空间中逐步地地处理(或者说扩散)信息- 输入:文本嵌入和一个高维噪声张量
- 输出:经过处理得到的信息张量
AutoEncoder Decoder
根据信息张量画出图像- 输入:信息张量(维度 (4, 64, 64))
- 输出:图像(维度:(3, 512, 512))
LDM
从最早的 DDPM 开始,一步步还原 Latent Diffusion Model (LDM)的采样算法
DDPM 采样算法:
def ddpm_sample(image_shape):
ddpm_scheduler = DDPMScheduler() # 维护扩散模型的a,b等变量
unet = UNet() # unet, 计算去噪过程中的图像应该去除的噪声eps
xt = randn(image_shape) # 标准正态分布中采样纯噪声图像
T = 1000
# 逐步去噪,最终变成一幅图片
for t in T ... 1: #
eps = unet(xt, t) # 当前应该去除的噪声
std = ddpm_scheduler.get_std(t) # 图像方差
xt = ddpm_scheduler.get_xt_prev(xt, t, eps, std)
return xt
DDIM
对 DDPM
的采样过程做了两点改进:
- 1) 去噪有效步数可以少于T步,由另一个变量ddim_steps决定;
- 2) 采样方差大小可以由eta决定。
因此,改进后 DDIM算法:
def ddim_sample(image_shape, ddim_steps = 20, eta = 0):
"""
ddim_steps 去噪循环步数
"""
ddim_scheduler = DDIMScheduler()
unet = UNet()
xt = randn(image_shape)
T = 1000
timesteps = ddim_scheduler.get_timesteps(T, ddim_steps) # [1000, 950, 900, ...]
for t in timesteps:
eps = unet(xt, t)
std = ddim_scheduler.get_std(t, eta)
xt = ddim_scheduler.get_xt_prev(xt, t, eps, std)
return xt
DDIM 的基础上,LDM 从生成像素空间上的图像变为生成隐空间上的图像。
- 隐空间图像需要再做一次解码才能变回真实图像。
从代码上来看,使用LDM后,只需要多准备一个VAE,并对最后的隐空间图像zt解码。
def ldm_ddim_sample(image_shape, ddim_steps = 20, eta = 0):
ddim_scheduler = DDIMScheduler()
vae = VAE()
unet = UNet()
zt = randn(image_shape)
T = 1000
timesteps = ddim_scheduler.get_timesteps(T, ddim_steps) # [1000, 950, 900, ...]
for t in timesteps:
eps = unet(zt, t)
std = ddim_scheduler.get_std(t, eta)
zt = ddim_scheduler.get_xt_prev(zt, t, eps, std)
xt = vae.decoder.decode(zt)
return xt
而想用 LDM 实现文生图,则需要给一个额外的文本输入text。
- 文本编码器会把文本编码成张量c,输入进unet。
- 其他地方的实现都和之前的LDM一样。
def ldm_text_to_image(image_shape, text, ddim_steps = 20, eta = 0):
ddim_scheduler = DDIMScheduler()
vae = VAE()
unet = UNet()
zt = randn(image_shape)
T = 1000
timesteps = ddim_scheduler.get_timesteps(T, ddim_steps) # [1000, 950, 900, ...]
text_encoder = CLIP()
c = text_encoder.encode(text)
for t = timesteps:
eps = unet(zt, t, c)
std = ddim_scheduler.get_std(t, eta)
zt = ddim_scheduler.get_xt_prev(zt, t, eps, std)
xt = vae.decoder.decode(zt)
return xt
最后, 这个能实现文生图的LDM就是 Stable Diffusion。
Stable Diffusion 采样算法看上去比较复杂,从DDPM开始把各个功能都拆开来看,理解起来就容易了。
UNet
2015 年,Olaf Ronneberger 等人提出一种经典的卷积神经网络(CNN)架构,UNet,专为生物医学图像分割设计。
UNet 因为网络的整体结构形似字母U而得名。
- Unet 以图像作为入口,通过减少采样来找到该图像的低维表示后再通过增加采样将图像恢复回来。
- UNet 在图像分割任务中表现优异,尤其是在需要精细边界的场景中广泛应用,如医学影像分割、卫星图像分割等。
UNet 成功源于其有效的特征提取与恢复机制,特别是跳跃连接的设计,使得编码过程中丢失的细节能够通过解码阶段恢复。
UNet 演变
DDPM会用到一个U-Net神经网络unet,用于计算去噪过程中图像应该去除的噪声eps
U-Net 结构
U-Net 阶段 | 变化 | 图解 |
---|---|---|
DDIM | 早期; 纯卷积 | |
DDPM | 1. 卷积层->残差卷积模块,深层还有自注意力 2. 每层还有个短路连接 |
|
LDM | 增加额外约束信息,自注意力->交叉注意力(transformer) | |
Stable Diffusion | 每个大层都有transformer块,不只是深层 |
Stable Diffusion 解读(三):原版实现及Diffusers实现源码解读
UNet 网络结构
独特之处
-
编码器-解码器对称结构,多尺度上有效提取特征并生成精确的像素级分割结果。
UNet 设计理念:
- 将输入图像经过一系列卷积和下采样操作, 逐渐提取高层次特征(编码路径)
- 然后通过上采样逐步恢复原始的分辨率(解码路径)
- 并将编码路径中对应的特征与解码路径进行
跳跃连接
(skip connection), 帮助网络结合低层次细节信息和高层次语义信息,实现精确的像素级分割。
UNet 模型由两部分组成:编码器
和解码器
,中间通过跳跃连接
(Skip Connections)相连。
Unet 整体结构包含了4层编码器
和4层解码器
。 每层编码器
和解码器
中,均包含了一个两层的卷积网络
- (1) UNet
编码器
任务: 逐渐压缩输入图像的空间分辨率,提取更高层次的特征。编码器
具有4层结构,每层由一个双层卷积网络构成。- 经过一层最大池化(max pooling)提取出关键特征之后传递到下一层,每次池化操作都会将图像的空间维度减少一半
- 同时通过 Skip-Connection 将结果传递给对应的解码器。
- (2)
UNet 解码器
通过逐渐恢复图像的空间分辨率,将编码器部分提取到的高层次特征映射回原始的图像分辨率。- 同时接收了来自下一层网络的输出,与同层编码器池化前的结果,通过拼接后传递到上一层。
- 解码器包含反卷积(上采样)操作,并结合来
自编码器
的相应特征层,以实现精细的边界恢复。
- (3) 跳跃连接: UNet 的关键创新点。
- 每个
编码器
层的输出特征图与解码器
中对应层的特征图进行拼接,形成跳跃连接
。将编码器中的局部信息和解码器中的全局信息进行融合,从而提高分割结果的精度。
- 每个
UNet 实现
UNet 代码示例
class DoubleConv(nn.Module):
def __init__(self, in_ch, out_ch, mid_ch=None):
super().__init__()
if not mid_ch:
mid_ch = out_ch
self.conv = nn.Sequential(
nn.Conv2d(in_ch, mid_ch, kernel_size=3, padding=1),
nn.BatchNorm2d(mid_ch),
nn.ReLU(inplace=True),
nn.Conv2d(mid_ch, out_ch, kernel_size=3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True)
)
def forward(self, x):
x = self.conv(x)
return x
class Down(nn.Module): # 编码器
"""Downscaling with maxpool then double conv"""
def __init__(self, in_ch, out_ch):
super(Down, self).__init__()
self.maxpool_conv = nn.Sequential(
nn.MaxPool2d(2), # 先进行maxpool,再进行两层链接
DoubleConv(in_ch, out_ch)
)
def forward(self, x):
x = self.maxpool_conv(x)
return x
class Up(nn.Module): # 解码器
"""
up path
conv_transpose => double_conv
"""
def __init__(self, in_ch, out_ch, bilinear=True):
super(Up, self).__init__()
if bilinear:
self.up = lambda x: nn.functional.interpolate(x, scale_factor=2, mode='bilinear', align_corners=True)
self.conv = DoubleConv(in_ch, out_ch, in_ch // 2)
else:
self.up = nn.ConvTranspose2d(in_ch, in_ch // 2, kernel_size=2, stride=2)
self.conv = DoubleConv(in_ch, out_ch)
def forward(self, x1, x2):
"""
conv output shape = (input_shape - Filter_shape + 2 * padding)/stride + 1
"""
x1 = self.up(x1)
diffY = x2.size()[2] - x1.size()[2] # [N,C,H,W],diffY refers to height
diffX = x2.size()[3] - x1.size()[3] # [N,C,H,W],diffX refers to width
x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
diffY // 2, diffY - diffY // 2])
x = torch.cat([x2, x1], dim=1) # 在通道层将skip传递过来的数据与下层传递来的数据进行拼接
x = self.conv(x)
return x
网络实现
import torch
import torch.nn as nn
import torch.nn.functional as F
from model.components import DoubleConv, InConv, Down, Up, OutConv
class Unet(nn.Module):
def __init__(self, in_ch, out_ch, gpu_ids=None, bilinear=False): # inch, 图片的通道数,1表示灰度图像,3表示彩色图像
super(Unet, self).__init__()
if gpu_ids is None:
gpu_ids = []
self.loss = None
self.matrix_iou = None
self.pred_y = None
self.x = None
self.y = None
self.loss_stack = 0
self.matrix_iou_stack = 0
self.stack_count = 0
self.display_names = ['loss_stack', 'matrix_iou_stack']
self.gpu_ids = gpu_ids
self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if torch.cuda.is_available() else torch.device(
'cpu')
self.bilinear = bilinear
factor = 2 if bilinear else 1
self.bce_loss = nn.BCELoss()
self.inc = (DoubleConv(in_ch, 64))
self.down1 = Down(64, 128)
self.down2 = Down(128, 256)
self.down3 = Down(256, 512)
self.drop3 = nn.Dropout2d(0.5)
self.down4 = Down(512, 1024)
self.drop4 = nn.Dropout2d(0.5)
self.up1 = Up(1024, 512 // factor, bilinear)
self.up2 = Up(512, 256 // factor, bilinear)
self.up3 = Up(256, 128 // factor, bilinear)
self.up4 = Up(128, 64 // factor, bilinear)
self.out = OutConv(64, out_ch)
self.optimizer = torch.optim.Adam(self.parameters(), lr=1e-4)
def forward(self):
x1 = self.inc(self.x)
x2 = self.down1(x1)
x3 = self.down2(x2)
x4 = self.down3(x3)
x4 = self.drop3(x4)
x5 = self.down4(x4)
x5 = self.drop4(x5)
# skip connection与采样结果融合
x = self.up1(x5, x4)
x = self.up2(x, x3)
x = self.up3(x, x2)
x = self.up4(x, x1)
x = self.out(x)
self.pred_y = nn.functional.sigmoid(x)
def set_input(self, x, y):
self.x = x.to(self.device)
self.y = y.to(self.device)
self.to(self.device)
def optimize_params(self):
self.forward()
self._bce_iou_loss()
_ = self.accu_iou()
self.stack_count += 1
self.zero_grad()
self.loss.backward()
self.optimizer.step()
def accu_iou(self):
y_pred = (self.pred_y > 0.5) * 1.0
y_true = (self.y > 0.5) * 1.0
pred_flat = y_pred.view(y_pred.numel())
true_flat = y_true.view(y_true.numel())
intersection = float(torch.sum(pred_flat * true_flat)) + 1e-7
denominator = float(torch.sum(pred_flat + true_flat)) - intersection + 2e-7
self.matrix_iou = intersection / denominator
self.matrix_iou_stack += self.matrix_iou
return self.matrix_iou
def _bce_iou_loss(self):
y_pred = self.pred_y
y_true = self.y
pred_flat = y_pred.view(y_pred.numel())
true_flat = y_true.view(y_true.numel())
intersection = torch.sum(pred_flat * true_flat) + 1e-7
denominator = torch.sum(pred_flat + true_flat) - intersection + 1e-7
iou = torch.div(intersection, denominator)
bce_loss = self.bce_loss(pred_flat, true_flat)
self.loss = bce_loss - iou + 1
self.loss_stack += self.loss
def get_current_losses(self):
errors_ret = {}
for name in self.display_names:
if isinstance(name, str):
errors_ret[name] = float(getattr(self, name)) / self.stack_count
self.loss_stack = 0
self.matrix_iou_stack = 0
self.stack_count = 0
return errors_ret
def eval_iou(self):
with torch.no_grad():
self.forward()
self._bce_iou_loss()
_ = self.accu_iou()
self.stack_count += 1
diffuser
HuggingFace 推出基于 Stable Diffusion 的封装库 diffusers
Diffusers 实现了 safety_checker, 防止冒犯性或有害内容的功能,但该模型改进的图像生成功能仍然可以产生潜在的有害内容
Diffusers 能够生成图像、语音、三维分子结构,且包含SOTA扩散模型的训练、推理工具箱
特性
- DiffusionPipeline 是一个高级端到端类,从预训练的扩散模型中快速生成用于推理样本。
- SOTA 预训练模型架构和模块,可用作创建扩散模型的构件。
- 许多不同的调度器算法可控制如何在训练中添加噪声,以及如何在推理过程中生成去噪图像。
组件
三个主要组件:
DiffusionPipeline
(扩散管道):基于预训练扩散模型快速生成样本的封装类Model
(模型):预训练模型架构和模块可用作创建扩散系统的构建块。Scheduler
(调度器):用于控制如何在训练中添加噪声以及如何在推理过程中生成去噪图像的算法。- 不同的调度器具有不同的去噪速度和质量权衡,可自定义
- 默认: PNDMScheduler
pipeline
常见 pipeline
- AutoPipeline
- 文生图 text2image:
AutoPipelineForText2Image
- 图生图 image2iamge:
AutoPipelineForImage2Image
- 图像修复 inpainting:
AutoPipelineForInpainting
- 文生图 text2image:
DiffusionPipeline
用预训练扩散系统进行推理,最简单。一个包含模型和调度程序的端到端系统。开箱即用的DiffusionPipeline 执行许多任务
任务 | 描述 | 管道 |
---|---|---|
无条件图像生成 | 从高斯噪声生成图像 | 无条件图像生成 unconditional_image_generation |
文本引导图像生成 | 根据文本提示生成图像 | 条件图像生成 conditional_image_generation |
文本引导的图像到图像翻译 | 根据文本提示调整图像 | img2img |
文本引导图像修复 | 给定图像、蒙版和文本提示,填充图像的蒙版部分 | inpaint |
文本引导深度图像翻译 | 调整由文本提示引导的图像部分,同时通过深度估计保留结构 | depth2img |
text2image
示例代码
from diffusers import AutoPipelineForText2Image
import torch
pipe_txt2img = AutoPipelineForText2Image.from_pretrained(
"dreamlike-art/dreamlike-photoreal-2.0", torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
prompt = "cinematic photo of Godzilla eating sushi with a cat in a izakaya, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(37)
image = pipe_txt2img(prompt, generator=generator).images[0]
image
image2iamge
from diffusers import AutoPipelineForImage2Image
from diffusers.utils import load_image
import torch
pipe_img2img = AutoPipelineForImage2Image.from_pretrained(
"dreamlike-art/dreamlike-photoreal-2.0", torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
init_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-text2img.png")
prompt = "cinematic photo of Godzilla eating burgers with a cat in a fast food restaurant, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(53)
image = pipe_img2img(prompt, image=init_image, generator=generator).images[0]
image
inpainting
from diffusers import AutoPipelineForInpainting
from diffusers.utils import load_image
import torch
pipeline = AutoPipelineForInpainting.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
init_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-img2img.png")
mask_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-mask.png")
prompt = "cinematic photo of a owl, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(38)
image = pipeline(prompt, image=init_image, mask_image=mask_image, generator=generator, strength=0.4).images[0]
image
模型 Models
AutoPipeline
The AutoPipeline supports Stable Diffusion, Stable Diffusion XL, ControlNet, Kandinsky 2.1, Kandinsky 2.2, and DeepFloyd IF checkpoints.
from_pretrained
大多数模型采用噪声样本,并在每个时间步预测噪声残差。
可以混合搭配模型来创建其他扩散系统。
模型用 from_pretrained()
方法启动,本地缓存模型权重,下次加载模型时速度会更快。
加载 无条件图像生成模型(UNet2DModel),带有在猫图像上训练的检查点:
- 访问模型参数,请调用
model.config
from diffusers import UNet2DModel
repo_id = "google/ddpm-cat-256"
model = UNet2DModel.from_pretrained(repo_id, use_safetensors=True)
model.config # 访问模型参数
模型配置是一个🧊冻结的字典,创建后无法更改。有意为之,确保一开始用于定义模型架构的参数保持不变,而其他参数仍然可以在推理过程中进行调整。
一些重要参数:
sample_size
: 输入样本高度和宽度尺寸。in_channels
: 输入样本输入通道数。down_block_types
和up_block_types
: 创建 UNet 架构的下采样和上采样模块类型。block_out_channels
: 下采样块的输出通道数;也以相反的顺序用于上采样块的输入通道的数量。layers_per_block
: 每个 UNet 块中存在的 ResNet 块的数量。
如需推理,首先需要使用随机高斯噪声创建图像(图像往往通过一个复杂的多维张量表示,不同的维度代表不同的含义),这里张量 shape 是 batch * channel * width * height。
batch
:一个批次想生成的图片张数channel
:一般为3,RGB色彩空间width
: 图像宽height
: 图像高
import torch
torch.manual_seed(0)
noisy_sample = torch.randn(1, model.config.in_channels, model.config.sample_size, model.config.sample_size)
noisy_sample.shape
对于推理,将噪声图像(noisy_sample)和时间步长(timestep)传递给模型。
- 时间步长表示输入图像的噪声程度,开始时噪声多,结束时噪声少。
这有助于模型确定其在扩散过程中的位置,是更接近起点还是更接近终点。
使用样例方法得到模型输出:
with torch.no_grad():
noisy_residual = model(sample=noisy_sample, timestep=2).sample
不过,要生成实际示例,要一个调度程序来指导去噪过程。
完整
from diffusers import UNet2DModel
import torch
# 加载模型 load model
repo_id = "google/ddpm-cat-256"
model = UNet2DModel.from_pretrained(repo_id, use_safetensors=True)
model.config
# 噪声输入(噪声图像) noise as input
torch.manual_seed(0)
noisy_sample = torch.randn(1, model.config.in_channels, model.config.sample_size, model.config.sample_size)
noisy_sample.shape
# 推理 inference
with torch.no_grad():
# 噪声图像和时间步长传进去
noisy_residual = model(sample=noisy_sample, timestep=2).sample
调度器 Schedulers
给定模型输出,调度程序管理从噪声样本到噪声较小的样本 - 本例中是 noisy_residual.
Diffusers 用于构建扩散系统的工具箱。虽然 DiffusionPipeline 是使用预构建扩散系统的便捷方法,但也可以单独选择自己的模型和调度程序组件来构建自定义扩散系统。
调度程序根据模型的输出结果(示例中模型输出结果就是噪声残差),将噪声样本转换为噪声较小的样本。
用其 DDPMScheduler
的 from_config()
from diffusers import DDPMScheduler
scheduler = DDPMScheduler.from_pretrained(repo_id)
scheduler
# 根据模型的输出结果(噪声残差),将噪声样本转换为噪声较小的样本。
less_noisy_sample = scheduler.step(model_output=noisy_residual, timestep=2, sample=noisy_sample).prev_sample
less_noisy_sample.shape
自定义调度器
from diffusers import EulerDiscreteScheduler
# 自定义调度程序: 默认 PNDMScheduler 替换为 EulerDiscreteScheduler
pipeline.scheduler = EulerDiscreteScheduler.from_config(pipeline.scheduler.config)
💡
- 与模型不同,调度程序没有可训练的权重并且无参数
一些最重要参数:
num_train_timesteps
:去噪过程的长度,或者换句话说,将随机高斯噪声处理为数据样本所需的时间步数。beta_schedule
:用于推理和训练的噪声计划类型。beta_start
和beta_end
:噪声表的开始和结束噪声值。
要预测噪声稍低的图像,需要传入: 模型输出(noisy residual)、步长(timestep) 和 当前样本(noisy sample)。
less_noisy_sample = scheduler.step(model_output=noisy_residual, timestep=2, sample=noisy_sample).prev_sample
less_noisy_sample.shape
如果将 less_noisy_sample 作为输入,递归调用,将得到一个噪音更小、质量更好的图像!现在让我们将所有内容放在一起并可视化整个去噪过程。
首先,创建一个函数,对去噪图像进行后处理并将其显示为 PIL.Image
:
为了加速去噪过程,请将输入和模型移至 GPU:
model.to("cuda")
noisy_sample = noisy_sample.to("cuda")
创建去噪循环来预测噪声较小的样本的残差,并使用调度程序计算噪声较小的样本:
import tqdm
import PIL.Image
import numpy as np
# 去噪图像后处理
def display_sample(sample, i):
image_processed = sample.cpu().permute(0, 2, 3, 1)
image_processed = (image_processed + 1.0) * 127.5
image_processed = image_processed.numpy().astype(np.uint8)
image_pil = PIL.Image.fromarray(image_processed[0])
display(f"Image at step {i}")
display(image_pil)
# 创建去噪循环,预测噪声较小的样本的残差
sample = noisy_sample
for i, t in enumerate(tqdm.tqdm(scheduler.timesteps)):
# 1. 预测噪声残差 predict noise residual
with torch.no_grad():
residual = model(sample, t).sample
# 2. 计算噪声图像 compute less noisy image and set x_t -> x_t-1
sample = scheduler.step(residual, t, sample).prev_sample
# 3. 定期查看图像 optionally look at image
if (i + 1) % 50 == 0:
display_sample(sample, i + 1)
推理
推理 Inference code
模型、调度器、可视化、推理等
单机
from diffusers import DDPMScheduler, UNet2DModel
from PIL import Image
import torch
import numpy as np
scheduler = DDPMScheduler.from_pretrained("google/ddpm-cat-256")
model = UNet2DModel.from_pretrained("google/ddpm-cat-256").to("cuda")
scheduler.set_timesteps(50)
sample_size = model.config.sample_size
noise = torch.randn((1, 3, sample_size, sample_size)).to("cuda")
input = noise
for t in scheduler.timesteps:
with torch.no_grad():
noisy_residual = model(input, t).sample
prev_noisy_sample = scheduler.step(noisy_residual, t, input).prev_sample
input = prev_noisy_sample
image = (input / 2 + 0.5).clamp(0, 1)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8"))
image
分布式
Distributed inference 分布式
Accelerate
from accelerate import PartialState
from diffusers import DiffusionPipeline
pipeline = DiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, use_safetensors=True
)
distributed_state = PartialState()
pipeline.to(distributed_state.device)
with distributed_state.split_between_processes(["a dog", "a cat"]) as prompt:
result = pipeline(prompt).images[0]
result.save(f"result_{distributed_state.process_index}.png")
shell 代码
accelerate launch run_distributed.py --num_processes=2
PyTorch Distributed
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
from diffusers import DiffusionPipeline
sd = DiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, use_safetensors=True
)
def run_inference(rank, world_size):
dist.init_process_group("nccl", rank=rank, world_size=world_size)
sd.to(rank)
if torch.distributed.get_rank() == 0:
prompt = "a dog"
elif torch.distributed.get_rank() == 1:
prompt = "a cat"
image = sd(prompt).images[0]
image.save(f"./{'_'.join(prompt)}.png")
def main():
world_size = 2
mp.spawn(run_inference, args=(world_size,), nprocs=world_size, join=True)
if __name__ == "__main__":
main()
shell 代码
torchrun run_distributed.py --nproc_per_node=2
训练
单机
训练代码
from accelerate import Accelerator
from huggingface_hub import HfFolder, Repository, whoami
from tqdm.auto import tqdm
from pathlib import Path
import os
def get_full_repo_name(model_id: str, organization: str = None, token: str = None):
if token is None:
token = HfFolder.get_token()
if organization is None:
username = whoami(token)["name"]
return f"{username}/{model_id}"
else:
return f"{organization}/{model_id}"
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.push_to_hub:
repo_name = get_full_repo_name(Path(config.output_dir).name)
repo = Repository(config.output_dir, clone_from=repo_name)
elif config.output_dir is not None:
os.makedirs(config.output_dir, exist_ok=True)
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).to(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
).long()
# 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)
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:
repo.push_to_hub(commit_message=f"Epoch {epoch}", blocking=True)
else:
pipeline.save_pretrained(config.output_dir)
安装
安装
- mac, pytorch, python 3.8~3.11
pip install --upgrade diffusers accelerate transformers
#pip install diffusers["torch"] transformers
建议在 GPU 上运行,因为该模型由大约 14 亿个参数组成。通过to(“cuda”)即可将生成器对象移至GPU:
pipeline.to("cuda")
【2024-9-29】Ubuntu + v100s 上执行报错
Failed to import diffusers.loaders.unet because of the following error (look up to see its traceback):
No module named 'peft.tuners.tuners_utils'
示例
简易示例
from diffusers import DiffusionPipeline
# ==== 加载权重 ====
# 下载远程模型,比较大,默认下载目录为 ~/.cache/huggingface,可通过 export HF_HOME=指定目录,最好写入~/.bashrc持久化
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", use_safetensors=True)
pipeline = DiffusionPipeline.from_pretrained("./stable-diffusion-v1-5", use_safetensors=True) # 本地权重
# 自定义调度程序: 默认 PNDMScheduler 替换为 EulerDiscreteScheduler
pipeline.scheduler = EulerDiscreteScheduler.from_config(pipeline.scheduler.config)
# ==== GPU ====
pipeline.to("cuda")
# ==== text2image ====
image = pipeline("An image of a squirrel in Picasso style").images[0]
image # 图像输出包装在一个 PIL.Image 对象中
# ==== 保存 ====
image.save("image_of_squirrel_painting.png") # 保存图像
DiffusionPipeline 下载并缓存所有 model、tokenization、scheduling 组件。
示例中, StableDiffusionPipeline 由 UNet2DConditionModel 和 PNDMScheduler 等组成:
# pipeline
StableDiffusionPipeline {
"_class_name": "StableDiffusionPipeline",
"_diffusers_version": "0.21.4",
...,
"scheduler": [
"diffusers",
"PNDMScheduler"
],
...,
"unet": [
"diffusers",
"UNet2DConditionModel"
],
"vae": [
"diffusers",
"AutoencoderKL"
]
}
案例
- 文生图
- 图生图
from diffusers import StableDiffusionPipeline
from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image
import torch
# --------- 本地模型信息 ----------
model_path = '/mnt/bd/wangqiwen-hl/models/video'
model_id = "runwayml/stable-diffusion-v1-5"
# --------- text2image ---------
#pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16, cache_dir=model_path)
pipe = pipe.to("cuda")
prompt = "a man and a woman holding a cat and a dog"
prompt = "sexy lady walking on the bed"
negative_prompt = "distorted faces, low resolution"
images = pipe(prompt, num_images_per_prompt=3, negative_prompt=negative_prompt).images
num = len(images)
print(f"一共生成了{num}张图片, 默认保存第一张")
for i in range(num):
images[i].save(f"output_{i+1}.png")
#image[0].save("output.png")
# --------- image2image ---------
pipeimg = StableDiffusionImg2ImgPipeline.from_pretrained(model_id, torch_dtype=torch.float16, cache_dir=model_path)
pipeimg = pipe.to("cuda")
# 选取一张来进一步修改
from PIL import Image
init_image = Image.open("output_1.png").convert("RGB").resize((768, 512))
#Image.open("output_1.png").convert("RGB").resize((768, 512))
prompt = "add another girl"
prompt = "add more detail to make it more attractive"
images = pipe(prompt=prompt, num_images_per_prompt=3, negative_prompt=negative_prompt, image=init_image, strength=0.75, guidance_scale=7.5).images
num = len(images)
for i in range(num):
images[i].save(f"modify_{i+1}.png")
#images[0].save("output_modify.png")
扩散模型微调
Stable Diffusion 由于在多样化的数据集上进行广泛的训练,已经具备了大量概念的知识。
在训练 LoRA 时,充分利用这一点非常重要,并区分“新概念
(NC)”和“修改概念
(MC)”。
新概念
: 在 Stable Diffusion 原始训练中不存在或未得到充分体现的概念或元素。可能是模型之前未遇到过的独特主题、风格或物品。使用 NCs 进行训练涉及向模型引入全新信息。- 目标: 扩展模型对这些新颖元素的“理解”。通常会在数据集中添加“激活标签”来表示它们。
修改概念
(MC) 指模型已经识别但可能未准确或以期望方式表示的概念。- 这些可能是现有主题、风格或解释的变体。训练 MCs 涉及调整或精炼模型的现有知识。
- 目的: 不引入新知识,而是微调和精炼模型的现有理解。
训练 LoRA 模型时,需要理解 Stable Diffusion 的基础知识(即模型已经掌握得很好的部分)以及它所缺乏或误解的内容。基于这些知识,精心策划训练数据集,以填补这些空白或纠正错误,无论属于 NC 还是 MC。
然后,可以战略性地使用激活标签来引入新概念。
通过这种对 NC 和 MC 的理解来训练 LoRA,可以更有效地引导 Stable Diffusion 与特定愿景保持一致。
详见 LoRA 训练进阶指南
扩散模型案例
扩散模型有很多应用版本
DALL-E 1
DALLE-1 模型图
- 首先, 图像在第一阶段通过
dVAE
(离散变分自动编码机)训练得到图像的 image tokens。文本 caption 通过文本编码器得到 text tokens。 - Text tokens 和 image tokens 会一起拼接起来用作 Transformer 的训练。
- Transformer 的作用是将 text tokens 回归到 image tokens。
- 当完成这样的训练之后,实现了从文本特征到图像特征的对应。
- 生成阶段,caption 通过编码器得到 text tokens,然后通过 transformer 得到 image tokens,最后 image tokens 在通过第一阶段训练好的 image decoder 部分生成图像。
- 因为图像是通过采样生成,这里还使用了
CLIP
模型对生成的图像进行排序,选择与文本特征相似度最高的图像作为最终的生成对象。
- 因为图像是通过采样生成,这里还使用了
DALL-E 2
DALLE-2 模型图
DALLE-2 模型结构。
- text encoder 和 image encoder 就是用 CLIP 中的相应模块。在训练阶段通过训练 prior 模块,将 text tokens 和 image tokens 对应起来。
- 同时训练 GLIDE 扩散模型,这一步的目的是使得训练后的 GLIDE 模型可以生成保持原始图像特征,而具体内容不同的图像,达到生成图像的多样性。
- 当生成图像时,模型整体类似在 CLIP 模型中增加了 prior 模块,实现了文本特征到图像特征的对应。然后通过替换 image decoder 为 GLIDE 模型,最终实现了文本到图像的生成。
DALL-E 3
【2023-10-07】DALL-E 3 惊艳发布,完全免费!比肩Midjourney的AI绘图工具
DALL·E 3 没有一个单独网址,要在Bing里面使用它
- 切换代理到其他国家,然后打开bing, 直接输入中文
- 每生成一张照片都消耗电力,初始电力是100点, 目前还是免费
Imagen (未开源)
Imagen模型结构图
Imagen 生成模型还没有公布代码和模型,从论文中的模型结构来看,似乎除了文本编码器之外,是由一个文本-图像扩散模型来实现图像生成和两个超分辨率扩散模型来提升图像质量。
Imagic (未开源)
Imagic原理图
- 最新的 Imagic 模型,号称可以实现通过文本对图像进行 PS 级别的修改内容生成。目前没有公布模型和代码。
- 从原理图来看,似乎是通过在文本-图像扩散模型的基础上,通过对文本嵌入的改变和优化来实现生成内容的改变。如果把扩散模型替换成简单的 encoder 和 decoder,有点类似于在 VAE 模型上做不同人脸的生成。只不过是扩散模型的生成能力和特征空间要远超过 VAE。
Stable diffusion
Stable diffusion 结构图
Stable diffusion
是 Stability AI
公司开发并且开源的一个生成模型。
朴素的 DDPM 扩散模型,每一步都在对图像作“加噪”、“去噪”操作。而在 Stable diffusion 模型中,可以理解为是对图像进行编码后的 image tokens 作加噪去噪。而在去噪(生成)的过程中,加入了文本特征信息用来引导图像生成(图右 Conditioning 部分)。跟 VAE 中的条件 VAE 和 GAN 中的条件 GAN 原理是一样的,通过加入辅助信息,生成需要的图像。
扩散模型不足
原始扩散模型三个主要缺点:采样速度慢,最大化似然差、数据泛化能力弱
diffusion models 改进研究分为对应的三类:采样速度提升、最大似然增强和数据泛化增强。
首先说明改善的动机,再根据方法的特性将每个改进方向的研究进一步细化分类,从而清楚的展现方法之间的联系与区别。
未来研究方向
- A. 重审假设。需要重新审视和分析扩散模型中的许多典型假设。例如,假设扩散模型的正向过程完全消除了数据中的所有信息并且使其等效于先前分布可能并不总是成立。实际上,完全删除信息是在有限时间内无法实现,了解何时停止前向噪声处理以在采样效率和采样质量之间取得平衡是非常有意义的。
- B. diffusion model 已经成为一个强大的框架,可以在大多数应用中与生成对抗性网络(GAN)竞争,而无需诉诸对抗性训练。对于特定的任务,我们需要了解为什么以及何时扩散模型会比其他网络更加有效,理解扩散模型和其他生成模型的区别将有助于阐明为什么扩散模型能够产生优秀的样本同时拥有高似然值。另外,系统地确定扩散模型的各种超参数也是很重要的。
- C. diffusion model 如何在隐空间中提供良好的latent representation,以及如何将其用于data manipulation的任务也是值得研究的。
- D. 将 diffusion model 和 generative foundation model 结合,探索更多类似于ChatGPT,GPT-4等有趣的AIGC应用
扩散模型 vs 语言模型
【2023-7-1】扩散模型的作图缺点
扩散模型
- 优势
- 控制条件可设置
- 模型规模可控
- 劣势
- 语义控制不够精准:以标签为基准,无法识别标签属性关系,因为 CLIP 模型
- 缺乏语义逻辑性:第一个人在第二个人的左边 — 无法识别
语言模型
- 优势
- 理解语言与动作
- 更友好的交互方式
- 统一的任务框架
- 劣势
- 大量数据资源
- 大量计算资源
- 缺乏多模态控制