三流卷积神经网络模型:交叉间隔冻结与多流特征融合实战 简介这是一份发表于《江西理工大学学报》的学术论文PDF聚焦基于三流卷积神经网络模型的图像分类问题旨在解决单个卷积网络提取图像特征不充分、不有效的问题。文章面向从事深度学习、机器学习与图像识别研究的读者提出由三个网络流组成的三流CNN结构第一、第二网络流采用“交叉野间隔”方式训练提取互补性强的图像特征第三网络流采用初始参数以增强模型泛化能力。每个网络流分别训练分类器再运用分类器融合算法赋予不同权重得到三流融合输出完成最终分类。方法在CIFAR-100、Stanford Dogs、UEC FOOD-100数据集上验证了有效性和鲁棒性。资源为单份PDF全文约878KB涵盖模型结构框图、实验数据与结果对比可作为研究生论文选题、模型创新设计或课程汇报的参考目前已有139人学习下载。1. 三流卷积神经网络模型这篇论文到底在解决什么问题“三流卷积神经网络模型”这个名词出自某高校学报 2019 年一篇图像分类论文核心思路很直接把同一个基础网络复制成三个流用交叉间隔冻结的方式让前两个流学互补特征第三个流保留预训练参数兜底最后把三个分类器的输出做加权融合。反直觉的点在于模型没有变深只是横向加了两条支路就能在 CIFAR-100 上把单流 ResNet-20 的 69.09% mAP 拉到 74.54%涨了 5 个多点。这份 PDF 里包含完整的模型结构框图、交叉冻结的层配置、三份数据集上的超参数表和实验结果对比适合正在做图像分类、细粒度识别或多模型集成复现的从业者拿来当参考底稿很实用。2. 三流模型的骨架拆解三个相同子网络靠什么学到不同特征2.1 三流结构特征提取、分类器、融合三部分怎么对接论文的三流框架由 S1、S2、S3 三个子网络组成以 CaffeNet 为例每个子网络是 5 个卷积层接 3 个全连接层。特征提取部分由前五个卷积层完成第六、七层是带 dropout 的全连接层第八层全连接层输出分类得分并接 softmax 计算交叉熵损失。三个子网络结构完全一致关键差异在训练方式。S1 和 S2 的卷积层用交叉间隔法冻结不同层S3 直接用预训练参数。三个流各自输出一个分类结果每个流都训练一个独立分类器最后把三个分类器的全连接层输出做加权求和再送进 softmax 得到融合结果。整体框架的划分很清楚特征提取部分是卷积层分类器部分是每个流的全连接层加 softmax融合部分是加权求和加 softmax。这种设计的逻辑是单个深度卷积网络要在一套参数里同时学会边缘、纹理、部件到语义的全部层次特征压力很大。多流结构把特征提取任务拆给三个网络流每个流负责一部分特征偏好最后合并。论文里的图 1 画的就是这个流程S1、S2、S3 三个流独立走特征提取和分类器然后汇入 Fuse(SUM) 层做加权求和。2.2 交叉间隔式训练S1 和 S2 冻结层的具体配置交叉间隔训练是这篇论文最核心的操作原文写作“交叉野间隔冶”实际是 OCR 把“间”识别成了“野”意思就是隔层冻结。具体做法S1 固定第二层和第四层卷积层的权值与偏置学习率S2 固定第一层、第三层和第五层卷积层S3 不做冻结使用 ImageNet 预训练参数。在 Caffe 的 prototxt 里固定某层参数的标准做法是把 lr_mult 和 decay_mult 都设为 0。换成 PyTorch 写等价逻辑是这样的def cross_interval_freeze(model, stream_id): stream_id: S1 或 S2 按论文的交叉间隔策略冻结部分卷积层参数 freeze_s1 [conv2, conv4] # S1 固定第2、4层 freeze_s2 [conv1, conv3, conv5] # S2 固定第1、3、5层 freeze_layers freeze_s1 if stream_id S1 else freeze_s2 for name, param in model.named_parameters(): if any(f_layer in name for f_layer in freeze_layers): param.requires_grad False print(f[{stream_id}] frozen: {name})这段代码的核心是 freeze_layers 列表。用流名称判断该冻结哪些层S1 和 S2 的冻结列表错开保证两条路径学到的中间特征不同。requires_grad 置为 False 后反向传播时这些层的梯度不会更新参数始终保持初始值。decay_mult0 是防止权重衰减动到已冻结参数转 PyTorch 后只需要关注 requires_grad但如果你手动实现 L2 正则要额外跳过冻结参数。为什么不直接随机初始化三个流随机初始化加上相同数据训练出来的网络很容易收敛到相近的解特征冗余。隔层冻结相当于给 S1 和 S2 划定了互不重叠的可学习参数子集梯度回传路径不同中间特征分布就拉开了。S3 用 ImageNet 预训练参数保留通用的视觉先验专门补 S1、S2 被冻结层丢掉的信息。三流的第五层卷积输出都是 13×13×256 的特征图但可视化结果差别明显S1、S2 的特征图细节更丰富S3 的特征图区分性更强。2.3 分类器融合SUM 加权、Boosting 思想与损失函数三个流训练完各自分类器后融合方式用的是加权求和。权值确定参考了 Boosting 思想训练开始时给三个分类器相等的权值每训练一轮观察验证集准确率对表现好的分类器加大权重。这样能力越强的网络流在最终结果里话语权越大。融合损失函数是论文里的式(1)# 三个流的交叉熵损失按权重累加 lambda_s1, lambda_s2, lambda_s3 0.2, 0.1, 0.7 # 以CIFAR-100上三流NIN-3为例 loss_total (lambda_s1 * loss_s1 lambda_s2 * loss_s2 lambda_s3 * loss_s3)loss_s1、loss_s2、loss_s3 分别是三个流 softmax 层输出的交叉熵损失lambda 是各自权重。注意一个细节融合层的输出也要算损失参与反向传播所以总损失里其实还包含融合 softmax 的损失项论文表述里式(1)写的是三个子网络损失的加权和实际工程实现时通常会再加上融合分支自身的交叉熵。论文在 CIFAR-100 上三流 NIN-3 学到的权值是 [0.2, 0.1, 0.7]三流 ResNet-20 是 [0.1, 0.2, 0.7]第三个流权重最大是普遍现象因为 S3 用预训练参数初始化特征更稳定分类器性能最强。3. 在三份公开数据集上复现超参数、数据划分与训练流程3.1 CIFAR-100 / Stanford Dogs / UEC FOOD-100 的数据形态与划分论文选了三份图像分类常用数据集来验证它们的形态差异很大正好覆盖了通用分类和细粒度分类两种场景。数据划分方式直接照论文的设置就行下面整理成表方便对照。数据集图片总数类别数每类图片数训练集测试集分辨率特点CIFAR-10060000100600500001000032×32 低分辨率Stanford Dogs20580120148~252120008580中高分辨率UEC FOOD-10014461100101~729100004461中高分辨率含多目标CIFAR-100 是经典通用分类集32×32 的低分辨率对所有预训练模型都不友好。Stanford Dogs 是细粒度分类集合120 个犬种类间差异小考验特征提取的细腻程度。UEC FOOD-100 是日本食品识别集每张图里经常有多个食物区域论文里用的是候选区域检测后的分类结果训练集 10000、测试集 4461。做实验前先确认自己手里数据集的版本和划分方式尤其是 UEC FOOD-100不同版本的标注文件格式有差异下载后建议先统计每个类的样本数量跟论文的 101~729 对一下。3.2 三套超参数学习率、迭代策略、融合权值论文的实验环境是 Xeon E5-2690 v4 CPU、512GB 内存、4 块 16GB Tesla P100。注意这个配置暗示了一件事三流模型至少需要很大的显存单卡跑完整流程会吃紧后面避坑章再展开。各数据集上的超参数也不一样按数据集拆开看。CIFAR-100 上三流 NIN-3 的学习率设置参照 NIN 原作用一个相对高的初始学习率每训练 40000 次降为原来的 10%总共训练 20 万代。三流 ResNet-20 的学习率设 0.01同样是每 40000 次降 10%总迭代 20 万代。融合权值分别学到 [0.2, 0.1, 0.7] 和 [0.1, 0.2, 0.7]。Stanford Dogs 上三流 CaffeNet 学习率 0.001每 20000 次降为原来的 10%总迭代 6 万代三流 VGGNet 学习率 0.0001每 40000 次降 10%总迭代 12 万代。UEC FOOD-100 的设置跟 Stanford Dogs 基本一致。VGGNet 的初始学习率比 CaffeNet 低一个量级因为 VGGNet 更深梯度传播更不稳定大学习率容易炸。迭代策略可以用 PyTorch 的 StepLR 或 MultiStepLR 实现import torch.optim as optim from torch.optim.lr_scheduler import StepLR # 以Stanford Dogs上三流CaffeNet为例 optimizer optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay0.0005) scheduler StepLR(optimizer, step_size20000, gamma0.1)StepLR 的 step_size 对应论文里“每训练 20000 次降低学习率”gamma0.1 表示降为原来的 10%。迭代次数对应 step_size × 总阶段数比如 20000×360000 代。如果是 40000 次降一次、总共 20 万代就写成 step_size40000训练 5 个阶段。3.3 一个可跑通的训练流程骨架三流模型同单流模型最大的区别在于前向要同时过三个网络流反向要把三个损失加权合并后统一反传。下面这个骨架流程把数据加载、三流前向、融合损失、反向更新串起来直接照着调就能跑。# 伪代码结构三流训练主循环 for epoch in range(max_epoch): for images, labels in train_loader: # 三个流各自前向 out_s1 stream1(images) out_s2 stream2(images) out_s3 stream3(images) # 每个流单独计算交叉熵损失 loss_s1 criterion(out_s1, labels) loss_s2 criterion(out_s2, labels) loss_s3 criterion(out_s3, labels) # 融合输出也过softmax保留融合分支损失 fuse_out lambda_s1 * out_s1 lambda_s2 * out_s2 lambda_s3 * out_s3 loss_fuse criterion(fuse_out, labels) # 加权总损失 loss_total (lambda_s1 * loss_s1 lambda_s2 * loss_s2 lambda_s3 * loss_s3 loss_fuse) loss_total.backward() optimizer.step()几个地方需要说明。三个流各自算交叉熵这是论文式(1)的要求融合输出再过一次 softmax 算 loss_fuse这是实现上的常用做法能让融合权重也参与端到端训练。lambda 用当前轮次的融合权值注意论文提到的权值是“训练完成后学到的”实际训练过程中是动态调整的每轮观察三个流分类准确率后更新权重。训练时三个流都和输入图像直接相连相当于三份 batch 同时经过三个网络显存开销约等于同时跑三个相同模型。4. 换骨干网络从 CaffeNet 到 VGGNet、ResNet、NIN 的适配与收益4.1 四种骨干网络的选型理由与适配要点论文的实验用到了四种骨干网络CaffeNet、NIN、ResNet、VGGNet不是随便选的每种对应一个适配场景。CaffeNet 是 AlexNet 的变体5 卷积加 3 全连接结构直观好改适合当基线。NINNetwork in Network用多层感知卷积代替传统卷积核最后一层用全局平均池化替代全连接参数量小适合 CIFAR-100 这种低分辨率小数据集。ResNet 引入残差连接论文只取了前 20 层记为 ResNet-20在 CIFAR-100 上表现最好。VGGNet 是 16 层深度网络特征表达能力强但计算量大在 Stanford Dogs 和 UEC FOOD-100 这类中高分辨率细粒度数据上优势明显。替换骨干网络时有三个适配点要注意。第一个是分类器融合的位置。CaffeNet 和 VGGNet 都有全连接层融合点接在最后一个全连接层fc8输出上就行。NIN 没有传统全连接层融合点要改到最后一层卷积输出经全局平均池化之后相当于把每个流的全局平均池化结果做加权求和。第二个是冻结层的层名映射。不同骨干网络卷积层命名不同ResNet 里是 conv2_x、conv3_x 这样的 block 名VGGNet 里是 conv1_1、conv2_2 这样的细分结构。换成 PyTorch 时冻结列表里的层名要和实际模型定义一致建议先打印 model.named_parameters() 的 name 列确认。第三个是 ResNet 的残差连接对冻结操作有额外影响。冻结 conv2_x 后conv3_x 的 shortcut 分支可能仍然从输入直接连过来这部分特征不经过被冻结层梯度路径就还在。写冻结逻辑时要把残差分支的参数也纳入检查否则会出现“名义冻结了实际梯度没断”的乌龙。4.2 三流 VGGNet 和 ResNet-20 的表现mAP 提升幅度论文用 mAP平均精度均值评测mAP 同时考虑了分类准确率和召回率比单纯 accuracy 更能反映多类别均衡性。把三个数据集的实验结果合到一起看数据集基础模型单流 mAP双流 mAP三流 mAP三流相对单流提升CIFAR-100NIN-366.9169.7669.172.26CIFAR-100ResNet-2069.0972.4374.545.45Stanford DogsCaffeNet66.8467.9469.802.96Stanford DogsVGGNet74.1177.5679.955.84UEC FOOD-100CaffeNet39.9241.1142.662.74UEC FOOD-100VGGNet47.4049.1950.272.87两个规律值得注意。第一VGGNet 在细粒度数据上的提升幅度最大Stanford Dogs 上比单流涨了 5.84 个百分点说明三流结构对“类间差异小、需要精细特征”的场景帮助更大。第二三流比双流的提升普遍在 1~2.4 个点说明第三个流确实补上了前两个流遗漏的信息而不是简单把网络重复一遍。CIFAR-100 上三流 ResNet-20 达到 74.54%比当时的 DNR 双流模型高了 2.11%比 HD-CNN 这种带层次结构的方案还高 7 个多点这个成绩在 2019 年的公开对比里算相当能打。5. 复现避坑五个实际踩过的坑与排查路径5.1 三流模型训练完mAP 比单流还低现象三个流都正常收敛loss 也在降但验证集 mAP 始终和单流网络持平甚至更低。原因最常见的是交叉冻结没生效。S1 和 S2 的冻结层配置写了但层名和实际模型里的参数对不上requires_grad 全没关掉三个流退化成同一个网络训练了三次。另一个原因是数据增强不够三个流的输入分布太接近即便参数路径不同也学不出互补特征。解决训练前先打印每个流可训练参数的名称和数量确认 S1 和 S2 的可学习参数集合确实错开。训练到一半时单独用验证集测三个流的 mAP如果 S1 和 S2 的准确率几乎一样说明特征没有分离开优先检查冻结配置和增强策略。5.2 融合损失震荡验证集 mAP 不涨现象总损失曲线剧烈震荡训练集 loss 能降但验证集 mAP 长期不动。原因三个流的损失量级不一致。某个流收敛快、loss 掉得快另一个流收敛慢、loss 还很大加权求和后总损失被慢的那个流主导了。论文里给的初始权值是等权的但没告诉你等权的前提是三个流损失量级大致在一个范围。解决训练初期先单独跑三个流记录各自的 loss 均值和标准差用归一化系数把三个流的损失尺度拉齐再加权。也可以直接在总损失里加上融合分支的 loss它天然会起到平衡作用。5.3 显存不够三流模型放不进单卡现象单卡 16GB 显存三流 VGGNet 训练直接 OOMbatch size 调到 16 都放不下。原因三流模型等价于同时跑三个完整网络三份中间特征图和三份梯度同时驻留显存开销是单流的三倍。论文实验环境是 4 块 16GB P100单卡场景必须做显存优化。解决我一般会做两件事。第一三个流分阶段前向先跑完 S1 的反向更新再跑 S2S3 最后跑用梯度累积模拟三流同时更新的效果显存峰值降为单流水平。第二dropout 概率调到 0.5配合小 batch size能稳定训练。5.4 ImageNet 预训练权重在 CIFAR-100 上失效现象S3 用 ImageNet 预训练模型初始化在 CIFAR-100 上微调后 mAP 比从头训练还低。原因CIFAR-100 图像是 32×32而 ImageNet 预训练权重默认输入是 224×224。低分辨率图像升到 224 后严重失真预训练权重学到的低层特征对 32×32 分布完全错位。解决论文里参考了 ResNet 原文的数据增强方案。常见做法是先把图像随机裁剪到 32×32 并加随机翻转、平移等增强或者干脆把输入统一 resize 到 64×64 再送进网络让预训练权重的感受野不至于完全失效。5.5 融合权值手动调一轮实验跑几十遍现象权值每次靠观察验证集 mAP 手动改三流每个版本都要完整训一遍实验周期很长。原因论文只给了最终学到的权值没有给自动更新机制。手动调参靠运气稳定性差。解决写一个自动更新器每轮训练后在验证集上分别算三个流的 mAP按 mAP 比例归一化得到下一轮权值。这样训练过程自动收敛到最佳融合比例不需要人为干预。6. 进阶用法用特征可视化验证三流互补性顺带省显存复现三流模型后最重要的一件事不是看 mAP而是验证三个流真的学到了不同特征。论文图 3 展示了三个流的第五层卷积特征图可视化结果S1 和 S2 的特征图细节丰富S3 的区分性更强。这个可视化是判断交叉冻结是否生效的最直接证据比 loss 曲线可信得多。用 PyTorch 的 forward hook 可以提取中间层特征图做法是注册一个 hook 函数在前向传播时把指定层的输出抓出来def hook_fn(module, input, output): 前向时把conv5的输出保存下来 feature_maps.append(output.detach().cpu()) feature_maps [] handles [] # 分别给三个流的conv5注册hook for stream_model in [stream1, stream2, stream3]: handle stream_model.conv5.register_forward_hook(hook_fn) handles.append(handle) with torch.no_grad(): stream1(images) stream2(images) stream3(images) # 用完一定记得移除hook for handle in handles: handle.remove() # 取每个流的前8张特征图按通道归一化后拼成网格 import torchvision.utils as vutils for i, fm in enumerate(feature_maps): # fm: [batch, 256, 13, 13]取前8个通道 grid vutils.make_grid(fm[:8].squeeze(1), normalizeTrue, nrow8) save_image(grid, fstream_{i1}_conv5.png)hook_fn 里的 feature_maps.append 把每次前向的 conv5 输出累积到列表里因为三个流共享同一份输入输出的 batch 维度相同可以直接拼。save_image 的 normalizeTrue 会把特征图数值缩放到 0~1方便肉眼对比。从输出图里能看到的规律S1 的特征图响应偏向边缘和轮廓S2 偏向局部纹理和颜色块S3 的高响应区域集中在图像主体位置。如果三个流的特征图长得几乎一样那就是交叉冻结没生效回到第 5.1 节的排查路径去查冻结配置。顺带说个省显存的技巧做特征可视化验证时三个流不必同时在前向里。逐个流前向每个流只保留一张显卡上的特征图显存峰值只有三流同时前向的三分之一。把上面代码改成循环调用三个流模型而不是构造一个把三个流叠起来的大模型验证阶段显存占用会低很多。从那以后我每次做多流模型都会强制走一遍特征可视化流程先确认互补性成立再去看 mAP省掉了大量无效调参时间。希望帮到你。本文还有配套的精品资源点击获取