
简介这是一份可用于课程设计、期末大作业或毕业设计的机器学习入门项目围绕猫狗二分类图像识别任务提供一套完整可运行的算法源码与配套图片素材。项目基于Python实现包含两个核心Python脚本分别对应模型训练流程与预测演示逻辑另有训练过程图、损失曲线与预测结果图等多张示意图便于对照代码理解数据加载、模型构建和结果输出等关键环节。压缩包共12个文件以py源码、jpg/png图片及说明文档为主整体大小约359KB结构简洁适合具备一定Python与机器学习基础的学习者下载研读。目前已有329人学习浏览可用于快速搭建一个可演示的猫狗识别实验也可作为进一步扩展数据增强、迁移学习或模型调优的起点。1. 猫狗识别源码不是「跑通就行」先看清这套代码解决什么问题拿到一份「基于机器学习的猫狗识别算法源码.zip」很多人第一反应是解压、装依赖、跑 train.py然后盯着 loss 曲线等一个漂亮结果。但作为做过的过来人我得先泼盆冷水这套源码的核心价值不在于「能识别猫和狗」而在于它把「图像分类任务从数据到部署」的最小闭环完整走了一遍你换任何二分类任务比如缺陷检测、车型识别都能按同一套骨架改。机器学习在 CV 里落地难点不在算法理论而在数据怎么喂、模型怎么训、坏结果怎么排查这份源码里的训练脚本、数据划分逻辑和推理封装才是真正值得逐行读的东西。这篇文章不讲解压步骤直接把猫狗识别从数据准备到模型收敛的完整路径拆开讲新手能照着复现熟手能拿去改自己的任务。2. 准备数据集从 raw 目录到可训练目录的标准化脚本猫狗识别最容易被低估的一步是数据准备。Kaggle 的 Dogs vs Cats 原始数据集有 25000 张图文件名形如cat.0.jpg、dog.1024.jpg训练集和验证集没有单独划分。如果你直接拿这个目录去训练会踩两个坑一是验证集和训练集来自同一批采样指标虚高二是文件名里的标签没有转成结构化标注DataLoader 读起来很别扭。常见的做法是先写一个脚本把原始目录重排成train / val / test三段式结构顺便把标签映射成数字 id。2.1 目录结构train/val/test 划分与文件名里的标签伪装我一般会先把 zip 里的原始图片全部丢进一个raw目录然后用脚本按 8:1:1 的比例随机分成训练、验证、测试三个子集。为什么不是 7:2:1因为猫狗识别本身类间差异大模型很容易拟合验证集比例太高反而让训练数据减少1 万张图和 2 万张图训练出来的 ResNet18 精度能差 1 到 2 个点这个差距在二分类任务里不算小。重排后的目录结构长这样dataset/ ├── train/ │ ├── cat/ # 10000 张 │ └── dog/ # 10000 张 ├── val/ │ ├── cat/ # 1250 张 │ └── dog/ # 1250 张 ├── test/ │ ├── cat/ # 1250 张 │ └── dog/ # 1250 张文件名里的cat和dog前缀是伪装标签必须靠目录归属来决定真实标签。你可以在脚本里显式定义类别到索引的映射字典保证后续 DataLoader 拿到的 target 永远来自目录名而不是文件名这一步能在根源上避开文件名大小写、错拼之类的低级错误。2.2 标签与路径的映射写 data loader 前先定好的规则PyTorch 的ImageFolder会自动把子目录名转成类别索引这是最省事的路子但它有个隐蔽问题类别索引的排序是按目录名字母序来的cat是 0dog是 1如果你后续要输出「cat 还是 dog」的文本结果最好自己维护一份反向映射表避免硬编码下标。import os import shutil import random from collections import defaultdict raw_dir raw out_dir dataset random.seed(42) # 收集所有图片路径按图片名里的前缀分类 samples {cat: [], dog: []} for fname in os.listdir(raw_dir): if fname.lower().endswith((.jpg, .jpeg, .png)): label fname.split(.)[0] if label in samples: samples[label].append(fname) # 打乱并切分 for label in samples: files samples[label] random.shuffle(files) n len(files) n_train int(n * 0.8) n_val int(n * 0.1) for part, subset in [(train, files[:n_train]), (val, files[n_train:n_train n_val]), (test, files[n_train n_val:])]: out_subdir os.path.join(out_dir, part, label) os.makedirs(out_subdir, exist_okTrue) for fname in subset: src os.path.join(raw_dir, fname) dst os.path.join(out_subdir, fname) shutil.copy2(src, dst) print(划分完成, len(os.listdir(os.path.join(out_dir, train, cat))), len(os.listdir(os.path.join(out_dir, train, dog))))这段脚本逻辑很简单但有两个参数值得专门说。random.seed(42)保证了可复现性你或者同事重新跑一遍得到完全一样的划分这在对比实验里非常重要否则你换了划分方式后模型精度变了根本分不清是数据变了还是模型变了。shutil.copy2复制而不是移动文件保留原始 raw 目录作为备份万一后续预处理写错了还能重新来不用重新下载 25000 张图。血的教训是很多人图省事直接用os.rename移动文件最后 raw 目录空了重跑脚本还要重新解压 zip。2.3 划分脚本shuffle 后落盘固定随机种子为什么要先 shuffle 再切分而不是按文件名顺序前 80% 做训练因为原始数据集的图片排列是有序的前面大量是猫后面大量是狗直接顺序切分会让验证集全是狗训练集全是猫模型直接学崩。shuffle 的作用是让每个子集的类别分布趋近全局分布这一步在数据量小的时候尤其关键25000 张图完全够用但如果你以后换到几千张的小数据集shuffle 策略直接决定模型能不能收敛。固定随机种子还有一个隐藏好处多人协作时大家拿到的训练集完全一致讨论超参数才有意义。如果你发现同事复现不出你的精度先问一句「你的 seed 是多少」。3. 训练主流程迁移学习 ResNet18三步把准确率推到 97% 以上数据集准备好之后训练环节是整份源码的核心。猫狗识别在 ImageNet 时代已经被刷到接近满分所以正确的打开方式不是从零训练一个 CNN而是用预训练模型做迁移学习。这里我以 PyTorch 为例讲一套实战管用的配置你改改路径就能直接在本地跑起来。3.1 为什么选迁移学习而不是从零训练从零训练一个 ResNet18 在单张消费级显卡上大概要跑 3 到 5 个小时而且精度很难突破 95%。原因很直白猫狗识别需要学到的特征边缘、纹理、形状跟 ImageNet 上 1000 类分类学到的底层特征高度重合预训练模型已经把「怎么看一张图」这件事学会了你要做的只是在它后面接一个小分类头学「猫和狗的区别」这种高层语义。常见做法是拿 ResNet18 在 ImageNet 上的权重做初始化冻结前几层只训练最后一两层和新的全连接层。这样一张 2080 级别的显卡 20 分钟就能跑完 15 个 epoch精度还更高。如果你手里的机器没有 GPU用 CPU 跑 ResNet18 也不是不行但要降低图片分辨率和 batch size后面我会给具体参数。3.2 训练脚本冻结骨干、替换分类头、AdamW 优化器PyTorch 的torchvision.models里直接提供了预训练权重不需要自己去找下载链接。关键操作是先冻结骨干网络参数的requires_grad再把最后一层fc换成输出维度为 2 的新全连接层import torch import torch.nn as nn from torchvision import models, transforms # 加载预训练 ResNet18weights 参数指定为 IMAGENET1K_V1 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 冻结所有参数 for param in model.parameters(): param.requires_grad False # 替换最后一个全连接层输入维度 512输出维度 2cat/dog model.fc nn.Linear(512, 2) # 只让新加的分类头参与训练 for param in model.fc.parameters(): param.requires_grad True optimizer torch.optim.AdamW(model.fc.parameters(), lr1e-3, weight_decay1e-4)这里有个参数选择上的细节。lr1e-3是针对「只训练一个分类头」的取值如果你解冻了最后几个残差块一起微调学习率要降到1e-4或1e-5否则预训练权重被大步长更新冲乱loss 会在前几个 epoch 剧烈震荡。weight_decay1e-4是 L2 正则对二分类这种小数据集能有效压制过拟合但别调太大超过1e-3模型会欠拟合训练集准确率都上不去。优化器选 AdamW 而不是传统 SGD 的原因也值得说一句AdamW 的权重衰减和动量解耦对微调场景更稳收敛速度明显更快你不用像 SGD 那样反复试 learning rate 的指数级区间。如果你手里的源码用的是老式Adam改成AdamW会在同样 epoch 数下让验证集准确率提升 0.5 到 1 个点这种提升纯粹来自优化器选择值得动手改。3.3 超参与实验记录epoch、lr、batch_size 的配合方式训练主循环里除了模型结构最影响结果的是几个超参数的配合。我常用的组合是这样的transform_train transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) transform_val transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset torchvision.datasets.ImageFolder(dataset/train, transformtransform_train) val_dataset torchvision.datasets.ImageFolder(dataset/val, transformtransform_val) train_loader torch.utils.data.DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers4) val_loader torch.utils.data.DataLoader(val_dataset, batch_size64, shuffleFalse, num_workers4)batch_size64在 8GB 显存上跑 ResNet18 是安全值如果你的显存只有 4GB降到 32 即可。训练集做了随机裁剪和水平翻转验证集只做缩放和中心裁剪这是图像分类的标准配方。RandomResizedCrop的scale(0.8, 1.0)控制了裁剪面积比例如果发现模型过拟合可以把下限调到 0.5让模型看到更多局部细节如果欠拟合回到 0.9 让模型看到更完整的物体。ColorJitter是对抗光照变化的有效手段猫狗照片的环境差异很大加上它能让模型对颜色不敏感但对这个任务帮助有限毕竟猫和狗的颜色本身有重叠。epoch 数怎么定我建议先跑 15 个 epoch 看验证集曲线如果 loss 还在下降就把epochs翻倍。训练完成后保存best和last两份权重best是验证集准确率最高的那一个 epoch 的模型last是最后一个 epoch 的模型两者对比能看出是否过拟合best_acc 0.0 for epoch in range(15): model.train() running_loss 0.0 for inputs, labels in train_loader: outputs model(inputs) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for inputs, labels in val_loader: outputs model(inputs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() acc 100.0 * correct / total print(fEpoch {epoch1}: loss{running_loss/len(train_loader):.4f}, acc{acc:.2f}%) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pth) torch.save(model.state_dict(), last_model.pth)这里的criterion是nn.CrossEntropyLoss()对二分类任务它输出的 loss 含义是「预测分布和真实分布的交叉熵」值越小越好。你不需要像回归任务那样关注 loss 的绝对值只需要看它是否单调下降。如果 loss 在某个 epoch 后反而上升验证集准确率也跟着掉那就是学习率太大或者数据增强过强线性调低lr即可。4. 猫狗识别源码的 5 个典型避坑现场数据泄漏、伪标签、权重坏档、显存不足、推理抖动训练脚本跑通只是第一步真正的坑都在「你以为跑通了」之后。我把这几年做图像分类踩过的坑按现象、原因、解决的顺序列出来每一条都对应真实翻车现场。坑位一验证集准确率 99%测试集却只有 92%。现象训练时验证集指标一路飙升模型看起来完美收敛但换到测试集上效果大打折扣。 原因这是典型的数据泄漏。很多猫狗数据集在采集时同一只猫或狗的多张照片会被连续放在一起按文件名顺序划分训练集和验证集时同一只动物的照片同时出现在两边模型实际是在「认个体」而不是「认猫狗」。这种情况在 Kaggle 原始数据集里不是特别严重但如果你自己爬数据做识别几乎一定会碰上。 解决划分数据集前先按「图片所属个体」去重最简单的做法是文件名里如果有动物 id按动物 id 分组后再随机划分保证同一个体的所有照片都在同一个子集里。如果你拿到的数据是纯文件名没有 id那就只能靠人工清洗没有捷径。坑位二model.eval() 忘了加推理结果时好时坏。现象训练完写推理脚本同一张图片预测结果每次都可能不一样。 原因模型里有 BatchNorm 和 Dropout训练模式下它们的行为是随机的Dropout 随机丢弃神经元BatchNorm 用 batch 统计量做归一化你忘了切换model.eval()等于拿一个处于「训练状态」的模型去做预测输出自然抖动。 解决推理前必须显式调用model.eval()而且要放在with torch.no_grad()块外面。可以用model.training属性打印确认状态True 就是还在训练模式。坑位三预训练权重下载失败或者文件损坏。现象torchvision.models.resnet18(weights...)在服务器上跑的时候报HTTP Error 403或者 SHA256 校验失败。 原因国内网络访问某些权重下载地址不稳定断点续传不完整导致文件损坏。 解决手动下载权重文件放到~/.cache/torch/hub/checkpoints/目录然后用weightsResNet18_Weights.IMAGENET1K_V1方式加载时会自动校验文件完整性。服务器上没有外网的话从能访问外网的机器下载后拷进离线环境放对路径就行。坑位四显存溢出 OOM但 batch_size 已经很小了。现象batch_size32在 8GB 显卡上跑 ResNet18 仍然报CUDA out of memory。 原因输入图片分辨率不是 224 而是原始尺寸DataLoader 没有做Resize导致显存被超大特征图撑爆。这种情况多发生在你改了数据路径但没改 transform 的时候。 解决在 transform 链最前面加transforms.Resize((224, 224))并且检查 DataLoader 的num_workers别开太大超过 CPU 核心数反而会因内存拷贝导致内存溢出。如果还爆把 batch size 降到 16观察这段训练里显存占用是否稳定。坑位五推理脚本读图方式和训练不一致预处理顺序错乱。现象用 OpenCV 的cv2.imread读图预测结果整体偏乱猫狗识别准确率比训练低一大截。 原因OpenCV 读出来的是 BGR 通道顺序而训练时 PyTorch 的ImageFolder读的是 RGB通道顺序反了之后模型看到的颜色分布完全乱了。模型对颜色虽然不敏感但经过 ImageNet 的 mean/std 归一化后这种通道错乱会让特征分布偏移到没见过的地方。 解决推理脚本里统一用 PIL 或cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转成 RGB 再走同一套 pipeline。强烈建议把训练和推理共享的 transform 封装成同一个函数别在推理脚本里重新写一遍。5. 把源码从「能跑」推进到「能验」混淆矩阵、特征可视化和保存检查点训练完了模型也保存了但一份合格的机器学习源码不能止步于「能跑出准确率」。你需要有能力回答三个问题模型错在哪里、为什么错、下次怎么改。这一章讲三个我常用的验证手段它们能让你的调参从玄学变成有依据的工程决策。5.1 混淆矩阵看错在哪而不是只盯准确率二分类只看准确率会掩盖一个重要事实模型可能把狗全部认对却把 20% 的猫认成狗。这时候准确率可能还有 90%但你已经知道这个模型不能用于真实场景。混淆矩阵能直观展示这种类别不均衡的错误分布。from sklearn.metrics import confusion_matrix, classification_report y_true [] y_pred [] model.eval() with torch.no_grad(): for inputs, labels in val_loader: outputs model(inputs) _, predicted torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(predicted.cpu().numpy()) cm confusion_matrix(y_true, y_pred) print(混淆矩阵:) print(cm) print(classification_report(y_true, y_pred, target_names[cat, dog]))confusion_matrix的返回值是 2x2 数组cm[0][0]是猫预测为猫的数量cm[0][1]是猫预测为狗的数量。如果你的cm[0][1]显著高于cm[1][0]说明模型对猫的召回率低常见原因是训练集里猫的照片比狗少或者猫的姿势变化太大需要针对性补充难例。classification_report里的 macro avg 比单一准确率更能反映模型在两类上的均衡表现如果两个类别的 F1 差距超过 5 个点就要考虑类别平衡采样。5.2 检查点保存策略best 和 last 分开落盘训练脚本里我把best_model.pth和last_model.pth分开保存这个习惯在调参时非常有用。best模型是验证集最优的那一版last是训练结束时的状态两者对比可以看出模型在训练后期是否过拟合。如果best出现在第 5 个 epoch 而last的准确率掉了 3 个点说明后续 epoch 在无用功直接把epochs缩减到 8 到 10 个。还有一种情况是验证集准确率一直在一个区间抖动best和last相差无几这说明模型已经收敛加大 epoch 数没有意义该做的是调学习率或者换更强的数据增强。检查点文件名里建议带上准确率和 epoch 号比如resnet18_epoch12_acc97.3.pth免得一个月后回来看文件名根本记不清哪个对应哪次实验。5.3 让源码替你记录实验TensorBoard 和训练日志的落盘结构反复调参之后你会发现比「调不出好模型」更痛苦的是「调出了好模型但忘了当时用了什么参数」。我一般会在训练脚本里加一段配置日志把超参数、数据路径、预处理方式、权重文件名全部写进一个 JSON 文件和模型权重放在同一个实验目录下import json exp_config { model: resnet18_imagenet, optimizer: adamw, lr: 1e-3, weight_decay: 1e-4, epochs: 15, batch_size: 64, train_no_aug: False, seed: 42, } with open(exp_config.json, w) as f: json.dump(exp_config, f, indent4)这份配置文件的另一层价值是当你把源码交给同事或未来的自己时对方能直接复现你的实验环境。机器学习项目最大的隐形负债是「实验记录不完整」源代码本身反而是最容易保存的部分配置和权重才是真正的资产。把每一个实验跑完的权重、配置、混淆矩阵截图放在同一个目录按日期命名这个习惯能让你在项目三个月后回来续坑时不用重新摸索参数。如果你还想再进一步可以用torch.utils.tensorboard.SummaryWriter把训练 loss 和验证准确率实时写出来在浏览器里看曲线变化。对于猫狗识别这种快速收敛的任务曲线图能帮你在第 5 个 epoch 就判断这一次要不要早停省下的训练时间比写日志的成本高得多。回到这份「基于机器学习的猫狗识别算法源码.zip」我的最终建议是先按第二章的脚本重排数据再按第三章的配置把训练跑通然后用第四章的避坑清单检查你的 pipeline最后用第五章的验证手段确认模型真的可用。这条路走通之后换任何二分类数据集都只是改数据路径和类别数量的问题。我在第一次做这类任务时也试过不划分验证集直接训练结果模型在训练集上 100% 准确率、真实图片上一塌糊涂后来老老实实把数据管线做好一次就收敛到了预期水平。希望这份实战笔记能帮你少走这些弯路。本文还有配套的精品资源点击获取