从SRResNet到SRGAN:手把手复现经典论文中的生成对抗网络训练
2026/6/17 16:56:06 网站建设 项目流程

从SRResNet到SRGAN:PyTorch实战经典超分辨率生成对抗网络

当你在手机上放大一张模糊的老照片时,是否曾期待AI能神奇地还原那些丢失的细节?这正是超分辨率技术试图解决的问题。而SRGAN作为该领域的里程碑,首次实现了让机器生成肉眼难以辨别的"逼真"高清图像。本文将带你用PyTorch从零复现这一经典模型,揭开生成对抗网络在图像增强中的魔法。

1. 环境准备与数据加载

1.1 基础环境配置

推荐使用Python 3.8+和PyTorch 1.10+环境,以下是关键依赖:

pip install torch torchvision pillow matplotlib opencv-python

对于GPU加速,需额外安装对应版本的CUDA工具包。验证环境是否正常:

import torch print(f"PyTorch版本: {torch.__version__}") print(f"GPU可用: {torch.cuda.is_available()}")

1.2 数据集处理

SRGAN原始论文使用ImageNet的子集,我们准备两种方案:

标准数据集方案

from torchvision import datasets, transforms train_transform = transforms.Compose([ transforms.RandomCrop(96), transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) train_set = datasets.ImageFolder( root='path/to/imagenet/train', transform=train_transform )

轻量级替代方案(适合快速验证):

# 使用DIV2K数据集(约900张高质量图像) !wget http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_HR.zip

提示:数据预处理时将HR图像下采样4倍获得LR图像时,建议使用Pillow的BICUBIC插值以保持与论文一致。

2. 构建SRResNet生成器

2.1 残差块设计

SRResNet的核心是16个残差块,每个块包含:

class ResidualBlock(nn.Module): def __init__(self, channels): super().__init__() self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1) self.bn1 = nn.BatchNorm2d(channels) self.prelu = nn.PReLU() self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm2d(channels) def forward(self, x): residual = x out = self.conv1(x) out = self.bn1(out) out = self.prelu(out) out = self.conv2(out) out = self.bn2(out) return out + residual

2.2 完整生成器架构

生成器的拓扑结构可分为三个关键部分:

  1. 低层特征提取:2个卷积层 + PReLU
  2. 高层特征提取:16个残差块
  3. 上采样重建:PixelShuffle上采样层
class SRResNet(nn.Module): def __init__(self, scale_factor=4): super().__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=9, padding=4) self.prelu = nn.PReLU() self.res_blocks = nn.Sequential( *[ResidualBlock(64) for _ in range(16)] ) # 上采样部分 upscale = [] for _ in range(scale_factor // 2): upscale.extend([ nn.Conv2d(64, 256, kernel_size=3, padding=1), nn.PixelShuffle(2), nn.PReLU() ]) self.upscale = nn.Sequential(*upscale) self.final_conv = nn.Conv2d(64, 3, kernel_size=9, padding=4) def forward(self, x): x = self.prelu(self.conv1(x)) residual = x x = self.res_blocks(x) x = self.upscale(x + residual) return torch.tanh(self.final_conv(x))

注意:最后一层使用tanh激活将输出值约束到[-1,1]范围,需在数据预处理时做相应归一化。

3. 设计判别器网络

3.1 判别器架构

判别器采用类似VGG的深度CNN结构,但不使用池化层:

class Discriminator(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( # 输入尺寸: (3, 96, 96) nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1), nn.LeakyReLU(0.2), nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1), nn.BatchNorm2d(64), nn.LeakyReLU(0.2), # 后续包含7个类似块,逐步下采样... # 最终输出尺寸: (512, 6, 6) ) self.classifier = nn.Sequential( nn.Linear(512*6*6, 1024), nn.LeakyReLU(0.2), nn.Linear(1024, 1), nn.Sigmoid() ) def forward(self, x): x = self.features(x) x = torch.flatten(x, 1) return self.classifier(x)

3.2 关键设计细节

  • 使用LeakyReLU(α=0.2)防止梯度消失
  • 跨步卷积替代池化层进行下采样
  • 批归一化稳定训练过程
  • 最终sigmoid输出真假概率

4. 复合损失函数实现

4.1 内容损失(Content Loss)

提供两种实现方案:

MSE损失(像素级)

mse_loss = nn.MSELoss()

VGG感知损失(特征级)

class VGGLoss(nn.Module): def __init__(self): super().__init__() vgg = torchvision.models.vgg19(pretrained=True).features[:35] self.vgg = nn.Sequential(*list(vgg.children())[:35]).eval() for param in self.parameters(): param.requires_grad = False def forward(self, sr, hr): sr_features = self.vgg(sr) hr_features = self.vgg(hr) return F.mse_loss(sr_features, hr_features)

4.2 对抗损失(Adversarial Loss)

def adversarial_loss(disc_output, real=True): target = torch.ones_like(disc_output) if real else torch.zeros_like(disc_output) return F.binary_cross_entropy(disc_output, target)

4.3 完整损失组合

def total_loss(sr, hr, disc_output, vgg_loss, content_weight=1, adv_weight=1e-3): # 内容损失 content_l = vgg_loss(sr, hr) # 对抗损失 adv_l = adversarial_loss(disc_output, real=False) return content_weight * content_l + adv_weight * adv_l

5. 训练策略与技巧

5.1 两阶段训练流程

  1. 预训练SRResNet(仅用MSE损失)

    optimizer = torch.optim.Adam(generator.parameters(), lr=1e-4) for epoch in range(100): for lr, hr in dataloader: sr = generator(lr) loss = mse_loss(sr, hr) optimizer.zero_grad() loss.backward() optimizer.step()
  2. 联合训练SRGAN(感知损失+对抗损失)

    gen_optim = torch.optim.Adam(generator.parameters(), lr=1e-4) disc_optim = torch.optim.Adam(discriminator.parameters(), lr=1e-4) for epoch in range(100000): # 更新判别器 disc_optim.zero_grad() real_loss = adversarial_loss(discriminator(hr), real=True) fake_loss = adversarial_loss(discriminator(generator(lr).detach()), real=False) disc_loss = (real_loss + fake_loss) / 2 disc_loss.backward() disc_optim.step() # 更新生成器 gen_optim.zero_grad() sr = generator(lr) gen_loss = total_loss(sr, hr, discriminator(sr), vgg_loss) gen_loss.backward() gen_optim.step()

5.2 关键训练参数

参数推荐值说明
Batch Size16与论文保持一致
初始学习率1e-4使用Adam优化器
内容损失权重1平衡内容与对抗损失
对抗损失权重1e-3防止生成器过度关注欺骗判别器
训练迭代次数1e5 - 2e5观察PSNR和视觉质量

5.3 学习率调整策略

scheduler = torch.optim.lr_scheduler.StepLR( optimizer, step_size=1e5, gamma=0.1 )

6. 模型评估与结果分析

6.1 定量评估指标

def psnr(sr, hr, max_val=1.0): mse = torch.mean((sr - hr) ** 2) return 20 * torch.log10(max_val / torch.sqrt(mse))

6.2 定性评估技巧

  • 在验证集上定期保存生成样本
  • 使用t-SNE可视化特征空间分布
  • 对比不同损失组合的视觉效果
with torch.no_grad(): sr = generator(lr_sample) grid = torchvision.utils.make_grid([denormalize(lr_sample[0]), denormalize(sr[0]), denormalize(hr_sample[0])]) plt.imshow(grid.permute(1, 2, 0))

7. 实战中的常见问题与解决方案

7.1 模式崩溃(Mode Collapse)

现象:生成器总是输出相似的图像
解决方案

  • 增加判别器的更新频率
  • 尝试Wasserstein GAN损失
  • 添加小批量判别特征

7.2 训练不稳定

现象:损失值剧烈波动
解决方案

  • 使用梯度裁剪(nn.utils.clip_grad_norm_
  • 调整学习率(初期1e-4,后期1e-5)
  • 增加判别器的正则化(Dropout、权重衰减)

7.3 生成图像伪影

现象:输出图像存在棋盘格等伪影
解决方案

  • 检查PixelShuffle层的使用方式
  • 尝试替代上采样方法(如转置卷积)
  • 在损失函数中加入总变分正则项
def tv_loss(image): diff_x = image[:, :, 1:, :] - image[:, :, :-1, :] diff_y = image[:, :, :, 1:] - image[:, :, :, :-1] return torch.mean(diff_x**2) + torch.mean(diff_y**2)

8. 进阶优化方向

8.1 网络架构改进

  • 引入密集连接(DenseNet块)
  • 尝试注意力机制(如RCAN中的通道注意力)
  • 使用残差密集块(RRDB)

8.2 损失函数创新

  • 添加风格迁移损失(Gram矩阵匹配)
  • 结合SSIM指标作为损失项
  • 引入特征匹配损失(Feature Matching Loss)

8.3 训练策略优化

  • 渐进式增长训练(ProGAN)
  • 自注意力机制(SAGAN)
  • 两阶段对抗训练(ESRGAN)

在实际项目中,我发现先预训练SRResNet约10万次迭代,再微调SRGAN能显著提升稳定性。对于4倍超分任务,建议输入至少96x96像素的LR图像块,以确保有足够上下文信息。当处理人脸等特定类别时,在通用模型基础上进行领域自适应训练可提升约15%的MOS评分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询