CNN猫狗图像分类实战:数据增强与97%识别率实现

发布时间:2026/10/1 3:33:06
CNN猫狗图像分类实战:数据增强与97%识别率实现 简介这是一份基于Python和卷积神经网络的猫狗图像分类毕业设计项目适合计算机相关专业学生用于课程设计、毕业设计参考或图像识别入门实践。项目围绕二分类任务采用数据增强策略提升泛化能力当前模型在猫狗数据上识别率约97%源码中将2000张图像划分为训练集1000张作为验证集1000张作为测试集也保留了便于读者改用完整数据集自行训练的修改入口。压缩包共2000个文件主要由1992张jpg图片、7个Python脚本和1个说明文档组成整体大小约87.85MB图片数据与代码分层存放便于直接对照学习。目前已有1133人浏览学习。读者可从中获得完整可运行的CNN分类流程、数据预处理与增强思路、训练集划分方法和模型调优参考对理解深度学习工程落地与毕业设计写作均有帮助。1. 借毕业设计讲透 CNN 猫狗分类97% 识别率到底怎么来的一个基于 Python 卷积神经网络 CNN 的猫狗图像分类项目能在毕业设计里拿到高分靠的往往不只是模型本身而是从数据划分到训练验证的一整条链路都经得起追问。这份源码提取了 2000 张图像做训练集、1000 张做验证集、1000 张做测试集采用数据增强和 CNN 分类猫狗分类识别率达到 97%。对正在做图像分类毕业设计的同学来说它最大的价值不是那 97% 的数字而是一套可以照搬的完整流程数据怎么组织、增强怎么做、网络怎么搭、准确率怎么统计。这篇笔记就从这几个角度把项目拆开照着复现能少走不少弯路。2. 数据集准备与 CNN 选型2000/1000/1000 划分背后的门道2.1 目录结构与文件名规律先看懂数据再动手拿到压缩包后第一件事不是急着跑训练而是把目录结构和文件名规律摸清楚。这个项目里的图像文件名形如cat.835.jpg、dog.157.jpg命名规则是“类别.编号.jpg”。这种命名方式在 Kaggle 的猫狗大战数据集里很典型它的好处是类别信息直接写在文件名里不需要额外的标注文件。我一般会先写一段脚本统计每个目录下的文件数量确认训练集、验证集、测试集是否和摘要里描述的 2000/1000/1000 一致import os from collections import Counter base_dirs [train, val, test] for d in base_dirs: if not os.path.exists(d): print(f[警告] 缺少目录: {d}) continue files os.listdir(d) cats [f for f in files if f.startswith(cat.) and f.endswith(.jpg)] dogs [f for f in files if f.startswith(dog.) and f.endswith(.jpg)] print(f{d}: 猫 {len(cats)} 张, 狗 {len(dogs)} 张) # 检查是否有文件名不规范的图片 bad [f for f in files if not (f.startswith(cat.) or f.startswith(dog.))] if bad: print(f 发现 {len(bad)} 个异常文件名: {bad[:5]})这段脚本做三件事确认数据集目录是否存在统计猫狗数量是否平衡过滤出文件名不规范的样本。毕业设计答辩时这些准备工作是可以直接讲给老师听的它说明你对数据做过质量检查而不是拿过来就训练。这里有个细节值得注意如果某个目录下猫狗数量相差很大训练出来的模型会有明显的类别偏向。从摘要看2000 张训练集应该是猫狗各 1000 张验证集和测试集同理。拿到任何新数据集我都会先做这个统计确认类别平衡后再谈建模。2.2 数据增强为什么小样本也能撑起 97%2000 张训练图对于深度学习来说并不算多尤其 CNN 这种参数规模大的模型直接训练很容易过拟合。这个项目能到 97%数据增强功不可没。数据增强的本质是在不改变图像语义的前提下对原图做随机变换让模型见过更多“变体”从而提升泛化能力。常见的做法是用ImageDataGenerator在线增强训练时每一轮迭代都会随机生成一批新图像from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1./255, # 像素归一化到 [0, 1] rotation_range20, # 随机旋转 ±20 度 width_shift_range0.2, # 水平方向随机平移 20% height_shift_range0.2, # 垂直方向随机平移 20% shear_range0.2, # 错切变换强度 zoom_range0.2, # 随机缩放 80%~120% horizontal_flipTrue # 水平翻转猫狗图片翻转后语义不变 ) val_datagen ImageDataGenerator(rescale1./255) # 验证集只归一化不做增强 train_generator train_datagen.flow_from_directory( train, target_size(150, 150), batch_size32, class_modebinary ) val_generator val_datagen.flow_from_directory( val, target_size(150, 150), batch_size32, class_modebinary )参数说明rescale1./255是必须的因为 CNN 对输入数值范围敏感原始像素值 0~255 直接喂进去会导致梯度更新不稳定rotation_range、width_shift_range、height_shift_range控制几何变换幅度一般设在 0.1~0.3 之间太大会把猫狗的主体移出画面horizontal_flipTrue对猫狗分类是安全的因为左右对称不影响类别判断但不能开vertical_flip——把狗倒过来看起来就不像正常样本了反而会干扰学习。为什么验证集不做增强因为验证集的作用是评估模型在真实数据上的表现。如果验证集也做随机旋转、平移那每次评估的样本都在变结果不稳定无法横向对比不同训练轮次的模型好坏。这个道理很多初学者容易忽略总想着“增强越多越好”实际上增强只应该作用在训练集上。从摘要看项目对每张训练图做了这些变换后等效训练样本量变成了原来的几十倍2000 张图足以训练一个中等规模的 CNN。这也是为什么 97% 识别率在小数据集上能实现的原因之一。3. 模型搭建与训练参数从卷积层到全连接的完整套路3.1 网络结构怎么设计几层卷积、多少过滤器才合适猫狗分类是二分类问题网络不需要特别深。这个项目用的 CNN 属于经典的小型卷积网络结构大致是卷积层 池化层 卷积层 池化层 全连接层 输出层。我把常见做法写成可运行代码方便你对照自己的源码理解from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(150, 150, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dense(512, activationrelu), Dropout(0.5), Dense(1, activationsigmoid) ]) model.summary()每层的作用第一层Conv2D(32, (3, 3))提取低级特征比如边缘、颜色块池化层把特征图尺寸减半减少计算量并增强平移不变性后面卷积层通道数逐层翻倍对应的是提取越来越抽象的高级特征Flatten把三维特征图拉成一维向量接全连接层做分类Dropout(0.5)随机丢弃一半神经元防止过拟合最后一层用sigmoid输出一个 0~1 之间的概率值大于 0.5 判为狗小于 0.5 判为猫。注意input_shape(150, 150, 3)这决定了输入图像必须统一缩放为 150x150 的三通道 RGB 图。如果原始图像尺寸不统一flow_from_directory的target_size参数会自动帮你缩放但要注意拉伸变形。猫狗照片的长宽比差异不大直接缩放影响有限如果做其他项目遇到严重变形可以改用padding填充的方式保持比例。全连接层 512 这个数字是可调的。调大能提升模型容量但也更容易过拟合调小训练更快但可能欠拟合。在 2000 张训练图的前提下512 加 0.5 的 Dropout 是我见过比较稳的组合。如果你的训练集更小建议把 512 降到 256Dropout 提到 0.6。3.2 训练超参数设置学习率、batch_size 与轮次网络结构定好后训练超参数直接决定收敛速度和最终效果。这个项目用的是 Adam 优化器配合二分类交叉熵损失函数。常见配置如下from tensorflow.keras.optimizers import Adam model.compile( optimizerAdam(learning_rate1e-4), # 学习率 0.0001 lossbinary_crossentropy, metrics[accuracy] ) history model.fit( train_generator, steps_per_epochtrain_generator.samples // train_generator.batch_size, epochs50, validation_dataval_generator, validation_stepsval_generator.samples // val_generator.batch_size, verbose1 )学习率设成 1e-4 而不是默认的 1e-3是我的个人习惯。小数据集上默认学习率经常导致损失函数震荡调低之后曲线会平滑很多。batch_size32是在显存和梯度稳定性之间的折中32 的梯度估计比 8 更稳定又比 128 更省显存。steps_per_epoch等于训练集样本数除以 batch_size也就是每个 epoch 要迭代多少次才能看完一遍全部训练数据。2000 张图、batch_size 32算下来是 62 步。如果这里不设置新版 TensorFlow 会自动根据flow_from_directory推断但显式写出来更清晰答辩时也更好解释。epochs 设 50 不是死的关键是看验证集损失有没有继续下降。我的判断标准是如果连续 5~10 个 epoch 验证集准确率不再提升就提前停止省时间也避免过拟合。项目报告里写“训练到第 30 轮左右收敛”这种话比写“跑了 50 轮”更有说服力。3.3 保存模型与断点续训训练到一半崩了怎么办训练 50 个 epoch 在小数据集上可能只要几十分钟但如果换到全量数据集一次跑几个小时很正常。中途断电、显存溢出、系统重启任何一个意外都让前面的训练白费。所以保存模型是必须养成的习惯。我习惯同时保存两份一份是完整模型一份是只存权重的 checkpoint。完整模型方便直接加载预测checkpoint 方便恢复训练from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping checkpoint ModelCheckpoint( best_model.h5, # 保存路径 monitorval_accuracy, # 监控验证集准确率 modemax, save_best_onlyTrue, # 只在验证准确率提升时保存 verbose1 ) early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) model.fit( train_generator, steps_per_epochtrain_generator.samples // train_generator.batch_size, epochs50, validation_dataval_generator, validation_stepsval_generator.samples // val_generator.batch_size, callbacks[checkpoint, early_stop] )save_best_onlyTrue的含义是只有验证集准确率刷新纪录时才会覆盖保存这样最终还是能拿到历史上最好的模型而不是最后一轮的结果。EarlyStopping的patience10表示验证集损失连续 10 个 epoch 不下降就停。如果训练中断需要续训加载之前保存的权重继续跑model.load_weights(best_model.h5) # 重新 compile 之后再调用 model.fit()注意要重新传入新的 callbacks这里的坑是load_weights只恢复权重不恢复优化器状态所以续训时学习率会重置。如果你用的是可变学习率策略最好在训练脚本里加一个参数记录当前 epoch恢复训练时从断点继续调整学习率。毕业设计一般不需要这么复杂但知道有这个问题面试或答辩时被问到能答上来。4. 验证集与测试集97% 识别率是怎么统计出来的4.1 混淆矩阵与分类报告准确率不是唯一的指标摘要里写的 97% 识别率要搞清楚是在哪个数据集上统计的。如果是验证集上的结果那说明模型调参时已经“看过”这些样本数字会偏乐观如果是测试集上的结果那才有参考价值。这个项目独立分出了 1000 张测试集我建议最终写在报告里的 97% 以测试集为准。光看准确率不够还要看混淆矩阵了解模型在猫和狗上分别的表现。用 sklearn 可以很方便地生成from sklearn.metrics import confusion_matrix, classification_report import numpy as np # 假设 val_generator 的批次顺序固定先整体预测一遍 val_generator.reset() predictions model.predict(val_generator, stepsval_generator.samples // val_generator.batch_size) pred_classes (predictions 0.5).astype(int).flatten() true_classes val_generator.classes[:len(pred_classes)] cm confusion_matrix(true_classes, pred_classes) print(混淆矩阵:) print(cm) report classification_report(true_classes, pred_classes, target_names[cat, dog]) print(report)混淆矩阵的对角线是预测正确的数量非对角线是错误分类。如果猫被误判为狗的数量明显高于反方向说明模型对猫的特征学习得不够好可能需要增加猫的训练样本或调整数据增强参数。classification_report会输出精确率、召回率、F1 分数。在 1000 张测试集上假设结果为猫精确率 0.97、召回率 0.96狗精确率 0.96、召回率 0.97那么整体准确率就是加权平均后的结果。写论文时把这张报告放进去比只写一行“准确率 97%”要扎实得多。4.2 单张图片预测脚本接口与边界条件训练完模型后通常需要写一个预测脚本接收任意一张图片输出猫狗类别。这个脚本也是答辩时的演示工具我习惯把它写得简洁稳定from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import load_img, img_to_array import numpy as np model load_model(best_model.h5) def predict_single(image_path, model, target_size(150, 150)): img load_img(image_path, target_sizetarget_size) img_array img_to_array(img) img_array img_array / 255.0 img_array np.expand_dims(img_array, axis0) # 增加 batch 维度 prob model.predict(img_array, verbose0)[0][0] if prob 0.5: return fdog ({prob:.4f}) else: return fcat ({1 - prob:.4f}) # 测试几张图片 for p in [cat.835.jpg, dog.157.jpg, test/cat.1.jpg]: print(f{p}: {predict_single(p, model)})两个容易出错的细节第一load_img默认会按 RGB 读取但如果你的训练数据是从灰度图读的颜色通道数不一致会导致模型报错预测前要确认color_modergb第二model.predict接收的是四维张量(batch, height, width, channels)单张图必须用expand_dims或者reshape(1, 150, 150, 3)加一维否则会报维度错误。预测脚本里我还会加一个判断图片本身损坏或不是 JPEG 格式时load_img会抛异常所以实际项目中需要包一层 try-except输出清晰的错误信息而不是让程序崩溃。5. 避坑与常见问题毕业设计里最容易翻车的五个点5.1 训练集和验证集的图片混在一起现象训练过程中验证集准确率异常高甚至高于训练集但测试集准确率很低。原因最常见的是数据集划分时没有做去重或隔离同一个来源的图片同时出现在训练集和验证集里。部分公开数据集的时间序列图片相邻帧相似度很高随机划分会泄漏。解决先按文件名或目录切分不要用随机函数直接打乱整个文件列表。最稳妥的做法是先把数据分成 train/val/test 三个目录再在目录内做增强。检查时可以随机抽几张验证集图片看是否和训练集重复。5.2 训练准确率高但验证集波动大现象训练集准确率稳定上升验证集准确率忽高忽低曲线像锯齿。原因验证集样本太少比如 1000 张分成 32 的 batch只有 31 步每步的评估结果受随机批次影响很大。也可能是验证集图片本身有一些噪声样本。解决验证集不要做数据增强如果验证集确实小把validation_steps设为val_generator.samples // val_generator.batch_size让每次评估覆盖全部验证集数据而不是随机抽一部分。再看一下shuffleFalse是否设置验证集的顺序要固定。5.3 中文路径或空格导致 ImageDataGenerator 读取失败现象flow_from_directory报错找不到图片但路径明明存在。原因Windows 环境下项目目录带中文TensorFlow 旧版本读取中文路径时会编码报错目录名带空格也会导致解析异常。解决项目路径全部改成英文目录名不要带空格用下划线连接。压缩包解压后我一般会先放到D:/project/cat_dog/这种纯英文路径下再跑。如果是服务器环境注意检查当前用户是否有读取权限。5.4 显存不足OOM 错误中断训练现象模型刚开始训练直接报ResourceExhaustedError或者进程被 kill。原因图像尺寸、batch_size、模型层数三者共同决定了显存占用。150x150x3 的输入不算大但如果 batch_size 设成 128或卷积层过滤器数设成 256、512显存依然会爆。解决先降到batch_size16或8确认能跑通再往上调。也可以用model.summary()查看可训练参数量参数量太大说明网络结构需要精简。如果是 4GB 显存的 GPU把tf.config.experimental.set_memory_growth打开按需分配显存。5.5 97% 识别率复现不出来现象按描述改完参数自己训练准确率只有 90% 甚至更低。原因复现差异通常来自几个变量——随机种子没固定、数据增强参数不一致、训练轮次没跑够、图像缩放尺寸不同。特别是flow_from_directory在读取图片时的插值方式不同也会带来几个百分点的波动。解决在训练脚本最开头固定随机种子import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)然后把数据增强参数、target_size、batch_size 写成配置变量保证每次跑完全一致。如果自己的数据分布和原项目差异较大准确率有出入是正常的重点看训练曲线是否平稳收敛而不是死磕某个数字。6. 换自己的数据集重新训练微调预训练权重与结果复现如果你不想只用猫狗这套数据或者想把项目从“复现”升级成“有自己的贡献点”最受认可的做法是迁移学习。用 ImageNet 上预训练好的权重做初始化冻结前面的卷积层只训练后面的自定义层在小数据集上通常比从头训练效果更好。常见做法是把预训练模型当特征提取器加自定义分类头from tensorflow.keras.applications import VGG16 from tensorflow.keras.models import Model from tensorflow.keras.layers import GlobalAveragePooling2D, Dense base_model VGG16(weightsimagenet, include_topFalse, input_shape(150, 150, 3)) base_model.trainable False # 冻结全部卷积层 x base_model.output x GlobalAveragePooling2D()(x) x Dense(256, activationrelu)(x) x Dropout(0.5)(x) output Dense(1, activationsigmoid)(x) transfer_model Model(inputsbase_model.input, outputsoutput) transfer_model.compile(optimizerAdam(learning_rate1e-4), lossbinary_crossentropy, metrics[accuracy])这样做的价值是新数据集的训练时间从几小时缩短到几十分钟准确率往往比从零训练高出两三个百分点。如果你想把猫狗项目改成识别猫狗之外的动物比如区分兔子、仓鼠、鸟数据量又不大这条路比改卷积层数更实际。验证模型是否真的学到了东西我还会做一个“坏样本检查”把预测置信度低于 0.7 的图片导出来一张张看。这些往往是光线暗、遮挡严重、或者猫狗形态特别的样本。把这些图片整理成一个文档写进毕业设计的“失败案例分析”里反而是加分项。另外每次换数据集我都会强制自己完整走一遍流程目录统计 → 数据增强配置 → 模型训练 → 混淆矩阵 → 坏样本检查。从那以后几乎所有分类项目都跑得比较稳不会因为换个数据集就乱了阵脚。这个习惯也推荐给你希望帮到你。本文还有配套的精品资源点击获取