在生成图片、生成视频的 AI 工具集中爆发的 2023-2025 年,扩散模型(Diffusion Model)几乎成了所有主流图像生成产品的共同底座。从 Midjourney 的绘图质量,到 Stable Diffusion 的开源生态,再到各类可控视频生成工具的帧间一致性,背后都离不开“加噪-去噪”这套核心机制。
本文将作为扩散模型专题的“上篇”,重点拆解 AI 图像与视频生成的底层原理:扩散模型为什么能生成图、图像生成和视频生成的架构差异在哪里、一个最小可运行的扩散模型究竟怎么写。文章不要求读者有扎实的数学基础,只需要了解基本的 Python 和 PyTorch 操作即可。学完后,你会对 “Stable Diffusion 为什么叫 Diffusion”“视频生成为什么比图像生成难这么多” 这类问题有清晰的答案。
1. 为什么我们需要扩散模型
1.1 生成模型的老问题是“既要像、又要多样”
在扩散模型成为主流之前,图像生成领域主要由两类模型承担:生成对抗网络(GAN)和变分自编码器(VAE)。
GAN 的思路是让生成器和判别器相互博弈。生成器负责把随机噪声变成图片,判别器负责判断图片是真是假。训练到理想状态时,生成器造出的图片连判别器都分不出真假。GAN 的优点是生成速度快、图像风格清晰,但短板同样明显:训练不稳定,容易出现模式坍塌,也就是生成器只学会了少数几种看起来不错的图,多样性不够。
VAE 的思路则偏向“编码-解码”。输入图片先被编码成潜在向量,再被解码回图片,训练时要求潜在向量服从某种先验分布。VAE 训练稳定、潜在空间连续,但生成的图片往往偏模糊,细节不足。
扩散模型换了一个角度:与其一开始就尝试“无中生有”,不如学习“逆着破坏过程还原”。它先把一张真实图片逐步加噪声,直到变成近乎纯噪声;然后训练一个网络,学会从噪声中一步步把图片恢复出来。这个思路看起来简单,却同时解决了 GAN 的训练不稳定问题和 VAE 的细节模糊问题,因此迅速成为图像生成的主流方案。
1.2 扩散模型解决的核心问题
从宏观视角看,扩散模型解决的核心问题是:
- 从噪声分布到数据分布的映射。训练完成后,任意采样一组高斯噪声,经过多次迭代去噪,就能得到一张符合训练数据风格的图像。
- 细粒度控制。通过在去噪过程中注入条件信息,可以让模型生成指定类别、指定文本描述的图片,甚至控制姿态、构图和景深。
- 高保真度与高多样性平衡。扩散模型不依赖对抗博弈,生成结果天然具有更好的多样性;配合大模型容量和海量训练数据,细节保真度也远超早期生成模型。
1.3 扩散模型与 GAN、VAE 的直观对比
| 维度 | GAN | VAE | 扩散模型 |
|---|---|---|---|
| 训练稳定性 | 容易模式坍塌 | 较稳定 | 稳定 |
| 图像细节 | 好 | 偏模糊 | 最好 |
| 生成速度 | 快 | 快 | 慢(需多步采样) |
| 模式覆盖 | 容易窄化 | 较宽 | 宽 |
| 可控条件注入 | 较难 | 一般 | 灵活(文本、掩码、深度图) |
需要注意的是,速度是扩散模型的主要短板。一张 512x512 图片在普通显卡上可能需要几秒到几十秒,而 GAN 可以做到实时。这也是后来“加速采样器”“蒸馏模型”等优化方向要解决的核心矛盾。
2. 扩散模型的两条路:加噪与去噪
2.1 前向扩散过程:把图片一点点毁掉
扩散模型里有两个相反过程,第一个叫前向过程(Forward Process),也叫扩散过程。
假设我们有一张原始图片 ( x_0 ),前向过程会在一系列时间步 ( t = 1, 2, ..., T ) 上,逐步往图片里加入高斯噪声。时间步越大,噪声越强,图片越模糊,最后近似变成一张纯高斯噪声图。
每一步的数学描述可以概括为:
[ x_t = \sqrt{\bar{\alpha}_t} \cdot x_0 + \sqrt{1 - \bar{\alpha}_t} \cdot \epsilon ]
其中 ( \epsilon ) 是从标准正态分布中采样的噪声,( \bar{\alpha}_t ) 是由噪声调度表计算出的衰减系数。直观理解是:原始信号占比随着 ( t ) 增大而下降,噪声占比上升。
在代码实现里,前向过程往往不是真的循环 T 次,而是利用公式一步直接得到任意时间步 ( t ) 下的加噪图片。这样训练时会非常高效。
2.2 逆向去噪过程:训练一个“噪声预测器”
有了被破坏的图片,我们希望还原。但我们并不知道破坏过程中每一步具体用的是哪个噪声,所以需要训练一个神经网络去预测它。
这个网络通常写作 ( \epsilon_\theta(x_t, t) ),输入是“加噪后的图片”和“时间步信息”,输出是“模型预测的噪声”。网络训练好之后,就可以从纯噪声开始,不断执行:
[ x_{t-1} = \text{根据} x_t \text{和预测噪声} \epsilon_\theta \text{反推得到的上一时间步状态} ]
多步迭代,最终得到清晰的图片。
这里有个初学者容易困惑的点:网络究竟学习的是什么?它学习的不是“如何画一只猫”,而是一个更小的任务——“从加了噪声的猫图里,把噪声分量分离出来”。当模型能准确预测噪声时,它实际上就隐式学会了猫的长相、纹理、颜色分布等数据规律。正如一句话所说:要预测“什么是噪声”,首先得知道“什么是信号”。
2.3 噪声调度与训练目标
噪声调度(Noise Schedule)决定了每个时间步加入多少噪声。常见的调度有:
- 线性调度:从较小的 beta 值线性增加到较大的 beta 值。早期 DDPM 使用这种方式。
- 余弦调度:噪声变化更平滑,训练更稳定,在很多开源模型中被广泛采用。
- 缩放调度:根据数据分布调整噪声水平,常用于潜在扩散模型。
训练目标最简洁的形式是:
[ L = ||\epsilon - \epsilon_\theta(x_t, t)||^2 ]
也就是让模型预测的噪声与真实噪声之间的均方误差越小越好。这个损失函数写法简单,却非常有效,是扩散模型训练中最常见的损失形式。
2.4 采样节奏:从 DDPM 到 DDIM
训练时我们往往使用 1000 个时间步,但在推理时如果也迭代 1000 次,生成速度会非常慢。于是出现了很多采样加速方法:
- DDPM 采样:按原始时间步逐级反向去噪,质量高但速度慢。
- DDIM(Denoising Diffusion Implicit Models):允许跳步采样,可以用 20-50 步生成质量不错的图片。它把采样过程从“随机过程”改成“确定性过程”,还带来了潜在空间的插值能力。
- DPM-Solver、DPM++ 等现代采样器:基于微分方程数值求解的思路,用更少的步数获得高质量结果。ComfyUI、Stable Diffusion WebUI 里的采样器选项,本质就是这一层算法选择。
理解采样器的意义后,再回头看“为什么不同采样器出图效果不一样”“步数设多少合适”这些问题,就会清楚很多。
3. 图像生成中的三大设计
3.1 从像素空间到潜在空间
纯粹在像素空间做扩散,计算量非常巨大。一张 1024x1024 的 RGB 图片,意味着网络要处理百万级像素的每一位。为了降低计算成本,Stable Diffusion 等模型的思路是先做一个VAE 编码器,把 512x512 的图片压缩成 64x64 的潜在特征图,再在潜在特征空间做扩散,最后用 VAE 解码器恢复成像素图片。
这就是潜在扩散模型(Latent Diffusion Model,LDM)的核心思想:扩散过程不直接发生在像素空间,而是发生在低维潜在空间。这也是为什么很多人说“Stable Diffusion 的王牌一半来自 VAE”。
这样做的好处非常明显:
- 计算量大幅下降,普通消费级显卡可以运行;
- 潜在空间更容易表达高层的语义特征;
- 解码器能把潜在特征还原成细节丰富的图像。
3.2 条件控制:文本、类别与无分类器引导
扩散模型真正走进大众视野,靠的是“文本生成图像”能力。怎么把文本信息输入到去噪网络?
目前主流的做法是Cross-Attention(交叉注意力)。文本先经过文本编码器(比如 CLIP 的文本编码器)得到文本向量,然后在 UNet 的每个注意力层中与图像特征做交叉注意力,让每一层去噪过程都能参考文本提示(Prompt)。
同时,为了让模型对文本提示更“听话”,研究者提出了无分类器引导(Classifier-Free Guidance,CFG)。具体方式是:训练时同时让网络学习“带条件的去噪”和“不带条件的去噪”,推理时按下式组合:
[ \epsilon_{\text{final}} = \epsilon_{\text{uncond}} + \text{guidance_scale} \cdot (\epsilon_{\text{cond}} - \epsilon_{\text{uncond}}) ]
当 guidance_scale 大于 1 时,模型会更贴合文本条件;但设置过大也会导致色彩过饱和、构图失真。这就是 WebUI 里“提示词相关性(CFG Scale)”参数背后的原理。
3.3 采样器与步数权衡
图像生成不是“步数越多越好”。步数过多时,采样器可能引入额外噪声,也可能让画面产生不自然纹理。实际项目中:
- 低步数(10-20 步)适合快速草稿、实时交互;
- 中步数(20-30 步)是大多数高质量出图的常用范围;
- 高步数(50 步以上)对部分采样器收益不大。
具体最优步数要结合采样器类型和模型训练配置来测,不能简单照搬别人的参数。
4. 视频生成:多了一维时间
4.1 视频生成与图像生成的本质差异
视频生成可以看作“图像生成 + 时间一致性”。每一帧都需要清晰合理,同时帧与帧之间还要保证物体运动连续、人物身份一致、场景光照一致。
这是视频生成远比图像生成困难的核心原因。对图像生成来说,模型只需要生成静态分布;对视频生成来说,模型必须在更高维的“图像序列空间”中保持时间和空间的双重一致性。
4.2 时空联合扩散
一类主流方案是直接把扩散模型扩展到三维时空:
- 输入不再是单张潜在特征图,而是多帧图像堆叠成的视频潜在张量;
- 网络结构在原有 UNet 基础上增加时间注意力(Temporal Attention)或3D 卷积,让模型同时建模空间结构信息与时间运动信息;
- 训练数据从“单张图片”换成“视频片段”。
这种方案的优点是端到端、语义一致性强,缺点是对显存和训练数据要求极高。为了让普通设备也能运行,很多视频模型会把空间生成和时间建模拆开,先做空间生成,再做时间对齐。
4.3 先图像后视频的两阶段方案
另一种常见思路是把“文本到视频”拆成两步:
- 首先生成关键帧或首帧。用图像生成模型确定画面的主体、风格、构图。
- 再补全中间帧或生成运动序列。用一个条件模型,在已有帧的基础上生成后续帧,保持内容一致。
这个思路对应很多实际工具里的“图生视频”“帧插值”功能。它最大的优点是复用成熟的图像生成模型,计算成本相对可控;缺点是长视频累积误差明显,画面可能逐渐漂移。
4.4 一致性与资源开销问题
视频生成需要额外关注几个工程问题:
- 上下文长度:视频是连续帧序列,模型需要足够的“时间上下文”才能理解运动规律。
- 首尾循环:很多模型默认生成循环视频,训练数据如果不支持首尾衔接,就会在闭环处出现跳变。
- 显存瓶颈:视频张量比图像张量大一个数量级,通常需要使用上下文并行、模型并行、混合精度训练等手段。
从趋势上看,视频生成正在从“贴片式拼接”走向“原生时空联合建模”,但距离图像生成那么成熟的生态还有一段路。
5. 最小可运行的扩散模型实战
下面我们用 PyTorch 实现一个极简扩散模型。为了便于理解,这里不生成图片,而是让模型学会从一个形变的二维高斯分布中采样。麻雀虽小,但加噪、去噪网络、训练、采样四个环节全部保留。
5.1 示例目标与网络选型
在 2D 数据点上做扩散,去噪网络只需要一个带时间嵌入的多层感知机(MLP)。这个结构和图像领域的 UNet 在思想上完全一致:输入时间步编码和带噪样本,输出预测噪声。
完整代码逻辑如下。
5.2 数据准备与加噪
import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader, TensorDataset # 生成一个经过线性变换的二维高斯分布,共 20000 个样本 np.random.seed(0) data = np.random.randn(20000, 2) @ np.array([[2.0, 0.5], [0.5, 1.0]]) dataset = TensorDataset(torch.from_numpy(data).float()) loader = DataLoader(dataset, batch_size=256, shuffle=True) # 线性噪声调度 def linear_beta_schedule(timesteps=500, beta_start=1e-4, beta_end=0.02): return torch.linspace(beta_start, beta_end, timesteps) betas = linear_beta_schedule() alphas = 1.0 - betas alpha_bars = torch.cumprod(alphas, dim=0)前向加噪函数q_sample接收原始样本和时间步索引,直接返回加噪结果和真实噪声。
def q_sample(x_start, t): noise = torch.randn_like(x_start) sqrt_alpha_bar = torch.sqrt(alpha_bars[t])[:, None] sqrt_one_minus_alpha_bar = torch.sqrt(1.0 - alpha_bars[t])[:, None] x_t = sqrt_alpha_bar * x_start + sqrt_one_minus_alpha_bar * noise return x_t, noise这里的关键是理解alpha_bars[t]:它表示直到第 t 步时,原始信号保留的比例系数。
5.3 搭建去噪网络
网络包含两部分:时间步编码模块(TimeEmbedding)和主体 MLP。时间步编码让同一个网络能够感知“当前噪声有多重”,从而调整去噪策略。
class TimeEmbedding(nn.Module): def __init__(self, dim=16): super().__init__() self.dim = dim def forward(self, t): half = self.dim // 2 freqs = torch.exp( -np.log(10000) * torch.arange(half, dtype=torch.float32) / half ) args = t[:, None].float() * freqs[None, :] return torch.cat([torch.cos(args), torch.sin(args)], dim=-1) class SimpleDenoiser(nn.Module): def __init__(self, input_dim=2, hidden_dim=128, time_dim=16): super().__init__() self.time_embed = TimeEmbedding(time_dim) self.net = nn.Sequential( nn.Linear(input_dim + time_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, input_dim), ) def forward(self, x, t): te = self.time_embed(t) h = torch.cat([x, te], dim=-1) return self.net(h)5.4 训练
训练时随机抽取时间步 ( t ),对样本加噪,再让模型预测噪声,用均方误差计算损失。
model = SimpleDenoiser() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) epochs = 200 for epoch in range(epochs): total_loss = 0.0 for (xb,) in loader: t = torch.randint(0, len(betas), (xb.shape[0],), dtype=torch.long) x_t, noise = q_sample(xb, t) pred_noise = model(x_t, t) loss = F.mse_loss(pred_noise, noise) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() * xb.shape[0] if (epoch + 1) % 50 == 0: print(f"Epoch {epoch + 1}/{epochs}, Loss: {total_loss / len(dataset):.6f}")5.5 采样生成
训练完成后,从纯噪声出发,按时间步从后往前逐步去噪。为了方便理解,代码直接实现了 DDPM 采样过程。
def p_sample(model, num_samples=16, device="cpu"): model.eval() x = torch.randn(num_samples, 2, device=device) for i in reversed(range(len(betas))): t = torch.full((num_samples,), i, dtype=torch.long, device=device) with torch.no_grad(): pred_noise = model(x, t) alpha = alphas[i] alpha_bar = alpha_bars[i] if i > 0: z = torch.randn_like(x) else: z = 0 # DDPM 反向传播更新公式 mean = (x - (1.0 - alpha) / torch.sqrt(1.0 - alpha_bar) * pred_noise) / torch.sqrt(alpha) x = mean + torch.sqrt(betas[i]) * z return x samples = p_sample(model, num_samples=16) samples_np = samples.numpy() print("生成的 16 个样本点如下:") print(samples_np)5.6 运行提示与预期结果
- 训练 200 个 epoch 后,损失会从几十降低到零点几甚至更低。
- 采样得到的 16 个点应该大致符合原始数据分布的形状:整体呈椭圆形分布,主要方位沿 (2,0.5) 方向拉伸。
- 如果损失不下降或采样点分布异常,优先检查学习率、训练步数和噪声调度。
代码里的网络、函数都是完整可运行的。你也可以把data替换成 MNIST 图像数据,把 MLP 换成一个简单的 UNet,就得到标准的 DDPM 图像生成流程了。
6. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 训练损失不下降 | 学习率过大或过小;时间步编码维度不足 | 降低学习率到 1e-4 量级,增加时间嵌入维度 |
| 采样结果全是噪声 | 采样公式写错;时间步传入方向错误 | 检查反向循环是否从大到小逐时间步执行,核对 alpha_bar 索引 |
| 生成结果多样性差 | 训练数据太少或噪声调度范围不合适 | 增加数据量,尝试余弦噪声调度 |
| 图像生成显存不足 | 直接在像素空间做扩散,分辨率太高 | 改用潜在扩散模型,或降低批量大小、开启梯度检查点 |
| 视频生成出现闪烁和漂移 | 模型缺少时间注意力;上下文帧数不足 | 增加帧间注意力模块,扩大时序上下文窗口 |
| 文本条件不生效 | 条件特征没有正确注入网络;CFG 权重过低 | 检查 Cross-Attention 输入,调高 CFG Scale |
| 采样速度慢 | 推理步数过高 | 使用 DDIM 或 DPM++ 等加速采样器,降低到 20-30 步 |
7. 最佳实践与工程建议
7.1 技术选型与调参
实际项目中,不建议从零训练扩散模型,因为数据量和算力要求都非常高。更合理的路径是使用成熟的开源模型底座,再针对业务数据做微调:
- 图像生成优先考虑 Stable Diffusion 生态,社区资源多,插件丰富。
- 视频生成优先选择开源视频模型或商业 API,先验证效果再投入训练成本。
- 微调时不要一开始就全量训练,先尝试 LoRA 等参数高效微调方案。
调参方面,先固定噪声调度和网络结构,再单独调整学习率、批量大小和时间步。每改动一个变量,记录一组结果,避免多变量同时变动导致无法定位问题。
7.2 数据、评测与日志
数据质量直接决定生成质量。训练数据需要经过清洗、去重、版权审查。对于视频模型,还要额外关注帧率、时长分布和镜头切换频率。评测时建议同时使用定量指标(如 FID、CLIP Score)和人工主观评分,因为生成结果“是否好看”无法完全用数值衡量。
训练日志至少记录:损失、学习率、当前 epoch、GPU 利用率、显存峰值。分布式训练时要保存全局步数,方便中途恢复。
7.3 生产落地注意事项
- 资源规划:扩散模型推理比传统模型重,提前估算并发和显存需求,必要时使用模型并行和批处理。
- 安全合规:生成内容必须经过审核,避免生成虚假信息、侵权内容或不当内容。涉及真实人物肖像时需获得授权。
- 版本可控:模型权重、采样器、提示词模板需要版本化管理,方便回滚和复现。
- 成本控制:视频生成的成本是图像生成的数倍甚至数十倍,在生产环境中要考虑缓存高频生成结果,减少重复计算。
8. 下一步学什么
读到这里,你已经掌握了扩散模型的核心原理:加噪、去噪、噪声预测、条件控制,以及视频生成中的时空建模思路。下一步可以从三个方向深入:
- 动手跑一个开源图像生成项目:从 Stable Diffusion WebUI 或 ComfyUI 开始,亲手调提示词、切换采样器、对比 CFG Scale 差异,把本文讲到的参数在实际环境里体会一遍。
- 深入 LDM 结构:阅读 Stable Diffusion 的 UNet 和 VAE 代码,理解潜空间扩散、Cross-Attention 的具体实现。
- 尝试微调与训练:准备一个小型数据集,用 LoRA 或全量微调方式训练自己的图像风格模型,理解不同超参数对生成效果的影响。
扩散模型依然在快速演进。视频生成、多模态生成、实时生成是目前最活跃的方向,也意味着大量工程问题值得研究。对于初入 AI 生成领域的开发者来说,先把加噪-去噪这条主线吃透,再去看各种层出不穷的新架构,就不会被概念泡沫带偏。
如果这篇文章对你有帮助,可以收藏备用。下一篇“扩散模型(下)”,我们会继续拆解 Stable Diffusion 与视频生成工程落地的详细方案,欢迎保持关注,也可以把你在扩散模型实践中遇到的问题写在评论区一起讨论。