005、DRCN递归神经网络共享参数与监督式重建的收敛性分析去年调一个视频超分项目发现模型训练到第80个epoch时PSNR突然跳水从32.1dB掉到28.6dB。排查了三天最后定位到是递归深度设置不当导致的梯度爆炸——这个坑让我重新翻出DRCN的论文发现Kim在2016年就讨论过类似问题。今天把这段经历和DRCN的核心机制揉在一起聊。从“递归”这个词开始踩坑很多人看到DRCNDeeply-Recursive Convolutional Network就以为是RNN那种时序递归其实不是。DRCN的递归发生在空间维度——同一个卷积模块反复作用在特征图上相当于把单帧图像“循环处理”若干次。我第一次实现时天真地写了这样的代码# 别这样写我当时就是这么干的foriinrange(recursion_depth):xconv_block(x)结果训练时显存直接炸了。为什么因为PyTorch默认会保留每个递归步骤的计算图用于反向传播递归深度设为10显存占用就是10倍。正确的做法是用checkpoint或者手动控制梯度流——后面会细说。DRCN的核心设计有三个共享参数的递归模块、跳跃连接skip connection、以及多监督损失。这三个东西组合起来解决了“深度递归网络为什么能收敛”这个根本问题。共享参数省参数但不省计算DRCN的递归模块参数是共享的。假设递归深度为D普通卷积堆叠需要D套独立参数DRCN只需要1套。这带来两个直接好处参数量骤减。同样感受野下DRCN的参数量大约是VDSR的1/D。我实测D10时模型大小只有VDSR的1/3左右。隐式正则化。共享参数迫使网络学习“通用的特征变换”而不是为每一层定制不同的滤波器。这有点像权重共享在RNN中的作用——防止过拟合。但共享参数有个致命问题梯度消失/爆炸。因为同一个卷积模块被反复调用反向传播时梯度要经过D次相同的变换。如果这个变换的谱半径大于1梯度指数增长小于1梯度指数衰减。Kim在论文里用残差连接和跳跃连接缓解了这个问题但实际调参时我发现递归深度超过16后训练几乎必然不稳定。监督式重建每个递归步都算损失DRCN最巧妙的设计是“多监督”——不仅对最终输出计算损失还对每个递归步的中间输出计算损失。具体来说假设递归深度为D网络会产生D个重建结果每个递归步一个再加上最终的融合结果总共D1个损失项。我当时调试时犯过一个错误只对最终结果算损失中间结果不管。结果训练到一半发现前几个递归步的输出全是噪声只有最后几步才像样。加上中间监督后每个递归步的输出都逐渐逼近目标训练曲线平滑了很多。多监督的数学形式是L_total L_final λ * Σ L_intermediateλ通常设为0.1-0.5。我习惯用0.2因为中间监督太强会抑制递归模块的“深度表达能力”——它会让每个步都急于输出好结果反而失去了逐步优化的能力。收敛性分析为什么DRCN能收敛这个问题我花了整整一周才想明白。从优化角度看DRCN的训练相当于在解一个带约束的优化问题min θ Σ ||f_θ^d(x) - y||²其中f_θd表示递归d次后的输出。由于参数共享这个优化问题是非凸的而且高度耦合。但多监督机制实际上把问题分解成了D个独立的子问题——每个子问题只要求f_θd逼近y而不是要求整个递归链逼近y。这带来了两个收敛性保证梯度路径缩短。反向传播时每个中间损失的梯度只经过d次递归变换而不是D次。这避免了梯度穿过整个递归链导致的爆炸/消失。隐式课程学习。浅层递归d较小的任务更容易深层递归d较大的任务更难。多监督相当于让网络先学会简单任务再逐步过渡到困难任务。我观察训练过程时发现前几个epoch浅层损失下降很快深层损失下降慢但10个epoch后深层损失开始追赶——这就是课程学习的典型表现。实战中的三个坑坑一递归深度选择D5时模型太浅效果不如VDSRD10时效果最好PSNR比VDSR高0.3dB左右D16时训练开始不稳定偶尔出现loss爆炸。我的建议是D8-12之间具体看数据集。Set5上D10最优Urban100上D12更好。坑二梯度裁剪共享参数导致梯度尺度变化剧烈。我试过不裁剪训练到一半loss直接变成NaN。裁剪阈值设为0.1-0.5之间我习惯用0.2。注意裁剪的是整个梯度向量不是单个参数。坑三初始化DRCN对初始化敏感。用Xavier初始化时前几个epoch的loss下降很慢换成He初始化后收敛速度提升了一倍。原因可能是ReLU激活函数导致方差变化He初始化正好补偿了这一点。代码实现的关键点递归模块的实现要注意梯度流控制。我现在的写法是# 正确做法用checkpoint节省显存fromtorch.utils.checkpointimportcheckpointdefforward(self,x):outputs[]foriinrange(self.depth):# 这里用checkpoint反向传播时不保留中间计算图xcheckpoint(self.conv_block,x)outputs.append(x)# 融合所有中间输出returnself.fusion(torch.cat(outputs,dim1))注意checkpoint会增加前向计算时间约20%但显存占用从O(D)降到O(1)。如果显存够用也可以不用但递归深度超过8时建议加上。融合层我用1x1卷积把D个通道压缩回3通道RGB。Kim论文里用的是加权平均权重可学习。我试过两种1x1卷积效果略好但参数量多了一点点。个人经验DRCN的价值不在于它比EDSR或RCAN强——事实上在PSNR指标上它已经被超越了。但它的设计思想对理解“深度与递归的关系”非常有帮助。如果你在做视频超分或者需要处理长程依赖的任务DRCN的共享参数机制值得借鉴。最后说一句不要盲目追求递归深度。我见过有人把DRCN的深度设到32结果训练了三天loss纹丝不动。有时候浅一点反而更好——模型够用就行别为了炫技把项目搞崩了。