简介:这份资源是面向计算机相关专业学生与深度学习入门者的CNN猫狗图像识别实战项目,适用于期末大作业、毕业设计及项目实战练习,难度适中,评审得分98分。压缩包共约2000个文件,整体218.49MB,其中1990张jpg图片构成猫狗训练与测试数据集,另含1份pdf项目文档、少量xml标注文件及py源码、md说明等,覆盖数据、代码与文档三类核心内容。项目源码经本地编译与严格调试,可正常运行,读者可据此掌握卷积神经网络的数据预处理、模型搭建、训练调参与分类预测完整流程,并借助文档理解目录结构与实现思路,快速复现并改写为自己的作业方案。目前已有222人学习下载,适合需要一份可直接运行、结构清晰的高分参考项目来查漏补缺的学习者。
1. 从一份猫狗识别源码说起:CNN 图像分类到底能落地成什么
很多人第一次接触深度学习,都是从「猫狗分类」这个经典任务开始的。它看起来简单,但真正动手跑一遍就会发现,从环境配置、数据组织、模型搭建到训练调参,每一步都有坑。这个项目标题里的关键词是 Python、CNN、猫狗图像识别、图像分类、项目源码,本质上就是一套完整的图像二分类工程实践:用卷积神经网络把输入图片判定为猫或狗。
它适合谁?零基础想入门深度学习的人,可以用它跑通第一个端到端流程;有 Python 基础但没碰过 CNN 的人,可以借它理解卷积、池化、全连接这些概念怎么落到代码里;做过传统机器学习的人,可以对比 SVM 和 CNN 在图像任务上的差距。这篇文章不讲空泛概念,而是按「数据怎么准备、模型怎么搭、训练怎么调、坑在哪」的顺序,把一套可复现的方案讲清楚。你照着做,能在本地跑出一个准确率 90% 以上的猫狗分类模型。
2. 数据准备与目录结构:把 25000 张图整理成能直接喂给模型的样子
2.1 猫狗数据集从哪来、怎么划分
猫狗图像分类最常用的公开数据集是 Kaggle 上的 Dogs vs. Cats,训练集各 12500 张,测试集 12500 张。如果你拿到的项目源码里已经带了数据集,先确认目录结构。常见做法是整理成下面这样:
dataset/ ├── train/ │ ├── cats/ # 12500 张猫图 │ └── dogs/ # 12500 张狗图 ├── validation/ │ ├── cats/ # 从训练集切出 2500 张 │ └── dogs/ # 从训练集切出 2500 张 └── test/ ├── cats/ └── dogs/为什么要单独切验证集?因为训练过程中你需要一个模型没见过的数据来监控是否过拟合。如果只用训练集和测试集,你调参时实际上是在「偷看」测试集,最终指标会虚高。我一般按 8:1:1 或 9:1 的比例切,猫狗各自独立切,保证类别平衡。
如果原始数据是混在一起的,用下面这段脚本自动划分:
import os import random import shutil # 原始数据目录,猫狗图片混放或已分文件夹 src_cats = "raw/cats" src_dogs = "raw/dogs" base = "dataset" # 创建目标目录 for split in ["train", "validation", "test"]: for cls in ["cats", "dogs"]: os.makedirs(os.path.join(base, split, cls), exist_ok=True) def split_data(src_dir, cls_name, train_ratio=0.8, val_ratio=0.1): files = [f for f in os.listdir(src_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))] random.seed(42) # 固定随机种子,保证可复现 random.shuffle(files) n = len(files) n_train = int(n * train_ratio) n_val = int(n * val_ratio) splits = { "train": files[:n_train], "validation": files[n_train:n_train + n_val], "test": files[n_train + n_val:] } for split, flist in splits.items(): dst = os.path.join(base, split, cls_name) for f in flist: shutil.copy(os.path.join(src_dir, f), os.path.join(dst, f)) print(f"{cls_name}: train={len(splits['train'])}, val={len(splits['validation'])}, test={len(splits['test'])}") split_data(src_cats, "cats") split_data(src_dogs, "dogs")这段脚本的关键参数是random.seed(42),固定种子后每次划分结果一致,方便复现实验。train_ratio和val_ratio按你的数据量调整,数据少的时候验证集比例可以降到 0.1。注意图片扩展名要覆盖 jpg、jpeg、png,有些数据集里混了多种格式,漏掉会导致样本数对不上。
2.2 用 ImageDataGenerator 做在线增强,别把增强图存盘
很多人做数据增强时,先把增强后的图片生成出来存到硬盘,再读进来训练。这样做有两个问题:一是占空间,25000 张图增强 5 倍就是 12 万张;二是每轮训练看到的增强图是固定的,泛化效果打折。正确做法是用 Keras 的ImageDataGenerator做在线增强,每个 epoch 实时生成不同的增强版本。
from tensorflow.keras.preprocessing.image import ImageDataGenerator IMG_SIZE = (150, 150) # 统一缩放到 150x150 BATCH_SIZE = 32 # 训练集:带增强 train_datagen = ImageDataGenerator( rescale=1.0 / 255, # 像素归一化到 [0,1] rotation_range=20, # 随机旋转 ±20 度 width_shift_range=0.1, # 水平平移 10% height_shift_range=0.1, # 垂直平移 10% shear_range=0.1, # 剪切变换 zoom_range=0.1, # 随机缩放 horizontal_flip=True, # 水平翻转,猫狗都适用 fill_mode="nearest" # 变换后空白用最近像素填充 ) # 验证集和测试集:只归一化,不增强 val_datagen = ImageDataGenerator(rescale=1.0 / 255) train_gen = train_datagen.flow_from_directory( "dataset/train", target_size=IMG_SIZE, batch_size=BATCH_SIZE, class_mode="binary" # 二分类,标签为 0/1 ) val_gen = val_datagen.flow_from_directory( "dataset/validation", target_size=IMG_SIZE, batch_size=BATCH_SIZE, class_mode="binary" )rescale=1.0/255是必须的,CNN 对输入尺度敏感,不归一化会导致训练震荡。horizontal_flip=True对猫狗任务安全,因为翻转后的猫还是猫;但如果你做的是文字识别或方向敏感的任务,这个参数要关掉。class_mode="binary"表示输出一个 0 到 1 的概率值,配合sigmoid激活函数使用。如果你改成多分类,这里要换成categorical,输出层也要相应调整。
注意:
flow_from_directory会按文件夹名的字母顺序分配标签,cats 在前就是 0,dogs 是 1。预测时别把标签对应关系搞反,否则准确率再高也是白搭。
3. 搭一个能打的 CNN:从三层卷积到迁移学习的选型对比
3.1 从零搭 CNN 的结构设计与参数含义
项目源码里通常会给一个基础 CNN 结构,我一般从三层卷积起步,结构如下:
from tensorflow.keras import layers, models def build_cnn(input_shape=(150, 150, 3)): model = models.Sequential([ # 第一层卷积:提取边缘、纹理等低级特征 layers.Conv2D(32, (3, 3), activation="relu", input_shape=input_shape), layers.MaxPooling2D((2, 2)), # 第二层卷积:组合低级特征成局部形状 layers.Conv2D(64, (3, 3), activation="relu"), layers.MaxPooling2D((2, 2)), # 第三层卷积:提取更抽象的语义特征 layers.Conv2D(128, (3, 3), activation="relu"), layers.MaxPooling2D((2, 2)), # 展平后接全连接层 layers.Flatten(), layers.Dropout(0.5), # 随机丢弃 50%,抑制过拟合 layers.Dense(512, activation="relu"), layers.Dense(1, activation="sigmoid") # 二分类输出概率 ]) return model model = build_cnn() model.compile( optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"] ) model.summary()Conv2D(32, (3, 3))里的 32 是卷积核数量,也就是输出特征图通道数;(3,3) 是卷积核尺寸。核越多,能提取的特征模式越丰富,但参数量和计算量也越大。三层之后特征图尺寸从 150 降到 17 左右,再往下池化会丢太多空间信息,所以三层是个平衡点。Dropout(0.5)放在全连接层前,训练时随机丢弃一半神经元,是防止过拟合最直接的手段。sigmoid把输出压到 0 到 1,配合binary_crossentropy计算损失。
这个从零搭的模型在猫狗数据集上大概能跑到 80% 到 85% 的验证准确率,训练 20 到 30 个 epoch。想再往上提,就得靠迁移学习。
3.2 迁移学习:用预训练模型把准确率拉到 95% 以上
从零训练 CNN 需要大量数据和算力,而猫狗数据集只有 25000 张,容易过拟合。更实用的做法是拿在 ImageNet 上预训练好的模型做特征提取,只训练顶部分类层。常见选择有 VGG16、ResNet50、MobileNetV2。我一般先用 MobileNetV2,因为它轻量,CPU 也能跑。
from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras import layers, models # 加载预训练权重,去掉顶部分类层 base_model = MobileNetV2( input_shape=(150, 150, 3), include_top=False, # 不要 ImageNet 的 1000 类分类头 weights="imagenet" # 加载预训练权重 ) # 冻结卷积基,不让它在训练初期被破坏 base_model.trainable = False model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), # 替代 Flatten,减少参数 layers.Dropout(0.3), layers.Dense(1, activation="sigmoid") ]) model.compile( optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"] )include_top=False是关键,去掉原模型的分类头,换成我们自己的二分类输出。base_model.trainable = False先冻结卷积基,只训练新增层,这样收敛快且不容易过拟合。训练 10 个 epoch 后,可以解冻最后几层做微调,学习率要调小到 1e-5 级别,否则会把预训练权重冲垮。
| 方案 | 验证准确率 | 训练时间(CPU) | 适用场景 |
|---|---|---|---|
| 从零搭三层 CNN | 80%~85% | 较慢 | 学习原理、数据量大 |
| MobileNetV2 迁移 | 95%+ | 快 | 快速出结果、部署轻量 |
| ResNet50 迁移 | 96%+ | 中等 | 追求精度、有 GPU |
选哪个取决于你的目标。如果是为了理解 CNN 原理,从零搭;如果是为了尽快拿到可用模型,直接上迁移学习。项目源码里两种通常都会给,建议都跑一遍对比。
4. 训练、评估与推理:把模型跑起来并验证它真的能用
4.1 训练回调:早停与学习率衰减怎么配
训练时别傻跑固定 epoch,用回调函数自动控制。最常用的两个是EarlyStopping和ReduceLROnPlateau。
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint callbacks = [ # 验证损失 5 轮不降就停,并恢复最佳权重 EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True), # 验证损失 3 轮不降就把学习率减半 ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=3, min_lr=1e-6), # 只保存验证准确率最高的模型 ModelCheckpoint("best_model.h5", monitor="val_accuracy", save_best_only=True) ] history = model.fit( train_gen, epochs=30, validation_data=val_gen, callbacks=callbacks )patience=5表示连续 5 个 epoch 验证损失没改善就停,避免浪费时间。restore_best_weights=True保证训练结束后模型回到最佳状态,而不是最后一轮的状态。ReduceLROnPlateau在损失停滞时降低学习率,帮助模型跳出局部最优。ModelCheckpoint只存最好的模型,省得你手动挑。
4.2 评估与单张图片推理
训练完先看验证集准确率和混淆矩阵,别只看一个数字。
import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing import image # 加载最佳模型 model = load_model("best_model.h5") # 在测试集上评估 test_datagen = ImageDataGenerator(rescale=1.0 / 255) test_gen = test_datagen.flow_from_directory( "dataset/test", target_size=(150, 150), batch_size=32, class_mode="binary", shuffle=False # 评估时不要打乱,保证顺序对应 ) loss, acc = model.evaluate(test_gen) print(f"测试集准确率: {acc:.4f}") # 单张图片推理 def predict_image(img_path): img = image.load_img(img_path, target_size=(150, 150)) arr = image.img_to_array(img) / 255.0 arr = np.expand_dims(arr, axis=0) # 增加 batch 维度 pred = model.predict(arr)[0][0] label = "dog" if pred > 0.5 else "cat" print(f"{img_path} -> {label} (置信度: {pred:.4f})") return label predict_image("dataset/test/cats/cat_001.jpg")shuffle=False在评估时很重要,否则你没法把预测结果和真实标签一一对应。np.expand_dims是因为模型期望输入是(batch, height, width, channels),单张图只有三维,要补一个 batch 维度。置信度接近 0.5 的样本说明模型拿不准,实际部署时可以把这类样本挑出来人工复核。
5. 避坑与排查:猫狗分类项目里最容易翻车的 5 个地方
5.1 准确率卡在 50% 不动
现象:训练几个 epoch 后准确率一直在 0.5 附近,损失也不降。
原因:最常见的是标签和输出不匹配。比如class_mode设成了categorical但输出层用了sigmoid,或者标签顺序搞反了。另一个可能是学习率太大,模型直接发散。
解决:先检查class_mode和输出层激活函数是否配套,二分类用binary+sigmoid,多分类用categorical+softmax。然后把学习率降到 1e-4 试试。如果还不行,打印一个 batch 的标签看看是不是全 0 或全 1。
5.2 训练准确率高但验证准确率低
现象:训练集准确率 99%,验证集只有 70%,差距巨大。
原因:过拟合。数据量不够、模型太复杂、没加正则化都会导致。
解决:先加Dropout和数据增强,再把模型简化,或者直接换迁移学习。如果数据量确实少,考虑冻结更多层,只训练分类头。早停也能缓解,但治标不治本。
5.3 内存爆了或者训练极慢
现象:训练时内存占用飙升,或者一个 epoch 要跑很久。
原因:batch_size太大,或者图片没缩放直接读入原图。猫狗原图尺寸不一,有的超过 500x500,不缩放会拖垮内存。
解决:target_size统一设成 150x150 或 224x224,batch_size从 32 起步,内存不够就降到 16 或 8。用flow_from_directory是在线读取,不会一次性加载所有图片,比手动np.array全部读入省内存。
5.4 预测结果全是同一类
现象:不管输入什么图,模型都输出 cat 或都输出 dog。
原因:类别极度不平衡,或者归一化没做。如果训练集里猫狗数量差很多,模型会倾向于预测多数类。
解决:确认猫狗样本数是否接近,差太多就做重采样或加类别权重。检查rescale有没有加,像素值 0 到 255 直接输入会让训练不稳定。另外确认预测时的预处理和训练时一致,训练用了rescale预测也要用。
5.5 保存的模型加载后预测结果不对
现象:训练时验证准确率很高,保存后重新加载,预测全错。
原因:保存的是模型结构而不是权重,或者加载时没指定custom_objects。更常见的是保存了最后一轮的模型而不是最佳模型。
解决:用ModelCheckpoint保存最佳权重,加载时用load_model直接读 h5 文件。如果模型里用了自定义层或损失函数,加载时要传custom_objects。养成习惯:保存后立刻加载一次,用同一张图对比预测结果是否一致。
6. 把模型推到能用的程度:几个我反复验证过的技巧
训练出一个 95% 准确率的模型只是起点,真正要用起来还得处理一些细节。第一个技巧是测试时增强(TTA):对同一张测试图做多次增强变换,把预测结果平均,能稳定提升 1 到 2 个百分点。实现很简单,对每张图分别做原图、水平翻转、轻微旋转三种变换,各预测一次取平均。
def predict_with_tta(model, img_path): img = image.load_img(img_path, target_size=(150, 150)) arr = image.img_to_array(img) / 255.0 batch = np.expand_dims(arr, axis=0) # 原图 p1 = model.predict(batch)[0][0] # 水平翻转 p2 = model.predict(batch[:, :, ::-1, :])[0][0] # 轻微旋转用 numpy 简单实现 p3 = model.predict(np.rot90(batch, k=1, axes=(1, 2)))[0][0] return (p1 + p2 + p3) / 3第二个技巧是阈值调整。默认 0.5 作为分类阈值,但如果你的场景对漏检更敏感,可以把阈值降到 0.4,宁可错杀不可放过。反过来如果对误报敏感就提高到 0.6。这个阈值要在验证集上按业务需求调,没有万能值。
第三个技巧是错误分析。把验证集里预测错的样本挑出来,按置信度排序,看看模型在哪些图上翻车。我做过一次,发现大部分错误集中在远景、遮挡、多只动物同框的图上。针对这些情况,要么补充类似数据,要么在预处理阶段做裁剪。
最后一个习惯:每次改完参数,记录验证准确率、训练时间、模型大小三个指标。我一般用个简单的表格记,跑过十几轮后就能看出哪些改动真正有效。别凭感觉调参,血泪经验告诉我,感觉往往不靠谱。
这套流程跑下来,从数据整理到模型部署,大概两三天能走完。项目源码和数据集给你的是起点,真正值钱的是你踩过的坑和调过的参数。希望帮到你。
本文还有配套的精品资源,点击获取