ARTICLE DETAIL

资讯详情

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

Keras CNN猫狗二分类实战:数据增强与过拟合排查指南

Keras CNN猫狗二分类实战:数据增强与过拟合排查指南 简介这是一份基于卷积神经网络CNN的猫狗图像分类系统完整项目面向计算机相关专业准备期末大作业、课程设计或毕业设计的学生也适合希望从零开始练习图像分类实战的学习者。项目代码完整、可在本地直接运行配套的详细报告文档涵盖设计思路、网络结构、数据预处理、训练与结果分析能帮助快速理解CNN图像分类的完整流程代码注释清晰便于调整网络层数、超参数或替换数据集迁移到其他二分类场景对初次接触深度学习的小白也比较友好。压缩包共2000个文件以1990张JPG图片数据为主体另有Python源码脚本、XML配置、PDF报告和Markdown说明文档整体约219.49MB。已有106人学习下载项目提供从数据到代码再到文档的完整闭环配合报告可直接复现实验既适合作为课程设计、期末大作业或毕业设计的基础模板也可作为进一步研究图像分类的起点。1. 从Kaggle猫狗数据集到CNN分类器为什么我敢用Keras做二分类期末项目这个项目是我期末大作业从零搭起来的数据只用Kaggle Dogs vs Cats里抽出的4000张图没上预训练模型最终验证准确率做到了95%以上评审给了99分。核心收获不是“跑通了一个demo”而是把数据划分、数据增强、网络结构、训练回调、结果解释这一整套链路的边界条件摸了一遍。它适合两类人期末大作业、课程设计、毕业设计需要完整交付物的计算机相关专业学生以及想在真实小数据集上练手CNN二分类的项目练习者。2. 数据准备与CNN结构设计目录组织、ImageDataGenerator参数和网络层取舍2.1 目录结构先把数据划分定死训练集和验证集的划分必须在写代码前完成不能靠random_split在内存里临时切否则报告里写不清楚别人复现也对不上。我用的目录结构是标准的flow_from_directory约定验证集单独放一份训练集和验证集没有交集data/ ├── train/ │ ├── cats/ │ │ ├── cat.1.jpg │ │ └── ... │ └── dogs/ │ ├── dog.4266.jpg │ └── ... └── validation/ ├── cats/ └── dogs/划分比例我按8:2做的训练集约3200张验证集约800张。注意这里不需要单独拆出测试集因为课程设计的评价指标本来就该写在验证集上如果拆出第三个集合800张验证集再分一次每个类的样本不够看趋势。把原始图片存成cat.xxx.jpg和dog.xxx.jpg的命名格式是为了让flow_from_directory能直接按子目录名读标签。如果图片名是乱的class_indices仍然按目录名自动映射但报告里写数据来源时表述会绕所以文件名规整一点没有坏处。2.2 ImageDataGenerator的数据增强参数不能照抄数据增强是这个小数据集能跑到95%的关键但增强强度不是越大越好。rotation_range40会让狗的耳朵、猫的胡须这类边缘特征被旋转得面目全非模型反而学到奇怪的纹理。我在这个项目里用的是下面这组参数from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1. / 255, rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue, fill_modenearest ) val_datagen ImageDataGenerator(rescale1. / 255)rescale1. / 255是把0到255的像素值压到0到1区间sigmoid输出的概率分布才正常验证集的生成器只做归一化不做任何随机变换否则验证集的评估结果每次都变没法对比不同训练轮次的模型。horizontal_flip对猫狗任务可以开猫狗没有左右语义的硬约束但不要开vertical_flip狗抬头低头的姿态变化不是水平翻转能模拟的。fill_modenearest是平移或缩放产生空洞时用周边像素填充如果图片主体偏边缘width_shift_range设到0.3以上会出现主体被截断模型被迫学“半只狗也能判狗”这种错误逻辑。参数取值作用注意事项rescale1./255像素归一化训练/验证都要做rotation_range20随机旋转±20度超过30度会破坏姿态特征width_shift_range0.2水平平移20%配合fill_mode使用height_shift_range0.2垂直平移20%猫狗主体居中时效果更好shear_range0.2错切变换不建议超过0.3zoom_range0.2随机缩放过大会裁掉主体horizontal_flipTrue水平翻转猫狗任务可开fill_modenearest新像素填充常用值还有reflect/wrapflow_from_directory的batch_size我选了32太小比如8每个batch的梯度噪声太大loss曲线会抖太大比如128显存占用高而且小数据集上每轮更新次数太少收敛速度反而慢。class_modebinary是因为只有猫狗两个类别它返回的标签是0和1的数组配合最后一层的sigmoid输出刚好。2.3 CNN网络结构3x3卷积堆叠和特征图尺寸变化网络结构我用的是经典的卷积块堆叠没有上预训练模型。预训练模型比如VGG16、ResNet50在小数据集上效果好但报告里可写的东西会少一大截而且期末作业要求的是把卷积、池化、激活函数这些基础组件讲清楚。我的结构是这样from tensorflow.keras import layers, models model models.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, paddingsame, input_shape(150, 150, 3)), layers.MaxPooling2D(2, 2), layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D(2, 2), layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D(2, 2), layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D(2, 2), layers.Flatten(), layers.Dropout(0.5), layers.Dense(512, activationrelu), layers.Dense(1, activationsigmoid) ])第一层卷积用32个3x3卷积核输入是150x150x3的RGB图卷积后特征图尺寸不变因为是paddingsame。每经过一次2x2最大池化特征图边长减半150到75再到37再到18到最后一次池化变成9x9。最后一个卷积层输出128个通道所以Flatten之后是9x9x128等于10368维的向量这就是全连接层要处理的特征。4层卷积对小数据集已经足够再加到5到6层参数量上去了但训练集只有3000多张很容易过拟合。3x3卷积核堆叠感受野等效于更大的卷积核但参数量更少这是现代CNN结构的通用取舍。Dropout(0.5)放在全连接层前面训练时随机丢弃一半神经元让模型不依赖某几个特定节点这对小数据集对抗过拟合非常关键。最后Dense(1) sigmoid输出一个0到1之间的概率大于0.5判为狗小于0.5判为猫。整个模型参数量不大在GTX 1060上单轮训练不到10秒CPU跑也能接受这对学生党的笔记本很友好。3. 训练流程实现Keras编译参数、回调函数和训练输出怎么看3.1 损失函数、优化器和评价指标的选型理由二分类问题最直接的损失函数是binary_crossentropy它计算的是预测概率与真实标签之间的交叉熵。多分类一般用categorical_crossentropy但这里只有两个类用binary版本的数值更稳定。优化器我选了Adam初始学习率1e-4这是一个保守取值Adam自带动量机制学习率设太大会让loss在早期震荡设太小收敛太慢。from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau model.compile( optimizerAdam(learning_rate1e-4), lossbinary_crossentropy, metrics[accuracy] )metrics[accuracy]直接输出分类准确率对猫狗二分类足够直观。如果你想把报告写得更细可以再加Precision和Recall但从跑作业的角度先盯accuracy和loss就够了。compile阶段的这些参数记录在model.get_config()里写报告时可以直接导出来放附录。3.2 回调函数是训练不崩的保险丝不配回调函数的训练就是裸奔。EarlyStopping监控验证集loss连续多轮不下降就停止省时间ModelCheckpoint只在验证集准确率提升时保存权重保证最后拿到的文件是最优的一次ReduceLROnPlateau在loss卡住时自动把学习率减半再给它一次下降的机会callbacks [ EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ModelCheckpoint(cat_dog_best.h5, monitorval_accuracy, save_best_onlyTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-6) ] history model.fit( train_datagen.flow_from_directory( data/train, target_size(150, 150), batch_size32, class_modebinary ), steps_per_epochlen(train_datagen.flow_from_directory(data/train, target_size(150, 150), batch_size32, class_modebinary)), epochs50, validation_dataval_datagen.flow_from_directory( data/validation, target_size(150, 150), batch_size32, class_modebinary ), validation_stepslen(val_datagen.flow_from_directory(data/validation, target_size(150, 150), batch_size32, class_modebinary)), callbackscallbacks )steps_per_epoch应该等于训练集样本数除以batch_size我这里直接调用了len(generator)它会自动计算。epochs设50但一般跑到20到30轮就会被早停拦下来。patience8的意思是验证集loss连续8轮没有比历史最优值低就终止训练restore_best_weightsTrue让模型回滚到loss最低那轮的权重不然你保存的可能是最后几轮已经过拟合的版本。回调函数监听指标关键参数作用EarlyStoppingval_losspatience8, restore_best_weightsTrue连续8轮不降则停止ModelCheckpointval_accuracysave_best_onlyTrue只保存最优轮次的权重ReduceLROnPlateauval_lossfactor0.5, patience3, min_lr1e-63轮不降学习率减半3.3 训练输出怎么读acc上去了loss下不来才是问题训练过程中的输出长这样Epoch 25/50 - loss: 0.0321 - accuracy: 0.9880 - val_loss: 0.2241 - val_accuracy: 0.9486。这里的关键不是accuracy多高而是train和val的差距。训练集loss只有0.03验证集loss却有0.22说明模型开始记训练集的细节了。验证集准确率在95%附近波动是正常现象如果val_accuracy连续三轮不涨反而掉就是过拟合的早期信号检查第4章的三个地方。我一般把history.history存成JSON文件里面记录了每一轮的loss和accuracy写报告画图直接用。训练完后看history.epoch如果只跑到30轮就被早停拦下说明30轮之后都是无效更新这个观察写进报告的“训练策略”小节非常加分。4. 训练曲线诊断与过拟合排查学习率、BatchSize和混淆矩阵的排错实战4.1 训练集acc高但验证集acc低先查这三处第一个检查点是数据泄露验证集的ImageDataGenerator必须只做rescale不能带任何随机变换。第二个检查点是shuffleflow_from_directory默认shuffleTrue对训练集没问题但验证集在评估时要保证shuffleFalse否则每次predict的样本顺序和标签顺序对不上混淆矩阵算出来是错的。第三个检查点是Dropout位置和比例Dropout放在全连接层前效果最好如果放在了卷积层之间会破坏空间特征的局部性。val_gen val_datagen.flow_from_directory( data/validation, target_size(150, 150), batch_size32, class_modebinary, shuffleFalse )用PyCharm调试的时候如果在model.fit循环内部打断点想逐轮看loss你会发现断点根本不会命中因为fit是底层的C执行逻辑Python层的断点拦不到。想看中间过程正确做法是把自定义回调写成一个类比如class PrintLR(tf.keras.callbacks.Callback)在on_epoch_end里打印self.model.optimizer.lr.numpy()。4.2 学习率和BatchSize怎么联动调整学习率1e-4跑出来的loss曲线是一条平滑下降的曲线如果loss在前几轮震荡剧烈说明学习率太大优先降到3e-5重跑如果loss下降极其缓慢可能是学习率太小但小数据集上不太会遇到我更倾向于保持1e-4不动只调batch_size。BatchSize调到64梯度估计更准收敛更平滑但每轮更新次数减半需要更多epoch调到16更新频繁但梯度噪声大会让曲线抖动。我的经验是先固定batch_size32用ReduceLROnPlateau自动降学习率比手动来回试要快。下面是绘制训练曲线的代码import matplotlib.pyplot as plt def plot_training(history, save_pathtraining_curves.png): acc history.history[accuracy] val_acc history.history[val_accuracy] loss history.history[loss] val_loss history.history[val_loss] epochs range(1, len(acc) 1) plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(epochs, acc, labeltrain_acc) plt.plot(epochs, val_acc, labelval_acc) plt.legend() plt.subplot(1, 2, 2) plt.plot(epochs, loss, labeltrain_loss) plt.plot(epochs, val_loss, labelval_loss) plt.legend() plt.savefig(save_path, dpi150)保存出来的training_curves.png直接放进报告结果分析章节。如果train_loss和val_loss在第15轮之后开始分叉说明从第15轮起就在过拟合报告中要写明这一点对应的改进措施可以写“增加Dropout比例到0.6”或“增强width_shift_range到0.3”。4.3 混淆矩阵看误分类的图到底长什么样准确率95%掩盖了一个问题猫被当成狗和狗被当成猫的比例是不一样的。用验证集构造混淆矩阵注意预测前必须重置generator不然迭代位置不对import numpy as np from sklearn.metrics import confusion_matrix, classification_report val_gen.reset() y_pred_prob model.predict(val_gen, verbose0) y_pred (y_pred_prob 0.5).astype(int) y_true val_gen.classes[:len(y_pred)] cm confusion_matrix(y_true, y_pred) print(cm) print(classification_report(y_true, y_pred, target_names[cat, dog]))val_gen.reset()重置迭代器到起始位置val_gen.classes是所有样本的真实标签数组切片[:len(y_pred)]防止因batch整除问题导致多取或少取。跑出来的混淆矩阵如果猫被误判成狗的数量明显多于反向说明训练集里猫的图片质量或姿态多样性不足这时去data/train/cats里看有没有大量模糊图或带水印的图把这些脏数据清掉重训比调网络结构更有效。5. 单张图片推理与报告文档组织验证集指标、训练曲线和网络结构图怎么放5.1 单张预测代码和阈值选择验证集评估通过之后写一个单张图片的预测函数这也是答辩时演示最频繁的环节。加载图片时target_size必须和训练时一致都是150x150否则输入维度对不上from tensorflow.keras.preprocessing import image def predict_single(model, img_path): img image.load_img(img_path, target_size(150, 150)) x image.img_to_array(img) / 255.0 x np.expand_dims(x, axis0) prob model.predict(x, verbose0)[0][0] label dog if prob 0.5 else cat return label, prob print(predict_single(model, dog.4835.jpg))image.img_to_array(img)得到150x150x3的数组除以255做归一化。np.expand_dims(axis0)把它变成形状为(1, 150, 150, 3)的batch因为model.predict要求输入至少是4维张量。输出概率0.87置信度不错如果输出在0.45到0.55之间属于边界样本把图片原图打印出来看多半是目标太小或者背景干扰。这个边界样本的分析写进报告比贴十行准确率更有说服力。5.2 报告文档的组织把过程写清楚而不是只贴代码报告文档我建议按这个顺序组织摘要、实验环境与依赖、数据集说明与划分、模型结构设计、训练过程与超参数配置、实验结果分析、改进方向。改进方向不要写空话直接列两个具体做法一是把输入尺寸从150改成224配合更深层网络验证集acc能再涨二是把数据增强强度提高并用交叉验证选择最优参数组合。网络结构图可以用model.summary()导出的参数量表格或者用tf.keras.utils.plot_model(model, to_filemodel.png, show_shapesTrue)生成结构图。训练曲线图放前面说过的training_curves.png混淆矩阵用seaborn.heatmap画成彩色热力图每个格子标注数值。如果你的模型在验证集上跑出95%以上就把val_gen换成一个从网上找的猫狗混合图片文件夹重新跑一遍predict并保存输出结果图演示时点开给评审看这个“能吃外人给的图”的闭环是高分报告和普通报告的分水岭。model.save(cat_dog_model.h5)保存完整模型写进报告的使用说明里对方拿到手里一条命令就能加载并预测整个交付物就真正完整了。本文还有配套的精品资源点击获取
返回列表