资讯动态

猫狗图像分类实战:CNN、DNN与RNN模型对比及调参指南

发布时间:2026/9/24 18:37:28 来源:尧图企业网站定制
简介资源围绕猫狗图像分类这一经典视觉任务完整演示了如何用Python实现卷积神经网络、深度神经网络与循环神经网络三种网络模型。从数据预处理、模型构建、训练调参到评估部署覆盖深度学习项目主要流程适合计算机视觉初学者及希望横向对比不同网络结构的开发者。包内共9个文件包含三个模型脚本、两份数据集压缩包、两篇作业说明文档、说明文件与许可证压缩包总大小约105MB可配合文档边看边练。目前已有782人学习下载。通过该资源可以拿到可直接运行的分类脚本、灰度及原始图像数据集以及配套的作业报告式讲解便于在本地复现实验并进一步调整网络层数、激活函数或优化器加深对卷积神经网络特征提取、深度神经网络深层次表达和循环神经网络序列特性在图像任务中适用性的理解。1. 猫狗图像分类到底该选哪个网络不要张口就上 CNN拿到「基于Python实现猫狗图像分类」这个需求很多人的第一反应是直接甩一个卷积神经网络跑起来仿佛 DNN 和 RNN 只是凑数的名词。但真实业务里图像分类的难点从来不在模型本身而在数据怎么喂、训练怎么稳、推理怎么快。CNN、DNN、RNN 三种网络在猫狗分类这件事上各有各的位置CNN 负责把「猫狗长什么样」学出来DNN 负责把学到的特征映射成「猫还是狗」的判断而 RNN 看着和图像八竿子打不着处理得当也能用序列视角把图片像素扫一遍得出结果。这篇文章就围绕这三个网络把数据集准备、模型搭建、训练调参和踩坑排查完整讲一遍让你照着能跑通跑完能说清每个参数为什么要这么设。2. 数据准备猫狗图像分类的第一步不是建模是整理文件夹2.1 数据集目录结构与标签编码的约定做图像分类的第一步不是写网络而是先把数据按固定结构摆好。常见的做法是建一个 data 目录下面分 train 和 validation 两个子集每个子集里再按类别分文件夹。这样做的好处是后续用 Keras 的 ImageDataGenerator 或 PyTorch 的 ImageFolder 可以直接读取不需要手写路径解析逻辑。import os import shutil import random source_dir raw_images # 原始图片存放目录 train_dir data/train val_dir data/validation for cls in [cat, dog]: os.makedirs(os.path.join(train_dir, cls), exist_okTrue) os.makedirs(os.path.join(val_dir, cls), exist_okTrue) cls_images os.listdir(os.path.join(source_dir, cls)) random.shuffle(cls_images) split_idx int(len(cls_images) * 0.8) for img in cls_images[:split_idx]: shutil.copy(os.path.join(source_dir, cls, img), os.path.join(train_dir, cls, img)) for img in cls_images[split_idx:]: shutil.copy(os.path.join(source_dir, cls, img), os.path.join(val_dir, cls, img))这段代码做的事情很简单把原始图片按 8:2 的比例随机切分成训练集和验证集并复制到对应类别的文件夹下。注意这里用的是shutil.copy而不是os.rename因为训练时可能需要反复调参保留原始数据是必要的后悔药。标签信息完全由目录结构携带cat文件夹下的图片标签就是 0dog文件夹下的就是 1。文件命名上要注意两点图片名里不要有中文字符和空格否则在部分 Linux 环境或老版本 OpenCV 下会读取失败图片格式尽量统一成 JPG 或 PNG。如果手头数据是混着来的先统一转换格式再进训练流程不要在管线里临时处理。2.2 数据增强与预处理把 1000 张图用出 10000 张的效果很多初学者拿到猫狗分类项目数据集可能只有几千张图这时候直接训练 CNN 非常容易过拟合。常见做法是加数据增强随机旋转、平移、翻转、缩放、亮度调整。Keras 里用 ImageDataGenerator 一行就能配齐这些操作。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0 / 255, # 像素归一化到 [0, 1] rotation_range20, # 随机旋转 20 度 width_shift_range0.2, # 水平平移 20% height_shift_range0.2, # 垂直平移 20% shear_range0.2, # 错切变换 zoom_range0.2, # 随机缩放 horizontal_flipTrue, # 随机水平翻转 fill_modenearest # 填充模式 ) val_datagen ImageDataGenerator(rescale1.0 / 255) train_generator train_datagen.flow_from_directory( train_dir, target_size(128, 128), batch_size32, class_modebinary ) val_generator val_datagen.flow_from_directory( val_dir, target_size(128, 128), batch_size32, class_modebinary )rescale1.0 / 255是把 0-255 的像素值缩放到 0-1这一步极其重要。如果不做归一化网络在训练初期会非常不稳定loss 可能直接冲到 NaN。rotation_range和shift_range给模型注入平移旋转不变性horizontal_flip对猫狗分类尤其有效因为猫和狗的左右对称性很高翻转不会改变类别语义。注意验证集不能做数据增强否则验证 loss 不能真实反映模型泛化能力。flow_from_directory 返回的是一个生成器每轮迭代产出(batch_images, batch_labels)。这里class_modebinary适用于二分类场景标签会编码为 0 和 1。如果类别超过两个改成categorical并配合 softmax 输出层。3. 用 CNN 做猫狗分类网络搭建与训练流程3.1 一个能落地的 CNN 结构卷积核数量与池化策略卷积神经网络的核心思路是先用卷积层提取局部特征再用池化层逐步压缩特征图尺寸最后接全连接层做分类。对于猫狗图像这种相对简单的二分类任务不需要堆太深的网络3 到 4 个卷积块足够。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(128, 128, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(1, activationsigmoid) ]) model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy] )卷积核数量从 32 到 64 再到 128 逐层翻倍背后逻辑是浅层卷积提取边缘、颜色等低级特征特征图数量不需要太多深层卷积提取的语义特征更丰富需要更多卷积核来容纳信息。MaxPooling2D(2, 2)把特征图长宽各减半计算量指数下降同时带来一定的平移不变性。全连接层用了 128 个神经元加上Dropout(0.5)来缓解过拟合。Dropout 只在训练时生效推理阶段会自动关闭不需要额外处理。最后一层用sigmoid输出一个 0 到 1 的概率值大于 0.5 判为狗或猫看标签顺序。这里用binary_crossentropy作为损失函数配合 sigmoid 是二分类的标准搭配换成categorical_crossentropy反而会拖慢收敛。input_shape的三维是 (高度, 宽度, 通道数)。图片是 128x128 的 RGB 三通道图所以是(128, 128, 3)。如果你的图片是灰度图可以读入时用color_modegrayscale对应形状就是(128, 128, 1)。3.2 训练参数怎么设batch size、epochs 与步长训练参数是新手最容易踩坑的地方。batch size 设得太小梯度更新方向噪声大收敛慢设得太大显存吃不消而且容易陷入尖锐极小值。epochs 设少了欠拟合设多了过拟合。步长steps_per_epoch则是生成器模式下必须显式指定的参数。steps_per_epoch train_generator.samples // train_generator.batch_size validation_steps val_generator.samples // val_generator.batch_size history model.fit( train_generator, steps_per_epochsteps_per_epoch, epochs30, validation_dataval_generator, validation_stepsvalidation_steps )train_generator.samples是训练集图片总数除以 batch size 得到每个 epoch 需要迭代多少次才能跑完一遍全部数据。如果不设steps_per_epochKeras 会默认用len(train_generator)但在某些版本下会触发警告甚至报错建议手动算清楚。epochs 设 30 只是一个起点。实际训练时建议配合EarlyStopping回调监控验证集 loss连续多个 epoch 不下降就提前停止省时又防过拟合from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) history model.fit( train_generator, steps_per_epochsteps_per_epoch, epochs30, validation_dataval_generator, validation_stepsvalidation_steps, callbacks[early_stop] )patience5表示容忍验证集 loss 连续 5 个 epoch 不改善。restore_best_weightsTrue会在停止训练后自动把模型权重回滚到验证集表现最好的那个 epoch相当于给你一颗后悔药。4. 用 DNN 和 RNN 做猫狗分类从特征工程到序列视角4.1 DNN 实现把图像拉平用全连接网络硬怼分类深度神经网络DNN用在图像分类上的思路很直接把二维图片拉成一维向量然后接若干全连接层。这种做法的缺点是丢掉了空间结构信息优点是网络结构简单、训练快适合做 baseline。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Flatten dnn_model Sequential([ Flatten(input_shape(128, 128, 3)), Dense(512, activationrelu), Dropout(0.3), Dense(256, activationrelu), Dropout(0.3), Dense(1, activationsigmoid) ]) dnn_model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy] )Flatten层把 128x128x3 的图片展开成 49152 维的向量然后过两层全连接。注意这里参数量非常惊人第一层全连接就有 49152 x 512 ≈ 2500 万个参数在只有几千张训练图片的情况下这种规模几乎必过拟合。所以必须用 Dropout 控制而且通常 DNN baseline 的准确率会明显低于 CNN这是它的模型容量和先验假设决定的。DNN 的价值在于让你感受图像分类问题的最朴素解法不做任何特征提取直接让网络从像素里学映射关系。这个 baseline 出来后再对比 CNN 的提升幅度你才能理解卷积操作到底带来了什么。4.2 RNN 尝试把图像切成像素序列喂给 LSTM循环神经网络做图像分类听起来不靠谱但确实是这篇标题里跑不掉的一环。思路是把图像按行展开成一个序列每一行是一个时间步的输入然后用 LSTM 或 GRU 捕捉行与行之间的依赖关系。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, TimeDistributed rnn_model Sequential([ LSTM(64, input_shape(128, 384), return_sequencesFalse), Dense(128, activationrelu), Dropout(0.3), Dense(1, activationsigmoid) ]) rnn_model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy] )这里把 128x128x3 的图片 reshape 成 (128, 384) 的序列128 个时间步每一步对应原图的一行像素384 128 宽度 x 3 通道。LSTM 把这 128 行依次读入最后只输出最后一个时间步的隐状态再接全连接层分类。这种做法的实际效果通常不太理想。原因在于图像的空间局部性主要体现在相邻像素之间而 RNN 的序列建模优势在于处理长距离依赖用在图像的行列关系上有些拧巴。而且 LSTM 的计算量和训练时间远高于同等规模的 CNN收敛也更慢。这个模块的价值更多在于教学对比让你直观感受不同网络结构对图像这种数据形态的适配度差异。如果你非要用 RNN 做图像分类并想提升效果常见做法是先过几层 CNN 提取特征图再把特征图的行序列喂给 LSTM。这种 CNN RNN 混合结构会把准确率拉高不少但本质上还是 CNN 在起主要作用。5. 避坑指南训练猫狗分类器最常见的 5 个翻车现场5.1 Loss 不降反升准确率卡在 50%现象训练过程中 loss 在 0.69 附近震荡准确率稳定在 50% 上下无论怎么调 epochs 都没变化。原因最典型的是数据没做归一化。像素值在 0-255 范围输入网络梯度更新幅度会被放大优化器难以收敛。另一个常见原因是标签和数据不对应比如目录结构搞反了模型学到的是和标签无关的噪声。解决检查 ImageDataGenerator 的rescale参数是否为 1.0/255打印几个 batch 的标签手动看一眼图片是不是和标签匹配。0.69 这个 loss 值约等于 log(2)说明模型输出接近于均匀猜测基本可以断定数据或预处理出了问题。5.2 验证集准确率远低于训练集现象训练集准确率 95% 以上验证集只有 70% 左右而且随着 epoch 增加差距越来越大。原因典型的过拟合。模型把训练集的细节纹理甚至背景噪声都背了下来但对没见过的图片泛化能力很差。解决按优先级依次尝试增加 Dropout 比例从 0.3 提到 0.5加数据增强rotation、zoom、flip 都开起来降低模型容量减少卷积核数量或全连接层神经元数早停回调务必挂上。如果做完这些验证集还在 80% 以下那就不是调参问题而是数据量本身不够考虑收集更多数据或做迁移学习。5.3 训练到一半 loss 变成 NaN现象某个 epoch 开始 loss 骤变为 NaN且无法恢复。原因最常见的是学习率过大导致梯度爆炸其次是数据里有损坏的图片文件比如全黑图、截断的 JPG输入网络后产生异常值。解决把优化器的学习率调小Adam 默认的 0.001 在有些任务上仍然偏高可以降到 0.0001 再试在数据加载环节加一个图片完整性检查用 PIL 打开失败就直接跳过from PIL import Image def check_image_valid(img_path): try: with Image.open(img_path) as img: img.verify() return True except Exception: return False另外在ImageDataGenerator里加validate_filenamesFalse不是解决图片损坏的方案它只是跳过文件名检查真正的问题要在数据源头清除。5.4 中文路径导致的读取失败现象程序在本地 Windows 跑得好好的部署到 Linux 服务器后图片读取报错或者读取到的是空数组。原因图片路径或文件名包含中文字符cv2.imread在部分环境下对中文路径支持不友好。解决从根本上避免问题所有目录和文件一律用英文命名。如果数据已经存在且无法改名用PIL.Image.open读取它对中文路径的兼容性比 OpenCV 好得多。5.5 显存不足与 OOM现象模型编译没问题一跑 fit 就报Resource exhausted: OOM when allocating tensor。原因batch size 和输入图片尺寸共同决定的单批次显存占用超过了 GPU 可用内存。解决先把 batch size 减半从 32 降到 16再把图片尺寸从 128x128 降到 64x64。这两个改动对显存的影响是数量级的而且对于猫狗分类这种任务64x64 的输入仍然能拿到不错的效果。如果还在 OOM最后一招是改用 CPU 训练慢一点但不会崩。6. 三个让分类效果再上一个台阶的操作训练基本跑通之后文章的最后落点放在「怎么把准确率再往上抬一抬」这个具体技巧层面。第一个操作是加学习率衰减。实际训练中 Adam 虽然自带自适应步长但后期仍然容易在局部极小值附近震荡。挂一个ReduceLROnPlateau回调验证集 loss 连续不降时把学习率缩小为原来的 0.2 倍经常能看到准确率再涨 2 到 3 个百分点。这种趁手好用的技巧和玄学调参之间的区别在于它背后有明确信号驱动验证集不改善才触发衰减逻辑是可控的。第二个操作是调整steps_per_epoch。如果数据集不是特别大不要让steps_per_epoch刚好等于样本数整除 batch size 的结果稍微取大一点让模型在一个 epoch 内多看到几个随机增强后的样本变体相当于隐式增加了数据量。但注意这里有个边界取太大每个 epoch 会重复采样同一批数据验证集 loss 的参考意义会被稀释一般设置在样本数除以 batch size 再乘 1.2 到 1.5 的范围内。第三个操作是做一次模型对比实验记录把 CNN、DNN、RNN 三个模型分别训练记录每个模型的参数量、训练时间、验证集准确率做成一张表。CNN 通常以最少的参数量拿到最高的准确率DNN 参数量巨大但准确率垫底RNN 卡在中间且训练最慢。这个过程会带给你比任何教程都深刻的理解——网络结构不是越复杂越好而是要和数据的先验结构匹配。最后想说猫狗分类这个项目虽然看起来基础但它是少有的能同时覆盖数据工程、网络设计、训练调优、部署边界全流程的入门任务。我自己的习惯是先跑通 CNN 建立基准线再回头折腾 DNN 和 RNN 的对比过程中把每次翻车的现象和原因随手记下来。这套流程走完以后再碰真实的图像分类项目你至少知道问题出在数据还是模型能少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价