fix: filter frozen pools by requested task_types subset

This commit is contained in:
2026-07-16 07:13:31 -04:00
parent e1f08dcd3b
commit ee69721ea3
2 changed files with 34 additions and 1 deletions
+7 -1
View File
@@ -320,9 +320,15 @@ def _filter_untrainable_types(
""" """
diag_by_type = Counter(u.task_type for u in build_units(pools.diagnosis)) diag_by_type = Counter(u.task_type for u in build_units(pools.diagnosis))
val_by_type = Counter(u.task_type for u in build_units(pools.validation)) val_by_type = Counter(u.task_type for u in build_units(pools.validation))
# 先按调用方显式 task_types 收窄候选集:未请求的题型(哪怕可训练)不得进入
# keep,否则冻结全局 pools 后 batch/diagnosis 会训练非请求题型,而 gate 只覆盖
# 请求题型 → 静默语义偏差(I-4)。task_types=None 表示全部题型皆为候选。
candidates = set(diag_by_type) | set(val_by_type)
if task_types is not None:
candidates &= set(task_types)
keep: set[str] = set() keep: set[str] = set()
dropped: list[tuple[str, str]] = [] dropped: list[tuple[str, str]] = []
for tt in set(diag_by_type) | set(val_by_type): for tt in candidates:
n_val = val_by_type.get(tt, 0) n_val = val_by_type.get(tt, 0)
n_units = diag_by_type.get(tt, 0) + n_val n_units = diag_by_type.get(tt, 0) + n_val
if n_val < eval_min_per_class: if n_val < eval_min_per_class:
+27
View File
@@ -490,6 +490,33 @@ class TestFilterUntrainableTypes:
assert "P" not in new_types assert "P" not in new_types
assert "A" in new_types assert "A" in new_types
def test_task_types_subset_drops_non_requested_trainable(self) -> None:
"""指定 task_types 子集时:非请求但可训练的题型也被剔除出 diag/val。
回归 I-4:pools 过滤此前只按可训练性 keep、不按 task_types 收窄,导致
冻结全局 pools 后 batch/diagnosis 会训练非请求题型(gate 只覆盖请求题型
→ 静默语义偏差)。此处 A、B 均可训,仅请求 A,B 必须被剔除。
"""
# 题型 Adiag=8 + val=2 → units=10、val=2,可训
diag = [_FakeQuestion(question_id=f"A-d{i}", task_type="A") for i in range(8)]
val = [_FakeQuestion(question_id=f"A-v{i}", task_type="A") for i in range(2)]
# 题型 Bdiag=8 + val=2 → units=10、val=2,同样可训(但未被请求)
diag += [_FakeQuestion(question_id=f"B-d{i}", task_type="B") for i in range(8)]
val += [_FakeQuestion(question_id=f"B-v{i}", task_type="B") for i in range(2)]
pools = _FakePools(diagnosis=diag, validation=val, test=[])
new_pools, new_types = _filter_untrainable_types(
pools,
task_types=["A"],
eval_min_per_class=2,
trainable_min_units=8,
)
# 非请求题型 B(可训)被剔除出 diag/val
assert {q.task_type for q in new_pools.diagnosis} == {"A"}
assert {q.task_type for q in new_pools.validation} == {"A"}
assert new_types == ["A"]
def test_all_filtered_raises(self) -> None: def test_all_filtered_raises(self) -> None:
"""所有题型都被剔除时 fail-fastraise RuntimeError 并列出剔除原因。""" """所有题型都被剔除时 fail-fastraise RuntimeError 并列出剔除原因。"""
diag = [_FakeQuestion(question_id="B-d0", task_type="B")] # val=0 < 2 diag = [_FakeQuestion(question_id="B-d0", task_type="B")] # val=0 < 2