
之前在图像超分项目里尝试用扩散模型增强细节时最大的感受是纹理变丰富了但结果并不可控。模型会在原本平滑的区域脑补出不该存在的结构导致人眼看似清晰真实还原度却下滑。后来接触到 Uncertainty-Guided Latent Diffusion Models 这个方向才意识到“给扩散模型引入不确定性引导是缓解幻觉细节、提升重建忠实度的一条有效思路”。本文会从超分任务的核心难点讲起拆解 Uncertainty-Guided LDM 的方法逻辑再给出一个可运行的简化 PyTorch 示例覆盖环境准备、代码实现、训练验证、常见问题和工程建议。无论你是在做学术复现还是想把扩散超分落地到实际业务都值得往下看。1. 背景与核心概念1.1 超分辨率任务与“忠实”的细节超分辨率Super ResolutionSR的目标是给定一张低分辨率图像 LR恢复出对应的高分辨率图像 HR。传统退化过程可以抽象为LR (HR * k) ↓ s n其中k是模糊核↓ s是下采样操作n是噪声。超分模型本质上是在做这个退化过程的逆问题。但逆问题往往不是唯一解。同一张 LR 图像可以对应多张看起来合理的 HR 图像。比如一根头发丝在 LR 里可能只占 2-3 个像素放大后到底朝哪个方向延伸、弧度多大模型只能猜测。这就引出了超分领域非常核心的权衡保真度Fidelity重建结果是否在像素级接近真实 HR常用 PSNR、SSIM 衡量。感知质量Perception重建结果人眼看起来是否自然清晰常用 LPIPS、FID 衡量。传统 CNN 超分模型更偏向保真但结果容易过度平滑缺少真实纹理生成模型尤其是扩散模型能生成丰富细节却可能在“猜”的过程中产生幻觉导致重建内容与真实场景不符。所谓Faithful Super Resolution就是希望在保留生成模型细节表达能力的同时让生成结果尽可能忠实于真实内容不凭空虚构结构。这也是 Uncertainty-Guided Latent Diffusion Models 这一方向重点解决的问题。1.2 潜在扩散模型为什么适合超分扩散模型Diffusion Model通过逐步向数据添加噪声再训练网络学习逆向去噪过程从而学习到数据分布。基础流程可以理解成两个阶段正向过程给干净图像逐步加噪直到变成接近纯噪声。反向过程从纯噪声出发逐步去噪恢复出干净图像。DDPM 是扩散模型的基础框架。它的理论清晰生成质量高但直接在像素空间执行去噪非常耗时。潜在扩散模型Latent Diffusion ModelLDM做了关键改进先用 VAE 把图像压缩到低维潜在空间再在潜在空间上执行扩散过程。这样做的好处主要有两个计算量大幅下降训练和推理都更快。潜在空间的语义表达更紧凑模型更容易学习高层次结构和纹理规律。在超分任务中LDM 通常把 LR 图像作为条件输入引导模型在潜在空间生成对应的 HR 内容。这种方式能生成比传统 CNN 更真实的纹理但如果条件利用不充分模型就可能在细节上“自由发挥”。1.3 Uncertainty-Guided 要解决的核心问题Uncertainty-Guided LDM 的思路很直接模型应该知道哪些区域的信息是可靠的哪些区域是模糊不确定的然后根据可靠程度决定生成策略。图中边缘、纹理清晰的地方LR 提供的约束强扩散模型应该严格还原而平滑区域、严重退化区域LR 提供的约束弱模型需要小心生成避免编造无法由 LR 支撑的细节。不确定性引导通常表现为一个额外分支或额外条件网络会预测每个像素/区域的不确定性值。在训练时不确定性用于调整损失权重让模型更关注可靠区域。在采样时不确定性可以控制去噪强度或条件注入强度减少不可靠区域的自由度过高。本质上这是给扩散模型加了一种“自我认知”能力让生成过程更加可控。2. 方法思路不确定性引导扩散模型2.1 不确定性来源认知不确定性与偶然不确定性在深度学习中不确定性通常分为两类偶然不确定性Aleatoric Uncertainty由数据本身的内在随机性导致例如 LR 图像中模糊、噪声和无纹理区域无法通过增加训练数据消除。在超分任务中这类不确定性主要来源于退化过程的随机性和下采样造成的信息丢失。认知不确定性Epistemic Uncertainty由模型对未知区域的认知不足导致可以通过更多数据、更大模型来降低。在超分任务中当遇到训练分布之外的退化类型时模型会产生较高的认知不确定性。Uncertainty-Guided LDM 通常更关注偶然不确定性因为超分中的信息丢失是客观存在的模型要做的就是感知这种不可靠性并据此调整生成策略。不过在工程实现中也可以通过集成多个模型或 Dropout 等方式近似估计认知不确定性两者可以结合使用。2.2 不确定性图的构造方式不确定性可以用不同的方式表达常见设计包括1. 基于图像特征的经验估计对输入 LR 图像计算局部方差、梯度幅度或边缘密度得到一张空间分布的不确定性图。纹理稀疏、边缘模糊的区域不确定性高结构清晰的区域不确定性低。这种方法的优点是轻量、可解释适合工程落地。2. 基于网络预测的显式估计在去噪网络旁边增加一个不确定性预测头输入 LR 和当前噪声特征输出逐像素不确定性图。训练时使用不确定性加权损失让网络自行学习哪些区域容易出错。这也是“Uncertainty-Guided”更严格意义上的做法。3. 基于分布的隐式估计在采样过程中对同一输入执行多次采样计算多张输出之间的像素级方差。方差大说明模型对结果不确定可以作为不确定性图。优点是几乎不用改模型结构缺点是需要额外采样成本。2.3 不确定性在训练与采样中的注入方式得到不确定性图后如何把它用起来决定了整个方案的下限。常见注入方式包括1. 损失加权这是最直接的方式。在训练时对 MSE 或 L1 损失按不确定性进行加权loss mean( (pred_noise - noise)^2 * (1 α * uncertainty) )其中α控制不确定性影响的强度。可靠区域权重更高模型优先拟合这些区域不可靠区域权重更低避免模型在信息不足的区域强行过拟合。2. 条件输入把不确定性图作为额外的通道输入给去噪网络与 LR 条件拼接。这样模型在每个去噪步骤中都能感知到各区域的可信度从而调整注意力分配。3. 生成强度控制在采样阶段如果不确定性高可以适度降低噪声预测的置信度或者对条件特征做软门控。例如通过不确定性图对条件特征加权cond_fused lr_feature gate * (1 - uncertainty) * generative_feature这样低不确定性区域更多依赖 LR高不确定性区域才允许生成模型发挥。3. 环境准备与项目结构3.1 运行环境与依赖本文示例使用 PyTorch 实现核心依赖如下。版本需要根据实际环境调整关键思想不受版本影响。Python 3.8 PyTorch 2.0 torchvision Pillow numpy tqdm硬件方面训练完整的 LDM 需要较高显存建议至少 16GB。示例代码允许把图像裁剪为较小 patch 运行显存不够时可以降到64x64或使用混合精度训练。3.2 项目结构一个清晰的项目结构能降低调试成本。本文示例目录如下uncertainty_guided_sr/ ├── config.py # 训练和采样参数 ├── diffusion.py # 噪声调度、加噪、采样 ├── uncertainty.py # 不确定性估计模块 ├── model.py # 条件去噪网络 ├── dataset.py # 数据集加载与退化 ├── train.py # 训练脚本 ├── infer.py # 推理脚本 └── sample_images/ # 推理输出目录为便于阅读下面会把关键代码拆开讲解并在代码前标注文件路径。3.3 数据准备与退化设置超分模型训练需要成对的 HR-LR 数据。如果使用通用数据集读取 HR 图像后可以动态生成 LR对 HR 图像做 4 倍双三次下采样得到 LR。再把 LR 上采样回 HR 尺寸作为条件输入。按需添加高斯噪声或模糊模拟真实退化。这样做的好处是训练数据可以无限扩充并且退化方式可控。4. 核心代码拆解4.1 噪声调度与前向加噪扩散模型的第一步是定义加噪过程。这里使用经典的线性 beta 调度。# 文件路径diffusion.py import torch def linear_beta_schedule(timesteps, beta_start1e-4, beta_end0.02): return torch.linspace(beta_start, beta_end, timesteps) timesteps 1000 betas linear_beta_schedule(timesteps) alphas 1.0 - betas alphas_cumprod torch.cumprod(alphas, dim0) sqrt_alphas_cumprod torch.sqrt(alphas_cumprod) sqrt_one_minus_alphas_cumprod torch.sqrt(1.0 - alphas_cumprod)前向加噪过程定义为def q_sample(x_start, t, noiseNone): # x_start: [B, C, H, W] # t: [B] if noise is None: noise torch.randn_like(x_start) sqrt_alpha_bar sqrt_alphas_cumprod[t].view(-1, 1, 1, 1) sqrt_one_minus sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1) return sqrt_alpha_bar * x_start sqrt_one_minus * noiset是每个样本对应的噪声步数索引t越大图像中保留的原始信息越少。模型要做的就是根据带噪图像、步数和条件预测出noise。这种写法是 DDPM 的常规前向流程也是后面训练数据准备的基础。4.2 不确定性估计模块先用轻量的局部统计量估计不确定性。这里以局部方差作为不确定性代理局部方差越小代表该区域越平滑信息可靠性越低。# 文件路径uncertainty.py import torch import torch.nn.functional as F def estimate_uncertainty(img): 输入: img [B, C, H, W]值范围约 [0, 1] 输出: unc [B, 1, H, W]范围约 [0, 1] # 转为灰度图减少通道计算 gray img.mean(dim1, keepdimTrue) # [B, 1, H, W] # 3x3 局部均值 kernel torch.ones(1, 1, 3, 3, deviceimg.device) / 9.0 mean F.conv2d(gray, kernel, padding1) mean_sq F.conv2d(gray ** 2, kernel, padding1) # 局部方差 var torch.clamp(mean_sq - mean ** 2, min0) # 归一化到 0~1 max_val var.max(dim2, keepdimTrue)[0].max(dim3, keepdimTrue)[0] unc var / (max_val 1e-5) return unc在实际论文实现中不确定性分支通常是从网络特征中学习出来的效果会更准。这里用局部方差近似主要目的是把引导机制跑通。你可以把它看成不确定性分支的替代品。4.3 条件去噪模型为了让模型能感知不确定性我在去噪网络中增加了一个不确定性卷积分支并把时间步嵌入加到隐藏层。# 文件路径model.py import torch import torch.nn as nn import torch.nn.functional as F def timestep_embedding(t, dim, max_period10000): half dim // 2 freqs torch.exp( -torch.log(torch.tensor(max_period, dtypetorch.float32, devicet.device)) * torch.arange(half, dtypetorch.float32, devicet.device) / half ) args t[:, None].float() * freqs[None, :] return torch.cat([torch.cos(args), torch.sin(args)], dim-1) class SimpleCondUNet(nn.Module): def __init__(self, in_ch7, hidden_ch64, time_dim128): super().__init__() self.time_dim time_dim self.time_embed nn.Sequential( nn.Linear(time_dim, time_dim * 2), nn.SiLU(), nn.Linear(time_dim * 2, time_dim * 2), nn.SiLU(), ) self.time_proj nn.Linear(time_dim * 2, hidden_ch) self.unc_conv nn.Conv2d(1, hidden_ch, 3, padding1) self.conv1 nn.Conv2d(in_ch, hidden_ch, 3, padding1) self.conv2 nn.Conv2d(hidden_ch, hidden_ch, 3, padding1) self.conv3 nn.Conv2d(hidden_ch, hidden_ch, 3, padding1) self.out nn.Conv2d(hidden_ch, 3, 3, padding1) def forward(self, x_noisy, t, lr_cond, unc_map): # x_noisy: [B, 3, H, W] # lr_cond: [B, 3, H, W] # unc_map: [B, 1, H, W] # 时间步嵌入 emb timestep_embedding(t, self.time_dim) emb self.time_embed(emb) emb self.time_proj(emb)[:, :, None, None] # [B, hidden, 1, 1] # 不确定性条件特征 unc_feat self.unc_conv(unc_map) # [B, hidden, H, W] # 拼接输入噪声图 LR 不确定性图 h torch.cat([x_noisy, lr_cond, unc_map], dim1) h F.silu(self.conv1(h)) h h emb unc_feat h F.silu(self.conv2(h)) h F.silu(self.conv3(h)) return self.out(h)这个模型是简化版用于演示核心机制。真实 LDM 中的 UNet 会有下采样、上采样、Attention 和 skip connection如果你复现论文需要把这里的SimpleCondUNet替换成完整的 UNet 或使用开源实现。输入通道为 7分别对应3 通道带噪 HR 图像3 通道上采样后的 LR 条件1 通道不确定性图时间步嵌入让网络知道当前去噪阶段不确定性分支让网络知道每块区域的可靠度。4.4 不确定性加权损失训练损失不能只做简单的像素级 MSE。我们希望模型优先拟合信任区域弱化不可靠区域的影响。def uncertainty_weighted_loss(pred_noise, noise, unc_map, alpha1.0): # pred_noise: [B, 3, H, W] # noise: [B, 3, H, W] # unc_map: [B, 1, H, W] mse F.mse_loss(pred_noise, noise, reductionnone) # [B, 3, H, W] mse mse.mean(dim1) # 转为逐像素损失 [B, H, W] # 高不确定性区域权重更低 weight 1.0 alpha * (1.0 - unc_map.squeeze(1)) # [B, H, W] loss (mse * weight).mean() return loss这个设计表达了一个直觉LR 中结构清晰的区域模型预测的噪声应该被严格监督而模糊、不确定的区域模型“猜错”的代价降低避免强迫模型把不可靠信息当成确定信息来学。4.5 采样推理训练完成后从一个随机噪声图出发逐步去噪得到 SR 结果。采样时同样把 LR 条件和不确定性图送入模型。torch.no_grad() def sample(model, lr_up, unc_map, device, timesteps1000): model.eval() b lr_up.size(0) x torch.randn_like(lr_up).to(device) for i in reversed(range(timesteps)): t torch.full((b,), i, devicedevice, dtypetorch.long) pred_noise model(x, t, lr_up, unc_map) alpha alphas[i] alpha_bar alphas_cumprod[i] sqrt_one_minus sqrt_one_minus_alphas_cumprod[i] x (1.0 / torch.sqrt(alpha)) * ( x - (1.0 - alpha) / sqrt_one_minus * pred_noise ) if i 0: beta betas[i] x x torch.sqrt(beta) * torch.randn_like(x) return x采样时每一步都参考同一张不确定性图所以在结构清晰区域模型受 LR 条件的约束更稳定在不确定区域模型能通过生成补充细节但不会无限发挥。5. 训练与验证完整流程5.1 数据集加载下面是一个简单的 SR 数据集实现。它读取 HR 图片随机裁剪 patch然后动态生成 LR。# 文件路径dataset.py import os import torch from torch.utils.data import Dataset from torchvision import transforms from PIL import Image class SRDataset(Dataset): def __init__(self, root, patch_size128): self.root root self.paths [ os.path.join(root, f) for f in os.listdir(root) if f.lower().endswith((.png, .jpg, .jpeg)) ] self.patch_size patch_size self.to_tensor transforms.ToTensor() def __len__(self): return len(self.paths) def __getitem__(self, idx): img Image.open(self.paths[idx]).convert(RGB) # 随机裁剪 w, h img.size if w self.patch_size and h self.patch_size: i torch.randint(0, h - self.patch_size 1, ()) j torch.randint(0, w - self.patch_size 1, ()) img img.crop((j, i, j self.patch_size, i self.patch_size)) else: img img.resize((self.patch_size, self.patch_size), Image.BICUBIC) hr self.to_tensor(img) # [3, H, W] return {hr: hr}使用时要保证图片路径存在。真实项目可以换成自定义列表读取 txt/csv 中的路径。5.2 训练循环训练脚本整合了前向加噪、不确定性估计、模型前向和损失计算。# 文件路径train.py import torch import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision import transforms from diffusion import q_sample, betas, alphas_cumprod from uncertainty import estimate_uncertainty from model import SimpleCondUNet from dataset import SRDataset device cuda if torch.cuda.is_available() else cpu timesteps 1000 epochs 20 batch_size 8 lr 1e-4 patch_size 128 model SimpleCondUNet().to(device) optimizer torch.optim.AdamW(model.parameters(), lrlr) dataset SRDataset(root./data/train, patch_sizepatch_size) dataloader DataLoader(dataset, batch_sizebatch_size, shuffleTrue, num_workers4) model.train() for epoch in range(epochs): total_loss 0.0 for batch in dataloader: hr batch[hr].to(device) # [B, 3, H, W] # 生成 LR 并上采样回 HR 尺寸 lr F.interpolate(hr, scale_factor0.25, modebicubic, align_cornersFalse) lr_up F.interpolate(lr, size(hr.size(2), hr.size(3)), modebicubic, align_cornersFalse) # 估计不确定性 unc estimate_uncertainty(lr_up) # 随机时间步 t torch.randint(0, timesteps, (hr.size(0),), devicedevice).long() noise torch.randn_like(hr) noisy_hr q_sample(hr, t, noise) pred_noise model(noisy_hr, t, lr_up, unc) loss F.mse_loss(pred_noise, noise, reductionnone) loss loss.mean(dim1) # [B, H, W] weight 1.0 1.0 * (1.0 - unc.squeeze(1)) loss (loss * weight).mean() optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch 1}/{epochs} - loss: {total_loss / len(dataloader):.6f})如果显存不足可以降低patch_size或batch_size。如果模型收敛慢可以检查 lr 是否合适以及不确定性图是否被过度归一化。5.3 推理脚本推理脚本加载 HR 图片生成 LR再调用采样函数得到 SR 结果和真实 HR 对比。# 文件路径infer.py import torch import torch.nn.functional as F from torchvision import transforms from PIL import Image from diffusion import betas, alphas, alphas_cumprod, sqrt_one_minus_alphas_cumprod from uncertainty import estimate_uncertainty from model import SimpleCondUNet device cuda if torch.cuda.is_available() else cpu timesteps 1000 model SimpleCondUNet().to(device) model.load_state_dict(torch.load(model.pth, map_locationdevice)) to_tensor transforms.ToTensor() to_pil transforms.ToPILImage() img Image.open(./test.png).convert(RGB) hr to_tensor(img).unsqueeze(0).to(device) lr F.interpolate(hr, scale_factor0.25, modebicubic, align_cornersFalse) lr_up F.interpolate(lr, size(hr.size(2), hr.size(3)), modebicubic, align_cornersFalse) unc estimate_uncertainty(lr_up) sr sample(model, lr_up, unc, device, timesteps) sr torch.clamp(sr, 0, 1) to_pil(sr.squeeze(0)).save(./sample_images/result.png)运行后可以肉眼对比result.png与真实 HR 之间的差异。也可以继续计算 PSNR、SSIM、LPIPS 等指标。6. 常见问题与排查思路扩散模型训练本身就有不少坑加入不确定性引导后又会多出一些新问题。下面整理几个高频场景问题现象常见原因解决思路训练 loss 不下降学习率过大或过小不确定性图归一化异常调小学习率打印不确定性图的数值分布确认范围在 0~1生成结果过于模糊条件注入太强模型只依赖 LR去噪步数不足降低条件权重增加步数检查不确定性加权是否过度压制生成生成结果出现伪细节不确定性引导失效模型在平滑区域过度发挥提高不可靠区域损失压制强度或改用网络预测的不确定性分支采样结果噪声明显推理时未加上噪声调度或最后一步处理错误检查sample函数中beta是否使用正确最后一轮不要加噪声GPU 显存不足patch 太大或 batch 太大降低patch_size到 64开启 AMP 混合精度不确定性图变成全 0局部方差计算后归一化除零检查var.max()添加1e-5防止除零结果与 LR 内容不对齐条件信息不足或模型没有多尺度结构使用真实 UNet Attention增加 LR 多尺度特征拼接排查时建议先做最小实验固定 schedule去掉不确定性加权跑 1000 步确认基础流程能收敛再加入不确定性分支做对比。7. 最佳实践与工程建议7.1 数据与退化设置训练扩散超分模型的退化设置非常关键。如果只用双三次下采样模型面对真实退化时会退化明显。建议采用随机退化组合随机模糊核高斯、运动模糊随机噪声高斯噪声、JPEG 压缩噪声随机下采样倍率退化类型越接近线上数据不确定性估计越有实际意义模型的忠实度也越高。7.2 训练稳定性扩散模型的训练稳定性与噪声调度、批大小、学习率强相关。经验上建议使用AdamW初始学习率从1e-4到3e-4配合梯度裁剪max_grad_norm1.0。如果显存允许尽量增大 batch size如果 batch 受限可以降低 patch size但不要低于 64。否则批量归一化和卷积的感受野都会受到明显影响。7.3 评估指标超分模型不能只看 PSNR。对 Uncertainty-Guided LDM 这类生成式模型建议同时关注PSNR、SSIM衡量保真度。LPIPS衡量感知相似度。FID衡量生成分布与真实分布的接近程度。忠实度专项指标例如边缘误差、结构相似度、区域级一致性。更严格的做法是人工评测重点看平坦区域是否出现伪造纹理、边缘是否清晰且贴合 LR 结构。7.4 部署与加速扩散模型推理速度慢是落地的主要瓶颈。可以从几个方向优化使用预训练 VAE 在潜在空间操作减少计算量。采用 DDIM 采样或 DPM-Solver减少推理步数。蒸馏为一步或少步模型例如 LCM、Turbo 等少步采样方案。使用 TensorRT 或 ONNX Runtime 部署配合 FP16。需要注意的是减少采样步数会对生成忠实度产生影响部署前必须做充分验证尤其是高不确定区域的细节表现。8. 下一步学习路线如果你看完本文后想继续深入 Uncertainty-Guided LDM 这个方向建议按下面的顺序推进先跑通本文的简化版代码理解 DDPM 加噪、去噪、采样全过程。阅读 DDPM / DDIM 原论文搞清楚每一步的公式推导。用开源 Stable Diffusion 的 VAE 和 UNet 替换简化模型把像素空间训练改成潜在空间训练。在潜在空间加入不确定性分支对比有/无不确定性引导的生成效果。在真实业务数据上测试模糊、压缩、混合退化场景统计不同区域的不确定性与重建误差关系。尝试用不确定性图控制采样的 guidance scale探索更细粒度的生成控制策略。做扩散超分落地时最需要警惕的就是“看起来清晰但内容不可靠”的假象。把不确定性引导加进去之后模型并不是变得更聪明而是变得更懂得“说不知道”。这种能力恰恰是忠实超分最稀缺的。