这个挑战的 Metric A 不是看单个答案对不对,而是看你输出的推理图(一条条 问题 → 下一个问题 的边)和 GT 的图有多接近:
Metric A = 0.05·BPV + 0.30·Edge-F1 + 0.25·MESS + 0.40·FinalReport
pred_used = {q: pred[q] for q in pred if q in asked},
其中 asked 来自 ground truth,还把模型没答的题用 GT 答案补上。这是“知道答案该问哪些题”的上限分,真实可部署容器做不到。assemble_edges 是对全部 85 个答案求并集加边——不是从根节点遍历。
于是图里塞进一堆 GT 根本没走到的“孤儿分支”。GT 的 CoT 是一棵从 What is the organ? / What is the procedure? 出发的树:每一步用
当前问题 + 模型给的答案 去查路由规则(routing_smart.json,2375 条),得到下一个问题,再继续走,直到“最终病理报告”。
我写的 assemble_edges_traverse 就是真正地从根 BFS 遍历这棵树,只保留“从根能走到”的边:
def assemble_edges_traverse(organ, ans):
qa = {norm(q): norm(a) for q, a in ans.items()} # 模型预测的 85 个答案
edges, seen = set(), set()
frontier = [organ_q, procedure_q] # 树根
while frontier:
q = frontier.pop()
if q in seen: continue
seen.add(q)
nqs = routing[(organ, q, qa[q])] # 用“答案”决定走哪条边
for nq in nqs:
edges.add((q, nq))
if nq answerable: frontier.append(nq) # 只跟进能走到的节点
return edges
另外两个让遍历稳健的细节:
#1 → #2 → … → #N → 最终报告 不靠答案文本路由(容易断),而是按数量 N 结构化连接,保证一定能走到“最终报告”节点。(器官, 问题, 预测答案) 在训练里没见过(预测答案罕见/错误)时,用该 (器官, 问题) 的多数下一题兜底,避免整条路径在中途断掉。#2 / #3 / #4 diagnosis 这三个头没有“空”这个类别——它们永远会输出一个诊断。
于是“诊断数量”被算成永远 = 4。而 94% 的 case 其实只有 1 个诊断。结果报告里塞满假诊断(如莫名其妙的
“Urothelial carcinoma in situ”“Microcalcification”),Report 和 Edge 双双崩掉。
我试过的方法:
| 方法 | 效果 |
|---|---|
| 按 (器官,#1诊断) 先验取多数 N | ≈ 永远 N=1(94% 基率),0.842 |
| 用 #2 头的 softmax 置信度判断 | 不行,top-8 精度仅 0.38(无空类,置信度不分离) |
| (器官,#1,#2) 共现先验 / 只在报告里加 #2 | 都是负收益(假阳性 #2 太多) |
| 用 “Is there any additional finding present?” 这个头 | 命中! |
Is there any additional finding present? = yes →
P(N≥2) = 0.99;答 no / 未问 → P(N≥2) ≈ 0.00–0.03。
这是一个 MIL 本来就在预测的头,几乎完美地指示“有没有第二个诊断”。
if used["Is there any additional finding present?"] startswith "yes":
N = 2 (或按 (#2,#3) 共现扩到 3)
else:
N = 1
用它之后:诊断数量 N 准确率 84% → 90%,Metric A 0.850 → 0.8649。
cap=1024 的包里有 30 个现成 MIL 权重(2 种聚合器 × 3 种放大率 × 5 seed)。我做的是离线扫不同组合(先把所有模型的 softmax 缓存,任意子集即时平均),挑无-GT 路径上最好的:
transmil_1020 + abmil_1020(20x+10x 拼接 bag),abmil 权重略高;测试时增强(多 seed patch 平均)实测无收益(0.8648),说明答案已经稳定。
| 步骤 | 方法 | Metric A (50 张) |
|---|---|---|
| 起点 | 仓库原版(并集 assemble_edges,无 GT) | 0.50 |
| + 树遍历 | assemble_edges_traverse + 诊断截断 | 0.68 |
| + 结构化诊断链 | 保证走到最终报告 | 0.72 → 0.80* |
| + 兜底 fallback | 路径不再中途断 | 0.82 |
| + 集成选择 | transmil_1020 + abmil_1020 加权 + 精简 fallback | 0.85 |
| + AF 头预测 N | “additional finding”=yes → N≥2 | 0.8649 |
| (参考)引擎上限 | 喂 GT 答案 | 0.957 |
* 0.72 是 10 张小样本、0.80 是 50 张全量,组织识别从 1 个错误恢复后跳升。
Metric A = 0.8649 (BPV 0.54 / Edge-F1 0.895 / MESS 0.881 / Report 0.873)
集成 : transmil_1020 ×5 (权重2) + abmil_1020 ×5 (权重3) [cap=1024, 多尺度]
Fallback: 仅 (器官,问题) 级多数兜底 (REG_NO_GLOBAL_FB=1)
N 预测 : "Is there any additional finding present?" == yes → N≥2 (REG_N_MODE=af)
CoT 重建: assemble_edges_traverse(根遍历 + 结构化诊断链 + 训练派生兜底)
代码 : src/reg/cot.py (assemble_edges_traverse), recon.py (N预测+组装), score_dir.py (官方评分)