ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python+TensorFlow卷积神经网络实战:猫狗识别从70%到97%准确率

Python+TensorFlow卷积神经网络实战:猫狗识别从70%到97%准确率 简介这份资源面向深度学习入门者与计算机视觉初学者围绕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, outputdataset, seed1337, ratio(0.8, 0.2), group_prefixNone )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 tensorflow2.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), activationrelu, input_shapeinput_shape), layers.MaxPooling2D((2, 2)), # 第二层64个卷积核特征图继续缩小 layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), # 第三层128个卷积核提取更高层语义 layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), # 展平后接全连接 layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), # 随机丢弃50%神经元抑制过拟合 layers.Dense(1, activationsigmoid) # 二分类输出 ]) return model model build_cnn() model.summary() model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-4), lossbinary_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_sizeIMG_SIZE, batch_sizeBATCH_SIZE, label_modebinary, # 二分类标签 shuffleTrue, seed42 ) val_ds tf.keras.utils.image_dataset_from_directory( dataset/val, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, label_modebinary, shuffleFalse ) # 归一化像素值从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_sizeAUTOTUNE) val_ds val_ds.cache().prefetch(buffer_sizeAUTOTUNE)label_modebinary会把猫和狗自动编码为 0 和 1具体哪个是 0 取决于文件夹名称的字母顺序——cat在前就是 0dog是 1。cache()把数据缓存在内存或磁盘避免每个 epoch 重新读盘prefetch让数据准备和模型计算重叠实测能提升 15% 到 20% 的训练速度。4. 训练、评估与单张预测把准确率从 70% 推到 90% 以上4.1 训练循环与回调早停和模型保存直接model.fit跑 50 个 epoch 很容易过拟合训练集准确率 99%验证集卡在 80% 不动。我一般会加两个回调EarlyStopping和ModelCheckpoint。callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, # 验证损失连续5轮不下降就停 restore_best_weightsTrue ), tf.keras.callbacks.ModelCheckpoint( best_cat_dog_model.keras, monitorval_accuracy, save_best_onlyTrue ), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, # 学习率减半 patience3, min_lr1e-7 ) ] history model.fit( train_ds, validation_dataval_ds, epochs50, callbackscallbacks )patience5意味着验证损失连续 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, verbose0) 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, axis0) # 增加batch维度 prediction model.predict(img_array, verbose0)[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, trainingTrue), 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], axis0) class_weights compute_class_weight( balanced, classesnp.unique(labels), ylabels ) class_weights dict(enumerate(class_weights)) model.fit(train_ds, validation_dataval_ds, epochs50, class_weightclass_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_topFalse, # 去掉ImageNet的1000类分类头 weightsimagenet ) base_model.trainable False # 先冻结 model models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(1, activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(1e-4), lossbinary_crossentropy, metrics[accuracy] ) # 第一阶段只训练分类头 history model.fit(train_ds, validation_dataval_ds, epochs10, callbackscallbacks) # 第二阶段解冻最后30层做微调学习率降到1e-5 base_model.trainable True for layer in base_model.layers[:-30]: layer.trainable False model.compile( optimizertf.keras.optimizers.Adam(1e-5), lossbinary_crossentropy, metrics[accuracy] ) history_fine model.fit(train_ds, validation_dataval_ds, epochs10, callbackscallbacks)这里有两个关键参数第一阶段学习率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 的像素范围确认和模型期望一致再开训。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表