简介:这是一份发表于《江西理工大学学报》的学术论文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_mult=0 是防止权重衰减动到已冻结参数,转 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-100 | 60000 | 100 | 600 | 50000 | 10000 | 32×32 低分辨率 |
| Stanford Dogs | 20580 | 120 | 148~252 | 12000 | 8580 | 中高分辨率 |
| UEC FOOD-100 | 14461 | 100 | 101~729 | 10000 | 4461 | 中高分辨率,含多目标 |
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(), lr=0.001, momentum=0.9, weight_decay=0.0005) scheduler = StepLR(optimizer, step_size=20000, gamma=0.1)StepLR 的 step_size 对应论文里“每训练 20000 次降低学习率”,gamma=0.1 表示降为原来的 10%。迭代次数对应 step_size × 总阶段数,比如 20000×3=60000 代。如果是 40000 次降一次、总共 20 万代,就写成 step_size=40000,训练 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 全连接,结构直观好改,适合当基线。NIN(Network 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-100 | NIN-3 | 66.91 | 69.76 | 69.17 | +2.26 |
| CIFAR-100 | ResNet-20 | 69.09 | 72.43 | 74.54 | +5.45 |
| Stanford Dogs | CaffeNet | 66.84 | 67.94 | 69.80 | +2.96 |
| Stanford Dogs | VGGNet | 74.11 | 77.56 | 79.95 | +5.84 |
| UEC FOOD-100 | CaffeNet | 39.92 | 41.11 | 42.66 | +2.74 |
| UEC FOOD-100 | VGGNet | 47.40 | 49.19 | 50.27 | +2.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 训练直接 OOM,batch size 调到 16 都放不下。
原因:三流模型等价于同时跑三个完整网络,三份中间特征图和三份梯度同时驻留显存,开销是单流的三倍。论文实验环境是 4 块 16GB P100,单卡场景必须做显存优化。
解决:我一般会做两件事。第一,三个流分阶段前向,先跑完 S1 的反向更新,再跑 S2,S3 最后跑,用梯度累积模拟三流同时更新的效果,显存峰值降为单流水平。第二,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), normalize=True, nrow=8) save_image(grid, f"stream_{i+1}_conv5.png")hook_fn 里的 feature_maps.append 把每次前向的 conv5 输出累积到列表里,因为三个流共享同一份输入,输出的 batch 维度相同,可以直接拼。save_image 的 normalize=True 会把特征图数值缩放到 0~1,方便肉眼对比。
从输出图里能看到的规律:S1 的特征图响应偏向边缘和轮廓,S2 偏向局部纹理和颜色块,S3 的高响应区域集中在图像主体位置。如果三个流的特征图长得几乎一样,那就是交叉冻结没生效,回到第 5.1 节的排查路径去查冻结配置。
顺带说个省显存的技巧:做特征可视化验证时,三个流不必同时在前向里。逐个流前向,每个流只保留一张显卡上的特征图,显存峰值只有三流同时前向的三分之一。把上面代码改成循环调用三个流模型,而不是构造一个把三个流叠起来的大模型,验证阶段显存占用会低很多。
从那以后,我每次做多流模型都会强制走一遍特征可视化流程,先确认互补性成立,再去看 mAP,省掉了大量无效调参时间。希望帮到你。
本文还有配套的精品资源,点击获取