医学图像分类实战:肾脏数据集组织、yolov5训练与混淆矩阵评估
2026/9/23 23:58:21 网站建设 项目流程

简介:一套针对肾脏结节与肿瘤识别的医学图像分类数据集,包含正常、结节、肿瘤三种标签,专为基于深度学习的影像分类场景设计,适用于医学影像入门实验、算法验证及竞赛练习。数据已按照训练集、验证集、测试集划分并保存在data目录下的独立文件夹中,三类图像数量分别为训练集2800张、验证集800张、测试集400张,可直接用作卷积神经网络分类或YOLOv5分类数据集的输入。资源包共2000个文件,主要由1998张JPG样本图组成,另含1个记录类别名称与索引对应关系的JSON字典文件,以及1个用于数据集可视化的Python脚本,整体压缩包约151.5MB,结构简洁、便于快速检查数据质量。目前已有299人学习下载,类别字典清晰标注类别与索引的对应关系,可视化脚本便于快速检查样本,减少数据整理与预处理的繁琐步骤。对于希望快速获得划分完善、格式规整医学图像数据的研究者,这套资源能显著缩短实验前期准备时间。

1. 医学图像分类的第一步:这份肾脏数据集的目录里藏着什么

做医学图像分类的人最怕的不是模型选型,而是数据本身没收拾利索。标签对不上、图片混在一起、训练验证切分不干净,这些问题往往在项目过半时才暴露,一返工就是几天。这次拆的资源是一份已经划分好的肾脏结节、肿瘤图像识别数据集,3 分类(正常、结节、肿瘤),训练集 2800 张、验证集 800 张、测试集 400 张,按文件夹组织好,还带类别字典 json 和一个可视化脚本,可以直接喂给 yolov5 分类模式或任意分类网络。

拿到手之后我的第一感觉是:这省掉的不是下载时间,是整理数据的时间。很多开源数据集只给一堆散图和一个标注文件,你得自己写脚本按标签归文件夹、按比例切分,稍不注意就漏图或者路径写错。这份资源把 train、valid、test 三套目录都铺好了,图片按类别归类,路径即标签。对刚上手医学图像分类的开发者来说,可以直接跳过数据预处理这个最容易翻车的环节,直接进入训练和调参;对熟手来说,省掉的是重复造轮子的时间,把精力放在类别边界和模型评估上。

2. 拆开数据集看门道:目录、类别字典与 4000 张图的分布逻辑

2.1 train / valid / test 三份目录:yolov5 分类模式默认的数据组织方式

我第一次拿这份资源时,先做的不是训练,而是把目录结构和文件数量完整数了一遍。分类任务和检测任务不一样,检测要读 xml 或 txt 标注,分类任务最简单可靠的组织方式就是「一个文件夹一个类别」,文件夹名就是标签。yolov5 的 classify/train.py 正是按这个约定读数据的,它内部用 torchvision 的 ImageFolder 机制,指定根目录后会自动扫描子目录,把每个子目录名当作一个类别。

# 在项目根目录执行,查看数据集目录树 tree -L 3 data
data/ ├── train/ │ ├── normal/ # 正常肾脏影像 │ ├── stone/ # 肾结石 / 结节 │ └── tumor/ # 肿瘤 ├── valid/ # 有些版本写作 val,yolov5 两个写法都认 │ ├── normal/ │ ├── stone/ │ └── tumor/ └── test/ ├── normal/ ├── stone/ └── tumor/

数完目录我心里的预期是 train 2800、valid 800、test 400,实测后跟摘要对得上。这里的逻辑说明很简单:yolov5 对训练集和验证集目录名有约定,train 和 valid(或 val)是它扫描的固定关键词,test 目录在训练阶段不会被动用,只在推理或最终评估时使用。参数说明一下:如果目录名写的是 validation 而不是 valid 或 val,那个文件夹会被忽略,模型把验证集当成了不存在,这个问题我见过不止一次。

2.2 类别字典 json:从文件夹名到训练标签的映射关系

数据文件夹本身已经把标签写在路径里了,那 json 文件还有什么用?它解决的是「数字标签」和「语义标签」的对应关系。模型输出的 argmax 结果是个整数,比如 0、1、2,你总得知道 1 到底代表结节还是肿瘤,这就是类别字典存在的意义。

import json with open("data/classes.json", "r", encoding="utf-8") as f: class_dict = json.load(f) print(class_dict) # {'0': 'normal', '1': 'stone', '2': 'tumor'} # 具体键值顺序以你下载到的 json 为准,这里只是示例 # 反向映射:训练时模型输出 0/1/2,通过它转成可读类别 idx_to_name = {int(k): v for k, v in class_dict.items()} name_to_idx = {v: k for k, v in idx_to_name.items()}

逻辑说明:这段代码把 json 读进来后同时生成了两个方向的映射。训练阶段用 name_to_idx 把文件夹名转成目标张量,验证和推理阶段用 idx_to_name 把模型输出转成可读的类别文字。参数说明:json 里的键不一定是字符串形式的数字,有的资源直接写的 {"normal": 0},如果你拿到的是这种结构,把 int(k) 那行去掉就行,读取前先 print 一下原文件是标准动作。

有一个容易忽略的点:torchvision 的 ImageFolder 在扫描子目录时是按字典序生成 class_to_idx 的,和 json 里定义的顺序不一定一致。如果你的类别名是 normal、stone、tumor,按字母序恰好是 normal=0、stone=1、tumor=2,和 json 一致算运气好;如果类别名是中文或者首字母大小写混乱,就要以 ImageFolder.class_to_idx 的实际结果为准,拿 json 来校验,而不是拿来直接替换。

2.3 show 脚本:先可视化再训练,省掉一半冤枉路

资源里附带了一个 show 脚本,我的习惯是在任何训练开始前先跑它。医学图像分类的视觉检查和自然图像不一样,很多图片在普通人眼里长得几乎一样,但模型要学习的纹理特征就藏在这些细微差异里。脚本的作用是把每个类别随机抽几张图拼成网格展示出来,确认图片能正常读取、没有黑图或损坏文件,顺便对类别特征有个直观印象。

import matplotlib.pyplot as plt import os import random from PIL import Image data_root = "data/train" classes = [d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))] fig, axes = plt.subplots(len(classes), 4, figsize=(12, len(classes) * 3)) for row, cls in enumerate(classes): cls_dir = os.path.join(data_root, cls) imgs = os.listdir(cls_dir) sample = random.sample(imgs, min(4, len(imgs))) for col, img_name in enumerate(sample): img = Image.open(os.path.join(cls_dir, img_name)) axes[row, col].imshow(img) axes[row, col].set_title(f"{cls}") axes[row, col].axis("off") plt.tight_layout() plt.show()

逻辑说明:这段脚本遍历训练集下每一个类别文件夹,每个类别随机抽 4 张图拼成一行网格。random.sample 做的是无放回抽样,避免同一张图重复出现。参数说明:figsize 的第二项用 len(classes) * 3 动态计算高度,类别越多图越高;min(4, len(imgs)) 是为了防止某个类别的图片数量不足 4 张时报错,这属于防御性写法,资源里每类都远超 4 张,但写脚本时保留这个保护没坏处。

跑完 show 脚本我还习惯顺手统计一下每个子目录的具体数量,这一步在分类任务里的重要性超出很多人想象,具体原因下一章展开讲。如果 show 脚本跑出来个别图片是纯黑或者明显是损坏文件,趁早剔除,而不是等训练到一半 loss 异常再去排查。

3. 把图片喂进模型:yolov5 分类模式与通用 CNN 两条训练路线

3.1 yolov5 分类模式:命令行训练与关键参数说明

这份数据集直接兼容 yolov5 的分类模式,这是最快跑通 baseline 的路线。yolov5 仓库里 classify 目录下提供了独立的训练入口,不需要额外写数据加载器,目录结构满足约定后一行命令就能开训。

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # 使用官方分类模型做预训练权重,在肾脏数据集上微调 python classify/train.py \ --model yolov5s-cls.pt \ --data ../kidney_dataset/data \ --epochs 50 \ --img 224 \ --batch 64 \ --name kidney_cls_run1

这段命令的逻辑说明:--data 指向数据集根目录,yolov5 会自动寻找该目录下的 train 和 valid(或 val)子目录;--model 指定模型结构同时加载官方在 ImageNet 上预训练好的权重,迁移学习可以显著加速收敛;--img 224 是把输入图缩放到 224×224,这是分类模型在 ImageNet 上的标准输入尺寸,改成 320 或 640 可以提升小目标细节的辨识度,但显存占用和训练时间会同步上涨。

参数说明里有两个值得单独拎出来:--batch 64 依赖 GPU 显存,在 8GB 显存以下的卡上建议降到 32 或 16,否则会出现 CUDA out of memory;--name 是实验标签,yolov5 会把权重和日志存到 runs/train-cls/name 目录下,换一组超参就换一个 name,这是最简单的实验管理方式。训练完成后,结果目录里会自动生成 confusion matrix 和 results.png,那个混淆矩阵图就是后文要重点看的东西之一。

我一般会先跑 50 个 epoch 看 loss 曲线是否正常下降,再决定要不要加时间。医学图像数据集通常比自然图像更「难学」,因为类间差异小,50 个 epoch 往往还不够收敛,如果还有时间预算加到 100 epoch 效果会更好。yolov5 分类模式默认会做早停,patience 默认 100,所以不用太担心过拟合跑太久。

3.2 通用 CNN:用 torchvision 的 ImageFolder 手写一个数据加载器

如果不想绑定 yolov5,想自己控制模型结构或做更精细的数据增强,torchvision 的 ImageFolder 是最顺手的加载方案。它做的事情正是章节 2.2 里说的:扫描根目录下的子文件夹,把文件夹名映射成整数标签,返回 (图像张量, 标签) 配对的数据集。这与本数据集的「文件夹保存」方式是完全对应的,不需要额外写标签解析逻辑。

import torch import torch.nn as nn from torchvision import datasets, transforms, models # 训练集增强:随机水平翻转 + 颜色扰动,抑制医学图像过拟合 transform_train = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 验证集只做缩放和归一化,不做随机增强 transform_val = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) train_ds = datasets.ImageFolder("data/train", transform=transform_train) val_ds = datasets.ImageFolder("data/valid", transform=transform_val) test_ds = datasets.ImageFolder("data/test", transform=transform_val) train_loader = torch.utils.data.DataLoader( train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True) val_loader = torch.utils.data.DataLoader( val_ds, batch_size=32, shuffle=False, num_workers=4, pin_memory=True) # 用 ResNet18 做迁移学习,把最后一层全连接换成 3 分类 model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, 3) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)

逻辑说明:ImageFolder 返回的数据集对象自带 classes 和 class_to_idx 两个属性,classes 是文件夹名列表,class_to_idx 是名字到数字的映射,建议打印出来和类别字典 json 对一遍,防止字母序导致的标签错位。验证集的 transform 不能带随机增强,否则每次评估结果都有波动,无法横向比较。参数说明:num_workers 在 Windows 上如果报多线程错误就改成 0,pin_memory 只在 GPU 训练时有意义,CPU 训练可以删掉。这里的优化器只传了 model.fc.parameters(),意思是冻结主干网络只训练新加的分类头,先用这个配置跑通流程,确认 loss 能降下来再解冻全部层做精调,可以省一次从头冷启动的时间。

对比 yolov5 路线和通用 CNN 路线,前者胜在开箱即用,日志、可视化、早停都内置了;后者胜在可控性强,想换 loss、改 backbone、加自定义增强都方便。两份数据是同一套,切换路线的成本只在于重写数据加载器,而这一节代码可以直接复用到后续所有分类实验里。

3.3 loss 曲线的正常形态与异常形态:跑通之后看什么

不管用哪条路线,训练起来之后第一件事是盯 loss 曲线。一个正常的训练过程,train loss 在前 10 个 epoch 应该有明显的下降趋势,val loss 紧随其后下降但始终比 train loss 高一些。如果 val loss 先降后升而 train loss 还在降,那就是过拟合信号,需要增强正则化或提前停止。

数据显示这张表的对比场景,我在 yolov5 的训练日志里整理过几类常见形态,写在这里可以当一份速查表用:

loss 曲线形态可能原因处理方式
train/val loss 都不降学习率过大或过小、数据没对齐先调到 1e-4 试跑 10 epoch,确认数据路径正确
train loss 降、val loss 升过拟合加数据增强、增大 weight_decay、提前停止
两个 loss 都震荡剧烈batch 太小或学习率偏大增大 batch 或降低学习率
loss 为 NaN学习率过大、输入含 NaN 像素用小学习率重启,检查图片是否损坏

这里有个血泪经验:如果 train loss 在第一个 epoch 就降到接近 0,但不是收敛而是「假拟合」,多半是标签和数据对不上——比如所有图片被当成同一类。遇到这种诡异情况,第一时间打印一个 batch 的 data 和 label 出来人工核对,不要急着调参,这是深度学习图像识别项目里最常见的「黑匣子」陷阱。

4. 三类标签的四个暗坑:正常、结节、肿瘤容易翻车的地方

4.1 类别不平衡:先数数再训练,别等 loss 异常才回头

分类任务里第一个暗坑就是类别不平衡。摘要说训练集总量 2800 张,但没保证三个类别各 933 张,现实是医学图像数据集里正常样本往往比肿瘤样本多得多。如果某个类别只有 500 张而另一个有 1300 张,模型会倾向于把模糊样本预测为多数类,整体准确率可能不低,但少数类的召回率会很难看。

import os from collections import Counter def count_images(root): counter = Counter() for cls in os.listdir(root): cls_path = os.path.join(root, cls) if os.path.isdir(cls_path): counter[cls] = len(os.listdir(cls_path)) return counter for split in ["train", "valid", "test"]: print(split, dict(count_images(f"data/{split}")))

逻辑说明:这段代码统计每个集合下每个类别的图片数量,输出形如 {'normal': 1200, 'stone': 800, 'tumor': 800} 的字典。拿到数量后做两件事:一是确认每个类别数量都在可训练范围内,二是算一下最少数量的类别能不能支撑训练。参数说明:如果发现某个类别只有一两百张,有两种处理路径,一是对少数类做过采样(在 DataLoader 里设 sampler 按类别权重抽样),二是用类别权重加权损失函数,把 torch.nn.CrossEntropyLoss 的 weight 参数设为 [1.0, 1.2, 1.5] 这类与样本量成反比的值。

这是一个容易被忽略的地方:验证集和测试集的比例分布也必须看一眼。很多项目训练集是平衡的,但验证集因为随机划分凑巧不平衡,导致评估结果虚高或虚低。这份资源已经划分好了,我们省了切分的力气,但「数数」这一步不能省。

4.2 数据泄漏:同一来源的图片被拆进 train 和 valid

医学图像和自然图像有一个本质区别:同一病人的多次扫描切片之间高度相似。如果划分数据时只按文件名随机打乱,同一个案例的相邻切片很可能同时出现在训练集和验证集里,模型等于提前看过答案,验证集指标会虚高,上真实场景就露馅。

这张表的坑怎么识别?看文件名。这份资源的文件名自带.rf.加一串哈希的后缀,如果多张图片的哈希前缀相同,大概率来自同一次标注批次或同一个病例。检查方法很简单:对所有文件名提取哈希段,统计同一个哈希前缀下的图片是否被分到了不同集合。

import os, re from collections import defaultdict def check_leakage(data_root): seen = defaultdict(set) for split in ["train", "valid", "test"]: split_dir = os.path.join(data_root, split) for cls in os.listdir(split_dir): cls_dir = os.path.join(split_dir, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): m = re.search(r"\.rf\.([a-f0-9]+)", fname) if m: seen[m.group(1)].add(split) leak = {k: v for k, v in seen.items() if len(v) > 1} return leak leak = check_leakage("data") if leak: print(f"发现 {len(leak)} 组同源图片被分到多个集合,需重新划分") else: print("未发现跨集合同源图片")

遇到数据泄漏时不要慌,如果只是个别图片串集,直接删掉测试集里那些同源图片就行。如果比例很高,要考虑重新划分数据,划分依据是哈希前缀而不是单张图片文件名。这就是这份资源「按文件夹划分好」背后的另一层价值:划分逻辑是可见的,排查起来有迹可循。

4.3 类别字典与 ImageFolder 的索引顺序不一致

这个坑非常隐蔽,我翻车过一次。ImageFolder 在构建 class_to_idx 时按文件夹名的字典序排序,如果类别文件夹是 normal、stone、tumor,索引恰好是 0、1、2;但如果有人把文件夹命名成 normal、tumor、stone(比如按创建时间排的),那索引顺序就变成 normal=0、tumor=1、stone=2,而类别字典 json 里可能是 stone=1、tumor=2,两者对不上,模型训练时的监督信号就全乱了。

# 打印 ImageFolder 实际生成的标签映射,和类别字典 json 对比 print(train_ds.class_to_idx) # 例如 {'normal': 0, 'stone': 1, 'tumor': 2} # 读取 json 里的映射 with open("data/classes.json", "r", encoding="utf-8") as f: json_map = json.load(f) print(json_map) # 如果两者不一致,以 ImageFolder 为准,因为训练时用的就是它

排查方法很简单:把两行打印结果并排对照。理论上这份资源在打包时已经做过一致性校验,但下载文件的版本可能不同,json 也可能被编辑过,跑这一步只需要一分钟,值得做。如果发现不一致,解决方式是修改 json 字典的键顺序,而不是去改文件夹名,因为文件夹名改起来牵连太多。

4.4 随机划分的「看似随机,实则不随机」

最后一类坑出在复现上。有人训练同一个模型两次,结果差异很大,于是开始怀疑模型问题或者环境问题,但其实问题出在数据加载顺序。PyTorch 的 DataLoader 在 shuffle=True 时用全局随机数生成器洗牌,如果不固定种子,每次跑数据顺序都不同,结果自然不同。这在调参阶段是灾难:你不知道指标变好是参数生效了还是随机性带来的。

def seed_everything(seed=42): import random import numpy as np random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) seed_everything(42)

这段代码要在训练脚本的最顶部执行,并且在 DataLoader 创建之前。参数说明:PyTorch 的 DataLoader 还可以单独传入 generator = torch.Generator().manual_seed(42) 来控制洗牌随机性,这样即便全局种子被其他库污染,数据顺序依然可控。固定种子的价值在于让每一次实验可复现,这是深度学习项目从「玄学」走向「工程」的分水岭。

5. 图像识别算法绕不开的三件收尾验证:分类报告、混淆矩阵与误判图

5.1 分类报告:别盯着准确率,看每类的 precision / recall / f1

训练结束后,第一步跑测试集评估。很多人只看整体准确率,这对三分类任务来说远远不够,尤其在三个类别数量不完全均衡时,准确率会被多数类主导。sklearn 的 classification_report 一行代码输出每个类别的精确率、召回率和 F1,这三列合起来才能反映模型对每个类的真实辨识能力。

import torch from sklearn.metrics import classification_report, confusion_matrix device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.eval() y_true, y_pred = [], [] with torch.no_grad(): for images, labels in test_loader: images = images.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(predicted.cpu().numpy()) target_names = list(train_ds.classes) print(classification_report(y_true, y_pred, target_names=target_names))

逻辑说明:model.eval() 是关键,它关闭 dropout 和 batch norm 的训练行为,否则同一张图多次推理结果不同。torch.max(outputs, 1) 取的是每个样本在 3 个类别上的最大得分索引,也就是模型的预测类别。参数说明:target_names 直接取 ImageFolder 的 classes 属性,保证报告里的行顺序和训练时一致,不要手写类名列表,省得顺序写错。这个报告还会输出 macro avg 和 weighted avg,前者是每个类指标的算术平均,后者按样本数加权,两者差距大说明模型在少数类上表现偏弱。

5.2 混淆矩阵:看模型把哪两类搞混了

分类报告给的是数值汇总,混淆矩阵给的是错误分布的具体位置。一个 3×3 的矩阵,第 i 行第 j 列表示「真实类别 i 被预测成 j」的样本数,对角线是预测正确的数量。看混淆矩阵我一般先找非对角线的最大值,那个位置就是模型最容易混淆的类别对。

cm = confusion_matrix(y_true, y_pred) print("混淆矩阵(行=真实,列=预测)") print(cm) # 找出错误最集中的位置 import numpy as np np.fill_diagonal(cm, 0) # 把对角线置零后,剩下的最大值就是最严重的混淆 worst = np.unravel_index(np.argmax(cm), cm.shape) print(f"最严重混淆: 真实{target_names[worst[0]]} -> 预测{target_names[worst[1]]}, 数量 {cm[worst]}")

对肾脏三分类数据来说,最常见的混淆发生在结节和肿瘤之间,因为两者在影像上的特征边界确实模糊。如果混淆矩阵显示 normal 和 stone 之间错误很多,那可能是标注本身有问题,或者图片采集条件不一致,这时候去调模型是徒劳的,要回头查数据质量。这一节里做的所有评估动作,本质是回答一个问题:模型学到了什么,又在什么地方学错了。

5.3 误判图可视化:把错误样本打出来看,别再黑匣子调参

混淆矩阵告诉你「哪里错了」,误判图告诉你「错的是什么样」。把预测错误的样本整理成网格画出来,标题同时标注真实类别和预测类别以及置信度分数,这一步对医学图像分类尤其有价值。

import matplotlib.pyplot as plt misclassified = [] model.eval() with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) prob, pred = torch.max(torch.softmax(outputs, dim=1), 1) for i in range(len(labels)): if pred[i] != labels[i]: misclassified.append((images[i].cpu(), labels[i].item(), pred[i].item(), prob[i].item())) if len(misclassified) >= 12: break if len(misclassified) >= 12: break fig, axes = plt.subplots(3, 4, figsize=(12, 9)) for idx, (img, true_label, pred_label, conf) in enumerate(misclassified): ax = axes[idx // 4][idx % 4] img = img.permute(1, 2, 0).numpy() img = img * [0.229, 0.224, 0.225] + [0.485, 0.456, 0.406] # 反归一化 img = img.clip(0, 1) ax.imshow(img) ax.set_title(f"真:{target_names[true_label]}\n预测:{target_names[pred_label]} ({conf:.2f})") ax.axis("off") plt.tight_layout() plt.show()

这段代码里的反归一化是常见的坑:模型训练时做了 Normalize 操作,显示图片前必须用相同的 mean 和 std 还原,否则看到的是一张偏色的图,无法判断问题出在哪。参数说明:clip(0, 1) 是为了防止数值越界导致图像出现不自然的色块。每次只取 12 个误判样本,人眼能在一张图内快速浏览,多了反而不容易看出规律。

对误判图的观察要落到具体模式上,比如「所有被误判成 tumor 的 stone 图片,都有明显的强回声区域」或者「正常样本里有钙化点所以被判成结节」。当误判呈现出明显的视觉规律时,说明数据里有干扰特征,下一步不是调模型结构,而是考虑加预处理步骤屏蔽干扰,或者人工清理这些边缘样本。

5.4 softmax 置信度的二次排查:低置信度样本单独拎出来

最后一层 softmax 输出的置信度分布也值得看。医学图像分类的实用场景里,我们往往需要模型在拿不准的时候「说出来」,而不是硬猜一个结果。统计一下测试集里置信度低于 0.6 的样本数量,如果占比很高,说明模型对大量样本都缺乏把握,部署时要考虑设置置信度阈值,低于阈值的样本转人工审核。

这个动作的成本很低,但对落地价值很大。yolov5 分类模式在推理时会输出每个类别的置信度,直接加判断条件即可;自己写的模型就是上面代码里那个 prob 变量。二分类里有个概念叫阈值调整,三分类同样适用,只是要对每个类分别设定。

6. 三分类项目收尾技巧:把训练配置固化成一张复现清单

训练完成、评估做完,项目还没结束。医学图像分类模型如果要写进报告或者准备上线,最忌讳的是「重启之后不知道这次实验用了什么参数」。我的收尾习惯是把整次实验的可复现信息收敛成一个训练脚本或配置文件,下次任何人跑一遍都能得到同一结果。

# 训练复现脚本示例,每次实验执行前先读取并记录环境状态 export PYTHONHASHSEED=0 python classify/train.py \ --model yolov5s-cls.pt \ --data data \ --epochs 100 \ --img 224 \ --batch 64 \ --seed 42 \ --name kidney_final

参数说明里有个容易被忽略的:PYTHONHASHSEED 控制 Python 字符串哈希的随机种子,不固定它,即使在代码里调了 random.seed 和 torch.manual_seed,某些依赖哈希次序的逻辑(比如集合遍历顺序)仍可能导致结果不一致。这个环境变量要放在训练命令的同一行 export,而不是在 Python 代码里设置,因为它在解释器启动前就必须生效。

我还会在训练脚本同目录下留一个 data_report.txt,把每次实验的数据集分布、类别字典内容、训练和验证的准确率记录在同一份文件里。这样一个月后回来看,不用重新打开任何一个权重文件,就能快速回忆起当时的数据条件和模型表现,那份文档比模型权重本身更能说明问题。

分享一个从不例外的习惯:不管项目多急,最后一轮实验完成后,我强制自己用 10 分钟把「数据集版本 + 划分文件 + 训练命令 + 最终指标」四件事写进仓库 README 里。做过的医学图像分类项目里,被返工最多的从来不是模型性能不够,而是后来者不知道当时的实验是怎么配出来的。这类资源真正的价值是帮你把数据从「看懂」推进到「跑通」——如果这篇拆解帮你少走了弯路,希望帮到你。

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

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

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

立即咨询