ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

猫狗大战深度学习项目:从CNN到迁移学习的完整实战指南

猫狗大战深度学习项目:从CNN到迁移学习的完整实战指南 简介面向优达学城机器学习课程的毕业项目“猫狗大战”这份Jupyter Notebook工程系统复现了图像分类项目的完整流程从数据导入与预处理、CNN模型设计到训练监控、验证评估与参数调优适合希望动手实践深度学习的入门及进阶学习者。压缩包内共9个文件以.ipynb主程序为核心辅以PDF毕业报告、PNG模型结构图、MD说明文档、CSV数据文件及HTML导出页等整体仅1.91MB文件精简但覆盖了项目交付所需的全部模块。已有90人学习浏览。资源不仅包含可运行的代码还提供了详细的实验记录和训练日志能够帮助读者理解卷积层与池化层的作用、学习率与批次设置、过拟合的判别与调整策略是完成同类图像识别项目时极具参考价值的完整样例同时也展示了规范的工程组织方式。1. 猫狗大战为什么是第一份拿得出手的深度学习简历先给结论这个项目虽然看起来是“分猫分狗”的入门玩具但它实际上是优达学城机器学习纳米学位里最能拉开差距的一道坎。原因很简单它的评价标准不是“预测对了多少张”而是“在未知测试集上能跑到多高的准确率”。2013年Kaggle刚上线这个比赛时顶尖方案准确率在98%左右而深度学习方法彻底改写游戏规则之后榜单头部一度冲到99%以上。你拿到的这个pgj.zip里如果只有猫狗图片和几个没跑通的notebook那你真正要补的不是代码而是一整套从数据加载、数据增强到迁移学习的标准动作。对从业五年的工程师来说这个标题可能显得浅但它背后涉及卷积神经网络的基础结构、过拟合的诊断、冻结与解冻的迁移学习策略是一份可以放进简历、面试时能讲清楚前因后果的端到端项目。对刚入门的人而言它又是第一个能让你完整走完“数据准备 → 模型训练 → 预测提交”全流程的实操样本。本文不假设你手头有那份pgj.zip的具体代码而是按优达学城项目要求的常规解法给你一套能在jupyter notebook里直接跑通的最小实现、调参思路和常见报错排查路径。2. 在jupyter notebook里搭好猫狗大战的基线环境2.1 用虚拟环境隔离项目依赖而不是直接在base环境装包优达学城的多数毕业项目对依赖版本有隐性要求尤其是TensorFlow和Keras的搭配。猫狗大战虽然数据量不大但如果你手头的notebook是别人留下的大概率会碰到版本不一致导致的API报错常见的有keras.preprocessing.image在TensorFlow 2.9之后被移除、ImageDataGenerator的flow_from_directory返回对象结构变化等。我一般会为这类项目单独建一个conda环境锁定Python和深度学习框架的版本组合避免污染日常工作环境。创建环境的命令如下conda create -n dogscats python3.8 conda activate dogscats pip install jupyter notebook pip install tensorflow2.8.0 pip install numpy pandas matplotlib scikit-learn说明一下版本选择的理由。Python 3.8是兼容性最好的版本TensorFlow 2.8对应Keras 2.8这个组合里keras.preprocessing.image.ImageDataGenerator还能直接用。如果你的机器有NVIDIA显卡则可以额外执行pip install tensorflow-gpu2.8.0但要注意CUDA必须匹配到cuDNN 8.1和CUDA 11.2配不上会在import阶段直接报could not load dynamic library的错。2.2 数据目录结构的硬性约定无论你手头的zip里图片怎么摆放我都建议先把数据整理成标准的目录树因为后面的flow_from_directory和tf.data都依赖这个结构。常见的做法是这样组织dogscats/ ├── train/ │ ├── cats/ # 按文件名前缀分类 │ └── dogs/ ├── validation/ │ ├── cats/ │ └── dogs/ └── test/ └── unknown/ # 待预测图片不做标签区分优达学城原项目的数据划分比例一般是训练集2万张左右、验证集约2500张、测试集约1万张不带标签。如果你手上数据不足2万可以用train_test_split手动切分。切分代码片段如下import os import shutil from sklearn.model_selection import train_test_split # 假设原始文件全部在 raw/ 目录下命名形如 cat.0.jpg / dog.0.jpg all_files [f for f in os.listdir(raw) if f.endswith(.jpg)] cats [f for f in all_files if f.startswith(cat)] dogs [f for f in all_files if f.startswith(dog)] train_files, val_files train_test_split(cats dogs, test_size0.2, random_state42)这里要注意random_state必须固定否则每次运行notebook切出来的验证集会变化前面跑的实验结果就没法复现了。2.3 在jupyter notebook里启动训练的会话写法启动训练时不要直接在notebook里用python train.py这种写法而是用魔法命令%run或者直接在单元格里执行函数调用。我常用的模式是%load_ext tensorboard然后在训练代码里加入TensorBoard回调训练完成后用%tensorboard --logdir ./logs查看曲线。这样做的价值不只是曲线可视化而是能让你在notebook页面上直观看到loss和accuracy的走向判断是否出现过拟合。需要说明的是ImageDataGenerator在验证集上做增强没有任何意义常见错误是把同一套增强同时应用在训练集和验证集上这会让验证集不再代表真实分布。3. 从零训练到迁移学习猫狗分类模型的三个必选层次3.1 自建小型CNN做基线的模型结构如果你直接上手迁移学习一旦效果不好很难判断是数据问题还是预训练权重适配问题。所以我一般会先写一个极简CNN把完整流程跑通确认数据加载、训练循环、预测提交都没有bug后再换预训练模型。这个思路在机器学习项目的实践中被反复验证先用最简单的模型建立baseline后面每一步改动才有对比基准。以下是一个用于猫狗二分类的最小CNN结构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(150, 150, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dropout(0.5), Dense(512, activationrelu), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy])每层的作用说明如下卷积层的(3, 3)是感受野大小activationrelu引入非线性MaxPooling2D降低特征图尺寸并增强平移不变性Dropout(0.5)是刻意的过拟合抑制手段但注意它只应该在训练时生效Keras在验证和预测时会自动关闭dropout。输入尺寸选(150, 150)而不是更大的(224, 224)原因是在自建CNN阶段、且没有GPU加速时150的尺寸能显著减少训练时间一个epoch大约缩短到四分之一。3.2 用ImageDataGenerator做数据增强的3个关键参数猫狗大战的数据集规模对于纯CNN来说很容易过拟合因为在25000张图中要学习的参数数量远大于数据能提供的约束信息。数据增强是最直接的缓解手段而ImageDataGenerator的出场率最高。我常用的三组核心参数是rotation_range、width_shift_range和horizontal_flip其中horizontal_flipTrue是必须的因为猫狗照片的左右镜像不改变类别语义。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0/255, rotation_range40, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue, fill_modenearest ) val_datagen ImageDataGenerator(rescale1.0/255)以下三点是根据我在实际项目里的经验总结出来的建议。rotation_range40代表最多旋转40度超过这个值猫狗照片的边缘会出现大量无意义填充模型容易学到黑色边框这种伪特征。fill_modenearest应对的是平移或旋转后留下的空白区域用最邻近像素填充若改为constant填充值为0即黑色反而会引入不存在的边缘信号。验证集的生成器只能做rescale任何在验证集上做几何增强的行为都会污染评估结果因为验证集的意义在于模拟真实预测时遇到的自然图片。3.3 迁移学习VGG16作为特征提取器的接入方式基线模型跑通后迁移学习是让准确率从90%提升到98%左右的最短路径。优达学城的参考方案里使用最多的预训练模型是VGG16我已经验证过可以稳定工作。选择VGG16的原因有两点结构简单层的堆叠方式适合在notebook里复现和解读它的预训练权重基于ImageNet它学到的边缘、纹理、形状特征对猫狗分类有直接的迁移价值。from tensorflow.keras.applications import VGG16 base_model VGG16(weightsimagenet, include_topFalse, input_shape(150, 150, 3)) base_model.trainable False model Sequential([ base_model, Flatten(), Dense(256, activationrelu), Dropout(0.5), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy])include_topFalse表示去掉ImageNet分类器部分的三个全连接层只保留卷积基。base_model.trainable False冻结整个预训练网络的权重训练阶段只更新后加的全连接层。在jupyter notebook里执行这段代码时第一次运行会下载约500MB的权重文件到~/.keras/models如果网速不好建议提前手动下载并放到对应目录否则单元格会长时间停留在下载阶段看起来像卡死了。3.4 冻结与解冻的微调策略当图3.3中的特征提取模型在验证集上准确率稳定在92%到95%区间时下一步可以解冻部分顶层卷积层继续微调。解冻的正确做法是逐层指定trainable属性而不是一键base_model.trainable True后者会让所有卷积层的权重同时更新在大学习率下大概率冲掉预训练特征。base_model.trainable True for layer in base_model.layers[:10]: layer.trainable False上面这段代码的逻辑是冻结VGG16的前10层只允许最后5层的权重参与训练然后必须用更低的学习率重新编译模型model.compile(optimizertf.keras.optimizers.RMSprop(learning_rate1e-5), lossbinary_crossentropy, metrics[accuracy])这里的1e-5不是随意取的数字。预训练模型的权重已经相对稳定如果学习率维持之前的1e-3微调时的梯度更新幅度会远超权重本身的数值范围验证准确率甚至可能从92%瞬间跌到70%。换成RMSprop而不是adam是因为在迁移学习中RMSprop在调整预训练权重时更平稳adam的动量机制容易在少量epoch内过度修正。4. 训练循环里的5个必调参数和3个排查指标4.1 batch size、epochs与steps_per_epoch的关系训练模型时最容易被忽略的关系是steps_per_epoch、batch_size和train_samples三者之间的数量关系。flow_from_directory默认不自动补全最后一个batch如果不显式指定steps_per_epochKeras会按ceil(样本数 / batch_size)来计算。但在用了data augmentation的情况下数据是实时生成的、每一轮epoch都会重新生成新样本所以通常设置steps_per_epoch 训练集图片总数 // batch_size不要依赖默认值避免最后的残缺batch造成统计偏置。history model.fit( train_generator, steps_per_epoch20000 // 32, epochs10, validation_dataval_generator, validation_steps2500 // 32, callbacks[model_checkpoint, early_stop, reduce_lr] )这里参数含义如下batch_size32是显存和梯度稳定性的折中显存充裕可以提高到64但过高的batch size会让梯度更平滑、收敛更快却也更容易收敛到泛化差的平底区域epochs不宜直接跑到15、20这种数字而是配合早停回调让它自己决定何时停validation_steps不设的话默认会跑完全部验证集数据量大时很耗时间设置为2500 // 32 78表示每个epoch用78个batch的验证数据已经具有足够的统计意义。4.2 三个回调的配置方法与触发逻辑我常用的回调组合是ModelCheckpoint、EarlyStopping和ReduceLROnPlateau三个配合使用。三者各司其职ModelCheckpoint保存最优权重EarlyStopping在验证集指标连续不提升时中断训练ReduceLROnPlateau在验证loss进入平台期时自动降低学习率来尝试跳出局部最小值。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau checkpoint ModelCheckpoint( best_model.h5, monitorval_accuracy, modemax, save_best_onlyTrue, verbose1 ) early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.2, patience3, min_lr1e-6 )patience这个参数的取值很关键。在微调阶段验证loss不下降持续3个epoch是正常的尤其在用很低学习率的时候loss曲线会呈阶梯状。如果把patience设得太小例如1那么训练会在第一次验证loss回升时立即停止你根本看不到后续阶梯下降的那部分。restore_best_weightsTrue会在训练结束时把权重回滚到验证集最优的那个epoch而不是保留最后一个epoch的状态这一点能避免训练后期过拟合导致的最终模型退化。4.3 训练过程应该盯住哪些指标而不是只看accuracy在jupyter notebook里跑model.fit时输出表格中的loss、accuracy、val_loss、val_accuracy四个指标需要在每个epoch结束时对比分析。只盯着accuracy是一个非常明显的误用因为准确率在小幅度波动时你觉得模型还能接受但val_loss已经在显著爬升暗示过拟合开始了等到准确率明显下降再停手就晚了。import matplotlib.pyplot as plt acc history.history[accuracy] val_acc history.history[val_accuracy] loss history.history[loss] val_loss history.history[val_loss] plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(acc, labeltrain_acc) plt.plot(val_acc, labelval_acc) plt.legend() plt.subplot(1, 2, 2) plt.plot(loss, labeltrain_loss) plt.plot(val_loss, labelval_loss) plt.legend() plt.show()判定过拟合的基本规则是训练accuracy持续上升、但val_accuracy在某个epoch之后停滞或下降同时train_loss还在降、val_loss却开始回升这两组曲线形成“开口”就是典型的过拟合信号。出现这个信号后优先调整的不是模型结构而是数据增强强度、dropout比例以及是否应该更早冻结更多层。如果连train_loss都无法下降则属于欠拟合说明模型容量不够或学习率过小此时增加层数或调大学习率更合适。4.4 解决Importerror等jupyter notebook运行环境问题不少人在打开别人留下的notebook时第一行import tensorflow就报错常见的错误信息包括ModuleNotFoundError: No module named tensorflow或ImportError: DLL load failed while importing rpds。后者通常在Windows环境出现原因是rpds这个Python包缺少对应的Visual C运行库常见于绿色版Python环境或精简版系统。通常的解决路径是先pip install --upgrade rpds-py再在系统里安装Microsoft Visual C Redistributable对应本机系统架构的x64版本并重启终端。如果重启后在jupyter里仍然报错确认一下当前notebook用的kernel是否对应你刚才安装包的conda环境在notebook界面右上角的kernel名称经常和实际环境不一致切换kernel后一般能解决问题。5. 预测提交与kernel重启后的模型复用5.1 用最优权重进行测试集预测模型训练完成后下一步是读取之前保存的模型权重并预测未知图片的类别。这个环节在优达学城毕业项目流程里通常是最后一步常见的问题在于忘记重新加载best_model.h5而是直接用训练结束时的内存模型对象predict导致结果不是最优的那组权重跑出来的。from tensorflow.keras.models import load_model model load_model(best_model.h5) def predict_image(img_path): from tensorflow.keras.preprocessing import image img image.load_img(img_path, target_size(150, 150)) x image.img_to_array(img) / 255.0 x x.reshape((1, 150, 150, 3)) pred model.predict(x)[0][0] return dog if pred 0.5 else catload_model会自动加载完整模型结构但一个很容易踩的坑是如果之前训练时用了自定义层或Lambda层则在load时必须显式传入custom_objects否则会报Unknown layer错误。target_size必须和训练时的输入尺寸完全一致上面例子是150如果训练时改成了224这里也得改成224。pred 0.5是因为最后一层sigmoid的输出在0到1之间0.5是二分类的天然决策边界。5.2 批量预测写CSV提交文件毕业项目通常要求将预测结果写入CSV文件使用文件名作为索引。这里提供一个批量处理版本同时注意处理图片读取失败的容错import os import pandas as pd test_dir test/unknown results [] for fname in sorted(os.listdir(test_dir)): fpath os.path.join(test_dir, fname) try: label predict_image(fpath) results.append({id: fname.split(.)[0], label: label}) except Exception as e: print(fFailed: {fname}, error: {e}) results.append({id: fname.split(.)[0], label: dog}) submission pd.DataFrame(results) submission.to_csv(submission.csv, indexFalse)关于预测速度的说明在CPU环境下每张150x150的图片经过VGG16预测大约耗时30到50ms1万张图片的测试集需要约10分钟。如果在notebook里执行这段代码时遇到kernel假死的现象不要慌把cell上面输出区的In [*]状态当作判断标准如果jupyter notebook无法运行的状态持续太久可以把这段代码包装成脚本用%run predict.py在外部执行能规避notebook内核的一些资源管理问题。5.3 notebook重启后环境丢失怎么办jupyter notebook的使用过程中最常见的痛点在于昨天还能跑的notebook今天重启后第一行import就报错。多数原因在于kernel没有对应到期望的conda环境。确认方式是在notebook里运行import sys print(sys.executable)输出的路径如果指向base环境的python说明kernel选错了。解决方法是先在终端里执行conda activate dogscats然后安装jupyter的kernel注册工具并注册环境pip install ipykernel python -m ipykernel install --user --name dogscats --display-name Python (dogscats)之后再打开notebook在Kernel菜单里的Change Kernel中选择Python (dogscats)。这一步做完import环境问题和DLL加载类报错中的绝大多数情况都能自动消失。注意--name后面的值必须和conda环境名一致否则kernel会启动一个不存在的环境。6. 让准确率再涨1到2个百分点的三个微操作模型主体已经固定为VGG16加全连接层的结构之后还想继续提升验证集准确率需要的是针对边界样本的微调手段。6.1 把验证集上错误的样本打印出来看训练结束后写四行代码把验证集里预测错误的图片路径和真实标签打出来。多数情况下你会发现错误集中在模糊照片、极端光照、或者猫狗姿势异常的样本上。这个步骤不需要改模型它真正的价值是帮你决定是否要调整数据增强的参数。比如错误大多来自暗光图片那就把亮度扰动加入增强如果错误全是小尺寸猫狗就该在预处理里加上裁剪策略。import numpy as np val_generator.reset() predictions model.predict(val_generator, stepsval_generator.samples // 32) y_true val_generator.classes[:len(predictions)] misclassified np.where((predictions.flatten() 0.5).astype(int) ! y_true)[0]6.2 在测试时做TTATest-Time Augmentation对同一张图片做多次轻微变换后取预测平均通常是提升准确率最稳妥的免费手段。常见实现是对每张测试图片做水平翻转、小幅旋转后把三个预测结果取平均。def predict_with_tta(img_path, model, n_aug5): from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator(rescale1.0/255, horizontal_flipTrue) img image.load_img(img_path, target_size(150, 150)) x image.img_to_array(img) x x.reshape((1, 150, 150, 3)) preds [] for _ in range(n_aug): batch datagen.flow(x, batch_size1).next() preds.append(model.predict(batch)[0][0]) return dog if np.mean(preds) 0.5 else catTTA的代价是预测时间成倍增长1万张测试图需要原来的3到5倍时间。它适用于你对时间限制不敏感但准确率还有硬指标要求的情况。对优达学城这个项目来说TTA通常能把准确率从97%左右提升到97.5%虽然幅度小但在达到优秀线边缘时可能正好是B和A的差别。6.3 输出两次预测结果的置信度差异最终提交CSV之前把每个预测结果的sigmoid输出值也保存下来绘制置信度分布图。正态分布且集中在0.05到0.95区间的模型说明对大多数样本都有清晰判断。如果你看到大量样本的预测概率集中在0.45到0.55之间说明模型对这批图片高度不确定这时提升准确率就不应该靠继续调参了而是需要回头检查预处理环节和标签噪声。这个验证操作我建议放在最后一次训练之后、提交CSV之前做一遍就能判断当前模型是否还能从模型层面获益。pred_probs [] for fname in sorted(os.listdir(test_dir))[:200]: prob model.predict(...)[0][0] pred_probs.append(prob) plt.hist(pred_probs, bins20) plt.show()这组操作做完如果所有样本都集中在两端说明模型已经接近当前结构的上限是时候收工了。本文还有配套的精品资源点击获取
返回列表