TensorFlow实战:从零搭建CNN猫狗识别模型
发布时间:2026/9/30 10:30:28来源:尧图网络
简介这份资源面向深度学习初学者与计算机视觉入门者围绕TensorFlow卷积神经网络实现猫狗图像识别这一经典任务提供一份完整的PDF实践文档。内容基于Kaggle的12500张猫图与12500张狗图数据集覆盖数据处理、批次流水线构建、CNN网络设计、模型训练评估与测试预测的完整链路帮助读者理解卷积层、池化层、全连接层与Dropout的配合方式。资源包共1个PDF文件约78KB篇幅精炼适合作为代码对照与思路梳理的参考材料。文档中给出了get_files、get_batch等关键函数的实现细节涉及图像解码、裁剪填充、标准化、队列读取与批次生成等操作并包含损失函数、优化器、模型保存与验证集评估的实践说明。目前已有4282人学习下载适合希望快速跑通猫狗识别流程、掌握CNN基本工程结构的读者参考也可作为进一步探索数据增强与迁移学习的起点。1. 从零搭一套猫狗识别为什么 CNN 是绕不开的第一课如果你手上有一堆猫狗照片想用 Python 把它们自动分开最直接的路径就是 TensorFlow 加卷积神经网络。这不是学术练习而是真实场景里最常见的图像二分类需求宠物医院分诊、宠物社交 App 内容打标、智能喂食器识别靠近的是猫还是狗底层逻辑都一样。猫狗识别之所以被当作 CNN 的入门标杆是因为它同时具备两个特征类间差异明显但类内变化大猫的姿势、光照、背景千差万别模型必须学会提取耳朵形状、脸部轮廓这类局部特征而不是死记像素。卷积层干的就是这件事。整套流程从数据准备、模型搭建、训练调参到导出推理用 TensorFlow 的 Keras 接口大约两百行代码就能跑通单卡甚至 CPU 都能出结果。适合有 Python 基础、想真正动手跑一次 CNN 的开发者也适合已经会调库但没搞懂卷积核在干什么的人。2. 数据管线与预处理猫狗图片怎么喂给 CNN 才不翻车2.1 数据集从哪来、怎么切分才靠谱猫狗识别最常用的公开数据集是 Kaggle 上的 Dogs vs. Cats包含 25000 张带标签的训练图猫狗各半。如果你手头是自己的照片数量少则几百张多则几千张同样能跑但切分策略要改。常见做法是按 8:1:1 切成训练集、验证集、测试集且必须保证同一只宠物的不同照片只出现在一个集合里否则验证准确率会虚高。我一般会先按文件名或拍摄时间排序再按比例分配避免随机切分导致数据泄漏。目录结构建议直接按类别分文件夹Keras 的image_dataset_from_directory能自动推断标签dataset/ train/ cats/ # 1000 张 dogs/ # 1000 张 val/ cats/ # 125 张 dogs/ # 125 张 test/ cats/ # 125 张 dogs/ # 125 张这种结构的好处是不用额外写标签文件加载时直接读文件夹名作为类别。注意文件夹名就是类别名别用中文或空格否则后续推理时标签映射容易乱。2.2 用 tf.data 搭一条不堵车的输入管线图片读取和增强如果放在 Python 层做GPU 会经常空等。TensorFlow 的tf.data能把预处理和训练重叠起来实测在 2000 张图上能把每轮训练时间压掉三成左右。下面是最小可用管线import tensorflow as tf IMG_SIZE (160, 160) BATCH_SIZE 32 train_ds tf.keras.utils.image_dataset_from_directory( dataset/train, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, label_modebinary, # 猫狗二分类输出 0/1 shuffleTrue, seed42 ) val_ds tf.keras.utils.image_dataset_from_directory( dataset/val, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, label_modebinary, shuffleFalse ) # 归一化到 [0,1]并做缓存和预取 normalization_layer tf.keras.layers.Rescaling(1./255) train_ds train_ds.map(lambda x, y: (normalization_layer(x), y)) train_ds train_ds.cache().prefetch(buffer_sizetf.data.AUTOTUNE) val_ds val_ds.map(lambda x, y: (normalization_layer(x), y)) val_ds val_ds.cache().prefetch(buffer_sizetf.data.AUTOTUNE)image_size设成 160×160 是权衡后的选择再小会丢耳朵、胡须这类关键特征再大显存和训练时间涨得厉害。label_modebinary让标签变成 0 和 1配合最后一层单神经元 sigmoid 输出。cache()把解码后的图片缓存在内存或本地磁盘prefetch让 CPU 预处理和 GPU 计算并行。如果数据集超过内存把cache()换成cache(cache_dir)写到磁盘。注意shuffleTrue只在训练集开验证集和测试集必须关掉否则评估指标会随批次波动没法复现。2.3 数据增强小数据集不增强等于白训如果训练图少于 5000 张不做增强几乎必然过拟合。增强层直接塞进模型里推理时自动关闭data_augmentation tf.keras.Sequential([ tf.keras.layers.RandomFlip(horizontal), tf.keras.layers.RandomRotation(0.1), tf.keras.layers.RandomZoom(0.1), tf.keras.layers.RandomContrast(0.1), ])水平翻转对猫狗都安全因为左右对称不改变类别。旋转和缩放幅度控制在 0.1 左右再大可能把猫耳朵转出画面模型学到的就是残缺特征。对比度扰动模拟不同光照对手机拍摄的宠物照尤其有用。增强只在model.fit的训练阶段生效验证和推理时 Keras 会自动跳过。3. 卷积神经网络结构怎么搭从三层 CNN 到迁移学习3.1 一个能跑通的基线 CNN 长什么样先别急着上 ResNet手写一个三层卷积加两层全连接的基线能帮你判断数据本身难不难。结构如下from tensorflow.keras import layers, models def build_baseline_cnn(): model models.Sequential([ data_augmentation, layers.Rescaling(1./255), # 如果管线没归一化这里补上 layers.Conv2D(32, (3,3), activationrelu, input_shape(160,160,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) # 二分类 ]) return model model build_baseline_cnn() model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy] ) model.summary()卷积核数量按 32→64→128 递增是因为浅层捕捉边缘和纹理通道需求少深层组合成耳朵、眼睛等部件需要更多通道表达。每个卷积后接最大池化把空间尺寸从 160 降到 20 再降到 10最后展平。Dropout(0.5)放在全连接前是防止过拟合最省事的一招。输出层单神经元加 sigmoid输出大于 0.5 判为狗小于 0.5 判为猫具体哪个是正类取决于文件夹顺序训练后打印train_ds.class_names确认。3.2 训练时看什么曲线、指标与早停编译完直接model.fit但必须挂上回调否则你会在过拟合之后才发现callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6 ), tf.keras.callbacks.ModelCheckpoint( best_cat_dog.keras, monitorval_accuracy, save_best_onlyTrue ) ] history model.fit( train_ds, validation_dataval_ds, epochs30, callbackscallbacks )EarlyStopping的patience5表示验证损失连续 5 轮不降就停并回滚到最佳权重。ReduceLROnPlateau在损失停滞时把学习率砍半帮模型跳出局部震荡。ModelCheckpoint保存验证准确率最高的模型避免训练后期过拟合的权重覆盖好结果。训练完把history.history里的loss和val_loss画出来两条线如果从第 8 轮开始分叉说明增强不够或模型太大优先加 Dropout 或减卷积核。3.3 迁移学习把 MobileNetV2 拿来微调基线 CNN 在 2000 张图上通常能到 85% 左右验证准确率想再往上走迁移学习是性价比最高的手段。TensorFlow Hub 或tf.keras.applications里预训练好的 MobileNetV2 在 ImageNet 上见过大量动物底层卷积核已经会提取毛发、轮廓特征。做法是冻结主干只训分类头base_model tf.keras.applications.MobileNetV2( input_shape(160,160,3), include_topFalse, weightsimagenet ) base_model.trainable False # 先冻结 model_tl models.Sequential([ data_augmentation, tf.keras.layers.Rescaling(1./127.5, offset-1), # MobileNet 要求 [-1,1] base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(1, activationsigmoid) ]) model_tl.compile( optimizertf.keras.optimizers.Adam(1e-3), lossbinary_crossentropy, metrics[accuracy] )include_topFalse去掉 ImageNet 的 1000 类输出层换成自己的二分类头。GlobalAveragePooling2D把特征图压成向量比 Flatten 参数少得多。归一化用Rescaling(1./127.5, offset-1)把像素映射到 [-1,1]这是 MobileNet 预训练时的输入范围不匹配会掉点。第一轮训完分类头后可以解冻最后 20 层做微调学习率降到 1e-5否则预训练权重会被大梯度冲垮。4. 训练完怎么用导出、推理与部署前必须做的验证4.1 单张图片推理的完整代码训练保存的.keras文件可以直接加载推理但预处理必须和训练时完全一致import numpy as np from tensorflow.keras.preprocessing import image model tf.keras.models.load_model(best_cat_dog.keras) class_names [cats, dogs] # 顺序与训练时文件夹一致 def predict_image(img_path): img image.load_img(img_path, target_size(160, 160)) arr image.img_to_array(img) # 形状 (160,160,3) arr np.expand_dims(arr, axis0) # 加 batch 维 # 如果模型里已含 Rescaling 层这里不要再除 255 pred model.predict(arr, verbose0)[0][0] label class_names[int(pred 0.5)] confidence pred if pred 0.5 else 1 - pred return label, float(confidence) print(predict_image(test.jpg))关键点是target_size必须和训练一致img_to_array后加 batch 维。如果模型内部已经包含Rescaling层外部就不要再归一化否则像素被除两次预测会全错。pred 0.5的阈值可以按业务调比如宠物医院希望少漏诊可以把阈值降到 0.3宁可错判狗为猫再人工复核。4.2 用混淆矩阵和测试集确认不是玄学验证准确率高不代表模型真的学到了猫狗特征可能只是记住了背景。用测试集跑一遍混淆矩阵from sklearn.metrics import confusion_matrix, classification_report test_ds tf.keras.utils.image_dataset_from_directory( dataset/test, image_size(160,160), batch_size32, label_modebinary, shuffleFalse ) test_ds test_ds.map(lambda x, y: (normalization_layer(x), y)) y_true np.concatenate([y for x, y in test_ds], axis0) y_pred (model.predict(test_ds) 0.5).astype(int).flatten() print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_namesclass_names))如果猫的召回率明显低于狗说明模型偏向狗常见原因是训练集里狗的图片背景更单一或者猫的图片里猫占比更小。解决办法是给猫的样本加更高权重的class_weight或者补充猫的困难样本。混淆矩阵还能暴露标签映射错误比如class_names顺序反了准确率会直接掉到 50% 以下。5. 避坑与排查猫狗识别训练中最容易翻车的 5 个点5.1 准确率卡在 50% 不动现象训练几轮后准确率一直在 0.5 附近损失不降。原因通常是标签映射反了或者最后一层激活函数用错。二分类必须用sigmoid加binary_crossentropy如果误用softmax加categorical_crossentropy而标签是 0/1梯度会乱。解决打印train_ds.class_names确认顺序检查输出层神经元数和损失函数是否匹配。5.2 验证损失先降后升训练损失一直降现象训练准确率冲到 95%验证准确率停在 80% 然后下滑。这是典型过拟合。原因可能是增强太弱、模型参数太多、训练集太小。解决先加Dropout和RandomFlip再把卷积核数量砍半最后考虑换迁移学习。别一上来就加 L2 正则调参成本高且效果不如直接减模型。5.3 推理时预测结果全是同一类现象单张图推理不管输入什么都输出猫或都输出狗。原因通常是预处理不一致训练时归一化到 [0,1]推理时忘了除 255或者 MobileNet 要求 [-1,1] 却用了 [0,1]。解决把训练管线里的Rescaling层直接写进模型推理时只做img_to_array和expand_dims不再手动归一化。5.4 GPU 显存够但利用率上不去现象nvidia-smi显示 GPU 利用率只有 20% 到 30%训练一轮很慢。原因多半是tf.data管线没开prefetch或者batch_size太小。解决加上.prefetch(buffer_sizetf.data.AUTOTUNE)把batch_size从 16 提到 32 或 64同时确认cache()没有把磁盘 IO 变成瓶颈。如果图片解码慢可以提前把图片转成 TFRecord。5.5 换了自己的数据集后准确率暴跌现象在 Kaggle 猫狗集上 90%换成自己拍的宠物照只有 60%。原因是域偏移自己照片的光照、角度、背景和公开数据集差异大。解决先用预训练模型做特征提取只训分类头再逐步解冻微调。同时检查自己数据集的类别是否平衡如果猫 800 张狗 200 张加class_weight或过采样猫的图片。6. 把模型塞进手机前先做一次量化与 TFLite 转换训练出 90% 准确率的模型只是半成品真正落地到宠物喂食器或手机 App还得过体积和速度这一关。Keras 模型直接转 TFLite 后MobileNetV2 版本大约 14MB基线 CNN 约 8MB但推理延迟在低端安卓机上可能超过 200ms。我一般会做动态范围量化把权重从 float32 压到 int8体积减到四分之一速度提升两到三倍准确率通常只掉 1 到 2 个百分点。converter tf.lite.TFLiteConverter.from_keras_model(model_tl) converter.optimizations [tf.lite.Optimize.DEFAULT] # 动态范围量化 tflite_model converter.convert() with open(cat_dog_quant.tflite, wb) as f: f.write(tflite_model)转换完必须用测试集验证量化后的精度不能只看文件大小。TFLite 推理时输入输出都是 numpy 数组但要注意量化后的输入尺度可能变成 int8需要根据转换时的代表数据集调整。如果精度掉超过 3 个百分点改用 float16 量化体积减半但精度几乎无损。另一个容易忽略的点是输入尺寸。训练用 160×160部署时如果摄像头输出 640×480必须在端侧做裁剪和缩放且缩放算法要和训练时的image.load_img保持一致否则模型看到的猫耳朵比例变了预测就会飘。我习惯在端侧先中心裁剪到正方形再缩放到 160这样和训练时的预处理最接近。最后说一个血泪教训别在训练集上反复调参然后拿验证集当测试集用。我早期做猫狗识别时验证集准确率调到 93% 就以为成了结果换一批新照片只有 78%。后来固定切出 10% 的测试集全程不碰只在最后评估一次才真正知道模型几斤几两。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网