1. 一个让病理医生头疼的问题四种病变特征为什么要识别定位一起做1.1 临床报告需要的不是有没有而是在哪里肝脏病理图像的自动分析最容易被外行误解的地方在于以为只要判断出这张切片里有没有纤维化、有没有脂肪变性就够了。但真正在临床上病理医生看完一张切片后要写出来的报告必须包含病变的分布范围、严重程度和累及区域。如果只知道有炎症却不知道炎症主要聚集在哪个汇管区也不知道炎症的密度高低那这份报告对临床治疗的价值就非常有限。我最早接手这个肝病病理图像项目时最大的困惑不是模型效果而是分类和检测这两件事到底该怎么做分割。后来跟有经验的病理医生反复沟通才意识到肝脏病理图像里的四种主要病变——肝气球样变、纤维化、炎症、脂肪变性——它们的形态差异很大有些是弥漫性的有些是局灶性的。弥漫性的脂肪变性也许用图像级分类就够了但纤维化往往表现为汇管区的条带状区域炎症则是灶性浸润气球样变的肝细胞在切片里呈散在或小片状分布。这四种特征如果只给一个阳性/阴性的全局标签就丢失了空间位置信息而如果做成逐像素分割又面临标注成本过高、模型过于复杂的问题。折中的方案就是做识别定位模型既要判断某个区域内是否存在这四类病变又要给出病变的位置用检测框或热力图表示。这也是项目标题里自动识别与定位的本意识别解决是什么定位解决在哪里。两者结合才能让输出结果真正接近病理报告的结构。1.2 四种特征的病理形态与标注难点要训练一个能同时识别并定位四种病变的模型首先得让参与项目的人对这四种病理特征有一个统一的认知标准。这里我把它们拆开来记录一下肝气球样变肝细胞体积明显增大胞浆疏松淡染看起来像吹起来的球。细胞核常常被挤到边缘严重时细胞膜都不完整。这种变化在常规HE切片上比较典型但早期气球样变和正常肝细胞的界限很模糊。纤维化汇管区周围或者窦周出现粉红色的条索状、网状胶原纤维。早期纤维化可能只有几条细丝后期则形成明显的间隔甚至假小叶。难点在于多少纤维算纤维化不同医生判读差异很大。炎症典型的表现为炎性细胞浸润最常见的是淋巴细胞也可见中性粒细胞。炎症通常围绕汇管区也可以分布在肝小叶内。炎症边界不清晰很难画一个严格的框。脂肪变性肝细胞内出现圆形空泡大泡性脂肪变性会把细胞核挤向一侧小泡性则有很多细小空泡。脂肪变性在整张切片里分布往往比较广泛但也可以呈区域性。标注这四类病变时最让人崩溃的是边界问题。纤维化和炎症都是弥漫性浸润的概念医生能用笔在数字切片上圈出一个大致范围但不同医生圈出来的边界可能差20%以上。气球样变和重度脂肪变性有时候会同时出现在同一个肝细胞内导致标注的类别互相重叠。项目组一开始采用每个区域只给一个主标签的标注规则结果在模型训练里出现了大量冲突样本。后来改成允许多标签重叠的标注方式即一个区域内可以同时存在炎症纤维化或脂肪变性气球样变才让标注质量明显提升。2. 数据准备能训练出一个可靠模型的真正前提2.1 标注协议先把医生之间的分歧消掉做病理AI项目数据标注的优先级永远高于模型调参。我们最初拿到的切片标注来自两位病理医生各有各的习惯一位习惯把整个汇管区周围的纤维都圈出来哪怕只有一点点另一位只圈明显增粗的纤维。结果模型训练出来后发现同一个测试集上两位医生的标注本身就存在约30%的不一致。这种不一致直接变成了模型的天花板——模型学到的其实是平均标注而不是真实病变。解决办法是建立一套标注协议把模糊规则变成可操作的判断标准。比如纤维化我们明确规定了只有在汇管区轮廓被胶原纤维增宽超过1倍以上或者出现纤维间隔时才标记为阳性。脂肪变性则规定了肝细胞内单个脂滴直径超过肝细胞核直径或脂滴数量超过5个才算阳性。这套协议由一位资深病理主任拍板然后让参与标注的医生先用20张切片做试标注逐张讨论分歧点直到kappa系数达到0.75以上才正式开始。这个过程听起来漫长但非常值得。后面训练出的模型在内部验证集上的表现基本能稳定在医生间一致性水平附近这说明模型的性能上限由标注一致性决定而不是由网络结构决定。2.2 从全切片到训练样本patch采样的逻辑整张数字病理切片WSI动辄几万乘几万像素不可能直接塞进深度学习模型。通常的做法是先切分成patch图像块然后对patch进行分类和检测。但怎么切直接影响模型效果。我们采用的是20倍放大倍率下、512x512像素的patch。为什么不用更大尺寸两个原因。第一GPU显存有限更大的patch意味着更小的batch size训练不稳定第二这四种病变的局部特征在512x512的视野内已经足够识别气球样变的细胞直径在20倍镜下也不过几十像素不需要太大的上下文。当然为了兼顾纤维化的结构性特征我们在采样时加入了128x128的context padding——即patch中心目标区域是512x512但输入图像实际是768x768多出来的边缘提供周围组织信息。这个细节对纤维化检测帮助很大因为纤维化的判定依赖于周围是否有汇管区结构。采样策略也很有讲究。如果直接在整张切片上随机采样会得到大量阴性patch——因为正常肝组织在切片里占比往往超过80%。我们采用两阶段采样先随机采一部分阴性patch作为底噪再根据标注框内的阳性区域做密集采样。正负样本比例控制在1:3左右而不是完全均衡因为过多重复采样阳性区域会导致模型过拟合到特定形态。2.3 类不平衡问题阴性patch占九成怎么办即使采用了上文的采样策略训练集里纤维化阳性patch仍然远少于阴性patch。尤其是气球样变这种病变在慢性肝炎切片里常常只是零星的几个细胞正样本稀缺到几乎需要手工一张张去找。我们试过几种常见的类不平衡处理手段加权采样、Focal Loss、在线困难样本挖掘。最有效的是两阶段训练。第一阶段先用一个预训练权重ImageNet上训过的ResNet50在粗略的正负样本上训练一个二分类器然后用这个二分类器跑一遍所有训练切片把所有预测概率在0.3到0.7之间的patch全部挑出来交给病理医生重新标注。这些分不清的patch往往就是模型最需要的困难样本。把它们补充进训练集后再做第二阶段微调四个类别的F1分数都提升了5个百分点以上。这个做法从原理上也很容易解释类不平衡不只是数量问题更是样本可分性问题。与其机械地调整损失函数权重不如主动找出模型想学却学不会的样本让标注的人给模型提供更清晰的监督信号。3. 网络结构设计一个模型同时输出分类和定位3.1 为什么没有选择端到端检测器最开始讨论方案时团队里有人提议直接用Faster R-CNN或YOLO这类目标检测器把四种病变当成四个类别来做框回归。这个思路看起来很直接但实际试下来效果并不好。原因在于这四种病变的形态和检测对象完全不同Fatty liver的脂肪变性区域可能覆盖整个视野面积占比极大而气球样变往往只有零散的几个细胞面积占比极小。目标检测器的锚框设计很难同时适应两个极端的尺度。另外病理图像里的定位并不一定需要一个严格的矩形框。炎症区域边界模糊纤维化呈条索状用矩形框去框会包含大量非病灶区域给后续定量分析引入噪声。因此我们转向了patch级多标签分类空间定位热力图的混合框架模型对每个patch输出一个四维向量表示存在四种病变的概率同时输出一个高分辨率的掩膜热力图指示每个像素属于哪种病变。这个设计本质上把有没有和在哪里两个任务分开但共享同一个特征提取主干训练效率更高。3.2 多分支头设计分类头回归头掩码头具体的网络结构分三层主干网络ResNet50充当特征提取器输出C4特征图分辨率是输入的1/16。这里没有用太深的ResNet101因为病理patch的纹理信息丰富但语义相对简单ResNet50已经够用过深的网络容易在少量标注数据下出现过拟合。分类头在C4特征图后接一个全局池化层再接两个全连接层输出4个logits每个logit对应一种病变的阳性概率。这里用sigmoid而不是softmax因为同一个patch可能同时存在脂肪变性和炎症多标签和多分类有本质区别。掩码头C4特征图上接一个特征金字塔FPN的上采样分支逐步恢复到1/4分辨率然后在每个像素上接一个4通道的sigmoid输出得到四张与输入对齐的病变概率热力图。损失函数是逐像素的BCE Loss但只在标注框内计算——这样既不需要逐像素的精确标注又能让模型学会病变集中的位置。这里有一个非常关键的细节掩码头和分类头不是完全独立的。我们在训练时让分类头产生的四维向量作为全局上下文重新加权掩码头的特征图。具体做法是把分类logits经过一个1x1卷积变成4维通道权重再乘到掩码头最后一层特征图的通道上。这样分类头判断这个patch有炎症之后掩码头会倾向于在炎症区域输出更高的概率。最终推理时如果分类头判定某类病变为阴性我们就直接把对应的掩膜清零避免肿瘤/病变区域外的噪声热力图干扰诊断。3.3 损失函数与训练细节总损失是三部分相加分类损失多标签BCE Loss。掩膜损失仅在阳性标注框内计算的BCE Loss框外不计算。一致性损失将掩膜下采样到patch级平均概率与分类头的logits求L2距离。一致性损失是我觉得最值得借鉴的设计。它强制让整体判断和局部判断互相校准如果分类头说这个patch有纤维化但掩膜解码器只在很小的区域产生低概率那么两者不一致损失会拉大。这个约束让模型在处理模糊样本时更加保守不会出现分类头高置信度、热力图稀稀拉拉的割裂情况。训练时采用AdamW优化器学习率初始为3e-5余弦退火衰减。由于病理图像数据量不大我们大概用了800张WSI切出来的20万patch我们使用ImageNet预训练权重但冻结了前两个block只微调后三个block和所有新增头。冻结前两个block是为了保留底层纹理特征避免病理图像中常见的染色差异把预训练权重破坏得太厉害。4. 评估与调优不能只看准确率4.1 指标体系的建立AUC、FROC、mAP怎么配合使用很多做图像分类项目的人习惯只看准确率Accuracy但在病理图像场景下准确率会产生严重误导。因为阴性patch占绝大多数一个把所有patch都预测为正常的模型准确率也能轻松超过80%。我们建立了一套组合指标AUCROC曲线下面积用于评估patch级分类的排序能力这个指标对类不平衡不敏感是最基础的判据。FROC曲线用于评估病灶定位能力。它的横坐标是平均每张切片的假阳性数纵坐标是灵敏度。病理AI领域常用在平均每张切片假阳性数为1、4、8时的灵敏度作为报告值。mAP平均精度均值用于评估检测框质量。我们这里虽然不用矩形框但会把连通域生成的检测区域和标注框计算IoU借用mAP衡量定位精度。实际项目中我们发现patch级AUC达到0.93以上的时候FROC的性能仍然可能很差。这说明模型能区分有病变和没病变但无法把病变位置准确圈出来。后来定位我们专门用了掩码头输出的热力图不再通过patch的滑动窗口来拼接定位结果FROC才明显改善。这也印证了混合框架的必要性分类任务和定位任务的优化目标不完全一致。4.2 热力图和检测框的可视化验证指标只是数字最终要用可视化结果说服病理医生。我们开发了一个简单的浏览工具把模型输出的四张热力图叠加到原始切片上用不同的颜色区分四类病变脂肪变性用黄色、纤维化用红色、炎症用绿色、气球样变用蓝色。第一次把热力图拿给病理医生看时效果非常震撼——模型画出的炎症区域和医生自己标注的区域高度重合尤其在汇管区周围。但问题也有模型喜欢在切片边缘、组织折叠处产生一些不连续的高亮斑点。后来分析发现这些区域往往伴有染色偏深或组织碎屑模型学到的不是炎症细胞而是深色纹理。我们通过在训练数据里加入大量边缘artifact 正常组织的负样本并模糊掉标注框外的热力图损失这个问题减轻了大半。可视化验证还有一个好处可以直观发现模型误解了病理概念。比如脂肪变性热力图在肝包膜区域经常亮起医生看一眼就说这是正常肝细胞不是脂肪变性。说明模型把包膜下的空泡样结构当成了脂滴。我们利用这个反馈找到了一批类似的误判patch补充标注后训练模型的泛化能力明显提升。4.3 从错误案例里学到的东西我把几类高频错误样本整理了出来最有代表性的是以下三类纤维化和炎症的混淆当汇管区同时存在纤维化和炎症浸润时二者在低倍镜下都呈深红色模型经常给出两个类别的高概率。但病理医生很清楚纤维化强调的是胶原纤维增多炎症强调的是细胞浸润。模型分不清是因为两者的纹理特征在patch尺度上太相似。我们为这个问题加了多尺度上下文融合把原始patch缩小一半作为额外输入让模型同时看到更大的组织布局。加了之后这个混淆显著减少。气球样变和脂肪变性的重叠这两种病变经常共存模型有时只输出其中一种。我们查看了数据发现标注阶段也存在类似分歧。后来在标注协议里明确规定同一个细胞如果脂滴大且胞浆疏松可以同时标两种。把标注对齐后模型的多标签输出立刻稳定多了。小病灶漏检散在的三五个气球样变细胞病理医生一眼就能看到但模型在低倍patch上经常漏掉。这其实是采样问题训练时含有小病灶的patch数量太少。我们专门做了小目标过采样用检测框中心附近的高倍patch反复训练小病灶的召回率从0.42提升到了0.68。5. 工程落地与避坑指南5.1 染色差异真实切片和训练集总是不一样的模型在训练集上表现良好但换了一家医院的切片后效果大幅下降。这是病理AI项目里最老的坑不同医院的染色试剂、切片厚度、扫描仪品牌都会改变图像的颜色分布。最开始我们用了简单的HE颜色增强把RGB图像随机扰动。但实践证明这种扰动还原不了真实的染色差异。后来我们采用了更稳妥的办法在推理之前先做染色归一化。具体做法是选一张参考切片作为标准把输入切片的每个patch的颜色直方图匹配到参考切片的统计分布上。这个方法的原理是HE染色图像的颜色分布通常集中在几个主要簇通过匹配每个簇的均值和标准差可以有效消除染色偏置而不会破坏组织结构信息。这一步听起来不起眼却能带来连续的性能提升。我们用某外部数据集的测试切片做过对比不做染色归一化时所有类别的AUC平均要掉0.03到0.05归一化之后基本恢复到了内部验证集水平。而且这个操作计算量不大尤其适合推理阶段强烈建议在真实病理数据上尝试。5.2 全切片推理的显存策略整张WSI切成了几千个patch推理时总不能把几千个patch一次性全放进GPU。我们最初用了一个简单的for循环每次只处理一个patch结果一张切片要跑7分钟完全没法用。后来改成了双缓冲队列动态batch的方式用一个CPU线程持续读取patch并进行归一化另一个GPU线程把归一化后的patch拼成尽可能大的batch去推理。由于patch之间没有任何依赖关系可以自由调整batch size。我们用一张24G显存的显卡在推理ResNet50这种骨干时batch size取64显存占用大约12G处理一张2万x2万像素的整张切片只需不到2分钟。还有一个小技巧是嵌套推理先在低倍率比如5倍上跑一次粗筛把所有可能包含病变的区域用分类头的概率筛选出来然后只在高倍patch上做精细定位。这样能跳过大约70%的正常区域推理速度大幅提升。代价是可能漏掉极小的病灶所以粗筛的阈值要设得保守一些宁可多选一些区域也不漏掉真实病变。5.3 给病理医生的界面设计不要输出一堆框工程化落地时很多技术人员容易犯一个错误把模型输出的所有检测框和热力图一股脑展示给医生。这种技术味十足的界面在临床上根本不受欢迎。病理医生最关心三件事这张切片哪种病变是主要问题病变范围占多大比例有没有我漏掉的可疑区域我们最后设计的辅助界面非常简单切片中间是原始的WSI右下角显示四种病变的火焰图缩略图点击任意一种病变就会在整张切片上叠加对应的概率热力图。医生可以手动框选任何区域界面立即显示框选区域里四种病变的概率。整个交互流程尽量贴近医生平时使用数字切片的方式不引入复杂的AI术语。界面还提供红点预警功能当模型检测到医生没有看过的、概率高于90%的病变区域时会在导航缩略图上打一个红色圆点提醒注意。这个界面上线后医生给出的反馈是只有需要的时候才看到AI平时不打扰。我认为这比把所有检测结果一次性铺开要好得多。6. 可复用的经验如果再让我做一次会怎么优化6.1 标注规范其实比模型更重要如果要给同样做病理图像分析的人一句忠告这句话我放在最前面先花70%的精力做标注协议再谈模型。很多人拿到切片后第一个想法就是先跑一个baseline模型但随着训练的推进你会不断返回去标注。来回返工的时间远远超过一开始制定规范的时间。我建议的流程是第一步找一位经验丰富的病理医生把四种病变的定义、边界、典型形态和非典型形态全部写成文档第二步随机抽50张切片让两位初级标注员分别标注计算一致性第三步把一致性低于0.6的类别单独拿出来重新定义规则第四步完成正式标注。这一套下来可能耗费两周但后续模型训练几乎不会因为标签冲突而崩溃。6.2 先做多标签分类再做定位如果资源有限不要一上来就做像素级的掩膜预测。先把patch级多标签分类做扎实用准确率、AUC、混淆矩阵这些指标排查出最恶性的问题。当分类模型在验证集上已经稳定时再在它的特征图上接一个解码头做热力图回归。这个顺序的优势在于分类任务能建立稳定的特征表示定位任务在稳定的特征上收敛得更快而且错误定位通常源于分类语义混淆早发现早解决。我们踩过相反路径的坑。一开始就同时优化分类头和掩码头结果两个任务的梯度相互干扰模型训练了80个epoch仍然不稳定。后来改为分阶段训练先用纯分类损失训练30个epoch冻结分类头后再联合训练掩码头。最终模型在定位精度和分类精度上都超过了端到端同时训练的结果。6.3 与病理医生协作的正确节奏最后想聊一下人机协作这件事。病理AI项目最忌惮的是技术人员自己关起门来调模型直到最终演示时才请医生来看。我个人的经验是从数据标注开始就让至少一名病理医生深度参与每周一次项目例会把模型最新输出的错误案例投到屏幕上让医生现场判断模型错在哪里。医生的解释往往非常具体比如这个区域组织破碎导致细胞核缺失你们模型把碎片当炎症了这种反馈比任何调参技巧都更有价值。模型并不能替代医生的诊断地位。在当前技术条件下它更像一个不知疲倦的初筛员提前把四种病变的大致区域标记出来让医生把注意力集中在真正可疑的位置。这种辅助角色定位既能让模型发挥价值也能让医生愿意信任它。我在实际项目中的体会是当医生开始主动向同事推荐这个工具时项目的工程化才算真正成功。
阅读完成 · 觉得有帮助?