Python猫狗图像分类实战:CNN、DNN、RNN三模型对比与迁移学习
简介这份资源面向深度学习入门者与计算机视觉方向的在校学生围绕猫狗图像分类这一经典任务用Python分别实现CNN、DNN与RNN三种网络结构帮助读者对比不同模型在图像分类中的表现与适用边界。压缩包共9个文件约105.33MB包含3个py脚本分别对应三种模型的训练与推理2个rar数据包提供灰度与原始图像数据另有docx作业说明、README与LICENSE等文档便于按模块查阅与复现。目前已有782人学习下载。资源不仅给出可运行的代码骨架还覆盖数据预处理、模型构建、训练调参、评估与保存部署的完整流程读者可据此理解卷积特征提取、深层网络梯度问题以及RNN处理序列信息的思路并借助文档中的作业要求检验学习效果适合作为课程作业或入门实战的参考方案。1. 三套神经网络跑同一份猫狗数据这份 Python 图像分类包到底值不值得拆同一份猫狗图片用 CNN、DNN、RNN 三种网络各跑一遍结果差距能有多大这个问题我在面试新人时问过不下十次能说清楚的人不多。这份编号 100011892 的资源包恰好就是拿同一批猫狗图像分别喂给卷积神经网络、深度神经网络和循环神经网络让你亲眼看到三种结构在图像分类任务上的真实表现差异。它不是那种只丢一个train.py让你自己猜的残缺项目而是把dog-or-cat_CNN.py、dog-or-cat_DNN.py、dog-or-cat_RNN.py三个独立脚本摆在一起配上classification-dogs-cats和data_gray.rar两套数据外加一份 Word 作业文档讲设计思路。适合谁刚学完 CNN 卷积层原理、想找个能跑通的完整项目练手的人也适合教深度学习课、需要一份三模型对照实验素材的老师。如果你只是想调包跑个 SOTA这份资源可能不够新但如果你想搞明白“为什么图像分类默认用 CNN 而不是 DNN 或 RNN”它给了一个成本极低的实验场。2. 拆包先看文件结构三个脚本、两套数据、一份文档怎么配合2.1 压缩包里的文件清单与各自职责拿到100011892-基于Python实现猫狗图像分类(CNN-DNN-RNN).zip之后别急着解压完就python train.py。先花两分钟把目录结构看清楚后面调参和排错会省很多事。解压后的顶层文件大致是这些文件/目录类型作用dog-or-cat_CNN.py脚本卷积神经网络训练与评估主程序dog-or-cat_DNN.py脚本全连接深度神经网络训练与评估主程序dog-or-cat_RNN.py脚本循环神经网络训练与评估主程序classification-dogs-cats/目录猫狗彩色图像数据集按类别分文件夹data_gray.rar压缩包灰度化后的图像数据体积更小、读取更快README.md文档环境依赖、运行顺序、参数说明作业一基于深度神经网络的猫狗图像分类.docx文档设计思路、模型结构、实验结果分析自然语言处理_作业一 .docx文档同批作业的另一份说明可忽略LICENSE协议开源许可商用前确认一下三个脚本是并列关系不是流水线。你可以只跑 CNN也可以三个都跑做对比。classification-dogs-cats是原始彩色图data_gray.rar是灰度图后者在 DNN 和 RNN 上更常用因为全连接层和循环层对通道数的处理不如卷积层自然。2.2 环境依赖与 Python 版本选择README 里一般会写依赖但这类课程项目往往写得比较粗。我实际跑下来的经验是Python 3.8 最稳3.10 以上偶尔会遇到 TensorFlow 和 Keras 版本对不上的问题。核心依赖就三个# 建议在虚拟环境里装避免污染全局 python -m venv catdog_env source catdog_env/bin/activate # Windows 用 catdog_env\Scripts\activate # 核心依赖版本不要追新 pip install tensorflow2.10.0 pip install numpy1.23.5 pip install matplotlib3.6.2 pip install opencv-python4.6.0.66 pip install pillow9.3.0这里为什么锁 TensorFlow 2.10因为 2.11 之后 Keras 被拆成独立包老脚本里from tensorflow.keras.models import Sequential这种写法在 2.11 上虽然还能用但model.fit的某些回调参数行为有变化容易在保存模型时翻车。NumPy 锁 1.23 是因为 1.24 移除了np.float等别名老代码里如果有np.float会直接报错。OpenCV 和 Pillow 用来做图像读取和增强版本差异不大但建议一起锁避免cv2.imread返回的通道顺序和PIL.Image.open不一致导致训练时图像颜色错乱。提示如果你已经装了 TensorFlow 2.15 或更高版本不要直接卸载重装先建虚拟环境。全局环境里可能有其他项目依赖旧版或新版混在一起排查成本很高。2.3 数据目录的组织方式与读取逻辑classification-dogs-cats目录通常长这样classification-dogs-cats/ ├── train/ │ ├── cats/ │ │ ├── cat.1.jpg │ │ └── ... │ └── dogs/ │ ├── dog.1.jpg │ └── ... ├── validation/ │ ├── cats/ │ └── dogs/ └── test/ ├── cats/ └── dogs/这种按类别分文件夹的结构配合 Keras 的ImageDataGenerator.flow_from_directory是最省事的。脚本里一般会写from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1./255, # 归一化到 0-1 rotation_range20, # 随机旋转 ±20 度 width_shift_range0.1, # 水平平移 10% height_shift_range0.1, # 垂直平移 10% horizontal_flipTrue, # 水平翻转 zoom_range0.1 # 随机缩放 10% ) train_generator train_datagen.flow_from_directory( classification-dogs-cats/train, target_size(150, 150), # 统一缩放到 150x150 batch_size32, class_modebinary # 猫狗二分类 )rescale1./255是把像素值从 0-255 压到 0-1这是图像分类的标准操作不做的话梯度会爆炸。target_size(150,150)是权衡后的选择太小丢失纹理细节太大显存吃紧且训练慢。class_modebinary对应二分类输出层用 sigmoid 激活。如果你把data_gray.rar解压后替换掉彩色图目录记得把color_mode改成grayscale否则生成器会按三通道读取灰度图导致维度对不上。3. CNN 脚本精读卷积层怎么堆、参数怎么设、训练怎么监控3.1 CNN 模型结构逐层拆解打开dog-or-cat_CNN.py核心是Sequential模型。典型结构如下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), # 第四组卷积进一步压缩空间维度 Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), # 展平成一维向量 Dropout(0.5), # 随机丢弃 50% 神经元防过拟合 Dense(512, activationrelu), Dense(1, activationsigmoid) # 二分类输出 ])逐层看第一层Conv2D(32, (3,3))表示用 32 个 3×3 的卷积核每个核在输入图像上滑动做点积输出 32 张特征图。input_shape(150,150,3)对应 RGB 三通道。MaxPooling2D(2,2)把特征图尺寸减半减少计算量同时保留最强响应。四组卷积下来空间尺寸从 150 降到 9 左右通道数从 3 升到 128。Flatten把 9×9×128 拉成 10368 维向量接Dense(512)做全连接最后Dense(1)输出一个 0-1 之间的概率值大于 0.5 判为狗小于 0.5 判为猫。Dropout(0.5)是血泪经验不加这个训练集准确率能到 95% 但验证集卡在 70% 上不去典型的过拟合。加了之后验证集通常能提 10 个点左右。3.2 编译参数优化器、损失函数、评估指标模型结构定好后编译阶段三个参数决定训练走向from tensorflow.keras.optimizers import Adam model.compile( optimizerAdam(learning_rate1e-4), # 学习率别用默认的 1e-3 lossbinary_crossentropy, # 二分类标准损失 metrics[accuracy] )Adam优化器对大多数图像任务都友好但学习率1e-4比默认的1e-3更稳。为什么猫狗数据集通常几千到几万张用 1e-3 前期 loss 震荡明显容易在局部最优点附近跳出去又跳回来。1e-4 收敛慢一点但曲线平滑最终精度往往更高。binary_crossentropy是二分类的标配不要用categorical_crossentropy后者要求标签是 one-hot 编码而class_modebinary给的是 0/1 标量对不上会报维度错误。3.3 训练循环与回调配置训练阶段用fit配合回调from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ModelCheckpoint(best_cnn_model.h5, monitorval_accuracy, save_best_onlyTrue) ] history model.fit( train_generator, epochs50, validation_dataval_generator, callbackscallbacks )EarlyStopping的patience5意思是验证损失连续 5 轮不下降就停restore_best_weightsTrue保证停的时候回滚到最优轮次的权重而不是停在最差的那一轮。ModelCheckpoint只保存验证准确率最高的模型避免最后几轮过拟合的权重覆盖掉好模型。epochs50是上限实际通常 20-30 轮就触发早停了。训练过程中重点看两个信号训练准确率和验证准确率的差距。如果训练到 90% 而验证只有 75%说明过拟合加 Dropout 或数据增强如果两者都卡在 60% 左右说明欠拟合加深网络或提高学习率。history.history里存了每轮的 loss 和 accuracy用 matplotlib 画出来一目了然。4. DNN 与 RNN 脚本对比全连接和循环结构在图像上到底差在哪4.1 DNN 脚本把图像拉平后全连接堆叠dog-or-cat_DNN.py的结构比 CNN 简单得多from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Flatten, Dense, Dropout model Sequential([ Flatten(input_shape(150, 150, 3)), # 直接拉平成 67500 维 Dense(512, activationrelu), Dropout(0.5), Dense(256, activationrelu), Dropout(0.5), Dense(128, activationrelu), Dense(1, activationsigmoid) ])关键区别在第一层CNN 用卷积核在空间上滑动保留像素之间的位置关系DNN 直接Flatten把 150×150×3 的图像拉成 67500 维向量每个像素独立对待。这意味着 DNN 看不到“相邻像素组成边缘”这种空间结构它只能学到“某些像素位置同时亮”的统计相关性。结果就是 DNN 在猫狗分类上通常比 CNN 低 10-15 个百分点而且参数量巨大——第一层全连接就是 67500×512≈3450 万个权重训练慢且容易过拟合。那为什么还要放 DNN 脚本因为它是一个很好的对照组。跑一遍 DNN你会直观感受到“没有空间归纳偏置”的代价。另外如果你的数据已经是提取好的特征向量比如 HOG 特征DNN 反而是合适的选择。4.2 RNN 脚本把图像当序列处理dog-or-cat_RNN.py的思路更“反直觉”from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Reshape model Sequential([ Reshape((150, 150*3), input_shape(150, 150, 3)), # 每行作为一个时间步 LSTM(128, return_sequencesFalse), Dense(64, activationrelu), Dense(1, activationsigmoid) ])这里把图像的每一行150 个像素×3 通道450 维当作一个时间步整张图就是 150 个时间步的序列。LSTM 沿垂直方向扫描试图捕捉行与行之间的依赖关系。但问题在于图像的空间关系是二维的行与行之间的依赖远不如卷积核的局部感受野直接。所以 RNN 在这个任务上通常表现最差训练也最慢。它的价值在于让你理解“为什么图像分类默认不用 RNN”——除非你有视频帧序列否则 RNN 的序列建模能力在静态图像上发挥不出来。4.3 三模型训练结果对比与选型建议三个脚本跑完后把结果填进一张表模型验证准确率典型值单轮训练时间参数量适用场景CNN85%-92%中等中等图像分类首选DNN70%-80%较慢最大特征向量输入RNN60%-72%最慢中等序列/视频数据CNN 胜在空间特征提取DNN 胜在结构简单但参数量爆炸RNN 胜在序列建模但图像上水土不服。选型建议很直接静态图像分类无脑选 CNN如果图像被展平成了特征向量DNN 可用如果处理的是视频连续帧才考虑 CNNLSTM 混合结构。5. 避坑与排查数据路径、显存、版本、过拟合的五个真实翻车点5.1 路径写死导致 FileNotFoundError现象脚本一运行就报FileNotFoundError: [Errno 2] No such file or directory: classification-dogs-cats/train。原因脚本里用的是相对路径而你的终端工作目录不在解压后的根目录。解决要么cd到脚本所在目录再运行要么把路径改成绝对路径。我一般会在脚本开头加import os; os.chdir(os.path.dirname(os.path.abspath(__file__)))一劳永逸。5.2 显存不足导致训练中断现象训练到一半报ResourceExhaustedError: OOM when allocating tensor。原因batch_size32加上 150×150 的输入在 4GB 显存的显卡上可能吃紧。解决把batch_size降到 16 或 8或者把target_size降到 128×128。如果用的是 CPU 训练把batch_size降到 8 以下否则内存也会爆。5.3 TensorFlow 版本不匹配导致导入报错现象ImportError: cannot import name Adam from tensorflow.keras.optimizers。原因TensorFlow 2.11 把 Keras 拆成了独立包导入路径变了。解决要么降级到 2.10要么把导入改成from tensorflow.keras.optimizers import Adam对应的新路径。最省事的办法还是建虚拟环境锁 2.10。5.4 数据增强过度导致训练不收敛现象loss 曲线剧烈震荡准确率上不去。原因rotation_range40、zoom_range0.3这类激进增强让图像变形太厉害模型学不到稳定特征。解决把旋转降到 20 度以内缩放降到 0.1平移降到 0.1。数据增强是“锦上添花”不是“越多越好”。5.5 验证集与训练集分布不一致导致虚高现象验证准确率 95%但拿测试集一跑只有 70%。原因验证集可能和训练集来自同一批图片的相邻帧或者验证集太小比如只有 100 张。解决确保validation目录和train目录的图片来源独立验证集至少 500 张以上。如果数据不够用train_test_split重新划分别偷懒。6. 进阶技巧用迁移学习把 CNN 准确率再提一截三个脚本跑通之后你大概会停在 CNN 85%-92% 的准确率上。想再往上走最划算的路子是迁移学习——拿 ImageNet 上预训练好的模型比如 VGG16、ResNet50冻结卷积基只训练顶部分类层。改动量很小但效果通常能到 95% 以上。from tensorflow.keras.applications import VGG16 from tensorflow.keras.models import Model from tensorflow.keras.layers import Dense, Flatten, Dropout # 加载预训练卷积基不含顶部全连接层 base_model VGG16(weightsimagenet, include_topFalse, input_shape(150, 150, 3)) # 冻结卷积基的所有层 for layer in base_model.layers: layer.trainable False # 在卷积基上接自己的分类头 x Flatten()(base_model.output) x Dense(256, activationrelu)(x) x Dropout(0.5)(x) output Dense(1, activationsigmoid)(x) model Model(inputsbase_model.input, outputsoutput) model.compile(optimizerAdam(learning_rate1e-4), lossbinary_crossentropy, metrics[accuracy])关键参数说明weightsimagenet表示加载在 ImageNet 上训练好的权重第一次运行会自动下载约 500MB确保网络通畅。include_topFalse去掉 VGG16 原来的 1000 类分类头只保留卷积部分。layer.trainable False冻结卷积基训练时只更新后面接的Dense层这样训练速度快、不容易过拟合。等顶部训练稳定后可以解冻最后几个卷积块做微调学习率再降一个数量级到 1e-5。验证方法很简单跑完迁移学习后用同一个测试集对比原 CNN 脚本的准确率。如果提升不到 3 个百分点检查一下target_size是否和预训练模型期望的输入尺寸匹配——VGG16 默认是 224×224你用 150×150 虽然能跑但特征提取效果会打折扣。改成 224×224 再试一次通常能看到明显差异。从那以后我每次拿到一个新的图像分类项目都强制自己先跑一遍基线 CNN再跑一遍迁移学习两个结果摆在一起看差距。如果迁移学习提升不明显说明数据集和 ImageNet 分布差异太大这时候再考虑自己设计网络结构或者找领域内的预训练模型。希望帮到你。本文还有配套的精品资源点击获取