资讯动态

计算机视觉实战:工业零件表面缺陷二分类课程作业全流程指南

发布时间:2026/10/14 10:24:10 来源:尧图企业网站定制
1. 课程作业的“隐藏需求”先搞清楚题目没写的那一半坦白讲计算机视觉课程作业的坑通常不在模型代码里而是藏在“题目要求”和“交作业方式”的夹缝中。我这次拿到的作业是工业零件表面缺陷的二分类识别输入灰度图输出“有缺陷/无缺陷”两个标签。题目本身只有一句话但实际交付要包含训练代码、推理接口、报告、数据说明和现场演示。如果你把作业等同于“把最后一个 epoch 跑完”那后面有得补。1.1 把课程作业当成一个小型项目来验收我第一次做这类作业时犯过一个非常幼稚的错误代码写完了准确率也算得过去结果提交前发现老师要求的是可复现的完整流程而不是单文件脚本。你不仅要给“能跑起来的代码”还要给出环境依赖、数据集划分说明、模型文件、指标表格以及最关键的可视化结果。换句话讲这已经不是一个算法题而是一个被压缩到两周内的小型项目。我的建议是第一天先别碰模型花半天把交付清单列出来。比如这次我按四块拆代码部分数据加载脚本、训练脚本、推理脚本、读取模型权重的接口。数据部分训练集、验证集、测试集的划分说明每类样本的数量统计。结果部分准确率、精确率、召回率、F1、混淆矩阵、典型错误样本可视化。演示部分一条可以直接运行的命令行推理命令以及 5 到 8 张覆盖不同情况的演示图。这个清单看起来很笨但后面所有环节都会受益。因为一旦你明确了“最后要拿出什么”你训练时就会顺手记录实验参数而不是等所有结果跑完了再回头补实验记录。课程作业最怕的就是“代码写完了但说不清楚过程”而这份清单就是防止这种情况的第一道保险。1.2 字面要求和评分期望的差距作业要求经常只写了结果指标但评分时看的往往是思路和解释能力。我把两者的差异整理成一张表后面所有实验决策都围绕这张表展开项目字面上看实际考察点数据集给你一批图片做二分类是否理解数据分布、类别比例、划分方法是否合理模型完成分类任务为什么选这个结构有没有对比实验是否了解限制训练跑出较高的准确率能否控制过拟合能否解释训练曲线的变化评估报一个准确率是否用混淆矩阵和各项指标定位问题而不是只看平均值演示展示结果现场运行是否稳定遇到错误图能否给出合理的分析这张表是我在交了两次作业之后总结出来的。第一次作业我只报了一个准确率答辩时被问到“你这个模型在召回率上表现如何”直接卡住因为我根本没算。第二次开始我先对着这张表做实验每个环节都留痕最后虽然准确率不是全班最高但整个流程讲得非常顺评分反而更理想。2. 环境和数据准备决定实验可复现还是“在你电脑上才能跑”很多人觉得环境配置是浪费时间其实这部分恰恰决定了你后面会不会返工。课程作业的算力通常有限可能是自己的笔记本也可能是课程提供的服务器环境一乱训练到一半报错重装几小时就没了。我这次做了一个正确的决定第一天就把环境固定住了。2.1 先固定依赖版本接触代码之前先碰 requirements我用的是视觉作业里最常见的一套组合conda create -n cv_hw python3.10 -y conda activate cv_hw pip install torch2.1.2 torchvision0.16.2 opencv-python4.8.1.78 pandas matplotlib scikit-learn版本号要写清楚不要装最新版。PyTorch、TorchVision 和 CUDA 之间有严格对应关系如果服务器上的驱动版本较老你本地装的新版本可能根本跑不起来。我的经验是先查一下训练机器的 GPU 驱动支持到哪个 CUDA 版本再装对应版本的 PyTorch比事后翻报错日志省时间得多。另外依赖固定之后要把安装命令写进requirements.txt或者一个setup.sh提交时一起交上去。课程老师复核你的项目时第一步就是按这个文件重建环境。如果你只说“我用了 PyTorch”对方装出来版本不同报错一堆第一印象就打折了。2.2 数据集目录结构按类别分文件夹是最朴素的规范数据准备阶段最容易出的问题是把所有图片扔在一个目录里然后用一个label.txt去对应。这个方法不是不行但对课程作业来说风险很高一旦某个标签漏了一行你甚至不容易发现。我更推荐按类别分目录的结构同时也方便绝大多数框架自带的ImageFolder直接读取data/ train/ ok/ defective/ val/ ok/ defective/ test/ ok/ defective/关于划分我这次用的是“先按类别统计再随机抽样”的方式训练、验证、测试按 7:2:1 分配。有一个细节值得注意划分之前先把图片命名为统一格式比如OK_0001.png、DEF_0001.png这样后续排查错误样本时能直接从文件名判断它属于哪一类而不是靠去找元数据。更关键的是避免数据泄漏。比如同一个零件的多张照片如果被拆到了训练集和测试集模型就会“认识”这个零件测试结果会虚高。我这次的做法是以“零件个体”为分组单位先给每个零件一个 ID再按 ID 划分数据集而不是直接按图片文件随机分。做二分类的时候这一条尤其重要因为缺陷形状往往高度重复模型要是记住了特定缺陷出现在哪个零件上泛化能力就是装出来的。2.3 预处理和增强要符合任务物理背景工业零件表面缺陷识别里灰度图本身就是很重要的信息。我没有额外做太多花哨的增强而是根据这个任务的特点选择了几个方向水平翻转零件图像左右翻转后缺陷类型不变可以用。小角度旋转拍摄时零件摆位略有偏差旋转 10 度以内是合理的。亮度扰动模拟不同光照环境防止模型过度依赖亮度。随机裁剪和缩放模拟距离变化但要控制幅度避免把缺陷边缘截掉。我踩过一个坑一开始用了一个很强的随机旋转增强范围设到 180 度结果训练损失降到很低验证却很糟糕。原因是缺陷在物理上有方向性比如划痕通常是水平方向你旋转 90 度之后这个缺陷形态在真实场景里基本不存在。模型学了一堆“不可能出现的样本”自然不会泛化。增强不是越多越好必须服从任务本身的物理约束。3. 模型选型逻辑什么时候该用预训练什么时候必须自己搭模型选择几乎是所有课程作业里最纠结的部分。我见过不少同学直接搬目标检测的大模型结果训练到一半显存爆掉也见过有人坚持从零写卷积网络最后准确率只有六成。这两条路都不是标准答案。标准答案取决于你的数据量和题目限制。3.1 预训练模型是课程作业的默认选项但不是免死金牌对于几千张图片的二分类任务从零训练一个深度网络非常困难因为你没有足够多的数据让上百万个参数稳定收敛。而预训练模型已经在大型数据集上学会了边缘、纹理、形状这些基础特征你只需要在它顶部换一个分类头微调几个 epoch就能在有限数据里达到很不错的效果。我这次选的是 ResNet-18原因很直接它够轻单张图推理速度快结构也简单到能跟老师解释清楚。如果换 ResNet-50精度可能略高但训练时间和显存占用都会明显上升对课程作业来说没必要。把你的任务规模列一下你会发现大部分时候用不上“最新最强”的模型。不过要提醒一点有些课程作业会明确禁止使用预训练权重目的是考察你从零搭建网络的能力。如果不确定最好第一周先问清楚。万一后面做完了才发现不能用预训练整个训练流程都要重来那种时间损失是最冤枉的。3.2 从零搭建时结构简单比效果漂亮更重要如果题目规定不能使用预训练我的建议是搭一个结构清晰、每层都能解释的浅层网络而不是去复现一篇论文里的复杂模块。课程作业考察的是你知不知道卷积、池化、激活函数、批归一化怎么配合而不是你能不能堆出一个大网络。我当时顺手搭了一个备选网络大概是这样三层卷积每层后接一个最大池化和批归一化最后通过全局平均池化接全连接层。这个结构没有任何新奇之处但它在单卡 CPU 上也能十几分钟跑完一个 epoch方便调参。而且答辩时老师问“每一层的输出尺寸变化”时你能直接手算出结果这就已经拿到分了。3.3 训练框架的关键代码和检查点保存我用 PyTorch 来写训练流程。为了这次作业我把训练脚本固定成下面这种结构后续所有实验都是在不改框架的前提下改参数import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader, Dataset model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, 2) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4)训练循环里有一个非常重要的设置每一轮结束都在验证集上跑一次只保留验证结果最好的那一份权重。不能单看训练 loss因为训练 loss 降低不代表泛化能力变好。把这个逻辑固定在训练脚本里你就不用每次手动去对比不同 epoch 的权重。best_val_acc 0.0 for epoch in range(epochs): model.train() for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() model.eval() val_acc evaluate(model, val_loader) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pt)这个evaluate函数建议返回验证准确率、每一类的召回率和置信度分布而不只是平均准确率。后面第五节会细说为什么。4. 训练过程最现实的三个问题过拟合、类别失衡、显存不足训练环节是所有“看起来简单”的问题集中爆发的地方。哪怕你的模型和数据都没问题训练策略不对照样会得到一个不靠谱的结果。4.1 过拟合训练精度高不是终点验证曲线才是真理我第一次训练这个二分类模型时训练集准确率一路冲到接近 100%但验证集在第六个 epoch 之后就开始上下抖动最终停在了 82%。这就是典型的过拟合模型把训练集里缺陷的具体纹理、零件表面的反光特征都背下来了但一遇到没见过的情况就失灵。应对过拟合有几个步骤我按优先级排列先看训练集样本是否太少有没有办法通过增强扩充。再降低模型复杂度比如减少 ResNet 微调层的数量或者在全连接层前加 Dropout。适当降低学习率让模型收敛得更慢但更稳。使用早停机制验证指标连续若干个 epoch 不提升就停止训练。这里有一个很多人会忽略的问题验证集上的波动有可能是正常的。如果训练曲线稳定下降验证曲线在小幅波动后保持平稳不需要恐慌只有当验证曲线出现持续下降趋势而训练曲线还在上升时才说明过拟合正在加剧。4.2 类别不平衡会导致“看似很高的准确率”如果你的数据里“无缺陷”占 90%“有缺陷”占 10%模型只需要把所有图片都预测成“无缺陷”就能拿到 90% 的准确率。这个结果看起来不错但实际一点用都没有。我这次的数据也存在不平衡缺陷样本大概只占总量的三分之一。处理方法我用了两种结合起来效果比较好在损失函数里给缺陷类一个更高的权重让模型更关注少数类。对缺陷类做过采样训练时每一个批次里都保证出现足够多的缺陷样本。class_counts torch.tensor([num_ok, num_defective], dtypetorch.float) class_weights class_counts.sum() / (2 * class_counts) criterion nn.CrossEntropyLoss(weightclass_weights)这里有个小经验类别权重不要设置得过于极端。如果缺陷类只用 1:5 的权重模型可能会走向另一个极端把所有正常零件都误判成缺陷。我一般会把权重比例控制在 1:2 到 1:3 之间然后根据验证结果微调。4.3 显存爆掉和 loss 不下降的更实际排查课程作业通常只有一张显卡显存可能还是老旧的 6GB 或 8GB。训练时最常遇到的是CUDA out of memory。遇到这个报错我的处理顺序是先把batch_size从 32 降到 16 或 8。损失不大但立竿见影。把输入图片尺寸从 224 缩到 160等代码稳定后再调回 224。冻结 ResNet 的 backbone只训练最后的全连接层前面的层用预训练权重不动。如果还不行使用 PyTorch 的混合精度训练能明显减少显存占用。loss完全不下降也是常见问题。我整理过一个排查表方便对照现象常见原因处理方式loss 保持初始值附近学习率过小学习率从 1e-3 起步观察前十个 epochloss 先降后涨学习率过大或过拟合降低学习率增加验证频率loss 剧烈震荡batch size 太小增大 batch size 或降低学习率训练 loss 下降但验证指标不动类别失衡或过拟合检查样本分布加正则化还有一个小技巧训练初期只跑 3 到 5 个 epoch用很小的子集验证整个数据加载和训练流程是否通畅再跑完整训练。否则你真跑了一个小时最后发现验证代码里有个 bug那才叫崩溃。5. 评估指标与结果分析别只会报一个准确率我见过太多课程作业的演示只放一张“准确率 94%”的截图然后就没有然后了。老师现场一追问连精确率、召回率都说不清。准确率只是众多指标中的一个尤其在类别不平衡的任务里它很容易掩盖真实问题。5.1 从混淆矩阵推算精确率、召回率和 F1二分类任务的评估我建议至少给出混淆矩阵以及由它算出的精确率、召回率和 F1。我这次的真实数字大概是这样的指标有缺陷类无缺陷类精确率0.870.95召回率0.920.91F10.890.93对应公式是精确率 预测为缺陷且确实为缺陷的数量 / 预测为缺陷的总数召回率 预测为缺陷且确实为缺陷的数量 / 真实缺陷总数F1 2 * 精确率 * 召回率 / (精确率 召回率)对于这个任务漏检一个缺陷零件比把正常零件误判成缺陷严重得多因为漏检意味着不合格产品流入下一道工序。所以我特别关注缺陷类的召回率只要召回率掉到 0.9 以下我就会回头检查是不是类别权重设置得太保守。5.2 错误样本可视化比指标表格更能说明问题报告里写一段文字分析正确率和错误这是在课程评分里拉分的关键。我当时从测试集里挑出 6 张被分错的图分三类讨论缺陷区域太小肉眼扫描都要仔细辨认模型漏检了。零件表面本身有反光反光区域被模型误认成缺陷。缺陷特征与实际数据分布不一致比如训练集里划痕大多出现在零件中央测试集的划痕却在边缘。每类错误我都配一张图和一段解释说明模型为什么会错以及后续可以怎么改。这个过程让我在答辩时底气足很多因为老师看到的不只是一个结果而是你确实理解模型在什么条件下会失效。5.3 测试集只能用于最终验证不能用于调参我要专门强调一条纪律验证集用来调参测试集只能在所有调试完成之后最终评估一次。如果你反复用测试集去判断“这次效果好不好”然后把测试集上表现好的那次权重留下来本质上就是把测试集变成了验证集最终评估结果会虚高。我这次的做法是把数据分成三份以后训练过程中完全不管测试集所有早停和调参都只看验证集。最后拿到最优权重之后才在测试集上跑一次把所有指标记录下来。这样报告里的数字才经得起推敲。6. 演示与答辩最后一步决定前面所有努力能不能被看到演示环节是课程作业里最容易翻车的地方。模型训练得再好如果现场跑不通或者被一个简单问题问住评分都会大打折扣。6.1 做一个稳定的命令行推理接口我不建议在答辩时打开 Jupyter Notebook 一步步运行因为随时可能因为内核内存不足或者某个输出被清空而卡住。更稳妥的方式是写一个简单的推理脚本通过命令行参数传入图片路径输出结果是缺陷还是正常并打印置信度。python inference.py --image data/test/defective/DEF_0001.png --checkpoint best_model.pt这个脚本的好处是可靠且快速。答辩现场老师可能随机指一张图你要能当场跑给他看。我在脚本里额外做了两件事第一输出了两个类别的置信度而不只是类别标签第二对置信度低于 0.6 的样本打印“低置信度请人工复核”的提示。后面这个设计在答辩时被老师夸过因为它在真实场景里有实用价值。6.2 提前准备好常见问题的回答思路演示之外老师最喜欢问的问题其实是围绕你训练过程的判断而不是背结论。我个人准备过这样几个问题“为什么选 ResNet-18 而不是更大的模型”我的回答超出数据量训练样本有限大模型更容易过拟合而且推理时换来一点精度要付出几倍的时间成本。“为什么你的模型把这张图分错了”我的回答要落到实际特征反光区域和缺陷在灰度纹理上相似模型没有学过“反光”这个概念容易混淆。“如果现场采集的数据光照环境完全不一样你要怎么处理”我的回答使用更强的亮度扰动增强或者收集少量新场景数据做微调。不要背答案但一定要提前在脑子里过一遍这些逻辑。因为老师会根据你的回答继续追问一旦发现你只是机械重复网上的模板印象分会掉得很快。最后再分享一个小技巧。我在提交前一周把从数据加载、训练、评估到生成混淆矩阵的整条流程封装成了一个从配置文件读取参数的脚本也就是说换一组数据、改一个网络结构跑一遍命令行就能得到新的完整结果。这个习惯让我后面改实验变得非常快因为我不需要一个个手动改代码只要改配置文件里的路径、学习率和模型名称就行。如果你以后再做类似的作业或者小项目先把这条“流水线”搭起来后面会轻松得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑