☰
基于CNN的工件毛刺分类毕业设计:从数据集构建到模型部署
2026/10/4 1:06:56 网站建设 项目流程

简介:面向计算机视觉与智能制造方向学习者的毕业设计资源包,聚焦工业场景下的工件毛刺检测与分类问题。基于Python实现CNN卷积神经网络模型,涵盖数据标注、训练、测试与分类全流程,适合作为毕设项目、课程设计或入门进阶的参考案例,也便于在此基础上二次开发。压缩包共1294个文件,总大小152.71MB,包含1210张png与40张jpg图像样本,24个py源码文件,以及csv标签文件、json配置、vott标注文件和训练好的pth模型权重,可支持直接复现或继续调优。资源目录结构清晰,数据、代码、模型和说明文档分层组织,便于快速定位。目前已有117人学习下载。完整打包了数据集、标注信息、可运行源码与训练模型,并附有必要的说明文档,既能帮助初学者理解CNN在工业质检中的实际应用,也能为毕业设计提供可演示、可扩展的代码基础。下载后遇到问题可通过站内私信交流,项目经测试运行成功,可放心使用。

1. 工件毛刺分类难在哪:这个毕业设计为什么选了CNN而不是边缘检测

机加工车间里,毛刺质检是每天重复几百次的动作:工件从数控机床下来,边缘带着打磨残留的毛刺,质检员目视或者用手指划过边缘来分级。问题出在两个方面,一是长时间工作后的疲劳漏检,二是“轻微”和“严重”的边界在两个人眼里完全可以是两套标准。用深度学习CNN网络做工件毛刺分类,本质是让卷积神经网络从图像纹理里直接学习毛刺的有无和等级,把主观判断变成可重复的模型输出。标题里这套毕业设计zip,通常就是三样东西:能跑的Python源码、训练好的模型权重、整理好的工件毛刺图像数据。适合机械、自动化专业做课题的学生,也适合想评估CNN方案能否搬上产线的工艺工程师。

2. 数据先行的毛刺样本集:裁剪策略、类别定义与数据增强的真实边界

任何CNN项目,数据决定上限,模型只是逼近上限的执行者。这套毕业设计zip里的“数据”目录能不能用,直接决定你后面是躺平还是折腾。我一般会先把标注格式、类别尺度和增强策略定下来,再去碰模型代码,因为数据预处理返工的成本比改模型高太多。

2.1 毛刺样本从哪来:工业相机实拍与历史图复用的取舍

毛刺样本的常见来源有两个。一是工业相机实拍,二是从质检环节积累的历史图像里抠。前者最容易控制变量:相机用普通USB工业相机就行,分辨率500万像素足够,关键是固定焦距和曝光,别开自动白平衡;光源用环形LED从镜头同轴方向打,或者两条条形光从侧面斜打,让毛刺的投影形成明显边缘;背景铺一块纯色亚克力板,黑色白色都行,但全流程必须统一。这样拍出来的图,毛刺和工件表面有稳定的对比度,后期模型学起来轻松很多。

历史质检图复用看起来省事,但坑在拍摄条件不一致:光照角度变过、工件摆放朝向变过、相机距离变过。这些差异会被CNN当成“特征”学进去,导致训练集和验证集指标都很好看,一到现场换一个工位就崩。我的经验是,如果历史图里拍摄条件五花八门,干脆放弃,重新拍一套。样本量方面,二分类每类至少300张,三分类每类至少250到500张,毛刺分类每个类目同质性强,300张左右就能跑出能看的收敛曲线;如果只有几十张,别指望CNN自己发明特征。

类别体系类别定义适用场景
二分类无毛刺 / 有毛刺只做合格判定,成本最低
三分类无 / 轻微 / 严重需要区分返工和报废,毕业设计最常用
四分类加一个“异物/磕碰”类产线上还要拦其他缺陷,课题扩展用

类别定义写得越具体,标注越不容易翻车。我给一个能落地的三分类标尺:无毛刺就是肉眼和触感都摸不到明显凸起;轻微是能看到但高度明显低于工件表面、指甲能刮到;严重是毛刺突出超过1毫米或者用游标卡尺能直接读出来。这套标尺直接写进标注规范,让标注的人照着执行,比“凭感觉”可靠得多。环境上,先保证Python安装好了torch、torchvision、albumentations和pandas这几个包,版本不用最新,能跑通就行。

2.2 用LabelImg框出毛刺再裁剪:为什么先定位后分类的准确率更高

这里有个很多人第一次做会踩的坑:直接把整张工件图喂给分类网络。毛刺在整幅图里往往只占1%都不到的区域,模型在这种图上会优先学到工件轮廓和表面纹理,而不是毛刺本身。常见做法是先做区域标注、再做分类:用LabelImg把毛刺区域框成矩形框,保存成Pascal VOC格式的XML,然后写一个脚本,把框里的区域裁剪成小图,按类别存成文件夹。这样输入模型的全是“毛刺长什么样”,学得又快又准。

import os import xml.etree.ElementTree as ET from PIL import Image def crop_from_xml(xml_path, img_dir, out_dir): tree = ET.parse(xml_path) root = tree.getroot() filename = root.find('filename').text img = Image.open(os.path.join(img_dir, filename)).convert('RGB') crops = [] for obj in root.findall('object'): label = obj.find('name').text bndbox = obj.find('bndbox') xmin = int(bndbox.find('xmin').text) ymin = int(bndbox.find('ymin').text) xmax = int(bndbox.find('xmax').text) ymax = int(bndbox.find('ymax').text) crop = img.crop((xmin, ymin, xmax, ymax)) label_dir = os.path.join(out_dir, label) os.makedirs(label_dir, exist_ok=True) base = os.path.splitext(filename)[0] out_path = os.path.join(label_dir, f'{base}_{xmin}_{ymin}.jpg') crop.save(out_path) crops.append((label, out_path)) return crops if __name__ == '__main__': xml_dir = 'annotations/' # LabelImg导出的XML目录 img_dir = 'raw_images/' # 原始工件大图目录 out_dir = 'cropped_dataset/' # 裁剪后按类别分好的数据集目录 all_crops = [] for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): all_crops.extend(crop_from_xml(os.path.join(xml_dir, xml_file), img_dir, out_dir)) print(f'共裁剪出 {len(all_crops)} 张毛刺样本')

逻辑说明:脚本遍历每个XML文件,找到每个object里的label和bndbox矩形框,用PIL的crop方法把原图对应区域裁出来,保存到以类别名命名的子目录里。文件名带上原图坐标是为了溯源,后续排查脏数据时能直接定位到原始大图。

参数说明:xml_dir是LabelImg的输出目录,img_dir是原始图目录,out_dir是裁剪结果目录。裁剪时矩形框最好比毛刺实际区域外扩几个像素,留一点上下文,别把毛刺切得只剩一根细线,否则Resize到224时细节会丢得很惨。

2.3 数据增强的参数边界:翻转、光照和模糊该给多大强度

数据增强能解决样本量不足,但毛刺分类有自己特殊的物理约束。水平翻转是安全的,左右翻转不改变毛刺形态;垂直翻转要谨慎,很多工件毛刺的方向和加工流程强相关,翻转后的样本在现实工况里根本不存在,模型推理时反而被带偏。这是增强里最容易被忽略的边界问题。

import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform = A.Compose([ A.Resize(224, 224), A.HorizontalFlip(p=0.5), A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.3), A.HueSaturationValue(hue_shift_limit=8, sat_shift_limit=10, val_shift_limit=10, p=0.3), A.GaussianBlur(blur_limit=(3, 5), p=0.15), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ]) val_transform = A.Compose([ A.Resize(224, 224), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ])

逻辑说明:训练时在线增强,每个epoch看到的图都不一样,变相扩充样本量;验证集只用Resize和Normalize,不做随机增强,保证指标可复现。GaussianBlur概率只给0.15,用来模拟质检照片轻微失焦的情况,给多了会把细小毛刺糊掉;亮度对比度扰动幅度控制在0.1以内,因为产线拍摄时光照不会有剧烈波动。

我习惯在增强没打开之前,先把Resize和Normalize跑通一遍,确认数据能被网络正常吃进去,再逐步把翻转和扰动打开。这样排查问题时变量少,不会出现“改了增强后准确率掉了,但不知道是哪一项导致的”。

3. 从零搭CNN毛刺分类网络:轻量结构、自定义Dataset与编码参数

很多人拿到这个题目第一反应是调一个预训练VGG16,其实对毛刺分类这种几百张图的小样本任务,自己搭一个轻量CNN往往更合适。理由有三个:参数量小不容易过拟合;训练速度快,可以反复试参数;答辩时讲结构有底气。先把小网络跑通,再决定要不要换成迁移学习,是两个模型深度学习的标准路径,不冲突。

3.1 模型选型:轻量CNN和预训练模型之间的现实分界线

VGG16参数量超过1.3亿,ResNet18也有1100万左右,注意这里的单位是百万参数(M),不是存储容量MB,当年被这个M坑过的人不止我一个。几百张训练图喂给千万级参数的网络,模型很容易把训练集的噪声背下来,验证集指标一路往下掉。

轻量CNN的参数量通常在几十万到几百万之间,配合BatchNorm和Dropout,在小样本上反而更稳。如果后面数据量扩到几千张,再换成ResNet18做迁移学习也不迟,两个模型可以共用同一套数据加载和训练框架,切换成本很低。显存方面,224分辨率输入加batch size 16,这种轻量网络在8G显存的显卡上能轻松跑,学生笔记本的GTX 1650都够用。

3.2 六层卷积结构:Conv+BN+Pool的堆叠逻辑与每个参数的含义

import torch.nn as nn class BurrClassifier(nn.Module): def __init__(self, num_classes=3): super(BurrClassifier, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(128, 256, kernel_size=3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True), nn.MaxPool2d(2), ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(256, 128), nn.ReLU(inplace=True), nn.Dropout(0.5), nn.Linear(128, num_classes), ) def forward(self, x): return self.classifier(self.features(x))

逻辑说明:四组卷积块,每组是3x3卷积加BN加ReLU加2倍池化,通道数从32翻到256。最后不用Flatten后直接接大全连接,而是先用AdaptiveAvgPool2d(1)把特征图压成256维向量,再接一个128维隐藏层,用Dropout(0.5)防过拟合,最后输出类别数。

参数说明:kernel_size=3配padding=1,特征图尺寸只靠池化降采样,不靠卷积缩水;BatchNorm放在ReLU前面,让每层输入分布稳定,训练收敛明显更快;MaxPool2d(2)把224的输入逐步压到14x14,感受野从局部纹理覆盖到整体轮廓。如果输入图改成128x128,这个结构不用改,因为AdaptiveAvgPool2d会自动适配尺寸,这就是为什么分类头不用固定全连接输入尺寸。

3.3 用自定义Dataset读CSV清单:比ImageFolder更可控的数据组织方式

ImageFolder的用法很省事,目录按类分好就能直接喂,但有个麻烦:样本是从XML裁剪来的,后面要做训练验证切分,ImageFolder就得反复挪文件夹。我习惯把裁剪结果写成一个CSV清单,再自定义Dataset读取,切分和调试都方便。

import pandas as pd import numpy as np from PIL import Image from torch.utils.data import Dataset class BurrDataset(Dataset): def __init__(self, csv_path, transform=None): self.df = pd.read_csv(csv_path) self.transform = transform self.labels = sorted(self.df['label'].unique()) self.label_map = {label: idx for idx, label in enumerate(self.labels)} def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] img = Image.open(row['image_path']).convert('RGB') label = self.label_map[row['label']] if self.transform is not None: img = self.transform(image=np.array(img))['image'] return img, label

逻辑说明:Dataset返回的是图像张量和类别索引。构造函数里用labels枚举生成label_map,保证标签从0开始连续编号。读图用PIL的convert('RGB')统一三通道,避免灰度图混进去让第一层Conv2d报错。csv_path里只要两列:image_path是裁剪图的绝对路径,label是类别字符串;transform就是我们前面定义的albumentations管道。

数据集切分用train_test_split,这里有一个必须注意的参数stratify:

from sklearn.model_selection import train_test_split train_paths, val_paths, train_labels, val_labels = train_test_split( df['image_path'], df['label'], test_size=0.2, stratify=df['label'], random_state=42 )

逻辑说明:stratify按类别比例分层抽样,毛刺分类里“严重”类别样本通常最少,不stratify的话验证集里可能一个严重样本都没有,混淆矩阵直接缺一行。

参数说明:test_size=0.2是常规比例,样本总量小,验证集给大一点反而波动剧烈;random_state固定住,保证每次跑出来的划分一致,排错时能复现。

4. 训练与调参的硬指标:学习率、早停与类别不平衡的三个必调项

模型搭好只是开始,真正决定毕业设计能不能按时交的是训练环节的参数设置。这一章讲三个必调项:学习率、早停策略和类别权重。这三项调好了,训练过程基本不用盯着,跑完看指标就行。

4.1 学习率与优化器选型:Adam配1e-3是最稳的起点

CNN分类任务,Adam配1e-3是最常规的起点,没有之一。这个组合对大多数图像分类问题都能正常收敛,不需要像SGD加Momentum那样花大量时间调学习率调度。跑起来之后重点观察训练loss和验证acc的曲线:loss在前5个epoch如果完全没有下降趋势,先检查数据加载对不对,再考虑是不是学习率太小;如果loss直接变成NaN,说明学习率太大,降到3e-4再试。

import torch from torch.optim import Adam import torch.nn as nn device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = BurrClassifier(num_classes=3).to(device) criterion = nn.CrossEntropyLoss(weight=class_weights.to(device)) optimizer = Adam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) for epoch in range(40): model.train() total_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() avg_loss = total_loss / len(train_loader) val_acc = evaluate(model, val_loader, device) scheduler.step() print(f'Epoch {epoch+1:02d} | loss {avg_loss:.4f} | val_acc {val_acc:.2%}')

逻辑说明:每个epoch清空梯度、前向传播、计算loss、反向传播、更新参数。CosineAnnealingLR把学习率在一个周期内从1e-3平滑降到接近0,相比每20个epoch砍半的StepLR,后期收敛更稳,训练曲线的视觉效果也更好。

参数说明:T_max=30对应一个余弦周期的epoch数,建议设成你计划跑的总epoch数;batch size我一般用16,224分辨率加上轻量网络,4个batch占显存不到2G,2060S这种卡能轻松跑。如果batch size拉到64,学习率也应该相应放大到4e-3左右,这是线性缩放规则,但毕业设计数据集小,不建议在这上面折腾。

4.2 早停与最优权重保存:验证集说了算,别跟训练loss走

训练结束时保存的模型,要看验证集准确率,而不是训练loss最低的那个点。训练到后期,loss还在降但验证集acc开始掉头,说明模型开始背训练集了。标准做法是EarlyStopping:连续多少个epoch验证集没有刷新最好成绩,就提前停。

best_acc = 0.0 patience = 10 bad_epochs = 0 for epoch in range(40): # ... 省略训练和验证代码 ... if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'best_burr_model.pth') bad_epochs = 0 else: bad_epochs += 1 if bad_epochs >= patience: print(f'Epoch {epoch+1}: 验证集连续 {patience} 次未刷新,提前停止') break

逻辑说明:只有验证集acc刷新历史最优时,才把model.state_dict()保存成文件,这保证了磁盘上的pth一定是对验证集最好的权重,而不是最后一个epoch的权重。bad_epochs累计连续未刷新的次数,到了patience就break退出训练,省下后面那些纯粹过拟合的时间。

参数说明:patience=10对毛刺分类这种小数据集够用;设太早比如3,模型还没收敛就被掐停了;设太晚比如30,等于没有早停,白占时间。保存的state_dict只是权重字典,加载时要先实例化一个相同结构的BurrClassifier,再调用load_state_dict。

4.3 类别不平衡的解法:加权CrossEntropy的参数怎么给

毛刺分类数据集里,“无毛刺”和“轻微毛刺”的样本量往往两倍甚至三倍于“严重毛刺”。如果不做任何处理,模型会把严重毛刺当成稀有事件,整体准确率倒是擦线,但严重类的召回率很低。等权重下每个类对loss的贡献是1/3,加权后让样本少的类别有更大的梯度权重。常见做法是用总数除以类别数再除以类样本数,得到每个类的权重。

from collections import Counter counts = Counter(df['label']) total = len(df) num_classes = len(counts) weights = {label: total / (num_classes * count) for label, count in counts.items()} weights_tensor = torch.tensor( [weights[label] for label in sorted(weights.keys())], dtype=torch.float32 ) print(weights_tensor)

逻辑说明:比如总共600张,3个类的样本数是400、150、50,那权重就是600/(3×400)=0.5、600/(3×150)=1.33、600/(3×50)=4.0。样本越少的类权重越大,把这个tensor传给CrossEntropyLoss的weight参数,它就自动做加权求和。

参数说明:权重顺序必须和label_map的顺序一致,也就是和模型的类别索引对齐,顺序错了加权就变成反向惩罚,这是个特别容易翻车的细节。sorted(weights.keys())是让类按字典序排列,前提是你构造BurrDataset时也是sorted,两边必须保持一致。加权后发现严重类的召回率上来但整体准确率掉了,这是正常的,因为模型开始把一些原本判成“无”的边缘样本判成“轻微”或“严重”。毕业设计里通常优先保证严重类不漏检,代价曲线不同。

5. 毛刺分类常见问题排查:从数据泄漏到ONNX导出的四个翻车点

这一章是我在类似小样本分类任务里积累的排查笔记,每条都按现象、原因、解决的顺序写,对照着查比自己瞎试快得多。

5.1 训练loss一路在降,验证集准确率纹丝不动:先查按工件ID划分

现象:前几个epoch训练loss很漂亮地往下走,到第5个epoch已经到0.5以下,但验证集准确率一直卡在50%或60%出头,和随机猜测差不多。

原因:最常见的是数据集划分时数据泄漏。毛刺数据往往一个工件拍了好几个角度,如果按单张图随机切分,同一个工件不同角度的图可能同时出现在训练集和验证集里。模型记住了同一工件的光照和表面纹理,换一批真正的陌生工件就现原形。

解决:按工件ID而不是按单张图分组。给每张图的文件名带上工件编号,比如part_001_side_a.jpg,然后用GroupShuffleSplit或者手动按part_id切分,保证同一个工件的所有角度只进训练集或只进验证集。这是我第一版模型翻车换来的血泪经验,改完划分方式后验证集指标直接涨了十几个点。

5.2 “轻微”和“严重”标了等于没标:标注分歧让模型学了个假边界

现象:模型训练完,验证集里“轻微”类经常和“严重”类互相错分,混淆矩阵打出来两个类的渗透很严重。

原因:标注规范里没有量化标准,标注的人用感觉在标。同一个毛刺,工件朝向、光线角度不同,人眼判断的级别会变。模型本质上是在拟合标注者的主观噪声,这个噪声大了,网络的分类边界就是乱的。

解决:把标尺量化写进规范。毛刺从根部到顶端的长度超过5毫米算严重,低于5毫米但肉眼可见算轻微;或者用毛刺面积占图面宽度的比例来卡。对分歧样本做二次复核,选第三个人仲裁。这一步看着慢,但能直接抬高模型上限,训练时间省回来的远不止这些。

5.3 裁剪后毛刺区域太小,Resize到224直接糊成一团

现象:裁剪出来的毛刺小图只有30×40像素,Resize到224后边缘全部糊掉,训练acc能到90%,测试一塌糊涂,因为测试时拍摄距离不同、裁剪大小不同。

原因:毛刺本身的物理尺寸很小,工件的拍摄距离又远,导致原始图里目标区域占比过低。

解决:拍摄时让工件尽量占满画面,毛刺在原始图里至少占到80×80像素以上再裁剪,保证Resize时还有足够细节。如果原始图里毛刺仍然很小,可以先把原图放大1.5到2倍再裁。不建议在这个阶段依赖超分辨率,它会把毛刺的轮廓“凭空”编造出来,训练集看着清晰,实测数据反而更差。

5.4 CPU推理太慢或GPU环境缺依赖:提前导出ONNX留好后路

现象:毕业设计演示安排在普通笔记本上,用PyTorch直接推理,CPU上跑一帧要200多毫秒,现场卡顿。或者现场机器没装CUDA,torch直接报找不到设备。

原因:PyTorch推理框架带着自动求导的额外开销,GPU和CPU环境又不一致,训练机上能跑不代表演示机能跑。

解决:训练完导出ONNX,用ONNX Runtime部署,CPU推理能快一个量级,而且不需要装完整PyTorch。导出代码很简单:

import torch model.load_state_dict(torch.load('best_burr_model.pth', map_location='cpu')) model.eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, 'burr_model.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}}, )

逻辑说明:dynamic_axes里的batch_size设为动态,部署时既能单张推理也能批量处理,输入张量形状是N×3×224×224。dummy_input的形状要和模型输入严格对齐,否则导出时直接报错。导出必须让模型处于eval模式,否则BatchNorm层还按训练时的统计量走,推理结果会莫名其妙地偏,这个问题很隐蔽。

6. 让模型对答辩负责:混淆矩阵与Grad-CAM热力图的落地做法

6.1 用classification_report盯住严重毛刺的召回率

毕业设计论文里,一个准确率数字根本撑不起“验证了方案可行性”这句话。答辩评委最关心的是严重毛刺到底被漏检了多少,因为漏检一个严重毛刺流向客户就是一次质量事故。所以我习惯固定输出混淆矩阵和分类报告:

import numpy as np from sklearn.metrics import confusion_matrix, classification_report model.eval() y_true, y_pred = [], [] with torch.no_grad(): for images, labels in test_loader: images = images.to(device) outputs = model(images) preds = outputs.argmax(dim=1).cpu().numpy() y_pred.extend(preds) y_true.extend(labels.numpy()) names = ['无毛刺', '轻微毛刺', '严重毛刺'] print(classification_report(y_true, y_pred, target_names=names)) print(confusion_matrix(y_true, y_pred))

逻辑说明:classification_report给出每个类的precision、recall和f1-score,这个表可以直接整理成论文实验章节的表。如果严重毛刺的recall低于0.8,说明加权系数或标注标尺还没调到位,这个数字应该成为你调参的硬指标,而不是盯着整体准确率。

6.2 Grad-CAM热力图:给“CNN是黑匣子”一个看得见的反驳

答辩必问“你这个模型凭什么这么分”。Grad-CAM是最好演示的回答:取最后一个卷积层的特征图,用类别对应梯度做加权,生成高亮区域叠加到原图上。毛刺分类的热力图如果可靠,高亮区域应该集中在毛刺边缘而不是工件表面纹理。这个图放论文里说服力强,也能反过来帮你发现模型学偏了的问题。

做法简单说:hook住features最后一层的输出,计算预测类别的梯度,对特征图通道做加权平均,取最大值归一化到0到255,再按热力图色表画出来。不需要额外安装复杂的库,用PyTorch的register_forward_hook和register_backward_hook就能拿到中间结果。

我做这类小样本分类课题的习惯是,模型训练完先不看准确率,而是把每一类的召回率列出来,再看几张Grad-CAM图和原图对比。我遇到过几次整体acc看起来很高,但热力图高亮在毛刺旁边的倒角上,说明模型学的是倒角的反射光,不是毛刺本身。这种问题数据和代码都救不了,只能靠可视化发现问题后回去补数据、调整标注,重跑训练。训练时间不长,数据也就几百张,多跑几个版本,损失的是时间,保住的是答辩现场的底气。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询