☰
Python+TensorFlow卷积神经网络实战:猫狗识别从70%到97%准确率
2026/9/30 7:34:00 网站建设 项目流程

简介:这份资源面向深度学习入门者与计算机视觉初学者,围绕TensorFlow卷积神经网络实现猫狗图像二分类这一经典任务展开,帮助读者打通从数据读取到模型测试的完整流程。压缩包内共1个PDF文件,约78KB,以图文讲解形式呈现,便于随时查阅与对照练习。内容涵盖Kaggle猫狗数据集(12500张猫图与12500张狗图)的读取与标签分配、图像裁剪填充与标准化、基于队列的批次输入流水线、卷积层与池化层及全连接层的网络搭建、Dropout防过拟合、交叉熵损失与Adam优化器训练、Saver模型保存以及验证集准确率评估等关键环节,并附有可直接参考的代码片段。目前已有4282人学习下载,适合希望系统理解CNN工作原理、动手复现猫狗识别项目的读者作为实践参考。

1. 从一堆毛茸茸的照片说起:Python + TensorFlow 卷积神经网络做猫狗识别到底在做什么

你手里可能有一个文件夹,里面塞了几千张猫和狗的照片,文件名乱七八糟,有的叫cat.1.jpg,有的叫IMG_20230412_093311.jpg。现在要做的不是人工一张张翻,而是让程序自己学会区分:这张图里是猫,那张图里是狗。这就是「Python 通过 TensorFlow 卷积神经网络实现猫狗识别」要解决的核心问题——一个标准的二分类图像任务。

它适合谁?适合刚学完 Python 基础语法、想找一个能跑通、能看到准确率数字往上跳的实战项目的人;也适合已经会调sklearn但没碰过深度学习框架、想理解卷积神经网络到底在图像上做了什么的人。整个流程不复杂:准备数据、搭一个 CNN、训练、评估、拿单张图预测。但真正动手时,坑往往不在模型结构上,而在数据读取、显存分配、过拟合判断这些地方。下面按我实际做过的顺序,把每一步拆开讲。

2. 数据准备与 TensorFlow 环境:别让第一步就翻车

2.1 猫狗数据集的目录结构与划分逻辑

Kaggle 上的猫狗数据集原始结构通常是PetImages/Cat/和PetImages/Dog/两个文件夹,各约 12500 张。但直接拿原始文件夹去训练有个问题:没有验证集,你无法判断模型是不是过拟合。常见做法是手动切分,或者用tf.keras.utils.image_dataset_from_directory配合validation_split参数。

我一般会先把数据整理成这样的结构:

dataset/ train/ cat/ dog/ val/ cat/ dog/

切分比例用 8:2 或 9:1。如果数据量少于 5000 张,建议用 8:2,验证集太小会导致评估波动大。切分脚本可以用 Python 的splitfolders库,也可以自己写shutil.move,但要注意别把同一张图同时放进训练集和验证集——这个错误在手动复制时经常发生。

import splitfolders # 输入原始文件夹,输出切分后的 train/val # ratio=(0.8, 0.2) 表示训练集80%,验证集20% splitfolders.ratio( "PetImages", output="dataset", seed=1337, ratio=(0.8, 0.2), group_prefix=None )

seed固定后每次切分结果一致,方便复现。group_prefix在猫狗任务里用不到,因为每张图是独立样本,没有分组需求。

2.2 TensorFlow 安装与 GPU 显存的两个关键设置

安装 TensorFlow 本身不复杂,但版本和 CUDA 的对应关系经常让人头疼。截至 2024 年,TensorFlow 2.10 是最后一个支持 Windows 原生 GPU 的版本,之后 Windows 上只能用 WSL2 或直接上 Linux。如果你在 Windows 上装tensorflow发现tf.config.list_physical_devices('GPU')返回空列表,大概率是版本和 CUDA 对不上。

# 创建独立环境,避免和系统 Python 冲突 python -m venv tf_env source tf_env/bin/activate # Linux/Mac # tf_env\Scripts\activate # Windows # 安装 TensorFlow 和常用依赖 pip install tensorflow==2.15.0 pip install numpy matplotlib pillow

装完后验证 GPU 是否可用:

import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU')) # 如果有多块 GPU,限制只使用第一块,避免显存占满 gpus = tf.config.list_physical_devices('GPU') if gpus: try: tf.config.set_visible_devices(gpus[0], 'GPU') # 按需分配显存,而不是一次性占满 tf.config.experimental.set_memory_growth(gpus[0], True) except RuntimeError as e: print(e)

set_memory_growth(True)这个设置非常关键。默认情况下 TensorFlow 会一次性申请几乎全部显存,导致其他程序无法使用,甚至自己的第二个模型也跑不起来。开启按需增长后,显存随训练过程逐步分配,实测在 8GB 显存的卡上训练猫狗分类模型,峰值占用约 3.5GB。

注意:如果你用的是 TensorFlow 2.16 及以上版本,Keras 3 成为默认后端,部分旧代码的tf.keras调用方式需要调整。新手建议先用 2.15 跑通,再考虑升级。

3. 卷积神经网络结构:从一张 224x224 的图到二分类输出

3.1 卷积、池化、全连接:每一层到底在算什么

卷积神经网络处理图像的核心思路是:用一个小窗口(卷积核)在图上滑动,每次滑动做一次加权求和,提取局部特征。比如一个 3x3 的卷积核在猫的耳朵边缘滑动时,会输出一个较大的值;在平坦的毛色区域滑动时,输出接近零。多层卷积叠加后,浅层学到边缘、纹理,深层学到耳朵、眼睛、鼻子这些语义特征。

池化层的作用是降维。最常用的是最大池化,2x2 窗口取最大值,把特征图尺寸减半。这样做有两个好处:减少计算量,同时让模型对图像的小幅平移不敏感——猫在图片左边还是右边,池化后的特征差异会变小。

全连接层在最后把特征图展平成一个向量,经过若干层神经元后输出一个概率值。二分类任务通常用 1 个神经元加sigmoid激活,输出 0 到 1 之间的数,大于 0.5 判为狗,小于 0.5 判为猫。

3.2 搭一个能跑通的 CNN:代码与参数逐行说明

下面这个结构是我在猫狗任务上反复用过的基线模型,参数量约 3.5M,在 8GB 显存的卡上 batch_size 设 32 可以稳定训练。

import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(input_shape=(224, 224, 3)): model = models.Sequential([ # 第一层卷积:32个3x3卷积核,输入224x224x3 layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape), layers.MaxPooling2D((2, 2)), # 第二层:64个卷积核,特征图继续缩小 layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), # 第三层:128个卷积核,提取更高层语义 layers.Conv2D(128, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), # 展平后接全连接 layers.Flatten(), layers.Dense(128, activation='relu'), layers.Dropout(0.5), # 随机丢弃50%神经元,抑制过拟合 layers.Dense(1, activation='sigmoid') # 二分类输出 ]) return model model = build_cnn() model.summary() model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), loss='binary_crossentropy', metrics=['accuracy'] )

Conv2D的第一个参数是卷积核数量,从 32 到 64 到 128 逐层翻倍,这是常见做法:浅层需要捕捉更多底层纹理,通道数少一些;深层需要组合成高级特征,通道数多一些。Dropout(0.5)放在全连接层之前,训练时随机丢弃一半神经元,迫使网络不依赖某个特定神经元,验证集准确率通常能提升 2 到 3 个百分点。

学习率设1e-4而不是默认的1e-3,是因为猫狗数据集的图像差异较大,学习率过高会导致损失震荡不下降。如果你用预训练模型做迁移学习,学习率还可以再降到1e-5。

3.3 数据管道:用 tf.data 把图片喂进模型

图片不能一次性全部读进内存,需要用tf.data.Dataset做流式加载。下面这段代码从目录读取图片,统一缩放到 224x224,并做归一化。

import tensorflow as tf BATCH_SIZE = 32 IMG_SIZE = (224, 224) train_ds = tf.keras.utils.image_dataset_from_directory( 'dataset/train', image_size=IMG_SIZE, batch_size=BATCH_SIZE, label_mode='binary', # 二分类标签 shuffle=True, seed=42 ) val_ds = tf.keras.utils.image_dataset_from_directory( 'dataset/val', image_size=IMG_SIZE, batch_size=BATCH_SIZE, label_mode='binary', shuffle=False ) # 归一化:像素值从0-255映射到0-1 normalization_layer = tf.keras.layers.Rescaling(1./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)) # 预取数据,训练时CPU准备下一批,GPU算当前批 AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

label_mode='binary'会把猫和狗自动编码为 0 和 1,具体哪个是 0 取决于文件夹名称的字母顺序——cat在前就是 0,dog是 1。cache()把数据缓存在内存或磁盘,避免每个 epoch 重新读盘;prefetch让数据准备和模型计算重叠,实测能提升 15% 到 20% 的训练速度。

4. 训练、评估与单张预测:把准确率从 70% 推到 90% 以上

4.1 训练循环与回调:早停和模型保存

直接model.fit跑 50 个 epoch 很容易过拟合,训练集准确率 99%,验证集卡在 80% 不动。我一般会加两个回调:EarlyStopping和ModelCheckpoint。

callbacks = [ tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=5, # 验证损失连续5轮不下降就停 restore_best_weights=True ), tf.keras.callbacks.ModelCheckpoint( 'best_cat_dog_model.keras', monitor='val_accuracy', save_best_only=True ), tf.keras.callbacks.ReduceLROnPlateau( monitor='val_loss', factor=0.5, # 学习率减半 patience=3, min_lr=1e-7 ) ] history = model.fit( train_ds, validation_data=val_ds, epochs=50, callbacks=callbacks )

patience=5意味着验证损失连续 5 轮没有改善就停止训练,并恢复到最佳权重。ReduceLROnPlateau在验证损失停滞时把学习率减半,让模型在局部最小值附近更精细地搜索。这两个回调配合使用,通常 20 到 30 个 epoch 就能收敛。

4.2 评估指标:准确率之外还要看什么

训练结束后用model.evaluate看验证集准确率,但只看准确率不够。猫狗数据集里如果猫和狗数量接近,准确率还有参考价值;如果某一类明显偏多,准确率会被多数类拉高。更稳妥的做法是看混淆矩阵和精确率、召回率。

import numpy as np from sklearn.metrics import confusion_matrix, classification_report # 收集验证集预测结果 y_true = [] y_pred = [] for images, labels in val_ds: preds = model.predict(images, verbose=0) y_true.extend(labels.numpy().flatten()) y_pred.extend((preds.flatten() > 0.5).astype(int)) print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_names=['cat', 'dog']))

如果混淆矩阵显示猫被误判成狗的比例明显高于狗被误判成猫,说明模型对猫的特征学习不足。常见原因是猫的图片里背景干扰更多,或者猫的姿势变化更大。解决办法是增加猫的训练样本,或者在数据增强时对猫的图片做更大幅度的随机裁剪和旋转。

4.3 单张图片预测:从文件到类别标签

训练好的模型要能对单张新图片做预测,流程是:读图、缩放、归一化、加 batch 维度、送入模型。

from tensorflow.keras.preprocessing import image import numpy as np def predict_single(img_path, model): # 读取并缩放到224x224 img = image.load_img(img_path, target_size=(224, 224)) img_array = image.img_to_array(img) img_array = img_array / 255.0 # 归一化 img_array = np.expand_dims(img_array, axis=0) # 增加batch维度 prediction = model.predict(img_array, verbose=0)[0][0] if prediction > 0.5: label = 'dog' confidence = prediction else: label = 'cat' confidence = 1 - prediction return label, confidence label, conf = predict_single('test.jpg', model) print(f'预测: {label}, 置信度: {conf:.4f}')

np.expand_dims这一步经常被漏掉,导致模型报错说输入维度不对。模型训练时输入是(batch_size, 224, 224, 3),单张图是(224, 224, 3),必须补上 batch 维度。

5. 避坑与排查:猫狗识别项目里最容易翻车的 5 个地方

5.1 验证集准确率远低于训练集,但损失还在降

现象:训练集准确率 98%,验证集只有 75%,且验证损失在训练后期开始上升。

原因:典型过拟合。模型记住了训练集的噪声和特定样本,没有学到泛化特征。

解决:先加Dropout(0.5)和L2正则化;如果效果不明显,用数据增强——随机水平翻转、随机旋转 20 度、随机缩放。增强代码放在image_dataset_from_directory之后:

data_augmentation = tf.keras.Sequential([ layers.RandomFlip('horizontal'), layers.RandomRotation(0.2), layers.RandomZoom(0.2), ]) train_ds = train_ds.map( lambda x, y: (data_augmentation(x, training=True), y) )

5.2 GPU 显存不足,报 OOM 错误

现象:训练开始几秒后报ResourceExhaustedError: OOM when allocating tensor。

原因:batch_size 太大,或者没有开启显存按需增长。

解决:先把 batch_size 从 32 降到 16 或 8;确认set_memory_growth已开启;如果输入图片尺寸是 512x512,改成 224x224 能省下约 80% 显存。

5.3 训练损失不下降,准确率停在 50% 左右

现象:loss 一直在 0.69 附近震荡,准确率接近随机猜测。

原因:标签和输入没有对齐,或者归一化没做。常见情况是image_dataset_from_directory读出来的像素值是 0 到 255,但模型期望 0 到 1。

解决:检查Rescaling(1./255)是否加在了map里;打印一个 batch 的像素最大值,确认在 0 到 1 之间。

5.4 预测时所有图片都输出同一个类别

现象:不管输入猫还是狗,模型都输出 0.5 以上,全判为狗。

原因:训练时某一类样本远多于另一类,模型学会了偷懒——全部预测为多数类就能拿到高准确率。

解决:在fit时加class_weight参数,让少数类的损失权重更大:

from sklearn.utils.class_weight import compute_class_weight import numpy as np labels = np.concatenate([y for x, y in train_ds], axis=0) class_weights = compute_class_weight( 'balanced', classes=np.unique(labels), y=labels ) class_weights = dict(enumerate(class_weights)) model.fit(train_ds, validation_data=val_ds, epochs=50, class_weight=class_weights)

5.5 保存的模型加载后预测结果和训练时不一致

现象:训练时验证集准确率 90%,保存后重新加载,同一批图片预测准确率掉到 70%。

原因:保存时用了model.save_weights只存权重,加载时模型结构初始化不同;或者保存格式和加载方式不匹配。

解决:统一用model.save('model.keras')保存完整模型,加载时用tf.keras.models.load_model('model.keras')。不要混用save_weights和load_model。

6. 用迁移学习把准确率推到 97%:一个值得试的进阶技巧

自己搭的 CNN 在猫狗数据集上跑到 90% 左右就到头了,再调结构收益很小。真正能把准确率推到 97% 以上的做法是迁移学习:拿一个在 ImageNet 上预训练好的模型,比如 MobileNetV2 或 EfficientNetB0,去掉顶部分类层,换成自己的二分类头,然后冻结预训练权重先训练几轮,再解冻部分层做微调。

base_model = tf.keras.applications.MobileNetV2( input_shape=(224, 224, 3), include_top=False, # 去掉ImageNet的1000类分类头 weights='imagenet' ) base_model.trainable = False # 先冻结 model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(1, activation='sigmoid') ]) model.compile( optimizer=tf.keras.optimizers.Adam(1e-4), loss='binary_crossentropy', metrics=['accuracy'] ) # 第一阶段:只训练分类头 history = model.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=callbacks) # 第二阶段:解冻最后30层做微调,学习率降到1e-5 base_model.trainable = True for layer in base_model.layers[:-30]: layer.trainable = False model.compile( optimizer=tf.keras.optimizers.Adam(1e-5), loss='binary_crossentropy', metrics=['accuracy'] ) history_fine = model.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=callbacks)

这里有两个关键参数:第一阶段学习率1e-4,第二阶段降到1e-5。微调时学习率必须小,否则预训练权重会被破坏,效果反而不如冻结。解冻层数从最后 30 层开始试,如果验证集准确率没有提升,再往前解冻更多层。

我在自己的机器上跑完这个流程,验证集准确率从 90.2% 提到 97.4%,单张预测的置信度也明显更稳定。唯一需要注意的是,MobileNetV2 的输入预处理和自定义 CNN 不同,它期望像素值在 -1 到 1 之间,所以不能用Rescaling(1./255),要用tf.keras.applications.mobilenet_v2.preprocess_input。

踩过最深的坑是:迁移学习时忘了把Rescaling换成preprocess_input,训练损失一直不降,排查了两个小时才发现是预处理不匹配。后来我养成了一个习惯——每次换模型,先打印一个 batch 的像素范围,确认和模型期望一致再开训。希望这个习惯也能帮到你。

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

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

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

立即咨询