你有没有刷到过这样的帖子:“Midjourney生成的图片也太逼真了吧!AI是不是已经可以取代设计师了?”要是完全不懂背后的技术,听完只能感慨一句“跪了”。
别担心,这次我们就来彻底搞懂“扩散模型”到底是怎么回事。这是现今火遍全网的Stable Diffusion、Midjourney、DALL·E这些AI绘画软件背后的核心技术。你可能会觉得它高深莫测,但一旦理解了,你会发现它绝对是迄今为止最优雅的AI模型之一。
一句话就能让你抓住精髓:扩散模型的核心非常简单:“先把一张好图一步一步‘糟蹋’成随机雪花点,然后让AI学会把这个过程反过来,‘一点一点’地把图片恢复原样。”
是不是听起来有点像魔法?那我们具体看看它是怎么做到的(文章结尾附完整代码下载)。
1. 扩散 = 一滴墨水 + 倒放录像
你可能不知道,AI绘画的发明灵感,其实来自我们日常生活中再常见不过的现象。
回想一下,你把一滴墨水滴进一杯清水里时发生了什么:墨水刚开始聚成一个小黑点,接着它开始在水中慢慢地散开,变成云雾一样的一团,最终墨水的分子均匀地溶进水里,整杯水都变成了均匀的淡蓝色。
水从“清澈透明”走向“均匀浑浊”的这个过程,在物理学里就叫 “扩散”——秩序走向混沌,清晰的画面被“溶解”了。
扩散模型(Diffusion Models, DM)就是把整个过程搬到电脑里。那AI学会了什么呢?它要学的东西简直太酷了:它要学的是 “倒放” 这个扩散的过程。也就是从一杯已经变成淡蓝色的墨水开始,一步一步地“长”回原来那滴刚刚滴进水里的墨水状态。这几乎不可能,但AI做到了。
Stable Diffusion、DALL·E、Midjourney,所有让全世界惊叹的AI绘画软件,本质上都在做同一件事:从一坨毫无意义的随机“雪花点”噪声里,一点一点地“吸走”噪声,使雪花点慢慢聚拢、成形,最后还原出一张清晰的图片。
这个“糟蹋图片”的过程被称为 “前向扩散” ,而AI学习的那个“倒放”过程,被称为 “反向扩散” 。接下来,我们深入拆解一下这两个步骤。
2. 正向扩散:怎样“毁掉”一张好照片
前向扩散,也就是“糟蹋照片”的过程。其实非常简单粗暴:不停地往照片上撒“雪花点”(专业术语叫高斯噪声)。
2.1 第一步:从高斯分布里“抓一把噪声”
你没必要害怕“高斯分布”这个词。你只要知道,它就是一张“均匀的雪花点”。模型根据一个叫 方差调度计划 的表格,来决定现在该撒多大的雪花。
一开始:用很小的“雪花点”,这样图片只是变模糊一点点而已。
越往后:敢放心地用越来越大的“雪花点”,反正图像已经看不清了。
这两个固定的beta值就是在控制雪花的大小。你看下面这段代码,第一步和最后一步的噪声方差相差了整整200倍:
import torch
T = 1000 # 总时间步数,经典论文设置1000步!
beta_start = 0.0001 # 刚开始,我加点很小很小的噪声,温柔地“糟蹋”
beta_end = 0.02 # 到第1000步,放大招!加更猛的噪声
# 下面这一行简单到哭的平均分配,就是“线性方差调度计划”
betas = torch.linspace(beta_start, beta_end, T)
print(f"第1步的噪声方差: {betas[0]:.6f}") # 输出: 0.000100
print(f"第500步的噪声方差: {betas[500]:.6f}") # 输出: 0.010055
print(f"第1000步的噪声方差: {betas[999]:.6f}")# 输出: 0.020000
2.2 第二步:一步到位的小技巧
你可能在想,想得到第500步的图像,难道真要从第1步加到第500步吗?那多慢啊。
数学家们早就替你想好了,根本不需要这样!他们发现,因为每次加的噪声都是标准的,所以可以用一个一步到位的公式,直接从原始干净图x₀算出第t步的样子x_t:
x_t = sqrt(ᾱ_t) × x₀ + sqrt(1 - ᾱ_t) × ε
这个公式什么意思? 简单说:这张半成品图片x_t = 保留一部分的原始图像信息 + 混入一定量的噪声。
如果你看不懂希腊字母,没关系,你只需要知道:ᾱ_t是一个系数,它随着t增大而减小。这意味着——
-
t很小(刚开始)
:ᾱ_t≈1,公式前半部分≈1×x₀(原始图),后半部分≈0×ε(几乎没有噪声)。所以早期的x_t跟原图非常像,只是稍微加了一点点噪声。
-
t很大(快结束)
:ᾱ_t≈0,公式前半部分≈0×x₀(原图几乎没了),后半部分≈1×ε(全是噪声)。所以后期的x_t基本就是纯粹的雪花点了。
用代码实现就会一目了然:
# 提前算好接下来要用的alfa和alfa_bar
alphas = 1 - betas # 这是一个长度为T的数组
alpha_bars = torch.cumprod(alphas, dim=0) # 累乘,核心!ᾱ_t = α₁×α₂×...×α_t
def add_noise(x_0, t):
"""
正向加噪:一步到位,直接从原图x_0算加噪后的x_t
x_0: 原始干净图像,形状 [batch, channels, height, width]
t: 时间步,例如[3, 5, 10, ...],形状 [batch]
返回: x_t (带噪图像), noise (所加的真实噪声)
"""
# 根据t抓取对应ᾱ_t
t_idx = t - 1
alpha_bar = alpha_bars[t_idx].reshape(-1, 1, 1, 1) # 调整形状用于广播
# 从标准正态分布抓一把随机雪花
noise = torch.randn_like(x_0)
# ✨奇迹发生的地方!一步到位闭式公式✨
x_t = torch.sqrt(alpha_bar) * x_0 + torch.sqrt(1 - alpha_bar) * noise
return x_t, noise
现在你是不是已经觉得懂了一半:前向扩散就是个懒人攻略,只要一张原始图和选的噪声方差,轻松破坏一张图,几行代码搞定。
3. 反向扩散:教会AI当“修复大师”
正向破坏容易,逆向修复难。
真正的难点来了:给你x_t,你要怎么还原出x_{t-1}?
你面对的是一幅模糊不清的图像,有无数种可能的上一步状态。这就像你在看一部电影的倒放,要从零散的混乱帧里想象出前一帧的样子。x_t里既有原来图像的信息,也有噪声,而AI的任务就是精准地把噪声部分剔除出去,同时保留图像内容。
3.1 换个思路:不猜原图,只猜噪声
直接让AI预测上一步长什么样太难了。但是注意到我们使用的闭式公式:
x_t = √ᾱ_t × x₀ + √(1−ᾱ_t) × ε
如果你能把噪声ε从x_t里减掉,那你不就知道x₀了嘛!
这就是DDPM史上最聪明的决定:让神经网络学预测噪声ε,而不是直接学怎么画图。 只要预测出ε,代进公式就可以算出x_{t-1}。
3.2 网络结构U-Net:扩散模型的“大脑”
那预测噪声这事,总不能拍脑袋瞎猜吧?谁来做这个“大脑”呢?答案是最适用扩散模型的架构——U-Net。
它的U型结构专门为“把一个模糊的东西变得清晰”而生。
编码器“下采样”:你想象一下,AI第一次看到一张噪声图,它先“退后几步”,整体扫描一遍。这一步是为了把握图片的全局结构,看看大概是个什么东西——猫?狗?还是人脸?
解码器“上采样”:搞懂了大概轮廓后,AI就“走近几步”,开始关注局部细节——这里是猫耳朵,那里是胡须尖。
跳跃连接:这就是U-Net最神奇的设计!在缩小(编码)的各个阶段,U-Net会偷偷“copy”一份当时的特征图(比如带着耳朵、眼睛位置的粗略信息)。在扩大(解码)的阶段,它会把这些旧信息“抄”回来和当前新信息拼在一起。这就好比修图师手里同时有模糊参考和清晰素描,细节不易丢。
为了让你看到U-Net是怎样一步步把噪声图片“修”干净的,下面用一个简化的PyTorch实现来演示完整的反向去噪过程:
def denoise_step(model, x, t):
"""
反向去噪干一件事:从x_t算出更干净的x_{t-1}
model: 训练好的U-Net神经网络
x: 当前的带噪声图像
t: 当前时间步(例如[500,500,...])
"""
# 拿到当前这一步的alpha_t和alpha_bar_t
idx = t - 1
alpha = alphas[idx].reshape(-1, 1, 1, 1)
alpha_bar = alpha_bars[idx].reshape(-1, 1, 1, 1)
# 关键一步:让U-Net预测当前噪声εθ
with torch.no_grad():
eps_pred = model(x, t) # 看!这就是U-Net的魔力!它猜出来噪声长啥样!
# 套公式算出均值mu(最有可能的干净图像)
mu = (x - ((1 - alpha) / torch.sqrt(1 - alpha_bar)) * eps_pred) / torch.sqrt(alpha)
# 除了最后一步之外,再加一点小小的随机扰动,保持多样性
if t > 1:
noise = torch.randn_like(x)
sigma = torch.sqrt(betas[idx])
return mu + sigma * noise
return mu
4. 时间步编码:告诉模型“这是第几步”
前面的代码里有一个细节:U-Net不仅接受噪声图片x_t,还接受时间步t作为输入。
思考:为什么必须告诉模型现在是第几步?
当
t
接近1000时,你的图片几乎都是噪声,没什么内容可挖。这时模型应该着重去猜图像的“整体骨架”——它是猫还是狗?它该朝哪个方向?
当
t
接近0时,图片的轮廓已经很清晰了,只是一些微小噪点还残留在画面上。这时模型应该关注的不是“猫的轮廓”,而是细节“消噪”——把毛发的颗粒感修掉。
如果模型不知道t,它就不清楚自己当前是该“勾勒大局”还是“精修细节”,最终效果会一塌糊涂。为了让模型能理解这一点,AI用了一种巧妙的 “时间步编码”(正弦位置编码) 技术(灵感来自Transformer)。它把整数t变成一个长长的向量,向量中不同维度的值按照不同频率振荡,类似于给AI一个“进度条”,告诉它现在是去噪的哪个阶段。
时间步编码的代码如下。其中最关键的是第20-21行:将MLP映射后的时间编码通过“广播加法”加到每个像素点上,让模型在每个空间位置都能感知当前的时间阶段:
def pos_encoding(timesteps, output_dim, device):
"""
将离散的时间步t[例如:1,2...]转译成一段连续的向量,供神经网络咀嚼消化
"""
batch_size = len(timesteps)
v = torch.zeros(batch_size, output_dim, device=device)
for i in range(batch_size):
t = timesteps[i]
D = output_dim
vec = torch.zeros(D, device=device)
positions = torch.arange(0, D, device=device)
freqs = 10000 ** (positions / D) # 不同维度对应不同频率
vec[0::2] = torch.sin(t / freqs[0::2]) # 偶数位置用sin
vec[1::2] = torch.cos(t / freqs[1::2]) # 奇数位置用cos
v[i] = vec
return v
class TimeAwareConvBlock(nn.Module):
...
def forward(self, x, v):
N, C, _, _ = x.shape
v = self.mlp(v) # 形状 [N, C]
v = v.view(N, C, 1, 1) # 变形成N, C, 1, 1,用于广播
return self.convs(x + v) # 将时间步编码以加法融入特征图
最终,时间编码融入U-Net的每一个卷积层,让模型在每一步都知道自己正处在去噪流程的哪个阶段,从而采取最合适的“修复策略”。
5. 训练目标:不再需要复杂的数学,只看MSE
把前向和反向搭建起来之后,最关键的问题来了。
怎么训练U-Net?让目标简单到极致——只用“猜噪声”
因为你已经知道每一步加的真实噪声是啥(从正态分布里抓的),而U-Net预测的是εθ(xt, t),所以训练目标就简化为最小化预测噪声和真实噪声的差异,这称为MSE损失。
# 训练循环的核心!在每个batch:
x0 = images.to(device) # 从数据集取出一张原图
t = torch.randint(1, 1001, ...) # 随机抽一个时间步
x_t, noise = diffuser.add_noise(x0, t) # 前向加噪,得到噪声和带噪图
noise_pred = unet(x_t, t) # U-Net猜测噪声值
loss = F.mse_loss(noise_pred, noise) # 计算预测噪声与真实噪声的差距
经过成千上万次迭代训练,U-Net会逐渐成为一个“噪声专家”——给它任何x_t和t,它都能精准地预测出当初加进去的噪声长什么样。
6. 从推理到生成:千步蜕变成画
训练完成之后,从随机噪声生成新图片的过程就是“倒放”:
-
生成一个纯噪声样本x_T。
-
for t = T down to 1
:
-
让U-Net预测噪声εθ。
-
用公式x_{t-1} = (x_t - 一个东西) / sqrt(α_t) + σ_t*z得到一个更干净的图片。
-
-
最后输出那张清晰的x_0。
7. 潜在扩散模型Stable Diffusion:让画质飞跃的升级版
你可能有疑问:上面跑MNIST可以,但换成高清大图(1024×1024),计算量得有多大?
这正是潜在扩散模型的创意所在!它不在原始的几百万像素点里加噪声,而是先让一个叫“VAE”(变分自编码器)的模型把图片压缩成一个超小图的“核心摘要”(潜在空间),然后在那个超小尺寸的空间里做加噪和去噪,最后再把摘要解码回原始尺寸。
效果:原本要用200亿次计算变成只要几亿次,快得不是一点点!
8. 扩散模型正在重塑AI创作的版图
现在,扩散模型已经不是实验室的玩具,而是各行各业的生产力工具:
-
DALL·E
系列是扩张模型的代表作。OpenAI已经在逐步推动未来替代模型;
-
Midjourney v7
仍然是很多人心目中的“美学之王”;
-
Stable Diffusion 3
等开源模型实现了消费级GPU高分辨率生成;
-
Seedance 2.0
走向视频生成,用扩散变压器架构,实现音频+视频的同时生成;
-
音频合成
、3D生成、科研等领域也在大规模采用扩散模型。
9. 从理论到实践:工程总结
从原理到代码,我们走完扩散模型的完整旅程,这里是几个最值得记住的要点:
-
反向扩散的核心是预测“噪声”
,而不是预测原图。
-
闭式采样
让正向加噪一步到位,训练飞速。
-
U-Net + 时间步编码 → 模型能感知去噪到了哪个阶段
。
-
MSE损失
稳定可靠。
-
潜在扩散
把计算量从像素层推向潜在空间,加快生成速度。
-
训练和推理结合**DDIM、DPM++**等加速采样可以数倍到数十倍提速。
扩散模型极富诗意的设计方案(破坏→学习修复)和惊人的实际效果,给了全球AI开发者新的启发。从今天开始,你也可以把笔墨挥洒出来,设计和训练属于你自己的AI绘画模型了!
完整代码下载:pan.baidu.com/s/1E09Vyocq…