基于TensorFlow的猫狗识别:CNN二分类完整实战教程

发布时间:2026/8/29 19:49:42
基于TensorFlow的猫狗识别:CNN二分类完整实战教程 猫狗识别在高校毕业设计和深度学习入门里一直是很稳的选题。它不像目标检测那样需要处理复杂的边框回归也不需要像语义分割那样逐像素标注它只有一个核心任务把输入图片判断成猫或狗。难度适中又覆盖了 TensorFlow 的安装、数据集加载、图像预处理、CNN 模型搭建、训练参数调整、模型评估和单图预测这一整套闭环。很多同学卡住的地方其实不是“看不懂网络结构”而是环境装不上、数据集加载报错、训练完不会保存模型、预测结果和标签对不上。这篇文章面向准备做毕设或刚开始接触 TensorFlow 的读者从环境准备讲到最终能跑通训练和预测并把每一步代码拆开解释。整个项目的技术主线很明确使用 TensorFlow 2.x 的 Keras API基于卷积神经网络CNN完成猫狗二分类。文章会先解释 CNN 的关键机制再搭建数据集目录结构然后用image_dataset_from_directory完成数据加载接着编写并训练一个小型 CNN 模型最后给出模型评估、单图预测、报错排查和扩展方向。代码量不大核心训练脚本保持在 100 行以内但每一步都不只是一段代码还包括“为什么要这么写”和“运行后应该看到什么结果”。1. 理解项目本质猫狗识别为什么能成为一个完整毕设1.1 任务定义和技术选型猫狗二分类的本质是一个监督学习问题。输入是形状为height, width, channels的图像张量输出是一个二分类标签一般用 0 表示猫、1 表示狗或者反过来。在 TensorFlow 中有两种常见的建模方式使用Dense层把图片展开成一维向量再接全连接层分类这种方法对图像的空间特征利用非常差。使用卷积层Conv2D提取局部特征再通过池化层压缩特征图最后连接全连接层分类这就是 CNN 的常规结构。第二种方式更适合图像任务因为卷积操作能够通过卷积核捕捉相邻像素之间的局部模式比如边缘、纹理、弧线这些特征叠加起来就能区分猫和狗在耳朵、脸型、毛发纹理上的差异。1.2 为什么 TensorFlow 适合作为毕设项目框架TensorFlow 是完成猫狗识别最常用的框架之一在本科毕设中使用有如下几个实际理由Keras 高层 API 封装完善用Sequential模型就可以快速搭建 CNN不用手动实现反向传播。官方和开源社区的猫狗数据集、预训练权重、教程材料非常丰富遇到问题容易搜到解决方案。支持 CPU 和 GPU 两种训练方式。数据集规模不大时用 CPU 也能完成训练有 NVIDIA GPU 的同学可以显著缩短训练时间。模型导出、保存和后续扩展方便model.save之后可以直接用 TensorFlow Serving 或转成 TFLite 做移动端推理。PyTorch 同样是优秀选择但如果你完全没接触过深度学习框架TensorFlow 的 Keras API 学习曲线相对平缓适合在有限时间内跑通毕设主线。1.3 本文的完整实现流程整个项目可以拆成 6 个阶段每个阶段都有明确的交付物环境准备安装 TensorFlow 2.x确认 Python、CUDA、cuDNN 版本匹配。数据集准备下载猫狗数据集整理成 train 和 validation 两个目录。数据加载使用tf.keras.utils.image_dataset_from_directory读取图片并自动打标签。模型搭建编写Sequential结构的 CNN 模型。训练与回调配置优化器、损失函数、评估指标和 Checkpoint 回调。评估与预测在验证集上计算准确率对单张图片进行预测并输出置信度。学完本文后你不仅能跑通猫狗识别还能把这套流程迁移到其他二分类任务上比如口罩检测、垃圾分类、零件缺陷识别。2. CNN 核心机制术语和计算流程必须理解2.1 图片在神经网络里到底是什么一张彩色图片在程序里是一个三维数组shape 是height, width, channels。猫狗数据集里的常见尺寸是宽高不统一、通道数为 3RGB。比如一张 128x128 的彩色图片它的 shape 就是128, 128, 3。模型不能直接处理不同尺寸的图片所以数据加载阶段必须统一所有图片的尺寸。后续代码中统一使用(128, 128, 3)作为输入形状这个选择不是唯一的但尺寸越小训练越快尺寸越大特征越充分。对于猫狗识别128 或 150 是常见的折中值。2.2 Conv2D、MaxPooling2D、Flatten、Dense 各负责什么CNN 模型由几种核心层组合而成它们的职责完全不同Conv2D是卷积层通过一组可学习的卷积核在图片上滑动输出多个特征图。每个卷积核负责检测一种局部特征初始权重是随机的训练过程中会根据损失函数的梯度不断更新。MaxPooling2D是最大池化层作用是在一个小窗口内取最大值从而降低特征图尺寸保留最显著的特征信息同时减少参数量能够缓解过拟合。Flatten是把多维特征图展平成一维向量目的是把卷积层输出的特征“拉直”后交给全连接层。Dense是全连接层把前面提取到的特征组合起来做分类决策。最后一层如果是 2 个神经元配合softmax就是多分类输出如果是 1 个神经元配合sigmoid就是二分类输出。2.3 二分类的损失函数和输出层设计二分类有两个常用方案输出层 1 个神经元激活函数为sigmoid损失函数为binary_crossentropy。输出值代表“是狗”的概率数值越接近 1 越可能是狗。输出层 2 个神经元激活函数为softmax损失函数为sparse_categorical_crossentropy输出一个长度为 2 的概率分布。本文采用第一种方案因为逻辑更直观而且预测时只需要取一个概率值即可完成判断。需要特别注意的是sigmoid输出必须配合binary_crossentropysoftmax输出必须配合sparse_categorical_crossentropy或categorical_crossentropy混用会导致训练曲线异常。3. 环境准备与数据集组织3.1 TensorFlow 版本与依赖关系TensorFlow 2.x 已经迭代了多个版本不同版本对 Python 和 CUDA 的要求不同。在实际项目中装环境出错的原因大多是 Python 版本过高或过低、显卡驱动不匹配、CUDA 和 cuDNN 版本对不上。环境项学习环境建议说明Python3.9 - 3.11版本过高时部分 TF 版本没有对应 wheelTensorFlow2.10 - 2.18以官方 PyPI 支持为准落地前先确认CUDA视 TensorFlow 版本而定不是必须CPU 也能跑通本项目NVIDIA 驱动推荐较新稳定版仅 GPU 训练需要如果你的机器没有 NVIDIA 显卡可以直接安装 CPU 版 TensorFlow依然可以完成本项目的全部代码只是训练时间会变长。安装命令如下任选其一# CPU 版适合学生电脑和没有独显的环境 pip install tensorflow # 如果需要指定版本比如 2.18 pip install tensorflow2.18.0安装完成后在 Python 中执行下面代码确认版本import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices(GPU))注意不要只确认能 import还要看打印出的版本号是否为 2.x。如果输出 1.x说明环境变量或安装源有问题。3.2 数据集获取和目录结构设计猫狗识别常用的数据集是 Kaggle 上的 Dogs vs. Cats。原始数据集包含 25000 张图片训练毕设时不需要全部使用可以只取一部分。如果无法访问数据集也可以先用少量图片搭建完整代码验证流程没问题后再换成完整数据集。在项目目录下建议按下面结构组织数据cat_dog_project/ ├── data/ │ ├── train/ │ │ ├── cats/ # 猫图片 │ │ └── dogs/ # 狗图片 │ └── validation/ │ ├── cats/ │ └── dogs/ ├── checkpoints/ # 模型保存目录 ├── train.py # 训练脚本 ├── predict.py # 预测脚本 └── requirements.txt这里要求训练集和验证集都按照“类名作为子目录名”的方式组织因为image_dataset_from_directory会自动读取子目录名作为标签。需要注意类名不要用中文否则后续标签映射会麻烦。3.3 使用脚本划分训练集如果下载的数据集是单一目录中混杂所有图片可以用脚本按比例划分。下面这段代码会把每张图片根据文件名中的 cat 或 dog 关键字复制到目标目录import os import shutil import random source_dir raw_images # 原始图片目录 train_dir data/train val_dir data/validation val_ratio 0.2 for label in [cat, dog]: os.makedirs(os.path.join(train_dir, label), exist_okTrue) os.makedirs(os.path.join(val_dir, label), exist_okTrue) for filename in os.listdir(source_dir): if cat in filename: label cat elif dog in filename: label dog else: continue src os.path.join(source_dir, filename) if random.random() val_ratio: dst os.path.join(val_dir, label, filename) else: dst os.path.join(train_dir, label, filename) shutil.copy(src, dst) print(数据集划分完成)这段代码的关键点是按文件名关键字判断类别真实项目中如果图片文件名是纯数字就需要从标注文件里获取标签而不能靠关键字。4. 数据加载与预处理4.1 使用 image_dataset_from_directory 加载图片有了目录结构之后数据加载可以直接交给 Keras 工具不需要手动写读取图片的循环。下面代码放在训练脚本中import tensorflow as tf IMG_SIZE (128, 128) BATCH_SIZE 32 train_ds tf.keras.utils.image_dataset_from_directory( data/train, validation_split0.2, subsettraining, seed123, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, label_modebinary, ) val_ds tf.keras.utils.image_dataset_from_directory( data/validation, label_modebinary, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, )这里有几个参数需要解释validation_split和subset在训练目录内部再留一部分做验证集适合训练数据没有单独划分验证集的情况。seed固定随机划分顺序保证多次运行结果可比。label_modebinary直接输出 0/1 标签和一层神经元加sigmoid匹配。image_size加载时自动缩放图片不需要自己写 resize。4.2 归一化处理图片像素值范围是 0 到 255把数据缩放到 0 到 1 之间可以让网络更稳定地训练。常见做法是使用Rescaling层from tensorflow.keras import layers normalization_layer layers.Rescaling(1.0 / 255) train_ds train_ds.map(lambda x, y: (normalization_layer(x), y)) val_ds val_ds.map(lambda x, y: (normalization_layer(x), y))也可以直接把Rescaling(1.0 / 255)作为模型的第一层。两种方式效果类似放在模型里更直观放在数据管道里可以缩短单个 epoch 的预处理时间。4.3 数据增强的延迟使用策略数据增强是防止过拟合的常用手段包括随机翻转、旋转、缩放等。需要注意的是增强操作只应用在训练集不能应用于验证集否则验证集每次评估都看到不同图片指标就不稳定。可以在模型内部加入增强层也可以只在实际训练时开启增强。data_augmentation tf.keras.Sequential([ layers.RandomFlip(horizontal), layers.RandomRotation(0.1), layers.RandomZoom(0.1), ])先用cache和prefetch优化数据管道让数据读取尽量不阻塞模型计算train_ds train_ds.cache().shuffle(1000).prefetch(buffer_sizetf.data.AUTOTUNE) val_ds val_ds.cache().prefetch(buffer_sizetf.data.AUTOTUNE)其中prefetch的作用是在 GPU 或 CPU 训练的同时预取下一批数据减少等待时间。5. 构建 CNN 模型并理解每层参数5.1 基础 CNN 模型结构下面是一个适合猫狗二分类的 CNN 模型结构简洁参数量适中CPU 也能训练from tensorflow.keras import layers, models model models.Sequential([ data_augmentation, layers.Conv2D(32, (3, 3), activationrelu, input_shape(128, 128, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(1, activationsigmoid), ])模型前半部分是卷积特征提取后半部分是全连接分类。input_shape必须和image_size以及通道数保持一致。如果前面使用Rescaling在模型中就要把它放在input_shape之后的第一层。5.2 编译模型时的关键选择model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy], )三个配置项都很关键optimizeradamAdam 优化器对学习率的敏感度较低通用性强适合大多数图像分类任务。lossbinary_crossentropy损失函数必须和最后一层的激活函数匹配。metrics[accuracy]用于观察每一轮训练和验证的准确率。5.3 打印模型摘要并确认参数量model.summary()运行后会看到每一层的输出 shape 和参数量。最后一层全连接之前的特征图维度取决于图片尺寸和池化次数这是判断网络结构是否合理的重要依据。如果参数远超预期说明深层卷积核过多或全连接层过大需要调整。6. 模型训练与回调配置6.1 训练脚本主体训练部分的核心代码如下from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping checkpoint ModelCheckpoint( checkpoints/best_model.keras, monitorval_accuracy, save_best_onlyTrue, verbose1, ) early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue, ) history model.fit( train_ds, validation_dataval_ds, epochs30, callbacks[checkpoint, early_stop], )ModelCheckpoint会根据验证集准确率自动保存最优模型训练中断或过拟合后也不用担心丢失最佳权重。EarlyStopping在验证损失连续多轮不下降时提前结束训练防止浪费时间。6.2 训练日志解读正常训练时输出大致如下Epoch 1/30 125/125 [] - 12s 95ms/step - loss: 0.6932 - accuracy: 0.5130 - val_loss: 0.6820 - val_accuracy: 0.5550 Epoch 2/30 125/125 [] - 11s 88ms/step - loss: 0.6765 - accuracy: 0.5720 - val_loss: 0.6640 - val_accuracy: 0.6020前几轮准确率接近 0.5 是正常现象因为初始权重是随机的。随着 epoch 增加训练准确率和验证准确率都应该逐步上升。如果训练准确率长时间不动很可能需要调整学习率或者数据增强过强导致模型难以拟合。6.3 绘制训练曲线训练结束后用 Matplotlib 绘制准确率和损失曲线可以直观判断是否过拟合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] plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(acc, labeltrain acc) plt.plot(val_acc, labelval acc) plt.legend() plt.title(Accuracy) plt.subplot(1, 2, 2) plt.plot(loss, labeltrain loss) plt.plot(val_loss, labelval loss) plt.legend() plt.title(Loss) plt.show()如果训练准确率很高而验证准确率偏低说明过拟合如果两边都很低说明模型欠拟合需要增加模型容量或减少数据增强强度。7. 模型评估与单图预测7.1 在验证集上评估模型训练完成后使用evaluate得到模型在验证集上的最终表现val_loss, val_acc model.evaluate(val_ds) print(f验证集损失: {val_loss:.4f}) print(f验证集准确率: {val_acc:.4f})7.2 对单张图片进行预测单图预测是毕设答辩里最常演示的部分。核心代码import numpy as np from tensorflow.keras.preprocessing import image def predict_image(img_path): img image.load_img(img_path, target_size(128, 128)) img_array image.img_to_array(img) img_array np.expand_dims(img_array, axis0) img_array img_array / 255.0 pred model.predict(img_array, verbose0)[0][0] class_name 狗 if pred 0.5 else 猫 print(f图片: {img_path}) print(f预测分数狗的概率: {pred:.4f}) print(f预测结果: {class_name})这段代码中有几个常见出错点训练时用了Rescaling(1/255)预测时也要手动做同样的归一化否则输入分布不一致结果会异常。target_size必须和训练时的image_size一致。np.expand_dims是为了把单张图片变成 shape 为(1, 128, 128, 3)的批次数据因为模型要求输入带 batch 维度。7.3 保存和加载模型ModelCheckpoint已经保存了最佳模型也可以单独保存整个模型model.save(cat_dog_model.keras)之后在预测脚本里加载loaded_model tf.keras.models.load_model(cat_dog_model.keras)Keras 3 推荐使用.keras后缀使用旧版.h5在 TensorFlow 2.18 中依然可用但新项目建议统一用.keras格式。8. 常见问题与排查链路8.1 显存不足或训练崩溃现象启动训练后程序报错提示CUDA_ERROR_OUT_OF_MEMORY或Resource exhausted。可能原因批量大小太大、图片尺寸太大、同时打开了多个模型。处理方式把BATCH_SIZE从 32 降到 16 或 8。把IMG_SIZE从 256 降到 128。关闭其他占用 GPU 显存的程序。nvidia-smi用上述命令确认 GPU 占用情况。8.2 训练准确率一直不上升现象多轮训练后准确率停留在 0.5 附近。排查顺序检查标签是否错乱。image_dataset_from_directory的标签顺序是按字母序排列的cat 在前为 0dog 在后为 1。检查归一化是否重复或遗漏。如果加载数据时已经归一化模型中又添加了Rescaling层相当于输入被除了两次 255。检查损失函数是否和输出层匹配。sigmoid binary_crossentropy搭配正确其他组合会出现训练异常。减少数据增强强度过强的翻转和缩放可能让模型难以学习。8.3 预测结果总是某一类现象无论输入什么图片预测结果都是猫或都是狗。排查重点确认预测脚本中的归一化方式和训练时一致。确认加载的是ModelCheckpoint保存的最优模型而不是最后一个 epoch 的权重。打印pred原始值。如果一直接近 0 或一直接近 1大概率是模型或数据问题如果接近 0.5说明模型没有学到有效特征。8.4 训练和验证准确率差异巨大现象训练准确率 95% 以上验证准确率只有 70% 左右。这是过拟合。处理思路降低全连接层参数或添加更多Dropout。增加RandomFlip、RandomRotation等数据增强。缩小训练轮次让EarlyStopping发挥作用。问题现象常见原因检查方式处理建议训练卡在 50% 准确率归一化重复或损失函数不匹配查看数据管道和模型输入统一归一化方式检查 loss 与激活函数验证准确率低数据量不足或过拟合对比 train/val acc增加增强、Dropout、早停预测全为同一类预测预处理不一致或加载错误权重打印预测分数统一 target_size 和归一化显存不足batch 或图片尺寸过大nvidia-smi 查看显存调小 batch 或图片尺寸9. 提升识别效果的方向与毕设扩展建议9.1 数据增强要合理不能盲目加数据增强是提升模型泛化能力的有效手段但增强强度过大会导致模型无法学习关键特征。对猫狗识别来说水平翻转比较安全垂直翻转则不适合因为真实照片中猫狗很少倒立出现。旋转角度也可以控制在 10 度以内。9.2 迁移学习通常是提分最快的方案从头训练一个小型 CNN最终准确率可能达到 85% 左右如果追求更高的效果使用预训练模型是性价比更高的选择。在 TensorFlow 中用迁移学习的思路是使用预训练模型如 MobileNetV2、ResNet50的卷积基冻结前置层的权重只训练全连接分类器。from tensorflow.keras.applications import MobileNetV2 base_model MobileNetV2( input_shape(128, 128, 3), include_topFalse, weightsimagenet, ) base_model.trainable False model models.Sequential([ layers.Rescaling(1.0 / 255), base_model, layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(1, activationsigmoid), ])迁移学习的思路同样适用于毕设论文中的“基于预训练模型的猫狗识别”选题。9.3 特征可视化和 Grad-CAM 是论文加分项如果毕设需要展示“模型到底看哪里”可以在模型中输出最后一层卷积层然后结合梯度计算 Grad-CAM 热力图。这部分能在论文中大幅提升工作量展示效果但要注意把每个步骤讲清楚避免只贴图不解释。9.4 扩展方向和使用场景一个猫狗二分类项目可以扩展成多种形式使用 OpenCV 读取摄像头画面实时判断画面中的猫狗。把模型转为 TFLite部署到 Android 或嵌入式设备。把二分类扩展到多分类比如猫、狗、鸟、兔子四类只需要改数据集目录结构和最后一层神经元数量。在前端做一个上传图片的 Web 页面后端调用 TensorFlow 推理形成完整系统演示。对于毕设来说从“能跑通”到“能讲明白”还有一段距离。建议做以下几件事记录每一次训练的超参数和结果形成实验记录表。比较基础 CNN 和迁移学习两种方案在相同数据集上的效果差异。把训练过程保存的 checkpoints 整理好便于答辩时现场演示。在项目 README 中写明环境版本、运行命令和遇到的问题方便自己复盘也让老师快速了解项目。猫狗识别本身不难但它是理解图像分类完整链路的最佳入门项目之一。从环境准备到最终预测每一步踩过的坑都对应着深度学习实践中真实会遇到的问题。建议先按照本文完整跑通一遍再尝试调整模型深度、图片尺寸、数据增强和优化器参数运行对比实验。这样交出来的毕设不只是“代码能跑”而是你真正理解每个环节为什么这么设计。