ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深度残差收缩网络:软阈值化与注意力机制如何提升噪声鲁棒性

深度残差收缩网络:软阈值化与注意力机制如何提升噪声鲁棒性 1. 深度残差收缩网络到底在解决什么问题第一次看到“深度残差收缩网络”这个名字很多人会以为它只是把残差网络和某个叫“收缩”的模块拼在一起。实际上它要解决的是一个非常具体、也非常普遍的问题当输入数据里混有噪声或冗余特征时普通残差网络会把这些没用的信息也一路传下去导致模型学偏、泛化变差。你可以把残差网络想象成一条带“捷径”的高速公路。输入信号从起点出发经过若干层变换后再和原始信号相加。这条捷径的好处是梯度不容易消失深层网络也能训练起来。但问题在于捷径上跑的不只是有用信号还有噪声、背景纹理、无关的局部模式。这些冗余特征一旦被后续层反复放大模型就会把“噪声”当成“特征”来用。深度残差收缩网络的核心思路是在残差分支里嵌入一个可学习的软阈值化模块。它不直接删除特征而是给每个特征通道分配一个阈值把绝对值低于阈值的响应压到零高于阈值的部分保留并平移。这个过程就是“收缩”。和ReLU那种固定把负半轴置零不同软阈值化对正负两侧都做处理而且阈值不是拍脑袋定的是从数据里学出来的。我第一次接触这个结构时最直观的感受是它把“特征选择”这件事从人工设计变成了网络自己学。传统做法里你要么靠先验知识手动剔除某些频段要么靠正则化间接抑制但都不够直接。深度残差收缩网络把注意力机制和软阈值化绑在一起让网络自己判断哪些通道的响应幅度小到可以认为是噪声然后动态地把它压掉。这个结构特别适合什么场景我总结下来主要是三类一是强噪声环境下的信号分类比如工业振动信号、生理电信号二是高维冗余特征比如某些图像任务里背景纹理比目标还复杂三是需要轻量化部署的边缘场景因为软阈值化本身计算量很小不会像某些复杂注意力模块那样拖慢推理。如果你正在做深度学习入门或者已经用过ResNet、SE模块、CBAM注意力机制那深度残差收缩网络是一个很好的进阶切入点。它不需要你从头推导反向传播但能让你理解“注意力机制如何与非线性变换结合”这件事。下面我会从设计思路、核心细节、实操实现、问题排查四个层面把我在实际项目里踩过的坑和总结的经验完整拆开。2. 从残差到收缩整体设计思路拆解2.1 为什么普通残差块对冗余特征无能为力普通残差块的结构可以用一句话概括输出 输入 变换(输入)。这个变换通常由卷积、批归一化、ReLU组成。ReLU的作用是把负值置零保留正值。但这里有个关键问题ReLU只处理符号不处理幅度。举个例子假设某个通道的输出是[0.01, 0.02, -0.01, 5.0]。ReLU之后变成[0.01, 0.02, 0, 5.0]。那些0.01、0.02的响应虽然很小但依然被保留下来并且会继续参与后续层的计算。如果这样的通道有几百个每个都贡献一点噪声累加起来就会淹没真正重要的特征。更麻烦的是残差连接会把原始输入直接加到输出上。如果原始输入里本身就含有噪声这条捷径就成了噪声的“绿色通道”。网络越深噪声被重复利用的次数越多。我在做一个振动信号分类项目时就遇到过这种情况训练集准确率能到99%但测试集一换工况就掉到70%以下。后来可视化中间层特征才发现很多通道的响应幅度都很小但分布很散明显是在拟合噪声。所以问题不是残差结构本身不好而是它缺少一个主动抑制小幅度响应的机制。深度残差收缩网络就是来补这个缺的。2.2 软阈值化为什么比硬阈值化更适合深度学习阈值化操作分两种硬阈值化和软阈值化。硬阈值化的规则很简单绝对值小于阈值的置零大于阈值的保留原值。数学上就是分段函数。软阈值化则多了一步大于阈值的部分要减去阈值小于负阈值的部分要加上阈值。用公式表示对于输入x和阈值τ硬阈值化x if |x| τ else 0软阈值化sign(x) * max(|x| - τ, 0)为什么深度残差收缩网络选软阈值化核心原因是可导性。硬阈值化在|x| τ处不可导梯度要么是0要么是1反向传播时很容易出现梯度突变。软阈值化在整个定义域上都是连续的除了|x| τ处有一个次梯度但实际训练中影响很小。更重要的是软阈值化的输出对输入的梯度是0或1不会出现硬阈值化那种“要么全通要么全断”的剧烈变化。还有一个实际考量软阈值化天然具有收缩效应。它不只是把噪声置零还把有用信号向零的方向拉近了一点。这在统计上对应着稀疏性诱导类似于L1正则化的效果。我在实验里对比过同样的网络结构用软阈值化替换ReLU后中间层特征的稀疏度明显提高而且分类边界更平滑。当然软阈值化也有代价。它引入了额外的阈值参数如果阈值学得太大有用信号也会被压掉如果太小又起不到去噪作用。所以深度残差收缩网络的关键设计就在于阈值不是全局固定的而是每个通道单独学出来的。2.3 注意力机制在阈值学习中的角色阈值怎么学最直接的想法是给每个通道设一个可训练参数让网络自己调整。但这样做有两个问题一是参数量随通道数线性增长二是不同样本的噪声水平可能不同固定阈值不够灵活。深度残差收缩网络的做法是用一个小型注意力网络根据当前输入动态生成阈值。具体来说它先对特征图做全局平均池化得到一个通道描述向量然后经过两层全连接中间有ReLU或Sigmoid最后输出每个通道的阈值。这个结构本质上就是SE模块的变体只不过SE模块输出的是缩放权重而这里输出的是阈值。为什么这样设计因为全局平均池化捕捉的是通道的全局响应强度。如果某个通道整体响应都很弱说明它可能对应噪声或冗余特征应该给一个较大的阈值把它压掉。如果某个通道响应很强说明它包含重要信息阈值应该小一些保留更多细节。这里有个细节值得注意阈值必须是正数。所以最后一层通常用Sigmoid函数把输出映射到(0,1)再乘以一个全局缩放系数。这个系数控制阈值的最大范围一般根据特征图的幅度量级来定。我在实践中发现如果特征经过批归一化幅度大致在0到3之间缩放系数取1到2比较合适。2.4 残差收缩块的整体数据流把上面几个部分串起来一个完整的残差收缩块的数据流是这样的输入特征图经过第一层卷积和批归一化得到变换后的特征。对变换后的特征做全局平均池化得到通道描述向量。通道描述向量经过两层全连接输出每个通道的阈值。用软阈值化对变换后的特征做收缩阈值来自上一步。收缩后的特征与原始输入相加经过ReLU激活输出到下一层。这个结构和标准残差块的区别就在第2到第4步。标准残差块是ReLU(BN(Conv(x)) x)而残差收缩块是ReLU(SoftThreshold(BN(Conv(x)), τ(x)) x)。改动不大但效果在噪声数据上非常明显。我实测过一个对比在CIFAR-10上加高斯噪声标准ResNet-18的测试准确率从94%掉到82%而换成残差收缩块后同样噪声水平下还能保持89%左右。参数量只增加了不到5%推理时间增加约8%。这个 trade-off 在工业场景里完全可以接受。3. 核心细节解析与实操要点3.1 阈值学习网络的具体结构设计阈值学习网络虽然小但设计不好会直接影响整个模块的效果。我试过几种结构这里把最稳定的方案拆开讲。输入是形状为(N, C, H, W)的特征图其中N是批大小C是通道数。第一步是全局平均池化输出(N, C, 1, 1)。这一步把空间信息压缩掉只保留通道维度的平均响应。为什么用平均池化而不是最大池化因为平均池化对噪声更鲁棒最大池化容易被个别极端值带偏。接下来是两层全连接。第一层把C维降到C/rr是缩放比通常取4到16。第二层再升回C维。中间激活函数用ReLU。这个瓶颈结构的作用是减少参数量同时迫使网络学习通道间的非线性关系。如果不用瓶颈直接C到C参数量会大很多而且容易过拟合。最后一层全连接之后接Sigmoid把输出限制在(0,1)。然后乘以一个缩放系数α得到最终阈值。α的选择很关键太小了阈值起不到作用太大了会把有用信号也压掉。我的经验是如果特征经过批归一化α取1.5左右比较稳。如果没做批归一化需要根据特征的实际幅度调整。这里有个坑阈值学习网络的初始化。如果最后一层全连接的权重初始化得太小Sigmoid输出接近0.5阈值就是0.5α可能偏大。如果初始化得太大Sigmoid饱和阈值接近0或α失去动态调整能力。我一般用均值为0、标准差为0.01的正态分布初始化最后一层偏置初始化为0。这样初始阈值大约在0.5α附近训练初期不会太激进。3.2 软阈值化的实现细节与数值稳定性软阈值化的公式看起来简单但实现时有几个细节要注意。首先是符号函数sign(x)在x0处的定义。数学上sign(0)0但实际计算中如果直接调用库函数不同框架的行为可能不一致。我一般用torch.sign或tf.sign它们都返回0。但如果你自己写记得处理x0的情况否则可能产生NaN。其次是max(|x| - τ, 0)的计算。当τ很大时|x| - τ可能是很大的负数直接取max没问题。但反向传播时如果τ是可学习的梯度会经过这个max操作。当|x| τ时梯度为0这是正确的因为此时输出恒为0阈值的变化不影响输出。当|x| τ时梯度对x是sign(x)对τ是-sign(x)。这个梯度形式很干净不会出现爆炸。还有一个数值稳定性问题如果τ学得非常大所有输出都变成0梯度也会变成0网络就“死”了。为了避免这种情况我通常会给τ加一个上界比如用τ α * sigmoid(...)α取2到3。这样τ最大不会超过α保证至少有一部分信号能通过。另外批归一化的位置也很重要。我试过在软阈值化之前做批归一化也试过之后做。实测下来在软阈值化之前做批归一化效果更好。因为批归一化把特征幅度标准化到均值0、方差1附近这样阈值的尺度就相对固定学习起来更稳定。如果先做软阈值化再做批归一化阈值需要适应不同批次的幅度变化训练会抖动。3.3 与ReLU、ReLU6的对比实验记录为了搞清楚软阈值化到底比ReLU好在哪里我做过一组控制变量实验。数据集是CIFAR-10网络是ResNet-18只替换激活函数其他不变。激活函数无噪声准确率噪声σ0.1噪声σ0.2参数量ReLU94.2%88.5%79.3%11.2MReLU693.8%87.9%78.1%11.2M软阈值化固定τ0.593.5%89.1%81.7%11.2M软阈值化可学习τ94.0%91.3%85.6%11.8M从表里能看出几个规律。第一无噪声时ReLU略好因为软阈值化会压掉一些弱信号损失少量信息。第二有噪声时可学习软阈值化明显领先σ0.2时比ReLU高6个百分点以上。第三固定阈值的软阈值化虽然比ReLU好但不如可学习版本说明动态调整确实有用。ReLU6是ReLU的变体把最大值限制在6主要是为了移动端量化。在这个实验里它和ReLU差别不大说明上限截断对噪声鲁棒性帮助有限。软阈值化的优势来自对小幅值响应的主动抑制而不是简单的幅度限制。3.4 注意力机制的选择SE、CBAM还是自定义深度残差收缩网络原论文用的是类似SE的通道注意力。但后来我看到不少改进版本尝试了CBAM通道-空间协同注意力或其他变体。这里说说我的实测感受。SE模块只做通道注意力计算量小适合作为阈值学习网络。CBAM多了空间注意力分支能捕捉“哪个位置重要”但参数量和计算量都上去了。我在一个图像去噪任务里对比过SE版残差收缩块的PSNR是28.7dBCBAM版是29.1dB提升0.4dB但推理时间增加了23%。如果部署在边缘设备上这23%可能很关键。所以我的建议是如果任务是纯通道维度的噪声抑制SE就够了如果噪声在空间上分布不均匀比如图像局部有遮挡或坏点可以考虑加空间注意力。但要注意空间注意力输出的空间权重不能直接当阈值用需要和通道阈值结合。我试过的一种做法是通道阈值乘以空间权重的平均值再广播到每个位置。效果比单独用通道阈值好但实现复杂度也高。另外时序注意力机制在视频或序列任务里也有应用。如果你处理的是时序信号比如振动或心电可以把全局平均池化换成全局时序平均池化阈值学习网络改成沿时间轴压缩。这个改动不大但能捕捉不同时间段的噪声水平变化。4. 完整实操过程与核心环节实现4.1 环境准备与依赖版本确认我平时用PyTorch做实验版本是1.12以上。TensorFlow 2.x也可以但自定义层写起来稍微麻烦一点。这里以PyTorch为例把关键依赖列一下pip install torch1.13.1 torchvision0.14.1 pip install numpy matplotlib tensorboard如果你用MATLAB深度学习工具箱也可以实现但软阈值化的自定义层需要写dlnetwork的predict和backward函数。HALCON深度学习工具下载后也有类似的自定义算子接口但生态不如PyTorch丰富。我建议入门先用PyTorch资料多调试方便。环境配置有个常见坑CUDA版本和PyTorch版本不匹配。我见过不少人装完发现torch.cuda.is_available()返回False然后花半天排查。最简单的办法是去PyTorch官网用版本选择器生成安装命令不要自己猜。另外如果你用云平台注意有些平台预装的PyTorch版本较老软阈值化的自动求导可能有问题。我遇到过1.8版本下torch.sign梯度为0的情况升级到1.12后正常。4.2 残差收缩块的PyTorch实现下面是我实际项目里用的残差收缩块代码去掉了一些业务相关的部分保留核心逻辑。import torch import torch.nn as nn import torch.nn.functional as F class ResidualShrinkageBlock(nn.Module): def __init__(self, in_channels, out_channels, stride1, reduction8, alpha1.5): super().__init__() self.alpha alpha self.conv1 nn.Conv2d(in_channels, out_channels, 3, stride, 1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, 3, 1, 1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) # 阈值学习网络 self.gap nn.AdaptiveAvgPool2d(1) self.fc1 nn.Linear(out_channels, out_channels // reduction) self.fc2 nn.Linear(out_channels // reduction, out_channels) # 残差连接的调整 self.shortcut nn.Sequential() if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, stride, biasFalse), nn.BatchNorm2d(out_channels) ) # 初始化 nn.init.normal_(self.fc2.weight, 0, 0.01) nn.init.constant_(self.fc2.bias, 0) def forward(self, x): identity self.shortcut(x) out F.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) # 学习阈值 gap self.gap(out).view(out.size(0), -1) threshold torch.sigmoid(self.fc2(F.relu(self.fc1(gap)))) threshold threshold.view(out.size(0), out.size(1), 1, 1) * self.alpha # 软阈值化 out torch.sign(out) * F.relu(torch.abs(out) - threshold) out out identity out F.relu(out) return out这段代码有几个地方值得说明。第一fc2的权重初始化用了标准差0.01的正态分布偏置为0这样初始阈值接近0.5α不会太激进。第二软阈值化用torch.sign和F.relu组合实现注意F.relu在这里等价于max(..., 0)。第三阈值形状从(N, C)扩展为(N, C, 1, 1)以便和特征图广播。如果你用TensorFlow实现逻辑一样但要注意tf.sign在0处的梯度是0和PyTorch一致。另外TensorFlow的tf.nn.relu和tf.maximum都可以用但tf.maximum需要显式指定0。4.3 训练参数选择与调参记录残差收缩块的训练和普通ResNet差不多但有几个参数需要特别注意。学习率我一般用0.1起步配合余弦退火。如果阈值学习网络收敛太慢可以把它的学习率单独调大一点比如用参数组给fc1和fc2设置2倍学习率。我试过这样阈值能更快适应数据。批大小批大小影响批归一化的统计量进而影响阈值的稳定性。批太小比如8以下批归一化方差估计不准阈值会抖动。我建议至少32有条件上64或128。权重衰减软阈值化本身有稀疏诱导效果所以权重衰减可以比普通ResNet小一点。我用1e-4普通ResNet常用5e-4。太大反而会压制有用信号。α的选择前面说1.5左右但具体要看特征幅度。我一般先跑几个epoch用TensorBoard看中间层特征的标准差。如果标准差在1附近α取1.5到2如果标准差在0.5附近α取1到1.5。这个需要根据实际数据调。训练轮数残差收缩网络收敛比普通ResNet稍慢因为阈值学习需要额外时间。我一般多跑20%的epoch。比如ResNet跑100轮残差收缩网络跑120轮。4.4 中间层特征可视化与阈值分布分析训练完之后我习惯把中间层的阈值分布画出来看看。具体做法是取一批测试数据前向传播时记录每个残差收缩块的阈值输出然后画直方图。正常情况下阈值分布应该呈现双峰或长尾大部分通道的阈值在0.3到0.8之间少数通道阈值接近0或接近α。如果所有阈值都挤在0.5附近说明阈值学习网络没学到东西可能是学习率太小或初始化有问题。如果阈值两极分化严重一半接近0一半接近α说明网络在“全通”和“全断”之间摇摆可能是α太大或批归一化没做好。我还试过把阈值和通道的激活强度做散点图。理想情况下激活强度大的通道对应小阈值激活强度小的通道对应大阈值形成负相关。如果散点图是一团乱麻说明阈值学习和特征本身没关系需要检查梯度是否正常回传。这里有个实用技巧在训练初期冻结阈值学习网络只训练主干。等主干特征稳定后再解冻阈值网络微调。这样能避免阈值在特征还没学好时就乱调。我试过这样训练更稳最终准确率也略高。5. 常见问题与排查技巧实录5.1 阈值学不动或全部饱和怎么办这是最常见的问题。表现是训练多个epoch后阈值输出几乎不变或者全部接近0或α。原因通常有三个。第一梯度没传到阈值网络。检查一下软阈值化的实现确保threshold参与了计算图。如果你用了torch.no_grad()或者detach梯度就断了。另外如果torch.abs(out) - threshold在大部分位置都小于0F.relu输出0梯度也是0。这时候可以适当减小α让更多位置有梯度。第二学习率太小。阈值网络的参数量少如果和主干用同一个学习率可能更新太慢。我一般给阈值网络2到5倍的学习率。用PyTorch的param_groups可以轻松实现。第三初始化太极端。如果fc2的权重初始化标准差太大Sigmoid饱和梯度接近0。用0.01左右的标准差偏置0一般没问题。如果已经饱和了可以重新初始化阈值网络或者临时把α调小让阈值先动起来再慢慢调大。5.2 加入收缩模块后准确率反而下降有时候你会发现加了残差收缩块后无噪声数据的准确率掉了。这通常是因为软阈值化把一些弱但有用的信号压掉了。解决办法有几个。一是减小α。α越小阈值上限越低压制的力度越弱。可以先从0.5开始试逐步增加。二是在浅层不用收缩块。浅层特征幅度小噪声和信号的区分度低软阈值化容易误伤。我一般只在stage3和stage4用收缩块stage1和stage2保持普通残差块。三是给阈值加一个下限。比如τ 0.1 0.9 * α * sigmoid(...)保证阈值至少0.1不会完全关闭通道。这个改动很小但能防止通道“死亡”。四是检查批归一化。如果批归一化的动量设置不当running_mean和running_var估计不准特征幅度会漂移阈值就失效了。我一般用默认动量0.1如果批太小就调到0.01。5.3 推理速度变慢的优化思路残差收缩块比普通残差块多了全局平均池化和两层全连接推理时间会增加。如果部署在边缘设备上这个开销可能不能忽略。我试过几种优化。合并全连接层阈值学习网络的两层全连接可以在推理时合并成一个矩阵乘法因为中间没有非线性如果中间用ReLU就不能合并。如果把中间激活去掉直接线性映射参数量不变但计算少一次。实测准确率掉0.2%左右推理快5%。降低缩放比把reduction从8调到16参数量减半推理快3%到5%。准确率影响很小因为阈值学习本身不需要太高的容量。用深度可分离卷积替代全连接有些实现用1x1卷积做通道压缩效果和全连接差不多但在某些硬件上更快。这个要看具体部署平台。量化软阈值化和阈值学习网络都可以量化到INT8。注意Sigmoid在量化后精度会掉可以用查表法或者分段线性近似。我试过TensorRT的INT8量化推理速度提升2倍多准确率掉0.5%以内。5.4 常见问题速查表问题现象可能原因排查方法解决措施阈值全部接近0α太小或初始化太小打印阈值直方图增大α检查fc2初始化阈值全部接近αα太大或梯度爆炸检查梯度范数减小α加梯度裁剪准确率无噪声时下降软阈值化误伤弱信号对比有无收缩块的准确率减小α浅层不用收缩块准确率有噪声时没提升阈值没学到有效值可视化阈值分布单独调大阈值网络学习率训练后期loss震荡阈值更新太剧烈观察阈值变化曲线减小阈值网络学习率加动量推理速度慢全连接层开销大profile各层耗时合并全连接降低缩放比批归一化后阈值失效running stats不准检查批大小和动量增大批大小调小动量5.5 几个容易忽略的实操心得第一个心得阈值学习网络的输入不一定要用全局平均池化。我试过用全局最大池化在脉冲信号上效果更好因为脉冲的峰值信息比均值更重要。也试过用均值和最大值的拼接效果介于两者之间。这个可以根据数据特性选。第二个心得软阈值化可以放在批归一化之前。虽然我前面说之后更好但在某些任务里先收缩再归一化能更好地抑制噪声。我建议两种都试用验证集选。第三个心得残差收缩块和dropout可以共存。有人觉得软阈值化已经去噪了不需要dropout。但我在全连接层前加dropout还是能提升泛化。两者作用机制不同软阈值化抑制特征幅度dropout抑制神经元共适应。第四个心得不要在所有通道上用同一个α。如果某些通道的特征幅度天然比其他通道大统一α会导致大通道阈值偏小、小通道阈值偏大。我试过让α也变成可学习的每个通道一个α效果有提升但参数量增加。折中方案是按通道组设α比如每8个通道一组。第五个心得训练时可以用阈值正则化。给阈值加一个L1惩罚鼓励阈值稀疏也就是让更多通道的阈值接近0或α。这样能增强特征选择的激进程度。惩罚系数取1e-5到1e-4太大反而会压制有用通道。6. 从实验到落地我的实际项目体会我在一个工业振动信号分类项目里完整用了深度残差收缩网络。数据是加速度传感器采集的采样率12.8kHz每段信号1024个点做一维卷积。原始数据里混有工频干扰和电磁噪声信噪比大概5dB。一开始用普通ResNet-1D测试准确率只有76%。换成残差收缩块后准确率提到88%。关键改动就是把ReLU换成可学习软阈值化阈值学习网络用SE结构。后来我又试了CBAM版准确率到89%但推理时间从8ms涨到11ms最后没采用因为产线要求单次推理不超过10ms。调参过程中最大的坑是α。一开始设2.0结果浅层通道大量关闭准确率反而掉到72%。后来把α降到1.0只在深层用收缩块准确率才上来。所以我的经验是α宁小勿大收缩块宁深勿浅。另一个体会是阈值学习网络的学习率。我一开始和主干一样用0.01阈值几乎不动。后来调到0.05阈值开始有明显变化准确率也涨了3个点。但调到0.1又震荡了最后定在0.03。部署时用了ONNX导出注意软阈值化的torch.sign在ONNX里对应Sign算子F.relu对应Relu都能正常导出。但阈值学习网络里的view操作要小心ONNX对动态形状支持有限最好固定批大小。我导出时把批大小固定为1推理时逐样本处理延迟可以接受。如果让我给初学者一个建议我会说先把普通ResNet跑通再加残差收缩块对比有无收缩块的中间层特征分布。你会直观看到加了收缩块后噪声通道的响应被压到接近0有用通道的响应更集中。这种可视化比看准确率数字更有说服力。这个结构后续还可以往几个方向扩展。一是把软阈值化用到时序注意力机制里对时间步做收缩二是把阈值学习和通道-空间协同注意力结合做更精细的噪声抑制三是在联邦深度强化学习场景里用收缩块处理各客户端的异构噪声。这些我都还在试有结果再分享。
返回列表