我使用MONAI(医学人工智能开放网络)在一个公开的腹部超声数据集上构建了一个结构清晰、设计合理的深度学习流程。
该流程包括以下内容:
-
按主题分类的训练集/验证集划分方式
-
稳健的数据预处理步骤
-
正确解码的分割掩膜处理
-
合理设计的损失函数
-
一致的评价方法
然而,模型仍然难以取得良好的学习效果。
有趣的是,并不是模型的性能不佳,而是诊断过程中出现的问题:一系列简单的检查最终揭示了问题出在数据集本身,而非模型本身。
这些检查方法的应用范围远不止医学影像领域,几乎适用于所有机器学习项目。
如果你是机器学习领域的初学者,这个经验值得你在每个项目中牢记:在调整模型之前,首先要了解你的数据。
我原本打算编写一份关于医学图像分割的技术教程,但最终却领悟到了一个更为重要的道理:无论工程实现得多么精细,如果数据集本身无法支持相应的任务,模型也无法取得良好的学习效果。
读完这篇文章后,你将了解以下内容:
-
如何判断某个数据集是否适合用于你的项目
-
为什么“模型学不会”往往意味着数据存在问题
-
在指责数据之前,该如何先排除工程实现上的错误
-
哪些实用的诊断方法可以在几分钟内完成测试
-
为什么合成训练数据在实际应用中常常表现不佳
-
何时应该停止调整模型并放弃使用某个数据集
这并不是一篇针对初学者的深度学习入门教程,它假设读者已经熟悉UNet架构和训练流程等概念。不过,关于数据质量的相关内容确实适用于许多机器学习项目。
我们将涵盖的内容:
数据集
我使用了Kaggle上提供的US Simulation & Segmentation数据集,这个数据集包含了带有器官分割标签的腹部超声图像。
该数据集包含以下内容:
-
926张合成超声图像——这些图像是通过CT扫描结果利用射线投射模拟技术生成的,且都附有完整的器官标注信息
-
617张真实超声图像——这些图像来自实际的超声扫描设备
-
8个器官的标签信息——包括肝脏、肾脏、胆囊、胰腺、脾脏、骨骼、血管以及肾上腺
乍一看,这个数据集似乎非常理想:
-
拥有数千张图像
-
涵盖多种器官类别
-
同时包含合成超声数据和真实超声数据
但问题在于,这些数据是否真的适合用于完成相应的任务呢?这还有待验证。
步骤1:在指责数据之前,先检查数据处理流程是否存在问题
基本原则是:在批评数据之前,必须先检查整个数据处理流程是否正常。如果模型因为代码错误而失败,其表现与模型因数据质量问题而失败时是完全相同的。因此,数据处理流程的可靠性至关重要。
按受试者分组进行数据划分
在医学影像分析中,一个常见的错误就是将图像随机划分为训练集和测试集。
这种做法存在问题,因为许多图像实际上来自同一位患者。这些图像在解剖结构、扫描设备设置以及噪声特征等方面都是相同的。
如果同一位患者的图像同时被分到了训练集和测试集中,模型就可能会“记住”这些特定于该患者的特征。这样一来,模型的测试分数看起来就会很高,但实际上当遇到其他真正未知的患者时,模型可能仍然会失败。
这种现象被称为受试者信息泄露。
正确的解决办法是按照受试者的身份来划分数据,而不是按照图像本身:
from sklearn.model_selection import GroupShuffleSplit
def assign_splits(manifest, val_fraction=0.15, seed=42):
train_data = manifest[manifest["orig_split"] == "train"]
groups = train_data["subject_id"].values
gss = GroupShuffleSplit(n_splits=1, test_size=value Fraction, random_state=seed)
train_idx, val_idx = next(gss.split(X=train_data, y=None, groups=groups))
train_subjects = set(train_data.iloc[train_idx]["subject_id"].unique())
val_subjects = set(train_data.iloc[val_idx]["subject_id"].unique())
# 如果出现信息泄露,会立即产生错误提示
assert train_subjects.isdisjoint(val_subjects), "检测到受试者信息泄露!"
return train_subjects, val_subjects
这种检查机制非常重要。如果数据划分逻辑出现了问题,系统会立即发出警报,而不会产生误导性的评估结果。
正确解码掩膜信息
该数据集使用颜色编码的方式来存储标签信息。不同的器官对应不同的RGB颜色值。
在训练过程中,需要将这些颜色转换为整数形式的类别标签。
一种简单的实现方法是通过精确的颜色匹配来完成这一转换,但调整图像大小的操作可能会略微改变掩码边界处的颜色值。
一种更为可靠的方法是将每个像素映射到其最近的调色板颜色上:
import numpy as np
PALETTE = np.array([
[0, 0, 0],
[100, 0, 100],
[255, 255, 255],
[0, 255, 0],
[255, 255, 0],
[0, 0, 255],
[255, 0, 0],
[255, 0, 255],
[0, 255, 255],
], dtype=np.int32)
def decode_mask(mask_rgb):
h, w = maskrgb.shape[:2]
flat = mask rgb.reshape(-1, 3).astype(np.int32)
d2 = (
(flat[:, None, :] - PALETTE[None, :, :]) ** 2
).sum(-1)
classes = d2.argmin(axis=1).astype(np.uint8)
return classes.reshape(h, w)
在训练之前,最好先查看一些解码后的掩码与原始图像之间的对比情况。这样就能及时发现诸如调色板设置错误、RGB/BGR通道顺序颠倒,或者因图像大小调整而导致的标签错误等问题。
这类问题很少会引发明显的错误提示,相反,它们只会导致模型的训练效果变差。而“使用错误的标签进行训练”与“模型无法理解输入数据”所带来的结果其实是完全相同的。
提前验证掩码的正确性,就能消除这种不确定性。
损失函数的设计与类别权重设置
在训练过程中,我使用了标准的MONAI分割损失函数。我们的目标并不是为了极力提升模型性能,而是要建立一个稳定且可靠的基准。
下图所示的训练曲线表明,模型的优化过程是正常的:损失值持续下降,验证集上的Dice分数也趋于稳定,没有出现波动。这些数据排除了优化不稳定是导致最终训练效果不佳的主要原因这一可能性。

在损失函数的设计及类别权重设置方面,我特意做出了以下三个选择:
-
Dice分数与交叉熵损失相结合:交叉熵损失有助于模型在训练初期保持稳定的学习进度,而Dice分数则能直接反映区域重叠情况的优劣。这两种损失函数相互配合,能够达到平衡效果。
-
对于二分类分割任务,在针对单个器官进行分割的任务中,背景像素可能占图像总像素的85%到90%。如果将这些背景像素纳入损失计算中,就会掩盖真正需要关注的器官部分的信息,因此最好将它们排除在外。include_background=False: -
对于多类别分割任务,需要对不同类别进行权重设置:由于不同器官的大小差异很大,如果不对损失函数进行加权处理,模型很可能会忽略那些规模较小的、出现频率较低的类别,从而导致整体分类效果不佳。通过对稀有类别的错误进行更严格的惩罚,就可以有效弥补这一缺陷。
步骤2:模型仍存在问题
第一个实验关注的是肝脏分割——这是该数据集中最简单的单一器官识别任务。
| 测试集 | 肝脏分割结果 |
|---|---|
| 合成测试集 | 约0.68 |
| 真实超声测试集 | 约0.48 |
“骰子得分”的范围是从0(完全不重叠)到1(完美重叠)。
从质量上看,模型的预测结果大致能够识别出肝脏区域,但在边界处理以及不同扫描图像之间的一致性方面存在问题。
特别值得注意的是:
-
即使是在合成数据上,模型也表现不佳
-
在真实超声图像上的性能更是大幅下降
此时有两种可能的解释:
-
模型本身存在缺陷
-
数据集本身的局限性限制了模型的性能
由于相关的工程设计已经经过了仔细验证,因此第二种可能性值得我们认真探讨。
也正是从这里开始,真正的挑战出现了。
步骤3:深入分析数据集
与其无休止地调整模型参数,不如仔细研究数据集本身。
通过三项简单的检查,我们发现了真正的问题所在。这些检查都不需要重新训练模型或进行昂贵的实验。
诊断分析1:数据集实际上包含什么?
第一步就是绘制数据集的构成结构图。

-
926张带有标签的合成图像——这些构成了主要的训练数据
-
只有60张带有标签的真实图像——这仅占数据集总量的不到4%
-
557张未标记的真实图像——虽然有真实数据,但由于没有标签,这些数据无法用于监督训练
这一发现立即改变了我们对这个数据集的理解。
尽管该数据集包含大量的真实超声扫描图像,但几乎所有的带标签训练数据都是合成图像。
模型实际上是在合成超声图像的基础上进行训练的,而人们期望它能够泛化到真实超声图像上。
从一开始,这就构成了一个难以解决的迁移学习问题。
问题的关键在于:真实图像大多没有标签,因此监督训练几乎无法利用这些真实数据来进行学习。
经验教训:在开始训练任何模型之前,首先要绘制数据集的构成结构图。仅凭图片数量来了解数据集的情况是具有误导性的。“1,500张图像”这个数字听起来似乎很多,但当你发现其中只有极少一部分是目标领域的带标签样本时,你就会明白实际情况了。
诊断测试2:合成图像与真实图像看起来是否相似?
下一个问题是:这些合成图像与真实的超声图像在视觉分布上是否相同。
通过绘制强度直方图可以清楚地看到两者之间存在差异。

-
合成图像的强度值主要集中在较暗的范围。
-
而真实超声图像的强度分布则更为广泛。
虽然合成模拟器能够较为准确地再现解剖结构的几何形态,但它无法复制真实超声图像所具有的纹理特征和噪声现象:
-
斑点图案
-
强度衰减现象
-
不同扫描设备所产生的特殊干扰信号
这就是典型的“合成数据与真实数据之间的差异”。
该模型所学习到的特征是针对合成图像进行优化的,因此在实际应用中会遇到完全不同的数据分布情况。因此,训练效果不佳也就不足为奇了。
经验教训:每当训练数据和测试数据属于不同领域时——比如合成数据与真实数据、不同的扫描设备产生的数据,或者不同的医院收集的数据——都应该直接测量这两组数据之间的分布差异。简单的直方图对比就能在短时间内发现其中存在的问题。
诊断测试3:添加真实数据能否弥补这一差距?
下一个显而易见的想法是:在训练过程中加入一些带有标签的真实数据不就行了吗?
但在实施这一方法之前,首先需要确认到底有多少名患者的数据真正被标记过了。
带有标签的真实图像数量:60张
具有标签的不同患者人数:4人
每位患者的样本数量:
患者h:26张样本
患者a:16张样本
患者g:10张样本
患者b:8张样本
实际上只有4名患者的数据被标记过了。
这个结果彻底改变了整个情况。
进行合理的医学影像评估时,必须按照患者群体来划分训练数据与测试数据。但既然只有4名患者的数据被标记,那么任何评估结果都会在统计上变得不可靠。
如果只用2到3名患者进行训练,然后用1到2名患者进行测试,那么所得到的评估指标将会具有很大的不确定性,因为这些指标会受到哪些患者被留作测试样本这一因素的极大影响。
在这种情况下,这个数据集根本无法支持可靠的真实世界评估工作。
经验教训:在医学影像领域,真正重要的应该是患者的数量,而不是图像的数量。一个数据集的实际规模应该由独立患者的人数来决定,而不是文件的数量。
步骤4:知道何时停止
在这一点上,继续进行额外的调整已经没有意义了。
造成瓶颈的并不是架构、优化器或学习率,而是数据集本身。
整个数据处理流程仍然具有价值且可以重复使用,但这个特定的数据集无法可靠地支持预期的分割任务。
区分这两者非常重要:有时候问题虽然困难但可以解决,而有时候数据本身就无法支撑你想要得出的结论。
学会识别这种区别是机器学习中一项重要的技能。
实用的数据集评估检查清单
在投入数周时间进行模型开发之前,对任何数据集都应该进行以下检查:
-
分析数据集的构成——包括标记样本与未标记样本的比例、类别分布以及数据来源的分布情况。
-
统计样本数量,而非图像数量——独立的患者样本比图像的数量更为重要。
-
检查类别平衡性——如果没有采取加权或抽样策略,稀有类别往往会被忽略。
-
对比训练数据与实际应用数据之间的分布情况——尤其是对于跨领域问题而言,这一点尤为重要。
-
直观地验证标签的准确性——这样可以及早发现预处理或标注过程中出现的错误。
-
查找已发布的基准数据——如果某数据集的性能评估结果较低,可能说明该数据集存在局限性。
这些检查只需花费几分钟时间,却能避免数周不必要的调整工作。
我接下来会尝试什么
要想改善模型效果,更有可能需要质量更好的数据,而不是规模更大的模型。我会优先采取以下措施:
-
收集更多来自不同患者的、带有标签的真实超声扫描数据。
-
提高标注的一致性。
-
利用半监督学习方法来处理那些未标记的真实图像数据。
-
在合成数据和真实超声数据之间进行领域适配。
所有这些措施都是针对数据质量与多样性这一根本问题而言的。
更重要的启示
在机器学习领域,我们很容易将大部分注意力集中在架构、超参数、优化技巧以及新型模型上。
但实际上,数据集才是决定模型性能上限的关键因素。
用质量较差的数据训练出的复杂模型往往难以取得理想效果,而使用高质量数据训练出的简单模型却可能表现出惊人的性能。
这个项目给我的真正启示就是:最有价值的技能并不是构建数据处理流程,而是弄清楚模型为何无法成功运行,并且愿意相信数据本身所传达的信息。
这些检查步骤——分析数据集构成、统计样本数量、对比数据分布、排除技术故障以及判断何时停止实验——几乎适用于任何机器学习项目。
在许多情况下,对数据有更准确的了解远比拥有一个更复杂的模型更为重要。