
当前在做图像超分辨率Super Resolution项目的同学大概率会遇到一个共同瓶颈传统 CNN 超分模型比如 ESRGAN、SwinIR恢复出来的纹理不够自然换到 Diffusion 系列模型之后细节确实丰富了但模型又会“脑补”出原图根本没有的结构——文字变错、人脸特征漂移、高频纹理乱生成。这类结果看似清晰却并不“忠实”Faithful。本文围绕 Uncertainty-Guided Latent Diffusion Models for Faithful Super Resolution 这条技术路线拆解它解决什么问题、整体框架怎么设计、不确定性图如何参与训练和采样并给出一套可落地的 PyTorch 实现思路。适合正在做图像生成、超分方向复现实验的研究生也适合想在自己业务里引入扩散模型做画质增强的工程同学。读完之后你能掌握超分辨率任务中“忠实性”为什么重要Latent Diffusion 为什么适合做生成式超分不确定性Uncertainty如何在训练和推理阶段引导生成一套包含数据准备、不确定性模块、扩散训练损失、采样推断的代码骨架高频踩坑点和工程实践建议。1. 背景超分问题与扩散模型的“忠实性”挑战1.1 超分辨率任务到底在解决什么图像超分辨率的目标很直接给定一张低分辨率图像 LR恢复出与之对应的高分辨率图像 HR。从数学角度看这是一个典型的病态逆问题Ill-posed Inverse Problem。因为降质过程会丢失大量高频信息同一个 LR 输入可以对应无数个合理的 HR 输出。传统插值方法如双三次插值只能做平滑补偿细节恢复能力弱基于 CNN 的方法如 SRCNN、EDSR、SwinIR通过回归损失逼近真实 HR稳定但容易出现过度平滑基于 GAN 的方法如 SRGAN、ESRGAN引入了对抗损失纹理更锐利可是偶尔会产生不自然的伪影。扩散模型的出现让超分任务有了新选择它把“从噪声到图像”的生成过程建模成一连串去噪步骤理论上能生成比 GAN 更丰富的细节分布而且采样过程可控。1.2 扩散模型与 Latent Diffusion 的关系扩散模型Diffusion Model的核心思想是两阶段前向过程逐步向图像加入高斯噪声直到图像变成纯噪声反向过程则训练一个网络学会从带噪声的数据中一步步预测并去除噪声从而还原图像。直接在高分辨率像素空间跑扩散模型非常消耗显存和算力。Latent Diffusion ModelLDM做了一个关键改动先用一个预训练的自编码器VAE把图像压缩到低维潜在空间然后在潜在空间里执行扩散过程。这样既保留了生成质量又大幅降低了计算量。Stable Diffusion 系列就是 LDM 的典型代表。在超分场景里LDM 的使用方式是把 LR 图像作为条件输入让去噪网络通常是 U-Net在“参考 LR 信息”的前提下还原 HR 的潜在表示最后通过 VAE 解码得到高分辨率图像。这种条件生成范式对应的就是题目里的 Latent Diffusion Models for Super Resolution。1.3 什么是“忠实”的超分结果评估超分结果时我们通常看 PSNR 或 LPIPS。PSNR 高意味着像素误差小LPIPS 低意味着感知质量好。但这两个指标都不足以描述“忠实性”。所谓 Faithful重点在于生成结果应该忠于输入证据对 HR 做相同降质后应当能还原回 LR在 LR 没有明确信息的位置模型不应该自信地生成错误结构文字、人脸、商品 logo 这类语义敏感区域不能出现“看起来合理但其实不对”的细节。Diffusion 模型因为是生成模型分布先验很强容易在细节不足的区域“自由发挥”。这在艺术创作类任务里是优点但在医疗影像、遥感图像、监控截图还原等场景里就是严重问题。Uncertainty-Guided 的思路就是希望模型在“有把握”的地方大胆生成细节在“没把握”的地方保持保守和忠实。1.4 为什么要在超分里引入不确定性一张自然图像里不同区域的恢复难度差异很大平坦天空只需要平滑插值重复纹理可以用先验补全而密集文字、人脸器官、复杂边缘则高度依赖输入像素信息。用一个固定强度的生成策略处理所有区域显然不合理。不确定性估计Uncertainty Estimation能告诉模型当前这个像素位置我对预测结果的把握有多大。在超分任务中不确定性图通常与图像内容强相关边缘和高频纹理区域不确定性较高平坦区域不确定性较低被噪声或压缩破坏严重的区域不确定性也会升高。有了不确定性图训练时可以做损失加权采样时可以做区域自适应的引导最终实现在细节丰富度和忠实度之间取得平衡。这正是题目中 Uncertainty-Guided 的核心价值。2. Uncertainty-Guided LDM 方法框架拆解2.1 整体架构从 LR 到 HR 的潜在空间生成这类方法通常由四个核心模块组成VAE 编码器冻结把 LR 和 HR 图像编码到潜在空间去噪 U-Net在潜在空间执行条件去噪LR latent 作为条件不确定性估计模块从 LR latent 预测逐像素不确定性图VAE 解码器冻结把去噪后的 latent 解码回 HR 图像。整体生成流程可以简化为下面的步骤对 HR 图像做传统降质双三次下采样、模糊加噪声等得到 LR用 VAE 编码器得到 lr_latent 和 hr_latent不确定性模块根据 lr_latent 预测 mean 和 log_var扩散模型以 lr_latent 为条件从噪声逐步去噪得到 hr_latent 的预测解码 hr_latent 得到超分结果在训练或者推理阶段用不确定性图调整损失权重或采样方向保证忠实性。这里要特别注意两个 latent 的尺度对齐。常规 VAE 会把图像下采样 8 倍所以一个 256×256 的 LR 输入在 latent 空间里实际是 32×32假设以 256 为基准。不确定性图和扩散模型的预测都作用在这个分辨率上而不是直接作用在像素空间。2.2 不确定性从哪里来Aleatoric Uncertainty 估计深度学习里的不确定性通常分为两类Aleatoric Uncertainty偶然不确定性数据本身带来的噪声和歧义无法通过增加数据消除Epistemic Uncertainty认知不确定性模型对未见数据的认知不足可以通过更多训练数据缓解。在超分任务中LR 到 HR 的映射本身存在固有歧义所以更常用 Aleatoric Uncertainty。实现上做法是让模型输出一个高斯分布的均值和方差而不是直接回归一个确定值。训练时使用负对数似然NLL损失[ \mathcal{L}_{NLL} \frac{1}{2}\left( \log \hat{\sigma}^2 \frac{(y - \hat{\mu})^2}{\hat{\sigma}^2} \right) ]其中 (\hat{\mu}) 是模型预测的均值(\hat{\sigma}^2) 是预测方差(y) 是目标值。为了数值稳定网络通常输出 (\log \sigma^2)再通过指数运算得到方差。除了直接预测方差也有工作使用 MC-Dropout 或模型集成来估计不确定性。但前者需要多次前向后者需要多个模型训练和推理成本偏高。轻量不确定性头是工程上更常用的选择在 U-Net 基础上加一个卷积头输出 mean 和 log_var代价很小效果直接。2.3 不确定性如何指导生成不确定性图的价值体现在训练阶段和推理阶段两个地方。训练阶段可以把它作为损失权重。对 exp(-log_var) 较大的位置模型有把握给扩散损失更高的权重对不确定性大的位置降低扩散损失权重避免模型在证据不足的区域强行拟合噪声。另一种思路是反过来对不确定区域加重“忠实度约束”例如增加一个 LR 一致性损失要求生成结果下采样回去之后与 LR 尽量一致。推理阶段引导方式更灵活常见思路有三种采样方向修正在去噪过程中对不确定性大的区域把噪声预测方向向 LR 条件方向收缩抑制自由发挥区域融合生成结束后把不确定性大的区域替换或混合为 LR 插值结果保证这些区域不会偏离输入动态步数分配对高不确定性区域增加去噪步数让模型有更多时间“确认”细节。需要说明的是上面这些只是该类方法常用的设计方向。具体到某篇论文公式和实现细节可能会有差异建议以官方代码和论文原文为准。本文的代码骨架用于演示引导介入的位置不代表论文原始公式。2.4 训练与推断的整体流程训练阶段流程采样 HR 图像降质得到 LR组成训练对VAE 编码得到 lr_latent 和 hr_latent不确定性头预测 log_var对 hr_latent 加噪得到带噪 latentU-Net 根据时间步和 lr_latent 条件预测噪声使用不确定性加权的 MSE 损失 不确定性头 NLL 损失反向传播。推理阶段流程输入 LRVAE 编码得到 lr_latent不确定性头估计不确定性图随机初始化噪声 latent循环执行去噪步骤在每一步用不确定性图调整预测方向解码得到 HR同时可输出不确定性图用于可视化分析。3. 环境准备与工程选型3.1 开发环境与依赖本文示例使用 PyTorch 生态核心依赖如下依赖用途Python 3.8基础运行环境PyTorch 1.13 / 2.0深度学习框架diffusers扩散模型训练与采样组件transformers部分条件编码组件accelerate分布式与混合精度训练opencv-python / pillow图像读取与预处理einops张量维度变换版本不需要完全照抄但建议 PyTorch 使用 1.13 以上diffusers 使用较新版本因为老版本的 scheduler API 与新版差异较大。3.2 数据准备超分训练的经典数据集是 DIV2K、DF2KDIV2K Flickr2K等高清图像数据集。更大的数据量能显著提升生成效果但训练成本也会上涨。如果是个人实验可以先从几百张高清图开始验证流程再逐步扩大数据规模。降质方式会影响模型的泛化能力。最简单的方案是双三次下采样更接近真实场景的方案是加入模糊核、噪声和 JPEG 压缩的随机组合。本文代码先用双三次下采样演示完整流程。4. 核心代码实现下面给出一个可运行的代码骨架分为数据准备、不确定性模块、训练损失、采样推断四个部分。代码中的 VAE 和 U-Net 直接复用 diffusers 和预训练模型重点展示如何接入不确定性引导。4.1 数据集与退化模拟# 文件路径sr_dataset.py import os import random import numpy as np import torch import torch.nn.functional as F from torch.utils.data import Dataset from PIL import Image class SRDataset(Dataset): LR / HR 训练数据集。 加载 HR 图随机裁剪补丁再用双三次下采样模拟 LR。 def __init__(self, hr_dir, scale4, patch_size128, ext(.png, .jpg, .jpeg)): self.hr_paths [] for name in sorted(os.listdir(hr_dir)): if name.lower().endswith(ext): self.hr_paths.append(os.path.join(hr_dir, name)) self.scale scale self.patch_size patch_size def __len__(self): return len(self.hr_paths) def _load_hr_patch(self, idx): img Image.open(self.hr_paths[idx]).convert(RGB) hr torch.from_numpy(np.array(img)).permute(2, 0, 1).float() / 255.0 _, h, w hr.shape if h self.patch_size or w self.patch_size: pad_h max(0, self.patch_size - h) pad_w max(0, self.patch_size - w) hr F.pad(hr, (0, pad_w, 0, pad_h)) top random.randint(0, hr.shape[1] - self.patch_size) left random.randint(0, hr.shape[2] - self.patch_size) return hr[:, top:top self.patch_size, left:left self.patch_size] def __getitem__(self, idx): hr self._load_hr_patch(idx) # 生成 LR先缩小再放大回原尺寸便于在像素域直接计算监督 lr F.interpolate( hr.unsqueeze(0), scale_factor1.0 / self.scale, modebicubic, align_cornersFalse, ) lr F.interpolate( lr, scale_factorself.scale, modebicubic, align_cornersFalse, ).squeeze(0) return {lr: lr, hr: hr}这段代码的关键点是我们先把 HR 缩小为 LR再放大回原尺寸。这样做的好处是 lr 和 hr 可以直接做像素级损失真正的超分模型输入是缩放后的 LR因此也需要先把它放大到目标分辨率再交给模型。4.2 不确定性估计模块# 文件路径uncertainty_module.py import torch import torch.nn as nn class UncertaintyHead(nn.Module): 从 LR 编码后的 latent 中估计逐像素高斯分布的均值与方差。 输出通道为 2 * latent_channels - 前一半是预测均值 mean - 后一半是 log variance取 log 是为了保证方差恒为正 def __init__(self, in_channels4, hidden_channels128): super().__init__() self.net nn.Sequential( nn.Conv2d(in_channels, hidden_channels, 3, padding1), nn.GroupNorm(8, hidden_channels), nn.SiLU(), nn.Conv2d(hidden_channels, hidden_channels, 3, padding1), nn.GroupNorm(8, hidden_channels), nn.SiLU(), nn.Conv2d(hidden_channels, 2 * in_channels, 3, padding1), ) def forward(self, lr_latent): out self.net(lr_latent) mean, log_var torch.chunk(out, 2, dim1) return mean, log_var def gaussian_nll(mean, log_var, target): 不确定性模块的训练损失负对数似然。 precision torch.exp(-log_var) nll 0.5 * (log_var precision * (target - mean) ** 2) return nll.mean()这个模块设计得比较轻量只有三层卷积原因是它接收的 lr_latent 本身已经包含丰富的语义信息不需要太深的网络就能估计出有意义的方差图。log_var 的通道数与 latent 通道数保持一致这样每个 latent 位置都有独立的不确定性估计。4.3 扩散模型训练损失# 文件路径train_step.py import torch import torch.nn.functional as F def train_step(model, noise_scheduler, vae, uncertainty_head, optimizer, batch, device, unc_loss_weight0.1): 完整训练步扩散损失 不确定性加权 不确定性头 NLL 损失。 lr batch[lr].to(device) hr batch[hr].to(device) # 1. 使用冻结的 VAE 编码器得到 latent # 注意 scaling_factor 是为了对齐 latent 数值范围请按实际 VAE 配置调整 with torch.no_grad(): lr_latent vae.encode(lr).latent_dist.sample() * vae.config.scaling_factor hr_latent vae.encode(hr).latent_dist.sample() * vae.config.scaling_factor # 2. 不确定性模块从 LR latent 预测每像素 mean / log_var mean_pred, log_var uncertainty_head(lr_latent) unc_loss gaussian_nll(mean_pred, log_var, hr_latent) # 3. 对 HR latent 加噪U-Net 根据时间步和 LR 条件预测噪声 noise torch.randn_like(hr_latent) bsz hr_latent.shape[0] timesteps torch.randint( 0, noise_scheduler.config.num_train_timesteps, (bsz,), devicedevice, dtypetorch.long, ) noisy_latent noise_scheduler.add_noise(hr_latent, noise, timesteps) noise_pred model( noisy_latent, timesteps, encoder_hidden_stateslr_latent, ).sample # 4. 不确定性加权扩散损失 # exp(-log_var) 越大的位置模型越有把握损失权重越高 weight torch.exp(-log_var.detach()) diff_loss F.mse_loss(noise_pred, noise, reductionnone) diff_loss (diff_loss * weight).mean() # 5. 总损失 loss diff_loss unc_loss_weight * unc_loss optimizer.zero_grad() loss.backward() optimizer.step() return { diff_loss: diff_loss.item(), unc_loss: unc_loss.item(), loss: loss.item(), }这里有几个细节需要注意VAE 必须冻结否则梯度会同时更新 VAE导致 latent 空间不稳定uncertainty_head 的输入输出都是 latent 分辨率不需要额外下采样对 weight 使用 detach()让不确定性权重只作用于扩散损失的加权不把梯度传到不确定性头避免循环依赖unc_loss_weight 控制不确定性分支对总损失的贡献一般设置在 0.05 到 0.1 附近。4.4 带不确定性引导的采样推断# 文件路径inference.py import torch from diffusers import DDIMScheduler torch.no_grad() def sample_sr(model, vae, uncertainty_head, lr, schedulerNone, num_steps50, guidance_strength1.0): 带不确定性引导的超分采样。 guidance_strength 越大模型越偏向生成细节 设置为 0 时不确定性引导不参与修正。 device next(model.parameters()).device if scheduler is None: scheduler DDIMScheduler.from_config( runwayml/stable-diffusion-v1-5, subfolderscheduler ) lr lr.to(device) lr_latent vae.encode(lr).latent_dist.sample() * vae.config.scaling_factor # 估计不确定性图用于后续采样修正 _, log_var uncertainty_head(lr_latent) # 随机初始化噪声 latent x torch.randn_like(lr_latent) scheduler.set_timesteps(num_steps) for t in scheduler.timesteps: t_batch torch.full( (x.shape[0],), t, devicedevice, dtypetorch.long ) noise_pred model( x, t_batch, encoder_hidden_stateslr_latent ).sample # 不确定性引导修正 # 这只是演示“引导介入的位置”并不是论文原始公式。 # 思路是对不确定性大的位置让预测方向更贴近 LR 条件 # 从而减少模型自由发挥带来的幻觉细节。 if guidance_strength 0: unc_map torch.exp(log_var) alpha 1.0 - torch.sigmoid(unc_map) lr_direction (lr_latent - x) / max(t.item() / 1000.0, 1.0) noise_pred noise_pred guidance_strength * alpha * lr_direction x scheduler.step(noise_pred, t, x).prev_sample hr_latent x / vae.config.scaling_factor hr vae.decode(hr_latent).sample return hr, log_var关于这个采样函数需要强调三点第一lr_direction 的公式只是用来演示引导方向实际论文中可能会有更复杂的修正方式比如通过额外的 fidelity 分支或 classifier guidance 实现直接照搬这段代码并不代表复现了某篇论文。第二部分 diffusers 版本的 scheduler.step 返回的字段名可能是 sample 而不是 prev_sample需要根据版本调整。第三解码之前要对 latent 除以 scaling_factor这一步容易遗漏结果通常表现为生成图像整体偏亮或偏灰。5. 关键参数与工程细节实际操作中下面几个参数对结果影响最大参数建议范围作用guidance_strength0.5 2.0控制不确定性引导的强度太大会过度抑制生成细节unc_loss_weight0.05 0.1控制不确定性头训练权重训练 patch 尺寸128 256越大越稳定显存占用越高采样步数20 50步数越多细节越丰富推理越慢CFG scale3.0 7.5如果不使用 classifier-free guidance可忽略实验时建议先把 patch 尺寸调小确保流程能跑通再逐步增大。不确定性头的学习率可以和主模型一致也可以单独设置一个小学习率例如主模型的 0.1 倍避免不确定性分支收敛过快导致权重分布失衡。6. 常见问题与排查思路问题现象常见原因解决思路训练 loss 不下降LR 条件没有正确注入或 VAE latent 数值范围不对检查 encoder_hidden_states 是否传入打印 lr_latent 的均值方差推理结果颜色发灰latent 未除以 scaling_factor 就解码确认解码前执行 hr_latent / vae.config.scaling_factor生成结果模糊guidance_strength 过大把噪声预测修正过度降低 guidance_strength或增加采样步数生成结果过度锐利且有伪影不确定性权重或 CFG 权重过高降低 CFG scale观察不确定性图分布显存不足latent 分辨率高模型大降低 patch 尺寸开启 gradient_checkpointing使用混合精度scheduler.step 返回字段报错diffusers 版本 API 变更检查版本使用 prev_sample 还是 sample不确定性图几乎没有变化不确定性头收敛过快或 lr 过大单独降低不确定性头学习率增加 NLL 损失权重排查此类问题最有效的方法是可视化中间结果。建议把 LR、不确定性图、去噪过程中的中间 latent、最终 HR 一并保存到 TensorBoard可以快速定位问题出在训练还是推理阶段。7. 最佳实践与工程建议7.1 数据多样性决定上限只使用双三次下采样训练的模型在真实低质量图像上效果会明显下降。如果业务场景包含噪声、模糊、压缩伪影建议训练时采用随机退化组合甚至在推理时加入轻量先验降质模块让模型对真实退化更鲁棒。7.2 忠实性也要有量化指标除了 PSNR 和 LPIPS建议额外统计一个忠实性指标把生成结果重新降质回 LR计算与原始 LR 的误差Fidelity Gap。这个指标能直接反映模型是否偏离输入证据比单独看生成质量更有说服力。7.3 推理性能优化扩散模型逐步去噪推理速度天然较慢。工程落地时可以从这几个方向优化使用 DDIM 采样减少步数使用 xFormers 或 SDPA 加速注意力计算对大图进行分块推理tiling避免显存溢出使用 ONNX Runtime 或 TensorRT 做模型加速。7.4 训练工程规范训练时要做好 checkpoint 保存与恢复建议每个 epoch 保存一次完整权重同时记录 uncertainty_head 的独立 checkpoint。扩散模型训练时间较长中途中断是常态没有断点续训会非常痛苦。7.5 数据授权与安全超分模型训练数据应确保有合法授权。如果是人脸、医疗、安防等敏感图像要遵守数据使用规范不在生产环境使用来源不明的数据集并且对模型输出保持人工复核机制。8. 总结与学习路线本文围绕 Uncertainty-Guided Latent Diffusion Models 这条技术路线梳理了超分任务中的忠实性挑战、不确定性估计的基本原理、训练与采样阶段的引导方式并给出了一套包含数据准备、不确定性模块、扩散训练损失和采样推断的 PyTorch 代码骨架。如果你要深入这个方向建议按下面的顺序继续学习先熟悉 Stable Diffusion 的完整训练流程理解 VAE、U-Net、scheduler 三个组件的关系阅读不确定性估计的基础文献重点理解 Aleatoric Uncertainty 的 NLL 损失推导在自己的数据上把本文代码跑通尝试调整 guidance_strength 和 unc_loss_weight观察不确定性图的变化阅读该方向最新论文对比不同不确定性引导方式损失加权、采样修正、区域融合的差异在数据集和指标上做消融实验特别是 Fidelity Gap 指标验证不确定性引导是否真的提升了忠实性。这个方向最有趣的地方在于它把“模型对自身判断的置信度”引入了生成过程让模型学会在不确定时保持克制。如果你在复现过程中卡在训练不收敛、生成结果不忠实或显存优化等问题欢迎把报错信息和实验配置留在评论区一起讨论。