☰
Python CNN猫狗分类实战:从零搭建到95%准确率
2026/10/12 13:24:45 网站建设 项目流程

简介:这份资源是面向高校学生的卷积神经网络CNN猫狗图像分类期末大作业完整方案,适合正在学习深度学习、需要完成课程设计或项目实训的初学者与进阶者。包内共2000个文件,以1990张jpg猫狗图像数据集为主体,另含1份详细报告PDF、1个Python源码文件、5个xml标注文件及md说明等,压缩包约218.59MB,目录结构清晰,便于直接运行与二次修改。目前已有308人学习下载。项目经导师指导并获评审99分,源码均经本地编译调试可稳定运行,难度适中。读者可据此掌握图像数据预处理、CNN模型搭建、训练调参与分类评估的完整流程,报告PDF则提供实验背景、网络结构设计与结果分析,可作为撰写论文与答辩的参考模板,也可用于课程设计、期末大作业等场景。

1. 从零复现猫狗分类:这套 CNN 源码到底能帮你交出一份什么样的作业

期末大作业选图像分类,十有八九绕不开猫狗数据集。原因很实在:数据好找、任务直观、CNN 的卷积-池化-全连接这条主线能完整跑一遍,报告里该有的东西一样不缺。但真正动手时,多数人卡在三个地方——环境装不上、模型训不动、报告写不出。这套基于 Python 卷积神经网络 CNN 的猫狗图像分类识别源码,解决的就是从「跑通」到「讲清楚」的完整链路。它适合两类人:一是刚学完 python 入门、想拿一个能写进简历的深度学习项目练手的学生;二是需要交期末大作业、但不想在环境配置上耗掉三天的人。下面按实际落地顺序拆开讲,每一步都能直接抄。

2. 环境搭建与数据准备:把 Python 和猫狗数据集跑起来

2.1 为什么选 TensorFlow/Keras 而不是 PyTorch

做猫狗二分类,框架选型其实没那么多纠结。TensorFlow 2.x 自带 Keras 高层 API,ImageDataGenerator和flow_from_directory能直接从文件夹读图、自动打标签、做数据增强,省掉手写 Dataset 类的功夫。PyTorch 当然也能做,但对新手来说,数据管道要自己写Dataset和DataLoader,多一层理解成本。这套源码用 Keras 搭 Sequential 模型,代码量少、结构清晰,报告里画网络结构图也方便。如果你后续想转目标检测或更复杂的模型,PyTorch 生态更灵活,但就猫狗分类这个任务而言,Keras 是性价比最高的选择。

2.2 安装依赖:一条命令解决 90% 的报错

python 安装教程网上很多,这里不重复。直接说深度学习环境。我一般用 conda 建独立环境,避免和系统 Python 打架:

conda create -n catdog python=3.9 -y conda activate catdog pip install tensorflow==2.10.0 numpy==1.23.5 matplotlib==3.7.1 pillow==9.5.0

逻辑说明:TensorFlow 2.10 是最后一个支持 Windows 原生 GPU 的版本,如果你用 Windows 且没有 WSL2,选这个版本最稳。numpy 锁 1.23.5 是因为 1.24 以后移除了np.float等别名,老代码容易报AttributeError。matplotlib 和 pillow 分别用来画训练曲线和读图。

参数说明:python=3.9是兼容性最好的版本,3.10 以上部分包还没跟上。如果你用 GPU 训练,确认 CUDA 版本是 11.2,cuDNN 是 8.1,和 TF 2.10 匹配。装完跑一句python -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))",有输出就说明 GPU 可用。

2.3 数据集目录结构:别在路径上翻车

猫狗数据集常见来源是 Kaggle 的 Dogs vs. Cats,原始包是 train 文件夹下两万五千张图,文件名就是标签。但flow_from_directory要求按类别分文件夹。我一般先跑一个整理脚本:

import os, shutil src_dir = 'train' # 原始图片目录 dst_dir = 'data' # 整理后目录 for category in ['cat', 'dog']: os.makedirs(os.path.join(dst_dir, category), exist_ok=True) for fname in os.listdir(src_dir): if fname.startswith('cat'): shutil.copy(os.path.join(src_dir, fname), os.path.join(dst_dir, 'cat', fname)) elif fname.startswith('dog'): shutil.copy(os.path.join(src_dir, fname), os.path.join(dst_dir, 'dog', fname))

逻辑说明:按文件名前缀分流,cat 开头的进 cat 文件夹,dog 开头的进 dog 文件夹。exist_ok=True保证重复运行不报错。

参数说明:src_dir改成你解压后的实际路径。如果图片有损坏,shutil.copy不会报错但训练时会崩,建议加一句from PIL import Image; Image.open(path).verify()做完整性检查。整理完目录应该是data/cat/和data/dog/,各一万两千多张。

3. 用 Keras 搭一个能跑通的 CNN:从卷积层到编译参数

3.1 网络结构设计:三层卷积够不够

猫狗分类不是 ImageNet,不需要 ResNet 那种深度。我一般用三层卷积加两层全连接,参数量控制在两百万以内,CPU 也能训。结构如下:

from tensorflow.keras import layers, models model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)), 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.Dense(512, activation='relu'), layers.Dropout(0.5), layers.Dense(1, activation='sigmoid') ]) model.summary()

逻辑说明:输入 150x150 的 RGB 图,经过三次卷积-池化,特征图从 150 降到 17,通道数从 3 升到 128。Flatten 后接 512 维全连接,Dropout 丢一半防过拟合,最后 sigmoid 输出 0 到 1 之间的概率,大于 0.5 判为狗,小于 0.5 判为猫。

参数说明:卷积核统一 3x3,这是最通用的选择。第一层 32 个滤波器捕捉边缘和纹理,第二层 64 个组合成局部形状,第三层 128 个形成更抽象的部件。池化窗口 2x2 是默认操作。Dropout 率 0.5 是经验值,数据量小可以调到 0.3,数据量大可以到 0.6。input_shape必须和后面数据生成器的target_size一致,否则会报维度不匹配。

3.2 数据增强与生成器配置:让一万张图变出五万张

猫狗数据集每类一万多张,直接训容易过拟合。用ImageDataGenerator做实时增强,每个 epoch 看到的图都略有不同:

from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=40, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, validation_split=0.2 ) train_generator = train_datagen.flow_from_directory( 'data', target_size=(150, 150), batch_size=32, class_mode='binary', subset='training' ) val_generator = train_datagen.flow_from_directory( 'data', target_size=(150, 150), batch_size=32, class_mode='binary', subset='validation' )

逻辑说明:rescale把像素从 0-255 压到 0-1,这是 CNN 收敛的前提。旋转、平移、剪切、缩放、水平翻转都是对猫狗无害的变换——猫翻转还是猫,狗翻转还是狗。validation_split=0.2从训练集里切出 20% 做验证,不用单独准备验证集。

参数说明:rotation_range=40表示随机旋转正负 40 度,再大可能把猫转成倒立。width_shift_range和height_shift_range设 0.2 表示最多平移 20% 的宽度或高度。batch_size=32是显存和梯度的平衡点,显存小可以降到 16。class_mode='binary'对应二分类,标签自动变成 0 和 1。

3.3 编译与训练:学习率和 epoch 怎么定

编译阶段三个参数:优化器、损失函数、评估指标。二分类固定用binary_crossentropy,优化器选 Adam,学习率用默认的 0.001:

model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'] ) history = model.fit( train_generator, epochs=30, validation_data=val_generator )

逻辑说明:Adam 自适应调整学习率,比 SGD 省去手动调参的麻烦。binary_crossentropy衡量预测概率和真实标签的差距。history对象保存了每个 epoch 的训练和验证指标,后面画曲线要用。

参数说明:epochs=30是起点,观察验证准确率什么时候不再上升就停。如果 10 个 epoch 内验证准确率就到 90% 以上,可以减到 15。如果 30 个 epoch 还在涨,加到 50。学习率默认 0.001,如果 loss 震荡厉害,降到 0.0005;如果 loss 下降太慢,升到 0.002。训练完用model.save('cat_dog_cnn.h5')保存模型,下次直接加载不用重训。

4. 训练过程避坑:准确率上不去、loss 不下降、显存爆了怎么办

4.1 验证准确率卡在 50%:标签没对上

现象:训练 loss 正常下降,但验证准确率一直在 50% 附近晃,等于随机猜。

原因:flow_from_directory按文件夹名字母顺序分配标签,cat 在前是 0,dog 在后是 1。如果你自己写预测代码时把 0 当成狗,就会全部反着判。

解决:打印train_generator.class_indices确认映射关系,通常是{'cat': 0, 'dog': 1}。预测时用model.predict得到概率,大于 0.5 对应索引 1 即狗,小于 0.5 对应索引 0 即猫。别凭感觉猜。

4.2 训练 loss 震荡不收敛:学习率太大或 batch 太小

现象:loss 曲线像心电图,上下跳动,几个 epoch 都不降。

原因:学习率 0.001 对某些初始化可能偏大,或者 batch_size 太小导致梯度噪声大。

解决:先把学习率降到 0.0005 试 5 个 epoch,如果还震荡再降到 0.0001。同时把 batch_size 从 32 提到 64,梯度更稳。如果显存不够,用model.compile里的optimizer=tf.keras.optimizers.Adam(learning_rate=0.0005)显式指定。

4.3 显存爆了:OOM 报错怎么救

现象:训练开始几秒后报ResourceExhaustedError,提示显存不足。

原因:batch_size 太大,或者输入图片尺寸设成了 224 以上。

解决:先把 batch_size 降到 16 甚至 8。如果还爆,把target_size从 150 降到 128。再不行就在训练前加一句:

import tensorflow as tf gpus = tf.config.experimental.list_physical_devices('GPU') if gpus: for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True)

这段代码让 TensorFlow 按需分配显存,而不是一次性占满。注意必须放在任何模型定义之前。

4.4 过拟合:训练准确率 99% 验证只有 70%

现象:训练集上几乎全对,验证集上差一大截,曲线开口越来越大。

原因:模型记住了训练样本的噪声,没学到通用特征。

解决:三招组合。第一,加大数据增强力度,把rotation_range提到 60,加brightness_range=[0.8, 1.2]。第二,在全连接层前再加一个 Dropout,率设 0.3。第三,用EarlyStopping回调,验证 loss 连续 5 个 epoch 不降就停:

from tensorflow.keras.callbacks import EarlyStopping early_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True) model.fit(train_generator, epochs=50, validation_data=val_generator, callbacks=[early_stop])

restore_best_weights=True保证训练结束后模型回到验证 loss 最低的那个状态,相当于一颗后悔药。

5. 报告怎么写才像做过:把训练曲线和混淆矩阵用起来

5.1 画 loss 和 accuracy 曲线

报告里光贴代码不够,得有图。用history对象直接画:

import matplotlib.pyplot as plt acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(epochs, acc, 'b', label='Training acc') plt.plot(epochs, val_acc, 'r', label='Validation acc') plt.title('Training and Validation Accuracy') plt.legend() plt.subplot(1, 2, 2) plt.plot(epochs, loss, 'b', label='Training loss') plt.plot(epochs, val_loss, 'r', label='Validation loss') plt.title('Training and Validation Loss') plt.legend() plt.savefig('training_curve.png', dpi=150)

逻辑说明:左图看准确率,右图看 loss。理想情况是两条线一起上升然后趋平,验证线略低于训练线。如果验证线先升后降,说明过拟合,在报告里要分析原因并给出改进措施。

参数说明:dpi=150保证打印清晰。figsize=(12, 4)是宽 12 高 4,适合放进 Word 双栏排版。

5.2 混淆矩阵和分类报告

准确率只给一个数,混淆矩阵能看出猫狗各自判对多少:

from sklearn.metrics import confusion_matrix, classification_report import numpy as np val_generator.reset() preds = model.predict(val_generator) pred_labels = (preds > 0.5).astype(int).flatten() true_labels = val_generator.classes print(confusion_matrix(true_labels, pred_labels)) print(classification_report(true_labels, pred_labels, target_names=['cat', 'dog']))

逻辑说明:reset()防止生成器已经迭代完导致预测为空。preds > 0.5把概率转成 0/1 标签。confusion_matrix输出 2x2 矩阵,对角线是判对的,非对角线是判错的。classification_report给出精确率、召回率、F1 值。

参数说明:target_names按class_indices的顺序写,cat 在前 dog 在后。如果混淆矩阵显示猫判成狗的比例高,说明模型对猫的特征学得不够,可以在报告里写「增加猫类样本多样性」或「调整类别权重」。

5.3 单张图片预测:演示环节不翻车

答辩时老师常让你随便找张图测一下。写个函数封装:

from tensorflow.keras.preprocessing import image import numpy as np def predict_single(img_path, model): img = image.load_img(img_path, target_size=(150, 150)) x = image.img_to_array(img) / 255.0 x = np.expand_dims(x, axis=0) prob = model.predict(x)[0][0] label = 'dog' if prob > 0.5 else 'cat' print(f'预测: {label}, 置信度: {prob:.4f}') return label, prob predict_single('test.jpg', model)

逻辑说明:load_img自动把图片缩放到 150x150,img_to_array转成 numpy 数组,除以 255 归一化,expand_dims加一个 batch 维度。prob是 sigmoid 输出,越接近 1 越像狗,越接近 0 越像猫。

参数说明:target_size必须和训练时一致。如果预测结果离谱,先检查图片是不是 RGB 三通道,有些 PNG 带透明通道会报错,用img.convert('RGB')转一下。

6. 把准确率从 85% 推到 95%:迁移学习和微调的实操参数

自己搭的三层 CNN 在猫狗数据集上通常能到 85% 到 90%,再往上就吃力了。想冲 95% 以上,最稳的路子是迁移学习。拿在 ImageNet 上预训练过的模型当特征提取器,只训最后的分类层,然后解冻部分卷积层做微调。我一般用 VGG16,结构简单、权重好找。

第一步,加载预训练模型,去掉顶部分类层:

from tensorflow.keras.applications import VGG16 from tensorflow.keras import layers, models base_model = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3)) base_model.trainable = False model = models.Sequential([ base_model, layers.Flatten(), layers.Dense(256, activation='relu'), layers.Dropout(0.5), layers.Dense(1, activation='sigmoid') ])

include_top=False去掉 VGG16 原来的 1000 类输出层,trainable=False冻结所有卷积层权重。此时只训练新增的全连接层,学习率可以设 0.001,训 10 个 epoch 就能到 92% 左右。

第二步,解冻最后两个卷积块做微调:

base_model.trainable = True for layer in base_model.layers[:-4]: layer.trainable = False model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5), loss='binary_crossentropy', metrics=['accuracy'] ) history_fine = model.fit( train_generator, epochs=10, validation_data=val_generator )

这里学习率必须降到 1e-5,比之前小两个数量级。原因是预训练权重已经很好,大学习率会把它们冲垮。只解冻最后 4 层,让模型微调高层语义特征,低层的边缘和纹理保持不变。训完通常能到 94% 到 96%。

几个关键参数的经验值:解冻层数控制在 4 到 8 层,太多容易过拟合;微调学习率 1e-5 到 5e-5 之间;微调 epoch 不要超过 15,配合EarlyStopping盯着验证 loss。如果验证准确率反而降了,说明解冻太多或学习率太大,退回去只解冻 2 层。

最后说个习惯:每次改完参数,先把model.summary()打出来看一眼可训练参数量。冻结时应该是几十万,解冻后变成几百万。如果数字没变,说明trainable设错了位置。这个项目我前后复现过七八次,最深的教训就是别一上来就追求复杂模型,把数据管道和基线跑通,再一步步加迁移学习,报告里的每一张图都能讲出为什么。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询