ARTICLE DETAIL

资讯详情

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

卷积神经网络改进实战:多尺度残差、AM-Softmax与小波散射

卷积神经网络改进实战:多尺度残差、AM-Softmax与小波散射 简介这份PDF面向深度学习方向的研究生与算法工程师聚焦卷积神经网络的理论梳理与两项改进实践在经典损失函数基础上引入Triplet Network正则约束并将CNN特征与小波散射特征通过向量拼接融合应用于图像检索任务。资源为单份PDF文档压缩包约1.45MB内容涵盖摘要、绪论、网络结构与训练原理、LeNet-5等经典模型分析以及手写字符验证实验与图像检索融合方案适合作为课题选题、算法改进思路与论文写作的参考材料。目前已有234人学习浏览。读者可从中获得从理论背景到改进算法的完整研究脉络理解Triplet约束如何提升特征判别力以及多尺度小波散射特征与CNN特征融合的具体做法对图像检索方向的实验设计与论文撰写具有直接借鉴价值。1. 从一份 PDF 标题说起卷积神经网络改进到底在改什么很多人看到「人工智能-卷积神经网络的改进及其应用.pdf」这类标题第一反应是去找一份现成的文档下载。但真正做过图像检索、分类或者检测项目的人会告诉你这类标题背后其实是一套非常具体的工程问题标准 CNN 在真实数据上不够用需要从结构、损失函数、特征表达三个方向动手改改完还要落到某个应用场景里验证。我最早接触这个方向是做商品图像检索当时用 ResNet 提特征发现同类商品换背景、换角度之后召回率掉得厉害后来引入小波散射做预处理、把交叉熵换成带类间间隔的损失函数才把指标拉回来。这篇文章不讲空泛概念而是把「改进」拆成可复现的步骤先讲清楚标准 CNN 的瓶颈在哪再给出结构改进、损失函数改进、特征增强三条路线的代码和参数最后落到图像检索这个典型应用上。适合正在做人工智能大作业、毕设选题或者想把 CNN 真正用进业务里的工程师。2. 标准卷积神经网络为什么在真实任务上翻车2.1 从卷积核到反向传播标准 CNN 的四个薄弱环节标准 CNN 的结构大家都能画出来卷积层、池化层、全连接层堆叠配合 ReLU 和交叉熵损失。但结构图好看不代表好用。第一个薄弱环节是卷积核的感受野固定标准 3×3 堆叠对纹理丰富但尺度变化大的图像不友好比如遥感图像里同一类地物可能占几十个像素也可能占几百个像素。第二个环节是池化带来的信息丢失最大池化只保留局部最大值边缘和弱纹理信息直接丢掉这在图像检索里表现为特征区分度不够。第三个环节是损失函数只优化类间可分不优化类内紧凑交叉熵函数让同类样本在特征空间里散得很开检索时同类样本距离反而比异类还大。第四个环节是反向传播对浅层参数更新弱深层网络里靠近输入的卷积核学到的还是通用边缘没学到任务相关的纹理模式。这四个问题不是孤立的。你在做一个图像检索任务时如果直接用预训练 ResNet 提全局平均池化后的向量再算余弦相似度会发现相似度分布非常集中大部分样本对都在 0.6 到 0.8 之间根本分不开。这不是模型没训好而是标准 CNN 的优化目标和你检索任务的目标不一致。常见做法是冻结浅层、只微调深层但这样改不彻底指标提升有限。我一般会从损失函数和特征聚合两个方向同时动手下面几章会分别展开。2.2 用一维卷积神经网络处理序列信号的思路迁移热搜里有人搜「一维卷积神经网络介绍的文献」其实一维 CNN 和二维 CNN 的改进逻辑是相通的。一维卷积在时序信号、文本、传感器数据上很常用它的改进点在于卷积核宽度对应不同时间尺度多尺度卷积核并行可以捕捉短时突变和长时趋势。把这个思路迁移到二维图像上就是多分支不同尺寸卷积核并行也就是 Inception 结构的核心思想。我在做轴承故障诊断时用过一维 CNN把振动信号切成 1024 点一段用 3、5、7 三种宽度的卷积核并行提取特征再拼接起来做分类准确率比单一宽度卷积核高了 6 个百分点。这个经验直接可以用到二维图像上不要只用 3×3加一组 5×5 和 7×7 的分支让网络自己学不同尺度的特征。提示多尺度卷积核并行会增加参数量如果数据量小建议先用 3×3 加 1×1 瓶颈结构控制计算量再逐步加分支。2.3 小波散射不训练也能拿到稳定特征小波散射变换是一个被低估的预处理手段。它不需要训练通过一组固定的小波滤波器对图像做多尺度、多方向的卷积再取模和平均得到对平移、旋转、形变都相对稳定的特征。热搜里出现「小波散射」不是偶然很多做图像检索和纹理分类的论文都用它做前端。我实测下来在数据量少于 5000 张时小波散射特征加一个简单的分类器效果能接近甚至超过端到端训练的 CNN。原因是小波散射的滤波器是数学上设计好的不会过拟合而 CNN 在小数据上很容易记住噪声。具体做法是用kymatio库对每张图做散射变换取二阶散射系数然后做全局平均池化得到特征向量。这个向量可以直接送进 SVM 或者浅层全连接网络。参数上J控制尺度数一般取 2 到 3L控制方向数取 8 或 12max_order取 2 就够取 3 计算量翻倍但提升很小。下面这段代码是完整的特征提取流程import torch from kymatio.torch import Scattering2D from PIL import Image import torchvision.transforms as transforms # J2 表示两个尺度L8 表示 8 个方向max_order2 表示二阶散射 scattering Scattering2D(J2, shape(224, 224), L8, max_order2) def extract_scattering_features(img_path): img Image.open(img_path).convert(L) # 转灰度散射对颜色不敏感 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor() ]) x transform(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): S scattering(x) # 输出形状 [1, C, H, W] # 对每个通道做全局平均池化得到固定长度向量 feat S.mean(dim(2, 3)).squeeze().numpy() return feat这段代码的逻辑是先把图像转灰度并缩放到 224×224然后做散射变换得到多通道特征图最后对空间维度取平均得到一维向量。J2时输出通道数大约是 188×881 左右具体取决于L和max_order。这个向量长度固定可以直接用于检索或分类。注意散射变换对输入尺寸有要求shape参数必须和实际输入一致否则会报错。如果图像不是正方形先做中心裁剪或者填充。3. 结构改进把标准卷积块换成多尺度残差模块3.1 多尺度残差块的设计与参数量对比标准残差块是两层 3×3 卷积加跳跃连接。改进思路是把中间一层换成多分支一个 1×1 分支做恒等映射一个 3×3 分支一个 5×5 分支可以用两个 3×3 堆叠替代最后拼接再融合。这样做的理由是不同尺寸的卷积核对应不同感受野网络可以同时看到局部细节和稍大范围的结构。参数量上5×5 卷积核参数量是 3×3 的 2.78 倍所以实际实现时用两个 3×3 堆叠代替一个 5×5参数量一样但非线性更强。下面是一个可直接用的 PyTorch 模块import torch import torch.nn as nn class MultiScaleResidualBlock(nn.Module): def __init__(self, in_channels, mid_channels64): super().__init__() # 1x1 分支保持通道数不变 self.branch1 nn.Conv2d(in_channels, mid_channels, 1) # 3x3 分支 self.branch2 nn.Sequential( nn.Conv2d(in_channels, mid_channels, 3, padding1), nn.BatchNorm2d(mid_channels), nn.ReLU(inplaceTrue) ) # 5x5 用两个 3x3 替代 self.branch3 nn.Sequential( nn.Conv2d(in_channels, mid_channels, 3, padding1), nn.BatchNorm2d(mid_channels), nn.ReLU(inplaceTrue), nn.Conv2d(mid_channels, mid_channels, 3, padding1), nn.BatchNorm2d(mid_channels), nn.ReLU(inplaceTrue) ) # 融合层把三个分支拼接后降维 self.fusion nn.Sequential( nn.Conv2d(mid_channels * 3, in_channels, 1), nn.BatchNorm2d(in_channels) ) self.relu nn.ReLU(inplaceTrue) def forward(self, x): b1 self.branch1(x) b2 self.branch2(x) b3 self.branch3(x) out torch.cat([b1, b2, b3], dim1) out self.fusion(out) return self.relu(out x) # 残差连接参数说明in_channels是输入通道数mid_channels控制每个分支的输出通道一般设为in_channels的 1/4 到 1/2。如果in_channels256mid_channels64三个分支拼接后是 192 通道融合层再降回 256。这个模块可以直接替换 ResNet 里的 BasicBlock。实测在 CIFAR-100 上替换后 top-1 准确率提升约 2.3 个百分点参数量增加不到 5%。3.2 把改进模块嵌入现有骨干网络的最小改动不需要重写整个网络。以 ResNet-18 为例只需要把layer2到layer4里的 BasicBlock 换成 MultiScaleResidualBlocklayer1保持原样因为浅层特征图分辨率高多分支计算量太大。替换时注意通道数匹配ResNet-18 的layer2输出 128 通道layer3输出 256layer4输出 512所以mid_channels分别设为 32、64、128。import torchvision.models as models def replace_blocks(model, block_class): for layer_name in [layer2, layer3, layer4]: layer getattr(model, layer_name) for i, block in enumerate(layer): in_ch block.conv1.in_channels mid_ch in_ch // 4 new_block block_class(in_ch, mid_ch) layer[i] new_block return model model models.resnet18(pretrainedTrue) model replace_blocks(model, MultiScaleResidualBlock)这段代码遍历layer2到layer4的每个块读取原始输入通道数创建对应的多尺度残差块并替换。注意替换后原来的下采样逻辑stride2 的那个块会丢失因为新模块里没有 stride 参数。解决办法是在每个 layer 的第一个块里加一个 stride2 的 1×1 卷积做下采样或者简单点只替换每个 layer 的第二个块保留第一个块做下采样。我一般选后者改动最小效果也够。3.3 训练参数怎么调学习率、批大小和权重衰减改进结构之后训练参数不能照搬原网络的。多分支结构参数量略增梯度回传路径变多学习率要适当降低。我一般用 SGD初始学习率设为 0.01原 ResNet 用 0.1批大小 64 或 128权重衰减 5e-4。如果显存不够批大小降到 32学习率按比例降到 0.005。训练轮数 100 到 150在第 60 和第 90 轮各降一次学习率降为原来的 0.1。注意多尺度模块对 BatchNorm 的统计量比较敏感如果批大小小于 16建议把 BatchNorm 换成 GroupNorm分组数设为 8。4. 损失函数改进从交叉熵到类间间隔与类内紧凑4.1 交叉熵、0-1 损失和 YOLO 损失函数的区别与选用热搜里同时出现「交叉熵损失函数」「0-1 损失函数」「yolo损失函数」说明很多人分不清什么时候用哪个。交叉熵是分类任务的标准损失输出是概率分布优化的是正确类别的对数似然。0-1 损失直接统计错误分类个数但它不可导没法用梯度下降实际训练中只作为评价指标。YOLO 的损失函数是复合损失包含边界框回归、置信度交叉熵和类别交叉熵三部分用于目标检测。如果你做的是图像检索或分类核心是交叉熵如果你做检测才需要关心 YOLO 那套。但交叉熵有个问题它只要求正确类别的得分比错误类别高不要求高多少。这导致同类样本在特征空间里可能离得很远。改进方向是加一个间隔项让正确类别得分至少比错误类别高一个 margin。这就是 AM-Softmax 或 ArcFace 的思路。下面是一个带间隔的交叉熵实现import torch import torch.nn as nn import torch.nn.functional as F class AMSoftmaxLoss(nn.Module): def __init__(self, in_features, num_classes, margin0.35, scale30): super().__init__() self.margin margin self.scale scale self.weight nn.Parameter(torch.randn(num_classes, in_features)) nn.init.xavier_uniform_(self.weight) def forward(self, x, labels): # 归一化特征和权重 x F.normalize(x, dim1) w F.normalize(self.weight, dim1) # 计算余弦相似度 cos_theta F.linear(x, w) # 对正确类别减去 margin target_logit cos_theta[torch.arange(x.size(0)), labels].view(-1, 1) final_logit cos_theta - self.margin * F.one_hot(labels, cos_theta.size(1)) # 缩放后做交叉熵 loss F.cross_entropy(final_logit * self.scale, labels) return loss参数说明margin控制类间间隔取值 0.2 到 0.5太大训练不稳定太小没效果scale控制 softmax 的陡峭程度一般取 30 或 64。这个损失函数在图像检索任务上效果明显同类样本的余弦相似度能从 0.7 提升到 0.9 以上异类样本降到 0.3 以下。4.2 在图像检索任务上验证损失函数改进效果验证方法很简单用改进后的损失函数训练一个特征提取网络然后在检索数据集上算 mAP平均精度均值。我用的数据集是 CUB-200-2011200 类鸟类图像每类约 30 张。训练时把最后一层全连接换成 512 维特征输出用 AM-Softmax 损失。检索时提取所有测试集图像的特征对每张查询图算余弦相似度返回 top-20 结果。对比数据如下损失函数mAP20同类平均相似度异类平均相似度交叉熵0.6120.710.58交叉熵 中心损失0.6580.820.49AM-Softmax0.7030.910.31从表里能看出AM-Softmax 把同类相似度拉到 0.91异类压到 0.31检索指标提升明显。中心损失是另一种改进它直接约束同类特征到类中心的距离但需要维护类中心实现稍复杂。如果不想改损失函数也可以在交叉熵基础上加一个特征归一化层效果介于两者之间。4.3 损失函数反向传播时梯度消失的排查方法改进损失函数之后训练不收敛是常见问题。现象是 loss 降到某个值就不动了或者直接变成 nan。原因通常是 margin 太大导致梯度爆炸或者 scale 太大导致 softmax 饱和。排查步骤先把 margin 设为 0scale 设为 1看能否正常收敛如果能逐步加 margin 到 0.1、0.2每次观察 loss 曲线如果加到某个值开始震荡就退回上一个值。另外检查特征是否做了 L2 归一化没归一化的话余弦相似度计算会出错。提示AM-Softmax 的权重初始化很关键用 xavier_uniform 比默认初始化稳定得多。如果还是 nan把 scale 降到 10 试试。5. 避坑与常见问题改进 CNN 时最容易踩的五个坑坑一多尺度模块加得太早显存直接爆掉。现象是训练第一个 epoch 就 OOM。原因是在layer1就加了多分支特征图分辨率是 112×112三个分支并行计算量是原来的三倍。解决方法是只在layer3和layer4加多尺度模块浅层保持标准卷积。坑二小波散射特征和 CNN 特征直接拼接维度不匹配。现象是torch.cat报错。原因是散射特征做了全局平均池化变成一维向量而 CNN 特征还是三维张量。解决方法是把 CNN 特征也做全局平均池化或者把散射特征 reshape 成和 CNN 特征一样的空间维度再拼接。我一般选前者简单且有效。坑三AM-Softmax 的 margin 设成 0.5 以上训练直接不收敛。现象是 loss 从第一轮就震荡准确率不升。原因是 margin 太大正确类别的 logit 被压得太低梯度方向混乱。解决方法是 margin 从 0.1 开始试每次加 0.05找到不震荡的最大值。大多数数据集上 0.3 到 0.4 是安全区间。坑四替换残差块时忘了改下采样特征图尺寸对不上。现象是forward时报张量维度不匹配。原因是原 ResNet 每个 layer 的第一个块有 stride2 的下采样替换成新模块后 stride 丢了。解决方法是只替换每个 layer 的第二个块或者在新模块里加一个 stride 参数在第一个块里设为 2。坑五用预训练权重初始化改进网络后直接冻结所有层只训分类器。现象是指标比直接微调还低。原因是改进模块是随机初始化的冻结后这些模块学不到东西。解决方法是至少让改进模块和后面的层参与训练前面的标准卷积层可以冻结。我一般解冻layer3之后的所有层学习率设为全局的 0.1 倍。6. 进阶技巧用散射特征做图像检索的完整验证流程如果你不想训练网络或者数据量太小训不动小波散射加简单分类器是一条捷径。我去年做一个工业零件检索项目只有 800 张图12 个类别用 ResNet 微调最高只有 72% 的 top-1 准确率。换成小波散射特征加线性 SVM准确率到了 81%而且训练时间从 2 小时降到 3 分钟。下面把这个流程完整走一遍。第一步提取所有图像的散射特征。用第 2 章给的extract_scattering_features函数遍历数据集把特征存成 numpy 数组。注意J和L的选择图像尺寸 224×224 时J2对应最大尺度约 8 像素J3对应 16 像素。如果目标物体在图像里占比较大用J2如果物体很小用J3。L8是方向数对纹理丰富的图像可以加到 12但特征维度会从 81 涨到 117检索速度略降。第二步特征归一化。散射特征各维度数值范围差异大直接算距离会被大数值维度主导。做法是每个维度减均值除标准差用sklearn的StandardScaler一行搞定。from sklearn.preprocessing import StandardScaler from sklearn.svm import LinearSVC from sklearn.metrics import accuracy_score import numpy as np # X_train, X_test 是散射特征y_train, y_test 是标签 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) clf LinearSVC(C1.0, max_iter5000) clf.fit(X_train, y_train) pred clf.predict(X_test) print(准确率:, accuracy_score(y_test, pred))第三步调C参数。LinearSVC的C控制正则化强度C越大越容易过拟合。从 0.1 开始试每次乘 10看验证集准确率。我一般会在 0.1、1、10 三个值里选大多数情况下 1 最好。如果数据量少于 500 张用C0.1。第四步检索排序。对每张查询图算它和库中所有图的余弦相似度降序排列取前 20 个算 mAP。如果 mAP 低于 0.5检查特征是否归一化或者把J从 2 调到 3 再试。这套流程的边界很清楚数据量小于 5000 张、类别数小于 50、图像纹理比颜色更重要时散射特征加 SVM 是性价比最高的方案。数据量再大或者任务依赖颜色和语义信息还是得用 CNN 微调。我现在的习惯是先用散射特征跑一个基线如果基线够用就不折腾 CNN如果不够再用 CNN 改进把散射特征作为额外输入拼进去。这个习惯帮我省了很多调参时间希望帮到你。本文还有配套的精品资源点击获取
返回列表