资讯动态

细粒度鸟类识别迁移学习:AlexNet与ResNet-18对比

发布时间:2026/10/26 1:41:00 来源:尧图企业网站定制
简介这份基于卷积神经网络的鸟类识别实验报告面向机器学习与深度学习方向的科研人员、学生及技术人员围绕细粒度图像分类任务以AlexNet和ResNet-18为基线演示从ImageNet预训练权重初始化、将输出层改为200类到小学习率微调的完整流程适合用作课程项目或迁移学习练习。报告重点解析CUB-200-2011数据集200种鸟类、约11788张图像及边界框与属性标注的训练、验证、测试划分并结合Loss曲线、验证集Accuracy和bounding_boxes预测结果展示训练过程同时对比预训练与非预训练权重、训练步数、学习率及衰减策略对性能的影响给出可复用的调参思路。压缩包共1个docx文件约1.36MB内含网络结构图、数据集示例、可视化曲线与实验结果对比便于按章节复盘实验设计与结论。目前已有184人学习适合希望掌握CNN细粒度分类建模与实验分析方法的读者参考。1. 细粒度识别的反直觉门槛类别少十倍难度却翻上去ImageNet 上 1000 类的 top-1 能刷到七十几换成 CUB-200-2011 的 200 类鸟卷积神经网络却常常卡在七十几上不去——类别少了五倍精度反而没涨。这个反差是鸟类识别实验最值得先讲清楚的地方细粒度分类的难度不在类别数量而在类间距离。两种雀鸟的差别可能只是喙的长度和胸羽的一道色带这些差异在 224×224 的输入里往往只占几十个像素再被下采样几次就接近消失。理解这一点后面所有的设计选择——预训练权重、换输出层、分层学习率、残差结构——才有统一的解释框架。这套机制不只服务于鸟工业质检的缺陷分级、药品包装识别、车型细分本质都是同一类问题。2. CUB-200-2011 的分布结构200 类、60 张图与两个隐藏陷阱这个数据集经常被当成“小号 ImageNet”直接用但它的统计结构和 ImageNet 完全不同。200 个物种、约 11788 张图、平均每类 60 张这个规模放在深度学习的语境里属于“小样本 细粒度”的叠加态模型容量稍大就过拟合容量太小又学不出判别性部位。更麻烦的是它的标注体系比“图片—标签”二元组复杂得多这一点决定了你能做哪些实验、不能做哪些实验。2.1 类间距离与类内方差细粒度难度的真实来源细粒度分类的核心矛盾可以用一句话概括类间距离小于类内距离。同一只鸟站在树枝上、展翅飞行、侧头理羽这三张照片的像素级差异可能远大于两只不同物种在标准姿态下的差异。姿态、光照、背景杂波、遮挡、拍摄距离每一个因素都在往类内方差里加码而物种之间的判别依据往往只集中在喙形、眼圈、翼斑、尾羽这几处局部区域。传统手工特征在这种场景下很容易失效。颜色直方图会把背景的绿色枝叶和鸟的橄榄色羽毛混在一起HOG 这类全局梯度统计会把判别性部位的响应平均掉。卷积神经网络之所以能接住这个任务靠的是局部感受野加层次组合浅层卷积核在很小的一块区域上响应边缘和色块中层把这些响应组合成纹理和简单部件深层再组合成“喙”“翅尖”这类语义单元。换句话说CNN 的结构先验天然匹配“判别信息集中在局部”这一假设。但这里有个容易被忽略的细节全局平均池化会把空间信息压成一个向量如果判别性部位只占画面的一小块池化之后它的贡献会被大量背景响应稀释。这就是为什么很多细粒度工作要额外引入注意力机制或者部件定位分支——不是为了刷点而是为了在池化之前把信噪比拉回来。AlexNet 和 ResNet-18 都不带这种机制它们能work一部分功劳其实要记在预训练权重和 bbox 裁剪上。2.2 标注文件不只是标签bbox、部件与属性的字段结构CUB-200-2011 的每张图除了类别标签还带三类额外标注边界框、部件关键点、属性向量。边界框给出鸟在画面中的位置和大小部件关键点标出喙、左眼、右眼、头部等 15 个位置属性向量是 312 维的 0/1 编码描述羽毛颜色、喙的形状这类可解释特征。这三类标注在实验里的角色完全不同。类别标签是监督信号决定损失函数边界框最常见的用法是做数据预处理——先裁出鸟的主体区域再缩放等于人为把判别性部位的像素占比放大这对细粒度任务是实打实的增益部件关键点和属性则更多用于可解释性分析和辅助任务比如加一个属性预测的辅损失让骨干网络学到更结构化的特征。原文的实验里边界框主要出现在可视化环节用来展示模型预测结果和真实标注的对应关系。这个用法看起来只是“画个框”实际上承担了排查功能如果预测框系统性偏移往往说明坐标系的处理有问题而不是模型学错了。后面避坑那一章会专门讲这个坐标偏移的来源。提示边界框标注的是“鸟在哪”不是“鸟是什么”。它可以当预处理的裁剪依据也可以当定位任务的监督信号但不要把它和分类标签混在同一个损失里直接相加量纲和语义都不匹配。2.3 划分方式决定实验结论官方 split 与自建分层切分这个数据集的官方划分有一个很特殊的设定训练集 5994 张、覆盖 150 个物种测试集 5794 张、覆盖另外 50 个物种两组类别互不重叠。这个协议测的是“模型能不能把学到的特征迁移到没见过的物种上”属于开放类的泛化评估和“200 类闭集分类”不是同一件事。如果实验目标是 200 类闭集识别常见的实现思路是按类别分层重新切分比如每类抽 80% 做训练、20% 做验证保证验证集覆盖全部 200 类。随机切分在这里是个陷阱每类只有 60 张随机切很可能出现某个类别全部落进训练集、验证集里一张都没有的情况算出来的 accuracy 会系统性偏高或偏低。下面这段是按类分层的切分逻辑种子固定保证每次实验的可复现性import random from collections import defaultdict def stratified_split(samples, val_ratio0.2, seed42): samples: [(image_path, label), ...]按类别分层切分 rng random.Random(seed) by_cls defaultdict(list) for path, label in samples: by_cls[label].append(path) train, val [], [] for label, items in by_cls.items(): rng.shuffle(items) # 同类内打乱避免按文件名排序引入偏差 k max(1, int(len(items) * val_ratio)) # 每类至少留 1 张进验证集 val.extend((p, label) for p in items[:k]) train.extend((p, label) for p in items[k:]) return train, valval_ratio控制验证集比例seed固定随机源max(1, ...)这个保底处理是为了防止某个类别样本特别少时验证集为空。切完之后建议打印一下类别直方图确认 200 个类别在验证集里都出现过——这一步花不了多少时间但能挡掉后面很多莫名其妙的指标波动。3. 迁移学习的机制预训练权重到底迁移了什么“用 ImageNet 预训练权重初始化”这句话在实验报告里经常一笔带过但它其实是整个实验里信息量最大的一个操作。理解它为什么有效才能理解为什么新输出层要从头训练、为什么骨干网络要用更小的学习率、为什么从零训练在同样步数下几乎不可能追平。3.1 从 Gabor 到语义卷积层特征的可迁移性梯度卷积网络的特征可以粗略分成三层。最浅的一层学到的是边缘、颜色对立、方向性纹理这类特征和具体任务几乎无关换成医学影像、遥感图像照样能用性质上接近手工设计的 Gabor 滤波器。中间层学到的是纹理组合和简单部件比如圆弧、角点、重复条纹可迁移性开始下降但在鸟类识别里仍然有用因为羽毛纹理和喙的轮廓都属于这一类。最深层学到的是类别层面的语义组合ImageNet 的 1000 类是“狗”“车”“飞机”这种物体级概念和“某种雀”这种物种级概念之间存在明显的粒度落差。这个梯度直接决定微调策略浅层可以冻得很死甚至完全不动深层必须给足够的调整空间否则模型会把“鸟”当成一个整体语义而学不会区分具体是哪一种鸟。原文里“对预训练参数使用较小学习率微调”的做法本质上是在这个梯度上做了一个折中——既不想破坏浅层通用特征又要给深层留出适应细粒度任务的余地。3.2 换头手术把输出层改成 200 维之后发生了什么AlexNet 的最后一层全连接是 4096 到 1000ResNet-18 的fc是 512 到 1000改成 200 类就是把输出维度换掉。这一步在代码里只有一行但它在训练动力学上带来的变化不小。新层是随机初始化的输出接近均匀分布交叉熵损失在训练初期会很大反传到骨干的梯度也会很大。如果新层和预训练层共用同一个学习率这些大梯度会把辛苦预训练出来的特征冲散出现“预训练还不如随机初始化”的怪现象——这不是预训练没用是学习率没分层。常见的实现思路是给新层设置 10 倍于骨干的学习率让新层快速收敛到一个合理的输出空间同时骨干只做小幅调整。import torch.nn as nn from torchvision import models def build_model(arch: str, num_classes: int 200) - nn.Module: if arch resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features # 512 model.fc nn.Linear(in_features, num_classes) # 新层随机初始化 head_prefix fc elif arch alexnet: model models.alexnet(weightsmodels.AlexNet_Weights.IMAGENET1K_V1) in_features model.classifier[6].in_features # 4096 model.classifier[6] nn.Linear(in_features, num_classes) head_prefix classifier.6 else: raise ValueError(funsupported arch: {arch}) return model, head_prefix def build_param_groups(model, head_prefix, base_lr1e-4, head_mult10.0): head, backbone [], [] for name, p in model.named_parameters(): (head if name.startswith(head_prefix) else backbone).append(p) return [ {params: backbone, lr: base_lr}, # 预训练部分小步走 {params: head, lr: base_lr * head_mult}, # 新头大步走 ]in_features是从原模型里读出来的不是硬编码这样换架构时不用改数字。head_prefix用来区分新旧参数base_lr是骨干的学习率head_mult是新层的倍率。这套写法有个好处优化器构建和模型构建解耦换 AlexNet 还是 ResNet 只需要改一个字符串。注意weights参数的具体写法在不同版本里有差异如果运行时报参数不存在的错误先确认当前环境的 API 形式别急着怀疑模型结构。3.3 分层学习率与冻结为什么新层要给更大的步长除了分层学习率还有一条更保守的路线冻结骨干只训新头。这种做法叫线性探测在数据量极小的时候特别稳因为它完全不动预训练特征只找一个线性分类面。缺点是表达能力受限细粒度任务里往往比全量微调低几个点。介于两者之间的是逐层解冻先只训头几个 epoch 后解冻最后一级 block再解冻更前一层。这个策略在目标检测和分割里很常见分类任务里用得少一些但在 11788 张这个小数据集上确实能缓解过拟合。学习率衰减策略的选择也和这套机制有关。步进衰减在固定训练预算下更可控每到一个里程碑降一次loss 曲线会出现明显的台阶余弦退火更平滑适合预算明确、想跑一条漂亮曲线的场景。原文尝试了不同衰减策略并观察到性能差异这个差异的本质是衰减太快深层还没调整到位就被压死了衰减太慢后期在最优解附近来回震荡验证集 accuracy 上不去。训练曲线本身也是这套机制的观测窗口。预训练模型在前几个 epoch 的 loss 下降速度明显快于随机初始化因为它的浅层已经在输出有意义的特征梯度方向更明确从零训练的曲线前期会有一段平台期模型在学“什么是边缘”这种最基础的东西。原文对比实验里收敛速度的差距主要就来自这段平台期。4. AlexNet 与 ResNet-18 的结构账本两种归纳偏置的取舍把这两个网络放在一起对比很容易陷入“谁更深谁更强”的简单结论。但它们在参数分布、归一化方式、梯度通路上的差异才是决定细粒度任务表现的关键。理解这些差异也就理解了为什么同样一套训练配置换架构之后需要重新调参。4.1 AlexNet 的参数分布全连接层吃掉九成容量AlexNet 的五层卷积加起来大约 2.5M 参数而三个全连接层加起来接近 58Mfc6是 4096×9216约 37.7Mfc7是 4096×4096约 16.8Mfc8是 1000×4096约 4.1M。也就是说整个网络 95% 以上的参数集中在分类头附近。这个分布带来两个后果。其一模型容量对 11788 张图来说严重过剩全连接层极易记住训练样本训练 accuracy 能冲到很高而验证 accuracy 原地踏步。其二这些全连接层对输入的空间布局高度敏感它们学到的是全局模板而不是可复用的局部特征所以迁移到细粒度任务时深层全连接的迁移价值其实很低——真正有用的还是前面那几层卷积。这也解释了一个常见现象用 AlexNet 做微调时冻结fc6之后的效果往往不比全量微调差多少因为这一层本来就没学到多少可迁移的东西。4.2 残差与 BNResNet-18 在细粒度任务上更稳的两个原因ResNet-18 的总参数量大约 11.7M其中只有一个 512 到 1000 的全连接层占 0.5M。参数几乎全在卷积里这个分布对细粒度任务友好得多——卷积核自带平移等变性学到的局部纹理特征可以直接复用。批量归一化是第一个稳定器。每个卷积后面接 BN把激活值拉回标准分布允许使用更大的学习率而不发散。在小数据集上BN 的批统计量本身还带一点正则效果因为每个 batch 的均值和方差都在波动模型不会过度依赖某个固定的激活尺度。但这个效果有前提batch size 不能太小。batch 小到 8 以下时批统计量的噪声会盖过信号训练损失震荡得厉害这时候要么加大 batch要么换 GroupNorm 这类不依赖批维度的归一化。残差连接是第二个稳定器。恒等映射让梯度有一条直达浅层的通路反向传播时不用穿过一长串卷积层。18 层不算深残差的收益没有 ResNet-50 那么夸张但它提供了一个结构保证多加一层最差也就是学成一个恒等映射不会让性能倒退。这个性质在微调场景里很重要因为你往往需要在预训练结构上做修改残差让修改的容错空间更大。对比项AlexNetResNet-18卷积层数517含 stem归一化局部响应归一化现代实现常省略每个卷积后接 BNshortcut无每两个卷积一条恒等映射全局池化无直接展平进全连接全局平均池化后接单层 fc参数量约 61M约 11.7M分类头输入维度4096512表格里“局部响应归一化常被省略”这一点值得单独说一句很多现成实现为了省事直接去掉了 LRN这在 ImageNet 上影响不大但它意味着你拿到的 AlexNet 已经和原论文有细微差别做严格复现实验时要留意。4.3 输入分辨率与感受野224×224 对鸟的判别性部位够不够ResNet-18 的下采样倍率累计是 32224 的输入进到最后一层只剩 7×7 的特征图。一只鸟的喙在 224 的图里可能只有 30 个像素宽经过五次下采样后不到 1 个像素信息在早期就丢了。这不是模型容量的问题是分辨率带来的硬约束。有两条常见的应对路径。第一条是提高输入分辨率比如 320 或 448代价是计算量按面积增长训练时间翻倍不止。第二条是用边界框先把鸟裁出来再缩放等于在不增加计算量的前提下放大判别性部位的像素占比。第二条路径在细粒度任务里性价比很高而且这个数据集自带边界框标注预处理阶段就能做不需要额外标注成本。裁的时候要注意坐标系问题这个坑后面会展开讲。这里先记住结论标注文件里的坐标起点和图像库的坐标起点不一致直接拿来做裁剪会整体偏移一个像素视觉上不明显但会让裁剪框系统性偏向某个方向。5. 避坑与排查五条从曲线异常到标注错位的血泪记录实验报告里的曲线都是平滑的真实调试过程里全是锯齿。下面这五条是这类迁移学习实验里出现频率最高的问题每一条都按“看到什么现象、根因在哪、怎么改”的顺序写方便对着自己的训练日志排查。5.1 训练侧loss 不降与后期过拟合现象一前五个 epoch 训练 loss 几乎不动停在 5.3 附近。5.3 接近 ln(200)说明模型输出还是均匀分布等于什么都没学。根因通常有三个新层的学习率沿用了骨干的小学习率随机初始化的分类头根本推不动数据预处理把像素值归一化到了 [0,1] 却又减了一遍 ImageNet 均值输入分布彻底错位标签和数据没有对齐模型在学随机噪声。排查顺序是先打印一个 batch 的输入均值和方差确认在合理范围再把新层学习率单独调大十倍试一个 epoch最后检查标签映射表。现象二训练 accuracy 到 95% 以上验证 accuracy 卡在 60% 出头。典型的过拟合而且在这个数据集上几乎是必然的——11788 张图对 61M 参数的 AlexNet 来说是 1:5000 的比例。缓解手段按性价比排序先加数据增强随机裁剪、水平翻转、颜色抖动再上权重衰减然后考虑冻结骨干只训分类头最后才是减模型容量。直接换小模型往往是最费事、收益最不确定的一步。现象三loss 突然变成 NaN。常见原因是学习率过大导致梯度爆炸或者混合精度训练时损失缩放没配好也可能是某个 batch 的标签越界交叉熵对越界索引会返回异常值。排查时把学习率降一个数量级重跑加上梯度裁剪再检查标签的最大值是否等于类别数减一。5.2 数据侧标签错位与 bbox 坐标偏移现象四预测框整体往左上偏裁剪出来的图总是缺一块。这个坑的根因是坐标系约定不一致。标注文件里的边界框是[x, y, width, height]格式而且坐标起点是 1这是早期 MATLAB 工具链留下的习惯而 PIL 和 OpenCV 的裁剪接口用的是左上角加右下角、起点为 0。少减这个 1框就整体偏移一个像素再加上宽高的理解错误偏移会被放大。# 标注里的 bbox 是 [x_min, y_min, width, height]坐标从 1 开始 x, y, w, h ann[bbox] left, top int(x) - 1, int(y) - 1 # 1-based 转 0-based right, bottom left int(w), top int(h) # width/height 是尺寸不是右下角坐标 left, top max(0, left), max(0, top) # 防止越界 right min(img.width, right) bottom min(img.height, bottom) crop img.crop((left, top, right, bottom))int()截断而不是四舍五入是为了保证裁剪区域不越界两行max和min是边界保护标注偶尔会有超出图像范围的情况。改完之后建议抽十张图把裁剪结果和原图拼在一起看一眼比盯着数字核对快得多。现象五训练正常但某些类别的准确率异常低低到接近随机。先别怀疑模型去查标签映射。类别名到索引的映射如果是用sorted(set(labels))生成的训练和测试阶段必须用同一份映射表如果两次运行的文件遍历顺序不同映射就会错位模型学的和评的完全对不上。稳妥的做法是把映射表序列化存盘训练和评估都从同一个文件读。5.3 评估侧验证集虚高与测试集塌方现象六验证集 accuracy 一路调到 78%换测试集只剩 65%。两种可能。一种是验证集被反复用来调超参调了几十轮之后模型已经对验证集过拟合了这时候验证集不再是泛化性能的无偏估计。另一种是划分协议混用了——训练时按类别分层切了 200 类评估时又拿了官方那 50 个互斥类别的测试集两边类别空间对不上指标自然崩。解决办法很朴素验证集只用来做粗粒度的模型选择超参搜索的轮次控制在个位数测试集在整个实验周期里只跑一次跑之前把配置冻结。如果确实需要频繁评估从训练集里再切一个开发集出来和验证集分开用。6. 设计取舍预训练、模型容量与训练预算的三角平衡把前面几章的机制收拢这个实验真正的决策点只有三个用不用预训练、选多大容量的骨干、给多少训练预算。三者互相牵制改一个另外两个的最优值就变了。路线可训练参数适用条件主要风险从零训练全部数据量足够十万级或有强数据增强小数据集上必然过拟合收敛慢冻结骨干 训头约 0.1M数据极少、算力紧张表达能力受限细粒度上偏低几个点全量微调 分层学习率全部万级数据、有预训练权重学习率没分层会破坏预训练特征从零训练在这份数据上的劣势本质是参数量和数据量的比值失衡。AlexNet 六十多兆参数配一万多张图模型有足够容量把每张训练图都记住但它记的是背景、是拍摄角度、是某只鸟特有的姿态不是物种特征。预训练权重的价值在于它已经把“什么是边缘、什么是纹理”这类通用知识压缩好了模型只需要在这个基础上学“哪种纹理对应哪个物种”需要的数据量和步数都少一个量级。但预训练不是万能的。如果目标任务和 ImageNet 的域差异极大——比如灰度工业图像、显微图像——浅层特征的迁移价值会下降这时候冻结浅层反而可能拖后腿逐层解冻或者干脆从零训练加大量数据增强可能更合适。判断方法是看微调前几个 epoch 的 loss 下降速度如果和随机初始化差不多说明预训练特征没接上得回头检查输入归一化和通道数。验证这套取舍是否成立我习惯用一个小清单固定随机种子和划分文件保证每次对比只变一个变量把训练 loss、验证 loss、验证 accuracy 三条曲线画在同一张图上过拟合的拐点一目了然任何超参改动都记录在配置文件里不靠记忆复现测试集只在最后跑一次。这套流程不复杂但能挡掉大部分“结果不可复现”的麻烦。从那以后我每次跑迁移学习实验都先把新旧参数的学习率倍率和冻结顺序写进配置文件再开始训练不在训练脚本里临时改数字。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑