
009、ESRGAN改进RRDB残差密集块与相对对抗损失的实战优化上周帮师弟调一个老照片修复项目他直接套用SRGAN的生成器结果纹理细节糊成一团边缘还有诡异的伪影。我盯着loss曲线看了半天发现判别器收敛太快生成器根本没学到高频信息。这让我想起两年前自己踩过的坑——ESRGAN的论文读了三遍代码跑通却出不来效果最后发现是残差块结构写错了。今天把RRDB和相对对抗损失这两个核心改进的实战细节掰开揉碎全是血泪教训。从SRGAN到ESRGAN那个让纹理起死回生的RRDB先说说为什么SRGAN在真实场景下容易翻车。原始SRGAN用残差块堆叠每个块里两个卷积加BN层。问题出在BN上——训练时BN统计量依赖batch内其他样本超分任务里不同图像的高频特征差异极大BN反而把纹理差异抹平了。我试过把batch size调到16结果生成器输出像蒙了一层雾。ESRGAN的RRDBResidual-in-Residual Dense Block直接砍掉BN改用密集连接。这里有个容易写错的地方RRDB内部是三个密集块串联每个密集块里四个卷积层每层输出都拼接到后续层输入。别写成普通残差块那种加法要像DenseNet那样在通道维度拼接。我最初写代码时把torch.cat写成了torch.add跑了三天才发现PSNR不升反降。classDenseBlock(nn.Module):def__init__(self,in_channels,growth_rate32):super().__init__()self.conv1nn.Conv2d(in_channels,growth_rate,3,1,1)self.conv2nn.Conv2d(in_channelsgrowth_rate,growth_rate,3,1,1)self.conv3nn.Conv2d(in_channels2*growth_rate,growth_rate,3,1,1)self.conv4nn.Conv2d(in_channels3*growth_rate,growth_rate,3,1,1)# 这里踩过坑growth_rate别设太大显存会爆32够用self.lrelunn.LeakyReLU(0.2,inplaceTrue)defforward(self,x):x1self.lrelu(self.conv1(x))x2self.lrelu(self.conv2(torch.cat([x,x1],1)))x3self.lrelu(self.conv3(torch.cat([x,x1,x2],1)))x4self.conv4(torch.cat([x,x1,x2,x3],1))# 别这样写直接return x4要加上残差连接returntorch.cat([x,x1,x2,x3,x4],1)# 输出通道数 in_channels 4*growth_rateRRDB的“残差中的残差”体现在每个DenseBlock输出后要乘以残差缩放因子通常0.2再与输入相加。三个DenseBlock串联后整体再加一次残差。这个缩放因子很关键我试过0.1和0.50.2最稳太大容易梯度爆炸太小收敛慢。相对对抗损失让判别器学会“挑刺”SRGAN用的标准GAN损失有个致命问题判别器只判断输入是真实还是生成相当于二分类。超分任务里生成的高频细节哪怕有一丁点不自然判别器都能轻松区分导致生成器梯度消失。ESRGAN提出的RaGANRelativistic average GAN改成了“真实图像比生成图像更真实”的相对判断。具体实现时判别器输出不再是单个概率而是计算真实图像和生成图像特征之间的相对差异。我写代码时卡在怎么把标准判别器改成相对形式后来发现其实就改一行loss计算defragan_loss(discriminator,real_imgs,fake_imgs):# 这里踩过坑别直接用sigmoid要计算相对概率real_logitsdiscriminator(real_imgs)fake_logitsdiscriminator(fake_imgs)# 相对判别器真实图像比生成图像更真实的概率real_relreal_logits-torch.mean(fake_logits,dim0,keepdimTrue)fake_relfake_logits-torch.mean(real_logits,dim0,keepdimTrue)# 别这样写直接用BCEWithLogitsLoss要手动算sigmoidd_loss_real-torch.mean(torch.log(torch.sigmoid(real_rel)1e-12))d_loss_fake-torch.mean(torch.log(1-torch.sigmoid(fake_rel)1e-12))d_lossd_loss_reald_loss_fake# 生成器损失希望生成图像比真实图像更真实反向g_loss-torch.mean(torch.log(torch.sigmoid(fake_rel)1e-12))returnd_loss,g_loss注意这里生成器loss是让fake_rel变大即生成图像在相对比较中胜出。我刚开始写反了把g_loss写成了d_loss_fake的形式结果生成器疯狂生成模糊图像因为模糊图像更容易骗过判别器。训练策略那些论文没写的调参细节RRDB和RaGAN组合后训练稳定性比SRGAN好很多但仍有几个坑学习率设置生成器用1e-4判别器用4e-4这个比例是经验值。判别器学习率太低生成器会过拟合太高判别器太强导致生成器梯度消失。我试过两个都用1e-4结果判别器loss一直下不去。预热训练前10个epoch只训练生成器用L1损失。这步很关键让生成器先学会基本结构再引入对抗损失。别上来就开GAN否则生成器会陷入局部最优输出全是噪点。我有个项目因为没做预热跑了200个epoch还是糊的。损失权重感知损失VGG特征权重设为1e-2对抗损失权重设为1e-3。这个比例要微调感知损失太强会压制纹理多样性对抗损失太强会产生伪影。我习惯先固定感知损失权重调整对抗损失观察生成图像的高频细节是否自然。梯度惩罚虽然RaGAN比标准GAN稳定但判别器还是容易过拟合。我在判别器里加了个梯度惩罚项权重设为10。别用WGAN-GP那种复杂的梯度惩罚简单的L2正则化就够用。实战踩坑从PSNR到感知质量的转变用ESRGAN跑完训练PSNR可能比SRGAN还低0.5dB但人眼看起来更清晰。这是因为PSNR只衡量像素级差异而ESRGAN牺牲了像素精度换来了纹理真实感。我刚开始纠结PSNR后来发现用户反馈更好才明白超分任务的评价指标要转向感知质量。有个实际案例给一个老照片修复项目原图是1920x1080的模糊视频帧。用SRGAN放大4倍人脸五官清晰但皮肤像塑料用ESRGAN皮肤纹理自然但边缘有轻微锯齿。后来我在RRDB里加了边缘增强模块一个sobel滤波器分支锯齿问题解决但训练时间增加了30%。这个取舍要看应用场景——如果是医疗影像宁可模糊也不能有伪影如果是娱乐应用纹理真实感更重要。个人经验性建议别迷信论文里的超参数ESRGAN原论文用DIV2K数据集batch size 16训练60万步。你的数据集如果只有几千张把batch size降到4学习率减半否则过拟合。我试过用原参数训练小数据集生成器直接崩了。RRDB的深度要适配硬件原论文用23个RRDB显存占用约8GB。如果你只有4GB显存降到8个RRDB效果损失不大。别硬撑OOM了还得重来。相对对抗损失不是万能药对于严重模糊的图像RaGAN可能产生过度锐化的伪影。这时候可以降低对抗损失权重或者先用L1损失预训练到PSNR 30以上再开GAN。调试时盯着生成图像看loss曲线只能告诉你收敛趋势具体效果得看图像。我习惯每10个epoch保存一批生成结果观察纹理细节是否自然。如果出现棋盘格伪影检查反卷积层是否用了正确的上采样方式推荐pixel shuffle。代码实现要逐行检查RRDB的密集连接、残差缩放、RaGAN的损失计算这三个地方最容易出错。我建议先在小数据集比如Set5上跑通确认PSNR和论文一致再上大数据集。最后说句实在话ESRGAN的改进思路很巧妙但实战中90%的问题出在实现细节上。把RRDB和RaGAN写对你的超分效果就能超过90%的现有方法。剩下的10%靠的是对数据集的深入理解和耐心调参。