news 2026/10/7 1:43:22

西瓜病害图像分类实战:从数据集解析到ResNet迁移学习全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
西瓜病害图像分类实战:从数据集解析到ResNet迁移学习全流程

简介:面向农业病害识别与图像分类实践者,这是一套西瓜病识别图像分类数据集,包含约5700张已标注叶片图片,覆盖花叶病毒、健康、炭疽病和霜霉病四类。压缩包共2000个文件,以1998张jpg图像为主体,另含1个Python可视化脚本和1个json分类配置文件,整体约156.94MB。资源已划分训练集与测试集,按类别目录存放图片,json文件负责记录分类信息;运行show脚本即可快速浏览图像和标签分布,图片中还包含缩放、位移、亮度增强等多样化样本,有助于提升模型泛化能力。对于刚接触图像分类的开发者,这份数据格式规范、类目清晰,可直接用于数据加载、模型训练与指标评估,省去手动标注和格式转换的额外工作。目前已有61人学习下载,适合用于图像分类入门实践、西瓜病害识别模型训练,以及CNN网络结构对比与改进实验。

1. 西瓜病识别是个真问题:从 5,700 张标注图聊起

种西瓜的朋友常跟我抱怨,说叶子出毛病了只能靠农药老把式肉眼判断,拍张照片发群里问,十个人八个说法。花叶病毒、炭疽病、霜霉病,症状都长在叶子上,颜色纹理还特别像,光学特征区分度很低。这份西瓜病识别图像分类数据集,恰好把四类图像——花叶病毒、健康、炭疽病、霜霉病——整理成了约 5,700 张已标注图片,并且已经划分好训练集、测试集,省掉了自己做数据清洗和归属整理的功夫。做图像分类算法验证、毕业设计、农业病害识别 demo,都合适。它的 JSON 文件存着类别映射,附带 show 脚本能直接可视化数据分布,资源本身按训练集、测试集分目录存放,打开就能用。下面我按实际拆项目习惯,把数据解析、训练流程、常见翻车点逐一走一遍。

2. 先看懂这批数据再动手:目录结构、JSON 标注与 show 脚本实测

下载完数据集,第一件事不是急着写训练脚本,而是把目录结构和标注文件摸清楚。这份资源和很多随手打包的“数据集”不同,它是按图像分类的标准监督结构组织的,训练集、测试集分开,各类别图片各归各的文件夹。我通常会先花五分钟用命令行把整棵树打印出来,确认类别名称、统计各类别样本数,心里有个底再开始写代码。

2.1 目录组织方式与类别比例核对

dataset/ ├── train/ │ ├── Mosaic_Disease/ # 花叶病毒 │ ├── Healthy/ # 健康 │ ├── Anthracnose/ # 炭疽病 │ └── Downy_Mildew/ # 霜霉病 ├── test/ │ ├── Mosaic_Disease/ │ ├── Healthy/ │ ├── Anthracnose/ │ └── Downy_Mildew/ ├── labels.json # 类别名称与索引映射 └── show.py # 可视化脚本

这是最常见的 ImageFolder 式布局,PyTorch、TensorFlow 的flow_from_directory都能直接识别。我一般用下面的命令秒查类别平衡性:

for dir in dataset/train/*/; do echo "$(basename "$dir"): $(ls "$dir" | wc -l)" done

这段命令做的事很直接:遍历dataset/train/下的每个子目录,打印目录名和图片数量。参数说明:$(basename "$dir")取叶子目录名,ls | wc -l统计文件数。四类样本数量如果悬殊,说明数据分布本来就偏,训练时要在 loss 上加 class weight,否则模型会把样本多的类学得更熟。

接下来看labels.json,这个文件定义了类别字符串到数字标签的映射关系。

{ "Anthracnose": 0, "Downy_Mildew": 1, "Healthy": 2, "Mosaic_Disease": 3 }

注意这里类别索引的约定,训练输出会按这个顺序解释预测结果。我踩过一次坑:默认按文件夹名字母序建索引,得到的是Anthracnose=0, Downy_Mildew=1, Healthy=2, Mosaic_Disease=3,和 JSON 里的对齐关系不同,推理时输出混淆矩阵就需要转置映射。所以读标注文件时不光看类别名,还要记录索引顺序。

2.2 show 脚本里到底做了什么

资源里附带的show.py,从文件名推测就是随机抽若干张图,按 2×2 或 1×4 拼接平铺展示,同时打印对应标签。如果脚本直接运行报错,常见原因是相对路径不对——它默认从数据集根目录读。我习惯把它小改造一下,让它同时显示类别名和索引:

import os import json import matplotlib.pyplot as plt from PIL import Image import random data_dir = "dataset/train" with open("labels.json", "r", encoding="utf-8") as f: label_map = json.load(f) index_to_name = {v: k for k, v in label_map.items()} fig, axes = plt.subplots(2, 2, figsize=(8, 8)) random.seed(42) for ax in axes.flat: cls_name = random.choice(os.listdir(data_dir)) cls_dir = os.path.join(data_dir, cls_name) img_name = random.choice(os.listdir(cls_dir)) img_path = os.path.join(cls_dir, img_name) img = Image.open(img_path) ax.imshow(img) ax.set_title(f"{cls_name} ({img_name})") ax.axis("off") plt.tight_layout() plt.show()

这段代码的核心逻辑:读取 JSON 拿到类别映射,随机挑一个类别目录,再随机挑其中一张图,用 matplotlib 画出来并把文件名作为标题。参数说明:random.seed(42)固定随机种子,保证每次运行看到同样的样例,这不影响模型,但对复现报告截图有用;axis("off")去掉坐标轴刻度,只留图像本身。

运行这个脚本后你会明显看到四类图片的特点:健康叶片颜色均匀、叶脉清晰;炭疽病叶片上有圆形或不规则褐色病斑,病斑边缘有颜色渐晕;霜霉病叶片背面灰白色霉层,正面发黄;花叶病毒则叶片有黄绿镶嵌的花斑,纹理不规整。把这些视觉特征记在脑子里,后面分析模型误分类时有用。另外,如果数据集中有文件名带zoomed、shifted、brightened后缀的副本,说明数据做过离线增强,统计样本数时别把这些当成独立原始样本。

2.3 类别不均衡与样本质量先排查

做图像分类项目,数据质量检查环节不能省。我的固定流程是先跑一遍极小批量训练,同时用置信度筛选去查找坏图。文件路径是零字节的、色彩通道异常的、图片宽高比严重变形的,都要及时剔除。对于这份数据集,我还额外检查了增强副本与原始图是否同时出现在训练集和测试集——如果同一个原始样本的增强图被分到两边,测试分数会虚高,这个问题后面避坑章节展开说。

3. 把图片喂进 CNN:数据加载、归一化与训练流程全落地

数据看清了,接下来就是把目录结构变成一个可以训练的模型。常见做法是 PyTorch 的ImageFolder搭配DataLoader,也可以自定义Dataset做更细的控制。这一章我给出一个完整的训练流程,从数据增强到模型定义再到训练循环,参数解释放在代码块后面。

3.1 自定义 Dataset:读图、标签映射、缓存

虽然ImageFolder够用,但我倾向写一个极简自定义 Dataset,目的有两个:一是能在__getitem__里挂缓存和调试输出,二是为后面做 TTA(测试时增强)留接口。

import os import json import torch from torch.utils.data import Dataset from PIL import Image class WatermelonDataset(Dataset): def __init__(self, root_dir, transform=None): self.root_dir = root_dir self.transform = transform self.samples = [] with open(os.path.join(os.path.dirname(root_dir), "labels.json"), "r", encoding="utf-8") as f: self.label_map = json.load(f) # 遍历子目录,按 folder 名做映射 for folder_name, label in self.label_map.items(): folder = os.path.join(root_dir, folder_name) if not os.path.exists(folder): continue for fname in os.listdir(folder): if fname.lower().endswith((".jpg", ".jpeg", ".png")): self.samples.append((os.path.join(folder, fname), label)) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] img = Image.open(img_path).convert("RGB") if self.transform: img = self.transform(img) return img, label

逻辑说明:__init__里先加载labels.json,文件名与标签映射;然后遍历每个类别目录,把所有图片路径和标签组合成列表。注意convert("RGB")这行不能省——部分图片如果本身是灰度图或带 alpha 通道,不统一转换会出现通道数不一致,训练时直接报错。参数说明:transform传入 torchvision 的 transform 组合,在训练和测试阶段可以传不同的组合;root_dir是dataset/train或dataset/test,labels.json放在其上一级目录。

3.2 数据加载与归一化:ImageNet 均值方差是默认起点

图像分类的标准化,最常见做法是直接套 ImageNet 预训练模型的均值和方差,因为迁移学习时模型权重本来就是按这个分布优化的。如果资源后续计划换用自训练模型,再改成按数据集的真实均值方差计算。

from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) valid_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = WatermelonDataset("dataset/train", transform=train_transform) test_ds = WatermelonDataset("dataset/test", transform=valid_transform) train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) test_loader = torch.utils.data.DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=4)

参数说明:Resize(256)先把短边缩到 256,RandomResizedCrop(224)随机裁剪出一个 224×224 区域,这个是 ResNet/ConvNeXt 系列常用输入尺寸;RandomRotation(15)在正负 15 度内旋转,对叶子病害识别这种旋转不变性强的场景有效。CenterCrop(224)在验证阶段固定取中间区域,保证评测稳定。均值和方差这组数字是 ImageNet 统计出来的,别看它麻烦就不加,不加会导致训练初期 loss 异常,收敛速度明显变慢。

3.3 ResNet 迁移学习训练:四个核心配置项

资源关联的博客系列一直在做 CNN 分类网络改进,所以这里基础模型选 ResNet50 的预训练权重,改动小,训练快,四分类精度也够。写好训练主循环之前,先把四个关键点定下来:优化器、学习率、损失函数、epoch 数。

import torch.nn as nn from torchvision import models from torch.optim.lr_scheduler import CosineAnnealingLR model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, 4) model = model.cuda() criterion = nn.CrossEntropyLoss(label_smoothing=0.1) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=30) for epoch in range(30): model.train() total_loss = 0.0 for imgs, labels in train_loader: imgs, labels = imgs.cuda(), labels.cuda() optimizer.zero_grad() out = model(imgs) loss = criterion(out, labels) loss.backward() optimizer.step() total_loss += loss.item() scheduler.step() acc = evaluate(model, test_loader) print(f"epoch {epoch:02d} | loss {total_loss/len(train_loader):.4f} | acc {acc:.4f}")

逻辑说明:models.resnet50加载 ImageNet 预训练权重,把最后一层全连接替换成输出为 4 的新全连接层;label_smoothing=0.1给标签做平滑,缓解四分类模糊边界带来的过拟合。AdamW 配合weight_decay=1e-4是现在比较稳的组合,学习率 1e-4 是迁移学习常用起点,直接开 1e-3 很容易把预训练特征冲掉。CosineAnnealingLR的T_max设置成 30,和 epoch 数对齐,让学习率从 1e-4 余弦降到接近 0。evaluate函数按常规写法在测试集上算准确率,代码里没展开,实际就是no_grad模式跑一遍算均值。

顺带说明一个容易被忽略的点:ResNet50 在批量 32 时显存消耗约 6-8GB,如果你只有 4GB 显存,把batch_size降到 16,学习率也相应降到 5e-5,效果差异不会太大。

3.4 从训练日志定位问题区间

训练跑起来之后,我固定看三个指标:train loss 下降曲线、验证准确率、以及前 10 个 batch 的 loss 量级。如果第一个 epoch 结束后 loss 还在 0.5 上下徘徊,基本是学习率设置问题;如果 train loss 持续下降但验证 acc 卡在 60% 不动,大概率是数据增强策略不够或者类别不均衡。这两个诊断方向在下一章展开。

4. 训练翻车现场:准确率上不去、过拟合与标注不一致排查

这个章节写我拆项目时实际遇到过的坑,每一条都是“现象 → 原因 → 解决”三段式,照着排查能省半天到一天时间。

4.1 训练集测试集都分好了,但准确率还不如随机

现象:训练 30 个 epoch,测试准确率停在 45% 附近,四分类的随机基线是 25%,45% 虽然高于随机但远低于期望的 85%+。

原因:第一批自查发现,Resize 和 Crop 的尺寸设定不匹配。训练时用RandomResizedCrop(224),验证用CenterCrop(224),但 Resize 的长边才 256,这里面有个像素级问题:Resize(256)把短边缩到 256,但长边不一定是 256,如果原图是竖幅长条,短边 256 时长边可能到 600 多,CenterCrop(224)只取中央,恰好把病斑裁掉。叶子病害的位置并不总在图像中心,边缘病变被裁掉后模型根本看不到判据。

解决:把预处理改成先Resize(224)再CenterCrop(224),或者用transforms.Resize((224, 224))直接强制方形缩放。前者保留更多信息,后者会改变宽高比但省内存。对叶部病害这种纹理判据,我一般用Resize((256, 256))再RandomResizedCrop(224),避免宽高比失真。

4.2 花叶病毒和霜霉病互相认错

现象:混淆矩阵里,Mosaic_Disease 的样本有 30% 被预测成 Downy_Mildew,两者各自精度都很低。

原因:这两种病害在叶子上的症状表现都是叶色异常——花叶病毒是黄绿镶嵌,霜霉病是叶片黄化背面霉层,常规可见光照片里,正面看去两者的颜色分布很接近。只看单张静态图,模型缺少纹理深度的判别特征。另一个叠加因素是数据集中这两种病的原始样本数偏少,增强副本补不齐边界样本。

解决:我给这两种类别单独做了离线筛选,把标签错误的样本重新归类;同时在训练时对这俩类别使用更大的旋转角度(RandomRotation(30))和ColorJitter(brightness=0.2, contrast=0.2),强迫模型不再依赖单一颜色特征。实际操作中最好再补充一批人工标注的“疑似混淆”样本,那对提升区分度最有效。

4.3 同一图片的增强版同时出现在训练集和测试集

现象:验证准确率 94%,但拿到新图上一测,准确率掉到 70%,说明测试分数被污染了。

原因:数据划分发生在离线增强之前,或者划分脚本没有按原始图片的 basename 去重。文件名里带_zoomed、_shifted、_brightened的图片,和对应的原始图片可能被随机分到了两个集合,模型相当于见过测试图。

解决:强制做去重,用文件名去掉后缀之后的部分作为分组 key,保证同一个原始样本的整个增强家族都在同一边。这个动作在数据处理阶段必须做。不少数据集为了凑数量没注意这个,测试分数没有参考价值。

4.4 通道顺序错乱导致颜色特征彻底失效

现象:训练 loss 正常降低,但验证准确率异常低,甚至和随机猜差不多。

原因:用 OpenCV 读图得到的是 BGR 通道顺序,而训练代码里用 PIL 读图(RGB),两种情况混用后,模型看到的颜色通道语义错位。这个事在植物病害识别里尤其致命——病斑颜色本身就是关键特征,通道错乱等于特征丢失。

解决:统一读图方式,Dataset里全部用 PIL,或者在__getitem__里对 OpenCV 读进来的数组做cv2.cvtColor(img, cv2.COLOR_BGR2RGB),并加注释提醒。惯用做法是在项目入口处强制约定,不把读图方式分散在多个函数里。

4.5 把图像分类数据集当成目标检测数据用

现象:有同学看到文件夹里图片带病斑,想直接训 YOLO 检测病斑位置,结果发现没有 bbox 标注,无从下手。

原因:这份数据集定位就是图像分类,标签是整图级类别,不是目标检测的框标注。花叶病毒、炭疽病这类病斑是弥散性的,本身也不好画框。

解决:做病害位置定位时,要么换用目标检测数据集,要么先训练分类模型得到类别激活图(CAM),再后处理生成伪 mask。后者虽然粗糙,但作为前筛选是能用的。不要指望一个分类数据集直接套进 YOLO 的训练管线。

4.6 训练一段时间后 loss 变成 NaN

现象:第三个 epoch 开始 train loss 冒出nan,模型参数也全部变 NaN。

原因:学习率偏大时,预训练模型新接的全连接层梯度爆炸;尤其标签平滑为 0.1 时,交叉熵的输出梯度分布发生变化,叠加 BN 层的方差累积,数值不稳定。

解决:先调低学习率到 5e-5,再把torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)加在optimizer.step()之前。如果还有问题,检查输入图像中有没有全黑或全白的坏图,坏图经过标准化后数值范围异常,也会诱发 NaN。

5. 数据增强怎么做才有效:文件名里的 zoom、shift、brightened 三个细节

刚开始翻数据集时发现文件名里带_zoomed、_shifted、_brightened后缀,这是离线增强的痕迹。这些增强手段对应的是实际拍摄场景中的距离变化、机位偏移和光照变化,值得单独拉一章出来讲清楚为什么这么增、怎么增才合逻辑。

5.1 离线增强和在线增强:两条路线怎么选

离线增强是把变换后的图片直接落盘,生成新文件;在线增强是训练时对每张图实时做随机变换。这份资源的增强副本属于离线路线,好处是训练快(不用实时算变换),坏处是磁盘占用大,且变换组合固定,无法在训练时随机变化。

我平时对待离线增强的态度是:它应该跟实物拍摄条件的多样性对齐,而不是为了凑数量。举例来说,_zoomed后缀对应的是手机离叶片远近不同;_shifted对应拍摄时病斑不在画面中心;_brightened对应不同光照强度。这三种变换都是有物理意义的。反过来,如果只是随机生成 20 种旋转角度,数据量是上去了,但模型学到的还是同类特征,提升有限。

5.2 用 PIL 手动复现这三种增强:理解参数边界

如果想把离线增强在代码层面跑通,或者在自己数据集上增加更多变体,我一般这么写:

from PIL import Image, ImageEnhance import random def apply_zoom(img, factor=1.2): """模拟相机靠近叶片:中心放大裁剪""" w, h = img.size nw, nh = int(w / factor), int(h / factor) left = (w - nw) // 2 top = (h - nh) // 2 cropped = img.crop((left, top, left + nw, top + nh)) return cropped.resize((w, h), Image.BILINEAR) def apply_shift(img, dx_ratio=0.05, dy_ratio=0.05): """模拟机位偏移:像素平移后补边""" w, h = img.size dx, dy = int(w * dx_ratio), int(h * dy_ratio) shifted = Image.new("RGB", (w, h), (0, 0, 0)) shifted.paste(img, (dx, dy)) return shifted def apply_brighten(img, factor=1.15): """模拟光照变化:亮度增强""" enhancer = ImageEnhance.Brightness(img) return enhancer.enhance(factor)

逻辑说明:apply_zoom先按factor计算中心裁剪区域,裁完再放大回原尺寸,模拟镜头拉近效果。apply_shift是整图平移,超出边界的部分用黑色填充,注意黑色区域经过归一化会是负值特征,如果平移量超过 10%,模型容易学到把黑色当背景特征。apply_brighten用的是 PIL 的亮度增强接口,factor=1.15表示亮度提升 15%。

参数边界说明:factor=1.2是常用下限,超过 1.5 时病斑纹理会被过度放大失帧;dx_ratio=0.05即平移 5% 宽度的像素,超过 10% 会丢失太多边缘信息;brightened用 1.15 是为了不破坏高光区的颜色信息,调太高病斑的褐红色会过曝变白。这三个参数是经验值,实际操作时建议在完整数据上做小批量实验,看着混淆矩阵微调。

5.3 在线增强的规范性做法:先轻度后重度

训练阶段,我在线增强只加了RandomHorizontalFlip和RandomRotation(15),不再叠加 zoom 和 shift。原因很简单:离线增强已经包含距离和位置变化,在线再叠加同类变换,等于双重随机,破坏原始分布。如果你用的是没有离线增强的版本,那在线增强的强度要往上调,常规建议组合是变换 1:

train_transform_online = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomApply([transforms.ColorJitter(brightness=0.3, contrast=0.3)], p=0.8), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

这里scale=(0.7, 1.0)控制 RandomResizedCrop 的裁剪比例距离,0.7 是相对安全的边界,太低会让病斑被裁得只剩局部。RandomApply包一层ColorJitter只让 80% 的样本做颜色扰动,保持一部分干净样本让模型不受光照变化干扰。这些参数都是常规做法,不是唯一解,但可以作为起点。

5.4 增强效果评估:不要只盯着准确率

评估增强有没有用,核心看两个指标:测试准确率和混淆矩阵的稳定性。我的习惯是,固定随机种子和模型权重,分别跑“无增强”和“有增强”两组实验,对比第四个类别(霜霉病)能否被正确识别。如果增强后准确率上升但某一个类别的召回率下降,说明增强引入了更偏的分布,需要重新检查增强参数。

6. 模型验证与部署:批量推理脚本、混淆矩阵和迁移到棚拍场景的二分类技巧

训练结束不等于项目收尾,分类模型要真正给种西瓜的人用起来,还得做两件事:一是把 PyTorch 模型变成能单图推理的东西,二是做一套“发展周期”验证,确认模型不是只认这 5,700 张图片的统计特征。这一章写我的收尾习惯。

6.1 单图推理:从测试集随机抽 50 张,打印真实标签和预测标签

import torch from PIL import Image from torchvision import transforms def infer_one(model, img_path, index_to_name): img = Image.open(img_path).convert("RGB") tensor = valid_transform(img).unsqueeze(0).cuda() with torch.no_grad(): out = model(tensor) pred_idx = out.argmax(dim=1).item() return index_to_name[pred_idx]

逻辑说明:unsqueeze(0)把单张图扩成 batch 维度,因为模型期望输入是四维张量;torch.no_grad()关闭梯度计算,推理时省显存也快。把index_to_name从 JSON 反转后的映射传进来,输出就是可读的类别名,而不是数字。

我实际做验证时不会只跑准确率,而是把测试集里每类各抽 10 张,逐张打印画像:真实标签是花叶病毒但预测成霜霉病的样本,反过去看原图到底是什么样。这个动作能在几分钟内看出模型的系统性偏差——比如它是不是把所有带黄色调的都预测成霜霉病。如果确实如此,再回到数据增强里调brightened强度。

6.2 混淆矩阵和每类精确率召回率:评估报告不能只看总 Acc

四分类任务,总准确率掩盖了太多信息。我固定打印一个 format 化的分类报告:

from sklearn.metrics import classification_report, confusion_matrix all_preds, all_labels = [], [] for imgs, labels in test_loader: imgs = imgs.cuda() with torch.no_grad(): preds = model(imgs).argmax(dim=1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_names=list(label_map.keys()))) print(confusion_matrix(all_labels, all_preds))

classification_report会输出每个类别的 precision、recall、f1-score。注意这里的target_names顺序必须和 JSON 里定义的索引一致,否则打印出来的标签名和数字错位。实际项目中最后一版模型的 target 顺序是{"Anthracnose": 0, "Downy_Mildew": 1, "Healthy": 2, "Mosaic_Disease": 3},最终报告显示 Healthy 类 F1 最高,Mosaic_Disease 最低,这一步就定位到了下一轮优化重点。

6.3 棚拍场景迁移:二分类快筛的另一种用法

这份数据集是四分类,但实际去大棚里拍照,手机照片的光线、角度远比训练集里的增强图复杂。如果四分类模型在现场表现不稳,我把它降级成二分类快筛用:先判断“健康/异常”,异常再交给四分类模型精确判病种。这在工程上很容易做——把四分类输出概率中 Healthy 单独作为健康阈值,剩下三类的概率合并作为患病概率。比起重新训练二分类模型,这个方式少动代码,也保留原有诊断粒度。

6.4 从推理到落地的三个检查习惯

每次做完一版模型,我会强制走一遍这三个检查:第一,换 10 张完全没有出现在训练集和测试集里的真实棚拍照片,逐张肉眼对比预测结果和实际症状;第二,用灰度图和饱和度图各跑一遍推理,确认模型没退化成只认颜色;第三,记录每张推理的耗时,四分类 ResNet50 在普通 CPU 上约 80ms 一张,如果超过 200ms 就要考虑量化或换轻量模型。

从那以后我每次训图像分类模型,都强制走一遍“数据分布核对 → 离线增强去重 → 混淆矩阵定位 → 棚拍实测”这个闭环,确保分数不是自欺欺人。这套流程对这份西瓜病数据集适用,对你手里其它图像分类资源也通用。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 1:43:02

U2Net显著性目标检测实战:无标注图像的轻量级像素级分割

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:43:02

步进电机与驱动器实操指南:接线、细分、控制避坑全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:42:32

算法工程师的实战复盘:分治、DP与贪心的本质辨析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:42:16

遥感图像语义分割实战:5000张工业级数据集构建与训练指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:41:39

基于MCP协议与ctypes的IoT功耗计AI自动化测量方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:39:11

废品回收小程序全栈开发:Spring Boot订单状态机与微信端避坑实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华