尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从ResNet到Wide-ResNet:CIFAR-100分类实战与调优

从ResNet到Wide-ResNet:CIFAR-100分类实战与调优 简介CIFAR-100分类实战项目是一套面向深度学习与计算机视觉学习者的开源代码资源围绕ResNet与Wide-ResNet两种经典卷积神经网络演示了从数据处理、数据增强、模型搭建到训练与评估的完整流程也适合希望提升图像分类调优能力的开发者参考。项目针对100个小类、共6万张32×32彩色图像的数据集设计重点展示了残差连接与网络宽度两种优化思路对学习模型结构改进很有帮助。资源包共15个文件压缩后1.05MB包含5个ipynb交互式代码、3个py模型脚本、3个png实验结果图、3个md说明文档和1个introduce文件代码与说明配套便于边读边改。已有194人学习下载读者可借此掌握学习率调整、权重初始化、正则化等训练技巧并学会通过准确率与损失监控模型表现。 前一阵我把 CIFAR-100 分类的复现项目从 ResNet 一路换到 Wide-ResNet折腾了两周终于把测试集 top-1 错误率从 30% 压到 21% 上下。整个过程里最让我意外的是真正拖后腿的不是网络结构本身而是数据增强、学习率调度和 dropout 这些细节。所以我把这套可复现的 ResNet / Wide-ResNet 代码整理成了开源项目顺便把整个过程写成这篇实战总结。如果你是刚想拿小数据集练手或者准备快速验证某个分类模型的想法这篇应该能帮你省不少时间。1. 项目思路CIFAR-100 和残差网络怎么搭在一起1.1 CIFAR-100 到底难在哪CIFAR-100 是计算机视觉里最常用的图像分类基准之一训练集 50000 张、测试集 10000 张每张图都是 32×32 的彩色小图总共 100 个类别。很多人第一反应是“32×32 这么小随便一个 CNN 都能跑”但真正上手才发现这个数据集非常容易过拟合。原因很直接平均每个类别只有 500 张训练样本而且很多类都是细粒度物体比如海狸、海豚、水獭这类相似度极高的类别模型很容易把背景噪声当成类别特征。另一个小麻烦是图像分辨率低。ImageNet 上的主流 backbone 拿到 CIFAR-100 上不能直接套因为 224×224 输入对应的 7×7 下采样方式在 32×32 输入上会把特征图压得几乎不存在。这也是为什么 CIFAR 分类任务里大家普遍使用专门的“CIFAR 版本 ResNet”而不是直接搬 torchvision 里给 ImageNet 用的 ResNet-50。我在这个项目里首先定了一个基线用经典 ResNet-32 跑通整个训练流程先确认数据加载、增强、学习率调度都没问题再切换到 Wide-ResNet 去提升准确率。这种“先有个能跑的基线再逐步优化”的方式能避免一上来就在大模型上调参最后连代码哪里写错了都找不到。1.2 为什么选 ResNet 做基线又为什么换 Wide-ResNetResNet 在 CIFAR-100 上的优势是结构简单、训练稳定。它通过残差连接把梯度从深层直接传回浅层即便网络做到几十层反向传播时梯度也不会消失。我用 ResNet-32 作为基线时200 个 epoch 下来测试集 top-1 错误率大概在 28%30%这个数字虽然比随机猜测好很多但离主流报告的 20% 出头的水平还有明显距离。后接换 Wide-ResNet核心动机是“加宽比加深更容易优化”。ResNet 靠增加层数来提升容量但 CIFAR 图像只有 32×32深层网络能提取的语义特征有限层数一上去计算量暴涨收益却很慢。Wide-ResNet 的做法是保持深度不变把每一层残差块的通道数成倍增加比如宽度因子 k10 时通道数从 16 倍扩展到 160 倍模型能在同等优化难度下塞进更多参数。用简单类比来说ResNet 像把公路修得很长每一段只能过一辆车Wide-ResNet 像把每段公路拓宽成八车道车辆并行通过信息流通效率高出很多。图像分类任务里很多高频边缘和纹理特征其实不需要太深的语义抽象宽网络在这些细粒度类别上往往比深网络更有效。2. 开源代码的整体设计与核心选型2.1 工程结构从数据加载到训练循环这套开源代码我自己是按“配置驱动”来组织的核心目录很简单config.py # 所有超参数集中管理 dataset.py # 数据增强与加载 models/ __init__.py resnet.py # ResNet 模型定义 wideresnet.py # Wide-ResNet 模型定义 train.py # 训练主循环 evaluate.py # 测试集评估 utils.py # 日志、指标计算、seed 设置这么做的好处是换数据集或者调参时不用改模型代码。config.py 里把 epoch、batch_size、初始学习率、weight_decay、warmup、cutout 尺寸都做成字段我每次实验只改配置然后重新跑 train.py。训练循环本身没有用花哨的框架就是最标准的 PyTorch 代码。核心逻辑是模型设成 train 模式遍历 train_loader计算损失反向传播再调用优化器更新。唯一需要注意的是评估时一定要把模型切到 eval 模式并包在torch.no_grad()里否则 BN 层的统计量会被测试数据污染导致推理结果忽高忽低。数据集加载我直接用torchvision.datasets.CIFAR100(root..., trainTrue, downloadTrue)省去自己写下载和解析的麻烦。但要注意第一次运行时它会下载约 160MB 数据网络不稳定时容易中断建议先手动下载好再放到 root 指定目录下。2.2 数据增强与优化器配置CIFAR-100 的过拟合问题很大程度上要靠数据增强解决。我用的增强组合是RandomCrop(32, padding4)、RandomHorizontalFlip、Cutout(mask_size16)。其中 Cutout 是随机遮住图像中一个 16×16 的正方形区域强制模型不能只依赖局部强特征对细粒度分类特别有效。很多复现实验里加上 Cutout 之后错误率能直接降 12 个百分点。优化器我选了带动量的 SGD因为 CIFAR-100 这类中小数据集上 SGD 的泛化能力往往比 Adam 好。初始学习率设成 0.1momentum0.9weight_decay5e-4。学习率调度用的是 CosineAnnealingLR把周期设成总 epoch 数让学习率从 0.1 平滑衰减到接近 0。相比 step decay余弦退火不用频繁指定在哪个 epoch 降学习率省心且效果稳定。训练前期我还加了 5 个 epoch 的 warmup让学习率从 0 线性上升到 0.1。这是为了避免训练一开始就用过大的学习率导致 loss 直接爆炸尤其是 Wide-ResNet 这种大模型不加 warmup 时第一个 epoch 的 loss 很容易飘到 10 以上。3. 从 ResNet 到 Wide-ResNet关键实现细节3.1 标准 ResNet 在 CIFAR-100 上的要点很多人直接在网上找 ResNet 代码很容易找到 ImageNet 版本但那个版本的第一层是 7×7 卷积加 stride2CIFAR 上不适合。我在开源代码里专门写了 CIFAR 版本第一层用 3×3 卷积stride1不接 maxpool因为输入只有 32×32特征图再下采样就没东西了。ResNet-32 的三组残差阶段分别使用 [5, 5, 5] 个 BasicBlock每个 BasicBlock 包含两个 3×3 卷积中间夹 BN 和 ReLU。通道数从 16 增长到 32 再增长到 64每到一个新阶段shortcut 需要用一个 1×1 卷积把维度对齐这个细节不能漏否则 tensor shape 对不上。所有卷积层的初始化我用 KaimingNormalBN 的 weight 初始化为 1bias 初始化为 0。在 CIFAR-100 上我建议直接随机初始化训练不需要加载任何 ImageNet 预训练权重。因为 200 个 epoch 在单张 3080 上大约只要一到两小时训练时间并不长预训练权重的输入尺寸和低层卷积分布反而可能与 32×32 输入不匹配。3.2 Wide-ResNet 的宽度因子和 dropoutWide-ResNet 我实现的是 WRN-28-10 这个配置depth28widen_factor10。结构上和 ResNet-32 最大的区别是每个残差块内部多了一个 dropout 层而且通道数显著增大。具体来说三个阶段的通道数是 [16×10, 32×10, 64×10] [160, 320, 640]参数量约为 36.5M是 ResNet-32 的大约 78 倍。这里的 dropout 位置很有讲究。Wide-ResNet 原论文建议把 dropout 放在两个 3×3 卷积之间而不是放在 BN 层之前或残差相加之后。我试过放在其他位置效果都会变差。原因是宽度增大后模型容量提升但 500 张每类的训练数据根本撑不起这么多参数dropout 加在信息最密集的中间层能更有效地打散特征间的共适应测试错误率大约能降低 1 到 1.5 个百分点。还有一个容易忽略的点shortcut 实现不能偷懒用恒等映射在通道数不同时直接拼接。WRN 里当输入输出通道不一致时我会用 1×1 卷积调整维度同时 stride 也要对齐。如果只做简单 padding 虽然 shape 能对上但会引入大量无意义填充影响梯度回传。4. 训练结果对比与参数调优记录4.1 实验环境与评估指标我的实验环境是单张 RTX 3080 10GBPyTorch 2.1 CUDA 11.8混合精度用 torch.cuda.amp 自动开启。CIFAR-100 图像分辨率低WRN-28-10 在 batch_size128 时显存占用约 8GB单卡完全跑得动。开混合精度之后一个 epoch 大约 30 多秒200 个 epoch 大概两个多小时。评估指标我统一用 top-1 错误率也就是模型预测概率最大的类别是不是真实类别统计错误样本占比。模型评估时只做了简单数据标准化不做随机裁剪和翻转避免训练增强干扰测试结果。为了公平对比所有模型都采用相同的训练策略200 epoch、warmup 5 epoch、cosine 退火、weight_decay 5e-4、Cutout 16。除模型自身结构外其他条件保持一致。这样每一组数据差异都能归因到网络结构。4.2 ResNet-32 和 WRN-28-10 效果对比我跑出来的数据大概如下不同随机种子会有 0.3% 左右波动但整体趋势稳定模型参数量top-1 错误率备注ResNet-320.47M28.4%基线没加 dropoutResNet-560.86M26.7%只加深收益有限WRN-16-811.0M23.8%宽度加到 8效果明显WRN-28-1036.5M21.6%加宽又加多 block最优从表里能清楚看到两件事。第一ResNet 从 32 层加深到 56 层参数量翻了将近一倍错误率只降了 1.7 个百分点第二WRN-16-8 的参数量只有 ResNet-56 的十几倍但错误率却比 ResNet-56 低 3 个百分点说明在 CIFAR-100 这种小图数据集上宽度比深度的性价比高得多。4.3 为什么宽比深的收益更大我一开始也困惑为什么深度增加到 56 层反而不如宽度增加到 8 倍效果好。后来看训练曲线发现ResNet-56 的训练 loss 下降很快但验证 loss 到后期几乎不动明显是过拟合。宽度提升之后模型每一层都能保留更完整的空间细节不需要通过很深的非线性变换把特征层层蒸馏所以泛化能力更好。另外WRN 在小图上还有一个隐形成本优势虽然参数量大但计算量并不像 ImageNet 上那样爆炸。32×32 输入经过 3×3 卷积后特征图尺寸小通道数大但空间维度小所以单卡训练也 hold 得住。这也是我最终选定 CIFAR-100 作为实验场地的原因它能快速验证结构想法又不会让普通硬件等太久。5. 实战中的坑与排查方法5.1 Loss 不降或过拟合我在复现时踩过最大的坑是 loss 前几个 epoch 死活不下降。检查一圈发现是 weight decay 设置太大了WRN-28-10 大模型对 weight decay 极其敏感从 5e-4 调到 1e-3 都会让收敛变慢。另外还有一种常见情况是数据增强过强Cutout 尺寸设成 24 以上时很多图像主体被遮掉大半模型学不到有效特征表现为训练 loss 一直在 4 以上降不下去。如果训练 loss 正常下降但验证 loss 反弹那就是过拟合。优先调整顺序是先加 dropout再调高 weight decay最后再考虑加更强的数据增强。这三个手段都有副作用一次性全加上反而会让模型欠拟合最好每次只改一个变量。5.2 显存不够和训练速度慢WRN-28-10 在 batch_size128、32×32 输入下显存占用接近 8GB如果你的显卡只有 6GB可以把 batch_size 降到 64同时调低初始学习率到 0.05。启用梯度累积也能模拟大 batch但注意别忘把 loss 除以累积步数否则 loss scale 对不上。训练速度方面最有效的优化是开启混合精度。我实测从 FP32 切到 AMP 后训练速度提升约 50%显存占用降低约 30%。另外 dataloader 的num_workers建议设成 4 到 8pin_memoryTrue否则数据读取会成为瓶颈尤其第一次跑时数据没有缓存CPU 转张量会明显拖慢每个 epoch。5.3 评估结果不稳定的排查方法有几次我跑完 200 epoch 后测试错误率忽高忽低一度以为是模型代码写错了。后来发现是训练时没有固定随机种子导致每次初始化不同WRN-28-10 这种参数量大的模型对初始化很敏感。解决办法是在 main 入口固定random.seed(0)、torch.manual_seed(0)如果用到 CUDA 还要设置torch.cuda.manual_seed_all(0)并确保 dataloader 也设置对应 seed。另一个坑是混合精度下 BN 的数值稳定性。个别 batch 的输入 variance 极小FP16 下可能出现 NaN导致 loss 变成 inf。我用了torch.cuda.amp.GradScaler之后基本不会出现但如果继续复现可以给 BN 层单独设torch.float32来规避。最后再分享一个我的习惯在正式训练 WRN-28-10 之前先用 WRN-16-8 跑一遍完整流程确认数据增强、优化器、学习率调度都符合预期然后再切到大模型只做最后的 finetune。这样能避免大模型上几十小时训练后才发现某个小参数写错对我来说这是整个开源项目里最值钱的经验。本文还有配套的精品资源点击获取
返回列表