ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

卷积神经网络改进实战:可变形卷积、Focal Loss与小波散射

卷积神经网络改进实战:可变形卷积、Focal Loss与小波散射 简介这份PDF文档面向深度学习与计算机视觉方向的研究者、研究生及算法工程师聚焦卷积神经网络的理论梳理与算法改进适合希望深入理解CNN损失函数设计、特征融合与图像检索应用的读者。资源包为单一PDF文件共1个文件大小约1.45MB内容为完整的硕士学位论文包含中英文摘要、目录、绪论及各章节正文结构规范便于按章节研读。目前已有234人学习浏览。文档系统介绍了卷积神经网络的结构、训练过程与经典模型LeNet-5重点提出在传统损失函数基础上引入Triplet Network正则约束的改进算法并在手写字符识别任务上验证其有效性同时将CNN提取的特征与小波散射网络特征通过向量拼接融合应用于图像检索领域。读者可从中获取完整的理论推导、改进思路、实验验证方法及特征融合策略对开展相关课题研究或工程实践具有参考价值。1. 从一份 PDF 标题说起卷积神经网络改进到底在改什么很多人看到「人工智能-卷积神经网络的改进及其应用.pdf」这类标题第一反应是去找一份现成的文档下载。但真正做过图像方向落地的人会告诉你这类标题背后其实藏着一个非常具体的技术问题标准 CNN 在真实场景里不够用了得改。改哪里改卷积核结构、改损失函数、改特征融合方式或者干脆换一种特征提取思路比如小波散射。应用场景也很明确图像检索、分类、检测这些任务里CNN 是主力但主力也有翻车的时候。这篇文章不打算复述某份 PDF 的内容而是顺着这个标题把「卷积神经网络的改进」拆成能动手复现的路径。适合两类人一是正在做人工智能大作业或毕设选题、需要跑通一个改进 CNN 并写进论文的学生二是已经用过 YOLO 或 ResNet 做项目、想搞清楚改进点到底值不值得投入的工程师。读完你至少能判断哪些改进是真有效哪些只是论文里的玄学。2. 卷积神经网络改进的三个主攻方向结构、损失函数、特征前端2.1 结构改进从标准卷积到可变形卷积与深度可分离标准卷积的问题在于卷积核的采样位置是固定的矩形网格。遇到目标形变、尺度变化大的场景固定网格就抓不住关键特征。可变形卷积Deformable Convolution的思路是给每个采样点学一个偏移量让卷积核能「自适应」地贴到目标形状上。这在图像检索里特别有用因为检索库里的物体姿态千奇百怪。另一个方向是深度可分离卷积把标准卷积拆成逐通道卷积和逐点卷积参数量和计算量能降一个数量级。移动端部署 CNN 时这是最常见的改进手段。但要注意深度可分离卷积在小模型上容易欠拟合通道数太少时特征表达能力会明显下降。我一般会先跑一个基线比如 ResNet-18 或一个 5 层的小 CNN记录准确率和推理耗时。然后只改一个点比如把某个 stage 的标准卷积换成可变形卷积看指标变化。不要一次改三个地方否则你根本不知道是哪个改进起了作用。2.2 损失函数改进交叉熵之外的选择热搜里「损失函数」「交叉熵损失函数」「yolo损失函数」出现频率很高说明这是大家真正卡住的地方。分类任务默认用交叉熵但交叉熵对类别不平衡很敏感。图像检索里经常出现「一个 query 对应多个正样本」的情况这时候用对比损失Contrastive Loss或三元组损失Triplet Loss更合适。YOLO 系列的损失函数是另一个典型。YOLOv8 的损失由分类损失、回归损失和置信度损失三部分组成回归部分用的是 CIoU Loss。如果你要改进 YOLO 的损失函数常见做法是替换 CIoU 为 EIoU 或 SIoU或者在分类分支引入 Focal Loss 处理难易样本不平衡。import torch import torch.nn as nn class FocalLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, logits, targets): # logits: [N, C], targets: [N] ce_loss nn.functional.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) # 预测正确的概率 focal_loss self.alpha * (1 - pt) ** self.gamma * ce_loss return focal_loss.mean()这段代码的逻辑是对每个样本先算交叉熵再根据预测置信度加权。alpha控制正负样本权重gamma控制难易样本的聚焦程度。gamma2是原论文的默认值实际用的时候如果数据集噪声大可以降到 1.0 左右。注意 Focal Loss 在类别极度不平衡时有效但如果你的数据集本身比较均衡换它可能反而掉点。2.3 特征前端改进小波散射为什么值得关注小波散射变换Wavelet Scattering Transform是一种非学习的特征提取方法它通过多尺度小波分解和模运算得到对平移、旋转、形变稳定的特征表示。和 CNN 最大的区别是它不需要训练参数是固定的。这意味着在小样本场景下小波散射不会过拟合。把散射特征和 CNN 结合有两种常见做法一是用散射系数作为 CNN 的输入通道替代原始 RGB二是在 CNN 的浅层用散射变换做下采样保留更多高频信息。图像检索任务里散射特征对纹理和边缘的刻画比原始像素强很多尤其是医学图像和遥感图像。但小波散射的计算量不小尤其是多阶散射。我一般只在数据量小于 5000 张、或者纹理特征比语义特征更重要的时候才考虑它。如果你的数据集有几十万张图老老实实用 CNN 加数据增强更划算。3. 用 PyTorch 跑通一个改进 CNN 的最小闭环3.1 环境准备与数据加载先确认环境PyTorch 1.12 以上torchvision 对应版本GPU 显存至少 4GB。数据集用 CIFAR-10 或自己的一份图像分类数据目录结构按train/类别名/图片和val/类别名/图片组织。import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms train_tf transforms.Compose([ transforms.Resize((128, 128)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(data/train, transformtrain_tf) val_ds datasets.ImageFolder(data/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4)Resize到 128 是为了在显存和特征分辨率之间取平衡。Normalize的均值和方差用的是 ImageNet 的统计值如果你自己的数据集分布差异大建议重新算一遍。num_workers设成 4 是经验值设太高在 Windows 上容易卡死。3.2 定义一个带可变形卷积的改进网络下面这个网络在 ResNet 的基本残差块里把第二个 3x3 卷积换成了可变形卷积。PyTorch 从 1.10 开始内置了torchvision.ops.DeformConv2d不需要额外编译。import torch.nn as nn from torchvision.ops import DeformConv2d class DeformResBlock(nn.Module): def __init__(self, in_ch, out_ch, stride1): super().__init__() self.conv1 nn.Conv2d(in_ch, out_ch, 3, stride, 1, biasFalse) self.bn1 nn.BatchNorm2d(out_ch) # 可变形卷积需要额外的偏移量卷积层 self.offset_conv nn.Conv2d(out_ch, 18, 3, 1, 1) self.deform_conv DeformConv2d(out_ch, out_ch, 3, 1, 1, biasFalse) self.bn2 nn.BatchNorm2d(out_ch) self.relu nn.ReLU(inplaceTrue) self.downsample None if stride ! 1 or in_ch ! out_ch: self.downsample nn.Sequential( nn.Conv2d(in_ch, out_ch, 1, stride, biasFalse), nn.BatchNorm2d(out_ch) ) def forward(self, x): identity x out self.relu(self.bn1(self.conv1(x))) offset self.offset_conv(out) # 学习采样偏移 out self.deform_conv(out, offset) out self.bn2(out) if self.downsample is not None: identity self.downsample(x) out identity return self.relu(out)关键参数说明offset_conv输出通道是 18因为 3x3 卷积核有 9 个采样点每个点需要 x 和 y 两个方向的偏移所以 9×218。DeformConv2d的stride和padding要和普通卷积保持一致否则特征图尺寸对不上。这个块可以直接替换 ResNet 里的BasicBlock但建议只替换 stage3 和 stage4浅层用可变形卷积收益不大反而增加计算量。3.3 训练循环与损失函数切换训练循环里最关键的是损失函数的选择和验证指标的记录。下面这段代码同时支持交叉熵和 Focal Loss通过参数切换。import torch.optim as optim from tqdm import tqdm def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0, 0, 0 for imgs, labels in tqdm(loader): imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) preds outputs.argmax(dim1) correct (preds labels).sum().item() total imgs.size(0) return total_loss / total, correct / total # 使用方式 device torch.device(cuda if torch.cuda.is_available() else cpu) model DeformResNet(num_classes10).to(device) criterion FocalLoss(alpha0.25, gamma2.0) # 或 nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4)AdamW的weight_decay设 1e-4 是 Transformer 类模型的常用值CNN 上也可以用 5e-4。学习率 1e-3 配合 AdamW 一般不会炸但如果 loss 出现 NaN先降到 1e-4。验证时记得切model.eval()并关闭梯度否则 BatchNorm 的统计量会跑偏。4. 改进 CNN 落地时最容易翻车的四个地方4.1 现象换了损失函数训练 loss 下降但验证指标不动原因通常是损失函数和评价指标不匹配。比如你换了 Triplet Loss 做图像检索但验证时只看分类准确率这两个目标不一致。Triplet Loss 优化的是特征空间里的距离分类准确率不一定同步提升。解决验证阶段直接用检索指标比如 mAP10 或 Recall1。如果必须看分类指标至少加一个特征归一化后的余弦相似度监控。4.2 现象可变形卷积的 offset 学出来全是零原因是 offset 卷积层的初始化有问题或者学习率太小。offset 初始输出接近零时可变形卷积退化成普通卷积梯度传不回去。解决把offset_conv的权重初始化成小方差的高斯分布偏置置零。学习率至少保持 1e-3不要用太小的 lr 训 offset 分支。4.3 现象小波散射特征和 CNN 特征拼接后模型不收敛原因是两路特征的量纲差异太大。散射系数通常在 0 到 1 之间而 CNN 中间层特征可能到几十。直接 concat 会让某一路主导梯度。解决在 concat 之前对两路特征各加一个 BatchNorm 或 LayerNorm。如果还是不行先单独训 CNN 分支冻结后再训融合层。4.4 现象YOLOv8 换损失函数后画出来的损失曲线震荡剧烈热搜里「yolov8画损失函数曲线图」很多人搜但换损失后曲线震荡是常见问题。原因是新损失的量级和原损失不匹配比如 EIoU 的数值范围比 CIoU 大导致总损失被回归项主导。解决给不同损失项加权重系数分类、回归、置信度三项的权重分别调。YOLOv8 默认是 0.5、7.5、0.5换损失后回归权重先降到 5.0 试。曲线图用 TensorBoard 或 CSV 日志画不要只看终端输出。5. 怎么验证你的改进真的有效消融实验与特征可视化5.1 消融实验的最小设计改进 CNN 最怕的是「改了一堆涨了点但不知道哪个有用」。消融实验就是每次只去掉一个改进点看指标掉多少。下面是一个典型的消融表格模板你可以直接套。实验编号可变形卷积Focal Loss小波散射前端准确率mAP10baseline否否否82.30.61A是否否84.10.63B否是否83.50.62C否否是83.80.66D是是是85.20.68从这张表能看出小波散射对检索指标 mAP 的提升最明显可变形卷积对分类准确率贡献更大。如果你的任务是图像检索优先加散射前端如果是分类优先改卷积结构。5.2 用 t-SNE 看特征空间有没有变好指标涨了不代表特征学好了。用 t-SNE 把验证集的特征降到二维看类别是否聚得更紧。改进有效的标志是同类样本抱团不同类之间边界清晰。如果 t-SNE 图上还是混在一起说明改进只记住了训练集泛化没提升。from sklearn.manifold import TSNE import matplotlib.pyplot as plt def visualize_features(model, loader, device): model.eval() feats, labels [], [] with torch.no_grad(): for imgs, lbls in loader: imgs imgs.to(device) # 取全局平均池化前的特征 f model.extract_features(imgs) feats.append(f.cpu()) labels.append(lbls) feats torch.cat(feats).numpy() labels torch.cat(labels).numpy() tsne TSNE(n_components2, perplexity30, random_state42) emb tsne.fit_transform(feats) plt.scatter(emb[:, 0], emb[:, 1], clabels, cmaptab10, s5) plt.savefig(tsne.png, dpi150)perplexity设 30 是常用值样本少于 1000 时降到 10。extract_features需要你在模型里单独定义一个方法返回池化后的特征向量不要用分类头的输出。5.3 一个我踩过的坑验证集不能用来调改进点早期我做改进 CNN 的时候习惯在验证集上看效果然后决定加不加某个模块。结果论文写出来测试集一跑就掉点。后来才明白验证集一旦参与决策它就不再是「验证」了变成了第二个训练集。正确做法是训练集训模型验证集只用来早停和选超参改进点的取舍靠交叉验证或者单独划一份 dev set。如果数据量实在小至少做 5 折交叉验证看均值和方差。方差大的改进点宁可不要。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进