一、DDIM 算法简介
Denoising Diffusion Implicit Models(DDIM)是在 Denoising Diffusion Probabilistic Models(DDPM)基础上发展而来的一种生成模型。相比于 DDPM,DDIM 主要的优势在于能够在不牺牲生成质量的前提下,显著提高采样速度。DDPM 进行反向采样时需要从纯噪声经过 个时间步逐步去噪生成数据,而 DDIM 可以通过较少的步数生成高质量的样本,这使得它在实际应用中更具效率。
与DDPM的关系与区别
关系
DDIM是基于DDPM的改进版本,其核心思想与DDPM相似,都是通过逐步去噪来生成数据。DDIM的训练过程与DDPM完全一致,都是通过最小化变分下界(ELBO)来优化模型参数。因此,DDIM可以直接复用DDPM训练好的模型,无需重新训练。
区别
采样过程:DDIM的采样过程不依赖于马尔可夫链,而是通过直接建模条件分布来实现去噪。这使得DDIM能够在更少的步骤内完成采样,显著提高了生成效率。
确定性生成:DDIM的生成过程是确定性的,即给定相同的初始噪声,每次生成的结果都相同。而DDPM的生成过程是随机的,即使初始噪声相同,生成的结果也可能不同。
加速采样:DDIM通过跳步去噪的方式加速采样过程,可以在50步左右达到DDPM 1000步的性能/图片质量,大大减少了采样时间。
二、正向扩散过程(与 DDPM 相同)
1. 单步扩散
假设初始数据 来自真实数据分布 。在正向扩散过程的第 步,给定 , 的条件概率分布为:其中, 是一个在 之间的噪声系数序列,控制每一步添加的噪声量; 是单位矩阵,表示噪声的协方差矩阵为对角矩阵,各维度噪声相互独立。
2. 多步扩散
设 ,。通过迭代单步扩散公式,可以直接得到从 到 的分布:这意味着 可以表示为:其中, 是高斯噪声。
三、反向采样过程(DDIM 核心)
1. 从 恢复
由 ,可以解出 :在实际中,我们使用神经网络 来预测噪声 ,则预测的 为:
2. 推导反向采样公式
DDIM 假设反向采样时 可以表示为:其中:
-
是一个超参数,用于控制采样过程的随机性。当 时,DDIM 退化为 DDPM;当 时,采样过程变为确定性的,可实现大步长快速采样。
-
是高斯噪声
下面详细推导这个公式:
我们希望找到一个合理的方式从 得到 。将 代入 的表达式:
3. 确定性采样()
当 时,反向采样公式简化为:这是一个确定性的过程,不需要每次采样都添加随机噪声,因此可以通过跳跃时间步的方式,大大减少采样步数,提高采样效率。
四、训练过程(与 DDPM 相似)
DDIM 的训练过程与 DDPM 基本相同,都是通过最小化预测噪声和真实噪声之间的均方误差损失来训练神经网络 。损失函数为:其中, 表示期望,我们需要对所有可能的时间步 、初始样本 和噪声 进行平均。
代码实现
以下是一个基于 PyTorch 实现的简单 DDIM 算法代码示例,用于在 MNIST 数据集上进行图像生成。该代码主要包含正向扩散过程、反向采样过程、神经网络模型定义以及训练和测试函数。
import torch``import torch.nn as nn``import torch.optim as optim``from torchvision import datasets, transforms``from torch.utils.data import DataLoader``import numpy as np``# 定义超参数``T = 1000 # 扩散步数``beta_1 = 1e-4``beta_T = 0.02``# 生成从 beta_1 到 beta_T 的 T 个均匀分布的噪声系数``betas = torch.linspace(beta_1, beta_T, T).double()``alphas = 1 - betas``# 计算累积的 alpha 值``alphas_bar = torch.cumprod(alphas, dim=0)``# DDIM 的超参数 eta,0 表示确定性采样``eta = 0``# 定义神经网络模型,用于预测噪声``class DenoiseNet(nn.Module):` `def __init__(self):` `super(DenoiseNet, self).__init__()` `self.model = nn.Sequential(` `# 输入层到第一个隐藏层` `nn.Linear(784, 128),` `nn.ReLU(),` `# 第一个隐藏层到第二个隐藏层` `nn.Linear(128, 128),` `nn.ReLU(),` `# 第二个隐藏层到输出层` `nn.Linear(128, 784)` `)` `def forward(self, x, t):` `# 获取时间步的嵌入表示` `t_embedding = self.get_timestep_embedding(t, 128)` `# 将输入数据和时间步嵌入拼接在一起` `x = torch.cat([x, t_embedding], dim=1)` `return self.model(x)` `def get_timestep_embedding(self, timesteps, embedding_dim):` `# 生成时间步的嵌入表示` `half_dim = embedding_dim // 2` `emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)` `emb = torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb)` `emb = timesteps[:, None].float() * emb[None, :]` `emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)` `if embedding_dim % 2 == 1:` `emb = torch.cat([emb, torch.zeros_like(emb[:, :1])], dim=1)` `return emb``# 正向扩散过程``def forward_diffusion(x_0, t):` `# 生成和 x_0 相同形状的高斯噪声` `noise = torch.randn_like(x_0)` `# 计算 \sqrt{\bar{\alpha}_t}` `sqrt_alpha_bar = alphas_bar[t].sqrt().view(-1, 1, 1, 1)` `# 计算 \sqrt{1 - \bar{\alpha}_t}` `sqrt_one_minus_alpha_bar = (1 - alphas_bar[t]).sqrt().view(-1, 1, 1, 1)` `# 根据正向扩散公式计算 \mathbf{x}_t` `x_t = sqrt_alpha_bar * x_0 + sqrt_one_minus_alpha_bar * noise` `return x_t, noise``# 计算 sigma_t``def get_sigma_t(t):` `alpha_t = alphas[t]` `alpha_bar_t = alphas_bar[t]` `alpha_bar_t_prev = alphas_bar[t - 1] if t > 0 else torch.tensor(1.0).double()` `sigma_t = np.sqrt((1 - alpha_bar_t_prev) / (1 - alpha_bar_t)) * np.sqrt(1 - alpha_t * alpha_bar_t_prev / alpha_bar_t)` `return sigma_t``# DDIM 反向采样过程``def ddim_reverse_process(model, x_t, t):` `t_tensor = torch.full((x_t.shape[0],), t, device=x_t.device, dtype=torch.long)` `# 预测噪声` `pred_noise = model(x_t.view(x_t.shape[0], -1), t_tensor).view(x_t.shape)` `alpha_t = alphas[t]` `alpha_bar_t = alphas_bar[t]` `alpha_bar_t_prev = alphas_bar[t - 1] if t > 0 else torch.tensor(1.0).double()` `# 计算预测的 x_0` `x_0_pred = (x_t - torch.sqrt(1 - alpha_bar_t) * pred_noise) / torch.sqrt(alpha_bar_t)` `# 计算 sigma_t` `sigma_t = get_sigma_t(t)` `# 计算系数` `c1 = torch.sqrt(alpha_bar_t_prev)` `c2 = torch.sqrt(1 - alpha_bar_t_prev - eta ** 2 * sigma_t ** 2)` `c3 = eta * sigma_t` `if t > 0:` `z = torch.randn_like(x_t)` `else:` `z = torch.zeros_like(x_t)` `# 根据 DDIM 反向采样公式计算 x_{t-1}` `x_t_prev = c1 * x_0_pred + c2 * pred_noise + c3 * z` `return x_t_prev``# 训练函数``def train(model, data_loader, optimizer, epochs):` `for epoch in range(epochs):` `for batch in data_loader:` `# 获取当前批次的数据` `x_0 = batch[0].double().view(-1, 784)` `batch_size = x_0.shape[0]` `# 随机采样时间步` `t = torch.randint(0, T, (batch_size,), device=x_0.device).long()` `# 进行正向扩散得到 \mathbf{x}_t 和真实噪声` `x_t, noise = forward_diffusion(x_0.view(-1, 1, 28, 28), t)` `# 输入 \mathbf{x}_t 和时间步 t 到神经网络中预测噪声` `pred_noise = model(x_t.view(batch_size, -1), t)` `# 计算预测噪声和真实噪声之间的均方误差损失` `loss = nn.functional.mse_loss(pred_noise, noise.view(batch_size, -1))` `# 清空梯度` `optimizer.zero_grad()` `# 反向传播计算梯度` `loss.backward()` `# 更新神经网络的参数` `optimizer.step()` `print(f'Epoch {epoch}: Loss {loss.item()}')``# 测试函数,用于从噪声中生成图像``def test(model):` `# 生成一个随机的噪声样本` `x_T = torch.randn(1, 1, 28, 28).double()` `with torch.no_grad():` `for t in range(T - 1, -1, -1):` `x_T = ddim_reverse_process(model, x_T, t)` `return x_T``# 主函数``def main():` `# 定义数据预处理` `transform = transforms.Compose([` `transforms.ToTensor(),` `transforms.Normalize((0.5,), (0.5,))` `])` `# 加载 MNIST 数据集` `train_dataset = datasets.MNIST('data', train=True, download=True, transform=transform)` `train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)` `# 初始化神经网络模型` `model = DenoiseNet()` `# 定义优化器` `optimizer = optim.Adam(model.parameters(), lr=1e-3)` `# 训练模型` `train(model, train_loader, optimizer, epochs=10)` `# 从噪声中生成图像` `generated_image = test(model)` `print(generated_image)``if __name__ == "__main__":` `main()
代码说明:
超参数设置:定义了扩散步数 T、噪声系数 beta_1 和 beta_T,并计算了 alphas 和 alphas_bar。同时设置了 eta 用于控制 DDIM 采样的随机性,eta = 0 表示确定性采样。
神经网络模型:DenoiseNet 是一个简单的全连接神经网络,用于预测噪声。它接收带噪样本 x_t 和时间步 t 作为输入,输出预测的噪声。
正向扩散过程:forward_diffusion 函数根据正向扩散公式从 x_0 计算出 x_t,并返回真实添加的噪声。
DDIM 反向采样过程:ddim_reverse_process 函数实现了 DDIM 的反向采样公式,根据当前时间步 t 和 x_t 计算出 x_{t - 1}。
训练函数:train 函数通过随机采样时间步和数据样本,进行正向扩散,然后计算预测噪声和真实噪声之间的均方误差损失,并更新神经网络的参数。
测试函数:test 函数从一个随机的噪声样本开始,通过反向采样过程逐步恢复出图像。
主函数:main 函数加载 MNIST 数据集,初始化模型和优化器,进行训练和测试。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。