资讯动态

PyTorch迁移学习实战:猫狗分类准确率突破99%

发布时间:2026/9/11 21:44:19 来源:尧图企业网站定制
简介基于 PyTorch 框架实现的 Kaggle 猫狗分类完整源码适合深度学习初学者、课程设计以及期末大作业参考。项目采用迁移学习思路选用 GoogLeNet、ResNet 与 ResNeXt 三个预训练模型提取图像特征将各自全局平均池化层输出的特征拼合为高维向量再通过单层全连接网络完成二分类最终准确率超过百分之九十九。资源本身共包含四个文件其中两个 Python 脚本分别承担特征提取和模型训练任务一个说明文档用于辅助理解代码流程一个提交样例文件可以对照提交格式。压缩包整体只有三十四 KB结构简洁适合快速阅读与二次修改。特征提取脚本会自动加载预训练权重并生成特征训练脚本直接使用处理好的特征进行训练同时应用随机失活防止过拟合在中央处理器上数秒即可跑完没有图形处理器也能快速验证。目前已有三百零四人学习对于希望了解迁移学习、特征融合并完成类似图像分类项目的开发者很有帮助。1. 猫狗分类是 Kaggle 最经典的二分类入门赛25000 张训练图、12500 张测试图判断每张图是猫还是狗。真正有意思的地方在“准确率超过 99%”——从零训练的 CNN 通常卡在 93% 到 96%缺口全在毛发纹理、光照、遮挡和犬种差异上。要做到 99%可靠路径是 PyTorch ImageNet 预训练权重 数据增强 分层学习率微调而不是自己堆更深的网络。下面沿这条路径拆环境配套、数据集整理、模型替换、训练调参、推理提交每步给可直接复制的命令和代码。适合刚做完课程作业、想从 95% 推到 99% 的同学也适合想一周内把图像分类 pipeline 从训练跑到上线的工程师。2. PyTorch 环境配套与 Kaggle 猫狗数据集的本地化拿到任何一套 PyTorch 图像分类代码第一件事不是看模型定义而是把环境钉在同一套版本上。PyTorch 的 CUDA 预编译 wheel 与 torchvision 强绑定版本错位最常见的表现是import torch直接报错或者训练时静默落到 CPU 上跑数据集的目录结构如果不是train/类别/图片这种层级ImageFolder 接口第一步就会抛异常。这两件事在猫狗分类这种入门项目里反而是翻车率最高的地方。2.1 Python / PyTorch / CUDA 版本匹配与安装一套被反复验证过的组合是Python 3.10.11、PyTorch 2.8.0、torchvision 0.23.0、CUDA 12.1。这套组合的二进制依赖齐全numpy、pandas、Pillow 都有对应 wheel装完不需要再编译任何东西。conda create -n dogscats python3.10.11 -y conda activate dogscats pip install torch2.8.0 torchvision0.23.0 --index-url https://download.pytorch.org/whl/cu121 pip install numpy pandas matplotlib tqdm pillow kaggle--index-url指向 PyTorch 官方 cu121 预编译源装的是带 CUDA 算子的版本体积约 2.5GB。torch2.8.0必须和torchvision0.23.0配套版本拆开装会导致后面torchvision.models接口不兼容。如果下载速度不理想先把 torch 那行装完、把官方源切回默认 PyPI再装其余依赖不要中途换源去混装。import torch, torchvision print(torch.__version__, torchvision.__version__) # 期望 2.8.0 0.23.0 print(torch.cuda.is_available()) # 期望 True print(torch.cuda.get_device_name(0)) # 期望显示你的显卡型号torch.cuda.is_available()返回 False 时先执行nvidia-smi确认驱动版本再检查当前环境里是否残留了 CPU 版 torchpip list里看 torch 构建标识最直接。显卡显存低于 6GB 时后面章节里 batch size 和输入分辨率都要跟着下调环境这一层不用强求统一。2.2 用 Kaggle API 下载数据集并重排目录原竞赛名是 dogs-vs-cats官方数据分 train 和 test1 两个目录。下载最快的方式是 Kaggle API前提是已经在账户后台生成了 kaggle.json。pip install kaggle mkdir -p ~/.kaggle cp ~/Downloads/kaggle.json ~/.kaggle/kaggle.json chmod 600 ~/.kaggle/kaggle.json kaggle competitions download -c dogs-vs-cats unzip -q dogs-vs-cats.zip解压后的原始结构必须搞清楚它决定了后面所有脚本的路径假设dogs-vs-cats/ ├── train/ │ ├── cat.0.jpg ... cat.12499.jpg │ └── dog.0.jpg ... dog.12499.jpg └── test1/ ├── 1.jpg └── ...train 里的文件名是类别.编号.jpgtest1 里是纯数字编号两类编号不存在对应关系。这里有两个坑要提前处理原始 train 目录按类别连续排列不随机打乱的话验证集的类别分布会偏测试集不带标签只能按文件名全量推理。第一步把数据重排成 PyTorch ImageFolder 要求的train/val两级结构import os, random, shutil src dogs-vs-cats/train for split in (data/train, data/val): for cls in (cat, dog): os.makedirs(os.path.join(split, cls), exist_okTrue) for fn in os.listdir(src): cls fn.split(.)[0] split train if random.random() 0.9 else val shutil.copy(os.path.join(src, fn), os.path.join(data, split, cls, fn)) print(train 猫:, len(os.listdir(data/train/cat))) print(val 猫:, len(os.listdir(data/val/cat)))random.random() 0.9形成 90/10 划分边拷贝边随机等价于全局 shuffle避免连续读取造成验证集全是同一类别。拷贝而不是移动是为了保留原始数据后面做全量重训时还能用。chmod 600那步不能省Kaggle API 对密钥文件权限有硬校验。2.3 数据增强配置训练集与验证集必须分开99% 准确率的第二个关键在增强。训练集的 transform 要做足验证集的 transform 要收敛两者不能共用。很多人直接复用训练增强做验证val_acc 凭空掉 0.5 到 1 个点然后误判模型过拟合去加正则越调越偏。参数训练集验证集作用RandomResizedCrop(224, scale(0.7,1.0))使用Resize(256)CenterCrop(224)模拟目标大小变化RandomHorizontalFlip(p0.5)使用不使用等价扩大两倍样本RandomRotation(15)使用不使用容忍拍摄角度偏差ColorJitter(0.3,0.3,0.3)使用不使用抗光照和色调差异from torchvision import transforms mean, std [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] train_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), transforms.ToTensor(), transforms.Normalize(mean, std), ]) val_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean, std), ])Normalize 必须使用 ImageNet 的 mean/std因为预训练权重就是在这个分布上学出来的换任何自定义统计量都会让权重失效。scale(0.7,1.0)不要用 torchvision 默认的 0.08猫狗主体占画面比例大裁剪比例过狠会把判别部位裁没。验证集只做 Resize 加 CenterCrop保证每次评估口径一致精度的浮动才能真实反映训练状态。3. 模型设计与预训练权重99% 准确率靠迁移学习不靠改网络3.1 从零训练的 CNN 为什么上不了 99%25000 张图对于一个二分类任务不算少但从零训练时网络底层卷积核要从噪声里重新学边缘、纹理、形状的层级表示。数据量和可用算力都不足以支撑模型在毛发走向、犬种差异这些细粒度特征上达到判别级精度。实际表现是训练损失能压到很低验证准确率却趴在 95% 附近调 dropout、换激活函数都难以突破。问题不在网络结构而在初始化。ImageNet 预训练权重在 128 万张图上已经学到了通用的视觉特征表示迁移学习把这份先验直接继承下来微调阶段只做局部适配把最后一层从 1000 类换成 2 类。这也是 Kaggle 猫狗竞赛历届前排方案从早期就清一色使用预训练模型的根本原因——比赛比的是数据利用效率不是结构创新。3.2 预训练模型怎么选ResNet50、EfficientNet-B4 还是 ConvNeXt-Tiny模型参数量单卡 224 推理耗时适用场景ResNet5025.6M约 10ms显存小先跑通流程EfficientNet-B419M约 25ms精度与算力平衡99% 的主力ConvNeXt-Tiny28.6M约 30ms结构现代微调收敛快默认选择是 EfficientNet-B4。它参数量比 ResNet50 更少但 depthwise 卷积的算力利用率高同等 224 输入下精度更好BN 层对批次大小的敏感度也比 ResNet 低。显存低于 6GB 就先用 ResNet50 把全套流程跑通记录验证集基线再换 B4 提点。不要在入门阶段直接追 ConvNeXt它的收益要在更大数据规模上才明显。3.3 加载 torchvision 预训练模型并替换分类头import torch import torchvision.models as models model models.efficientnet_b4( weightsmodels.EfficientNet_B4_Weights.IMAGENET1K_V1 ) in_features model.classifier[1].in_features model.classifier[1] torch.nn.Linear(in_features, 2) total sum(p.numel() for p in model.parameters()) print(f参数量: {total / 1e6:.1f}M)EfficientNet 的分类头是 Sequential(Dropout, Linear)下标 1 才是线性层ResNet 对应的是model.fc torch.nn.Linear(2048, 2)。weightsIMAGENET1K_V1表示加载官方预训练权重首次运行自动下载到TORCH_HOME缓存目录网络慢时可以先手动下载再设置这个环境变量指向本地。替换分类头后参数量不变变的只是最后输出维度。3.4 冻结主干与解冻微调分两阶段做一下把所有层解冻、学习率统一给到 1e-3预训练特征会被快速冲掉模型事实上在从零重学。常见做法是分两阶段# 阶段一只训练分类头 for p in model.features.parameters(): p.requires_grad False optimizer torch.optim.AdamW(model.classifier.parameters(), lr1e-3) # 阶段二解冻主干分层设置学习率 for p in model.features.parameters(): p.requires_grad True optimizer torch.optim.AdamW([ {params: model.features.parameters(), lr: 3e-5}, {params: model.classifier.parameters(), lr: 1e-4}, ])阶段一跑 2 个 epoch让随机初始化的分类头先适配预训练特征阶段二解冻整网主干用 3e-5、分类头用 1e-4 的分层学习率继续训练。很多人一上来就全量微调loss 下降很快验证集却停在 97% 附近问题多数出在这里——低层卷积的预训练特征被大步长更新破坏了前半程学到的 128 万张图的通用表示在前 1000 步迭代里就被覆盖掉。4. 训练与调参把猫狗分类验证准确率推到 99% 的 8 组参数4.1 优化器、学习率调度与损失函数训练部分真正决定上限的是一组参数组合单独调任何一个都出不来 99%。下面这张表是验证过的一整套配置参数取值设置理由optimizerAdamW带权重衰减的 Adam微调场景表现更稳主干 lr / 分类头 lr3e-5 / 1e-4预训练特征更新幅度必须小于新分类头weight_decay1e-4不加会掉 0.3 个点左右B4 尤其明显batch_size328GB 显存的安全值不够降 16schedulerCosineAnnealingLR余弦退火末段 lr 逼近 0 时精度还会涨一截T_max / eta_min15 / 1e-6与 epoch 数对齐lr 平滑降到 1e-6lossCrossEntropyLoss(label_smoothing0.05)软化标签减少过拟合epochs2 152 个 epoch 冻结微调15 个 epoch 全量微调label smoothing 在二分类里等价于把 0/1 目标改成 0.025/0.975模型不再追求把输出概率推向极端值验证集上通常能多拿 0.1 到 0.2 个点。CosineAnnealingLR 的 T_max 必须和实际训练 epoch 数保持一致否则学习率曲线会在中途出现跳变末段精度抬不上去。4.2 训练循环与混合精度from torch.cuda.amp import GradScaler, autocast model.to(device) criterion torch.nn.CrossEntropyLoss(label_smoothing0.05) optimizer torch.optim.AdamW([ {params: model.features.parameters(), lr: 3e-5}, {params: model.classifier.parameters(), lr: 1e-4}, ], weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max15, eta_min1e-6 ) scaler GradScaler() for epoch in range(15): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) with autocast(): loss criterion(model(x), y) optimizer.zero_grad() scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() scheduler.step()autocast在 RTX 20 系及以上显卡上默认走 fp16 计算显存占用近乎减半训练速度提升明显GradScaler负责梯度放大避免 fp16 下的梯度下溢。label 不需要转 halfCrossEntropyLoss 内部会自己处理精度。scheduler.step()放在每个 epoch 结束后调用对应 CosineAnnealingLR 按 epoch 步进不要放到每个 batch 里。DataLoader 记得设置num_workers4, pin_memoryTrue, shuffleTrue。shuffle 对训练集的随机性影响直接反映在 val_acc 曲线平滑度上pin_memory 能减少 CPU 到 GPU 的拷贝等待。每次 epoch 结束跑一次 val_tf 评估记录 val_acc把最优权重存成best.pt。上述配置在 8GB 显存的显卡上EfficientNet-B4 单 epoch 约 20 分钟15 个 epoch 内能看到 val_acc 从 95% 爬到 99% 以上。4.3 三个容易踩的准确率杀手第一个是学习率给得太统一。主干直接用 1e-3 更新低层卷积很快脱离 ImageNet 特征空间val_acc 永远差一口气。检查办法是加载 best.pt 后看层间梯度范数正常情况主干层梯度范数应该显著小于分类头如果两者同量级说明主干更新过猛。第二个是验证集误用训练增强。共用 train_tf 会让 val_acc 比真实水平低 0.5 到 1 个点于是误判模型能力不够盲目加大正则和 dropout实际是在带偏的系统上做无用功。验证集只用 Resize 和 CenterCrop保持口径干净。第三个是 batch size 与学习率不同步。batch 从 32 调到 64 时学习率没跟着调BN 统计量在两个尺度之间跳动val_acc 曲线出现锯齿。经验规则batch size 翻倍则 lr 翻倍batch size 减半则 lr 减半改完 batch 必须同时改 lr 和 T_max 的对应关系。5. 推理、TTA 与 Kaggle 提交格式的最后一公里训练结束到最终提交还有三个动作能再捞 0.2 到 0.4 个点的收益测试时增强、全量数据重训、TorchScript 导出。顺序不能乱——先确认验证集准确率达到预期再做 TTA 打分测试最后决定导出格式。5.1 TTA 推理代码from PIL import Image tta_tf transforms.Compose([ transforms.Resize(256), transforms.RandomHorizontalFlip(p0.5), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean, std), ]) def predict_tta(model, img_path, device, n_times4): model.eval() probs [] for _ in range(n_times): x tta_tf(Image.open(img_path)).unsqueeze(0).to(device) with torch.no_grad(): probs.append(torch.softmax(model(x), dim1).cpu()) return torch.mean(torch.stack(probs), dim0)TTA 的增强只保留随机翻转和中心裁切不加旋转、颜色扰动后者的方差大于收益会拉低均值投票的效果。二分类任务 4 次 TTA 通常能提升 0.1 到 0.2 个百分点代价是推理时间乘以 4在 12500 张测试图上属于可接受范围。5.2 提交文件的 id 与 label 列import pandas as pd ids, preds [], [] for fn in sorted(os.listdir(dogs-vs-cats/test1)): p_dog predict_tta(model, os.path.join(dogs-vs-cats/test1, fn), device)[0, 1] ids.append(fn.split(.)[0]) preds.append(float(p_dog)) sub pd.DataFrame({id: ids, label: preds}) sub.to_csv(submission.csv, indexFalse) print(sub.shape) # (12500, 2)提交格式固定两列id 是测试图片文件名去掉 .jpglabel 是该图属于狗的概率0 到 1 之间的浮点数。test1 目录里是1.jpg、2.jpg这样的纯数字编号与 train 里的 cat/dog 编号没有重叠按文件名排序推理即可Kaggle 会按 id 字段自动匹配。5.3 上生产环境的导出方式提交之外模型要脱离训练脚本运行推荐导出 TorchScript。先加载训练好的权重再用一张真实尺寸的输入做 trace固定输入 shape 后推理吞吐更高model.load_state_dict(torch.load(best.pt)) model.eval() example torch.rand(1, 3, 224, 224) traced torch.jit.trace(model.cpu(), example) traced.save(dogscat_ts.pt)Trace 要在model.eval()状态下进行否则 BN 层会记录推理时的统计量导致导出的模型输出漂移。最后一步是出分前用全部 25000 张图按同一组参数重训一次再做 TTA 打分和提交验证集只用来选超参和早停最终模型不该浪费那 10% 的数据。本文还有配套的精品资源点击获取

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

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

免费获取报价