ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PyTorch猫狗识别实战:从源码到部署的完整链路

PyTorch猫狗识别实战:从源码到部署的完整链路 简介这份资源是面向计算机、人工智能及相关专业学生与开发者的猫狗识别分类项目源码包可作为毕业设计、课程设计、作业或项目立项演示的完整参考也适合具备一定基础的学习者进阶练手。包内共16个文件以7个Python脚本为核心涵盖CNN、ResNet、Swin Transformer等多种模型实现与测试代码另含4个训练日志文件、2份Markdown说明文档、1份docx论文、1个txt数据说明及1个pth模型权重压缩包约1.67MB结构清晰便于按模块查阅。目前已有68人学习下载。项目基于PyTorch搭建提供从数据读取、模型训练到测试评估的完整流程并附有训练日志与已训练模型读者可借此理解不同网络结构在猫狗分类任务上的表现差异快速复现实验、对比模型效果并在此基础上修改扩展以实现其他识别功能遇到配置或运行问题还可获得远程指导。1. 从一份猫狗识别源码包说起它到底能帮你跑通什么很多人第一次接触机器学习都是从「猫狗分类」这个任务开始的。你手上如果拿到一份基于 Python 的猫狗识别分类项目源码里面还带着说明文档、论文和训练好的模型那它真正的价值不是让你复制粘贴交个作业而是给你一条从数据到推理的完整链路。这条链路里藏着几个关键问题图片怎么读进来、标签怎么对上、模型怎么选、训练怎么不崩、训完怎么用。把这条链路走通一遍你对机器学习的理解会从「调包」变成「知道每一步在干什么」。这份源码包适合两类人一类是刚学完 Python 基础、想找一个能跑起来的完整项目练手的新手另一类是做过后端或数据分析、想补上深度学习落地经验的工程师。它解决的核心问题是让你在一个可控的数据集上亲眼看到模型从随机猜测到能分辨猫狗的过程。下面我按实际动手的顺序把这份源码包里最值得拆开看的部分讲清楚包括环境怎么搭、数据怎么处理、模型怎么改、训练怎么盯、坑怎么躲。2. 环境搭建与数据准备把 PyTorch 和猫狗图片放到该在的位置2.1 为什么选 PyTorch 而不是别的框架猫狗识别这类图像二分类任务常见做法是用 PyTorch 或 TensorFlow。这份源码包用的是 PyTorch原因很实际它的动态图机制让调试变得直观你可以在训练循环里直接打印中间变量不用先建图再跑会话。对于新手来说报错信息也更容易看懂。另一个原因是迁移学习方便torchvision.models里预训练好的 ResNet、VGG 可以直接拿来改最后一层几分钟就能跑出一个不错的基线。安装 PyTorch 时最容易翻车的地方是 CUDA 版本对不上。如果你有 NVIDIA 显卡先确认驱动支持的 CUDA 版本再去 PyTorch 官网选对应的安装命令。没有显卡就用 CPU 版本训练慢但能跑通。下面这条命令是 CPU 版本的安装方式适合先验证流程# 创建独立环境避免和系统 Python 冲突 conda create -n catdog python3.9 conda activate catdog # 安装 CPU 版 PyTorch如果要用 GPU 请去官网选对应 CUDA 版本的命令 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 验证安装 python -c import torch; print(torch.__version__); print(torch.cuda.is_available())逻辑说明第一行建一个叫 catdog 的虚拟环境Python 版本选 3.9 是因为它在 PyTorch 各版本里兼容性最稳。第二行激活环境后面所有操作都在这个环境里做。第三行装 PyTorch 三件套--index-url指定官方源避免从默认源拉到不匹配的版本。最后一行验证如果输出False说明用的是 CPU能跑但慢输出True说明 GPU 可用。参数说明python3.9可以换成 3.8 或 3.10但不要用 3.11 以上部分旧版 torchvision 还没适配。torch.cuda.is_available()返回False时不要慌先确认显卡驱动装了没再确认安装命令里的 CUDA 版本和驱动匹配。2.2 猫狗图片的目录结构和标签生成源码包里通常会把数据分成训练集和验证集每个集合下面再分 cat 和 dog 两个文件夹。这种结构的好处是标签直接从文件夹名推断不用额外写标注文件。但实际拿到手的图片往往命名混乱、尺寸不一、还有损坏文件。我一般会先跑一遍清洗脚本把打不开的图片删掉再统一缩放到 224×224因为后面用的预训练模型输入就是 224。import os from PIL import Image from torchvision import transforms # 定义预处理缩放、转张量、归一化 data_transform transforms.Compose([ transforms.Resize((224, 224)), # 统一尺寸匹配预训练模型输入 transforms.ToTensor(), # 转成张量像素值从 0-255 变 0-1 transforms.Normalize( # 归一化用 ImageNet 的均值和标准差 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) def check_and_clean(root_dir): bad_files [] for subdir, _, files in os.walk(root_dir): for f in files: path os.path.join(subdir, f) try: img Image.open(path) img.verify() # 验证文件完整性 except Exception: bad_files.append(path) os.remove(path) # 损坏文件直接删掉 print(f清理完成删除 {len(bad_files)} 个损坏文件) check_and_clean(./data/train) check_and_clean(./data/val)逻辑说明transforms.Compose把多个预处理步骤串起来顺序不能乱。Resize放最前面是因为后面两步都依赖统一尺寸。ToTensor把 PIL 图片转成 PyTorch 张量同时把像素值从 0-255 压到 0-1。Normalize用 ImageNet 的统计值是因为我们后面要加载在 ImageNet 上预训练好的模型保持输入分布一致才能发挥迁移学习的效果。参数说明mean和std这三个数不是随便写的是 ImageNet 一百万张图统计出来的。如果你自己从零训练不用预训练权重可以改成你的数据集算出来的值但用预训练权重时千万别改。Resize((224, 224))里的尺寸要和模型期望的输入一致ResNet 和 VGG 都是 224。提示清洗脚本跑完记得检查一下删了多少张如果删掉超过 10%说明数据来源可能有问题最好重新检查原始数据。3. 模型构建与训练循环把预训练网络改成二分类器3.1 用 ResNet18 做迁移学习的三个改动点源码包里如果用的是 ResNet18那它原本是在 ImageNet 上做 1000 类分类的。我们要把它改成猫狗二分类只需要动最后一层全连接层。但这里有个细节直接替换最后一层后前面的卷积层参数要不要冻结我的经验是数据量小于一万张时先冻结前面的层只训练最后一层跑几个 epoch 后再解冻全部微调。这样能防止小数据集上过拟合。import torch import torch.nn as nn from torchvision import models # 加载预训练 ResNet18 model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 冻结所有参数 for param in model.parameters(): param.requires_grad False # 替换最后一层全连接层输出改为 2 类 num_features model.fc.in_features model.fc nn.Linear(num_features, 2) # 只训练最后一层优化器只传 fc 的参数 optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() # 训练几个 epoch 后解冻全部 def unfreeze_all(model): for param in model.parameters(): param.requires_grad True return torch.optim.Adam(model.parameters(), lr1e-4) # 学习率调小逻辑说明weightsmodels.ResNet18_Weights.DEFAULT会自动下载预训练权重第一次跑需要联网。冻结参数用requires_grad False这样反向传播不会更新这些层。替换model.fc时先取in_features拿到原来全连接层的输入维度ResNet18 是 512。优化器只传model.fc.parameters()因为其他层被冻结了传了也不会更新。解冻后学习率要调小因为预训练权重已经很好大步长会破坏它们。参数说明lr1e-3是只训练最后一层时的学习率lr1e-4是全部解冻后的学习率。如果你发现 loss 震荡厉害先把学习率降一个数量级。nn.CrossEntropyLoss()内部已经包含 softmax所以模型输出不要加 softmax。3.2 训练循环里必须盯住的四个量训练循环写起来简单但跑起来后你要知道看什么。我一般盯四个量训练 loss、训练准确率、验证 loss、验证准确率。训练 loss 一直降但验证 loss 开始升就是过拟合的信号。训练准确率高但验证准确率低很多也是过拟合。两个都低说明欠拟合要么模型太小要么训练不够。def train_one_epoch(model, dataloader, criterion, optimizer, device): model.train() # 切换到训练模式 running_loss 0.0 correct 0 total 0 for images, labels in dataloader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() # 清空上一轮梯度 outputs model(images) # 前向传播 loss criterion(outputs, labels) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新参数 running_loss loss.item() _, predicted torch.max(outputs, 1) # 取概率最大的类别 total labels.size(0) correct (predicted labels).sum().item() avg_loss running_loss / len(dataloader) acc correct / total return avg_loss, acc def validate(model, dataloader, criterion, device): model.eval() # 切换到评估模式 running_loss 0.0 correct 0 total 0 with torch.no_grad(): # 关闭梯度计算省显存 for images, labels in dataloader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) running_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() avg_loss running_loss / len(dataloader) acc correct / total return avg_loss, acc逻辑说明model.train()和model.eval()必须成对出现因为 Dropout 和 BatchNorm 在两种模式下行为不同。训练时optimizer.zero_grad()不能省否则梯度会累加。验证时用torch.no_grad()包起来不然显存会爆。torch.max(outputs, 1)返回每行最大值和对应索引索引就是预测类别。参数说明len(dataloader)是批次数量不是图片总数。labels.size(0)是当前批次的图片数。如果显存不够把 batch size 调小比如从 32 降到 16。注意验证集千万不要做数据增强只做 Resize 和 Normalize。训练集可以做随机翻转、随机裁剪来增加多样性。4. 避坑与排查猫狗识别项目里最容易翻车的五个地方4.1 现象训练准确率一直卡在 50% 不动原因二分类任务里 50% 就是随机猜。最常见的原因是标签没对上比如 cat 文件夹被标成 1dog 也标成 1。另一个可能是数据加载时shuffle没开模型只看到同一类图片。还有一种情况是学习率太大loss 直接炸成 NaN但你没发现。解决先打印一个 batch 的标签看看是不是既有 0 又有 1。再检查DataLoader的shuffleTrue有没有写。最后把学习率降到 1e-4 再跑一遍。如果 loss 是 NaN加一句torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)做梯度裁剪。4.2 现象验证集准确率比训练集高很多原因这种情况不常见但一旦出现通常是验证集太小或者和训练集分布不一致。比如验证集里只有容易分类的图片或者训练集做了太强的数据增强导致训练难度远大于验证。解决检查验证集数量至少要有几百张。再看训练增强是不是太狠比如随机裁剪比例太大把猫耳朵裁掉了。把增强强度降下来或者增大验证集比例。4.3 现象GPU 显存明明够却报 CUDA out of memory原因PyTorch 不会自动释放中间变量如果你在训练循环里累积了 loss 或输出没释放显存会越用越多。另一个常见原因是验证时忘了加torch.no_grad()。解决训练循环里用loss.item()取标量不要直接存 tensor。验证和推理一定要包torch.no_grad()。如果还不行在训练循环末尾加torch.cuda.empty_cache()但这不是根本办法根本办法是找到哪里泄漏了。4.4 现象预测时所有图片都输出同一类原因模型在训练时可能只学到了预测多数类。比如猫狗比例是 9:1模型全猜猫也能有 90% 准确率。另一个可能是归一化参数用错了输入分布和训练时不一致。解决先看训练集类别比例如果严重不平衡用WeightedRandomSampler做重采样或者在 loss 里加weight参数。再检查推理时的预处理是不是和训练时完全一致尤其是 Normalize 的均值和标准差。4.5 现象加载保存的模型后预测结果全乱原因保存模型时只存了state_dict加载时模型结构必须和保存时完全一致。如果你改了model.fc的写法加载就会对不上。另一个原因是保存时在 GPU 上加载时在 CPU 上设备不匹配。解决保存时用torch.save(model.state_dict(), model.pth)加载时先实例化同样的结构再model.load_state_dict(torch.load(model.pth, map_locationcpu))。map_location能自动处理设备差异。5. 从能跑到好用模型导出与推理提速的两个技巧5.1 把 PyTorch 模型转成 ONNX 做部署训练完的模型如果只在 Python 里跑那部署时还得装 PyTorch体积大启动慢。常见做法是转成 ONNX 格式然后用 ONNX Runtime 推理速度能快不少而且不依赖 PyTorch。转换时要注意输入尺寸必须固定动态轴要显式指定。import torch import torch.onnx # 加载训练好的模型 model models.resnet18(weightsNone) model.fc nn.Linear(512, 2) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() # 构造一个示例输入 dummy_input torch.randn(1, 3, 224, 224) # 导出 ONNX torch.onnx.export( model, # 要导出的模型 dummy_input, # 示例输入 catdog.onnx, # 输出文件名 input_names[input], # 输入名 output_names[output], # 输出名 dynamic_axes{ # 动态轴batch 维度可变 input: {0: batch_size}, output: {0: batch_size} }, opset_version11 # ONNX 算子集版本 ) print(导出完成)逻辑说明model.eval()必须在导出前调用否则 Dropout 和 BatchNorm 会按训练模式导出推理结果不对。dummy_input的尺寸要和实际推理时一致除了 batch 维度可以动态。dynamic_axes告诉 ONNX 第 0 维是 batch可以变化。opset_version11兼容性比较好太新的版本有些推理引擎不支持。参数说明input_names和output_names是给推理时用的可以自定义。如果导出时报错说某个算子不支持先把opset_version调高试试。导出后用onnxruntime加载验证一下输出是否一致。5.2 推理时的批处理与半精度如果你要一次预测很多张图片不要一张一张跑攒成 batch 一起推理能充分利用 GPU。另外如果用的是支持半精度的显卡把模型转成half()能再快一倍精度损失很小。import onnxruntime as ort import numpy as np # 加载 ONNX 模型 session ort.InferenceSession(catdog.onnx) # 模拟一个 batch 的输入 batch np.random.randn(8, 3, 224, 224).astype(np.float32) # 推理 outputs session.run(None, {input: batch}) predicted np.argmax(outputs[0], axis1) print(预测类别:, predicted)逻辑说明ort.InferenceSession加载模型后会做图优化。session.run第一个参数是输出名列表None表示取所有输出。第二个参数是输入字典键名要和导出时的input_names一致。np.argmax取每行最大值的索引就是预测类别。参数说明batch 里的数据类型必须是float32ONNX 默认不支持float64。如果要用半精度导出时就要指定推理时输入也要转成float16。批大小根据显存调整8 或 16 比较稳。我自己的习惯是每次训完一个模型先不急着调参而是把推理流程完整跑一遍确认从图片到预测结果这条链路没有断点。很多翻车不是模型不行而是预处理和推理时的输入对不上。希望帮到你。本文还有配套的精品资源点击获取
返回列表