chore: lint and format per-category pool strategy implementation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-37
@@ -254,9 +254,7 @@ def save_pools(
|
|||||||
ValueError: split_mode 为 "per_category" 但未提供 config。
|
ValueError: split_mode 为 "per_category" 但未提供 config。
|
||||||
"""
|
"""
|
||||||
if split_mode == "per_category" and config is None:
|
if split_mode == "per_category" and config is None:
|
||||||
raise ValueError(
|
raise ValueError("per_category 模式下 save_pools 必须提供 config 参数以写入元数据。")
|
||||||
"per_category 模式下 save_pools 必须提供 config 参数以写入元数据。"
|
|
||||||
)
|
|
||||||
|
|
||||||
data: dict = {
|
data: dict = {
|
||||||
"split_mode": split_mode,
|
"split_mode": split_mode,
|
||||||
@@ -285,9 +283,7 @@ def save_pools(
|
|||||||
data["categories"] = categories
|
data["categories"] = categories
|
||||||
data["seed"] = config.seed
|
data["seed"] = config.seed
|
||||||
data["train_ratio"] = config.train_ratio
|
data["train_ratio"] = config.train_ratio
|
||||||
data["test_source"] = (
|
data["test_source"] = str(config.test_questions_dir) if config.test_questions_dir else None
|
||||||
str(config.test_questions_dir) if config.test_questions_dir else None
|
|
||||||
)
|
|
||||||
|
|
||||||
path.write_text(
|
path.write_text(
|
||||||
json.dumps(data, ensure_ascii=False, indent=2),
|
json.dumps(data, ensure_ascii=False, indent=2),
|
||||||
@@ -402,23 +398,17 @@ def _validate_per_category_consistency(
|
|||||||
"""
|
"""
|
||||||
mismatches: list[str] = []
|
mismatches: list[str] = []
|
||||||
if frozen_data.get("seed") != pool_config.seed:
|
if frozen_data.get("seed") != pool_config.seed:
|
||||||
mismatches.append(
|
mismatches.append(f"seed: 冻结={frozen_data.get('seed')}, 当前={pool_config.seed}")
|
||||||
f"seed: 冻结={frozen_data.get('seed')}, 当前={pool_config.seed}"
|
|
||||||
)
|
|
||||||
if frozen_data.get("train_ratio") != pool_config.train_ratio:
|
if frozen_data.get("train_ratio") != pool_config.train_ratio:
|
||||||
mismatches.append(
|
mismatches.append(
|
||||||
f"train_ratio: 冻结={frozen_data.get('train_ratio')}, "
|
f"train_ratio: 冻结={frozen_data.get('train_ratio')}, 当前={pool_config.train_ratio}"
|
||||||
f"当前={pool_config.train_ratio}"
|
|
||||||
)
|
)
|
||||||
if frozen_data.get("baseline_run_id") != baseline_run_id:
|
if frozen_data.get("baseline_run_id") != baseline_run_id:
|
||||||
mismatches.append(
|
mismatches.append(
|
||||||
f"baseline_run_id: 冻结={frozen_data.get('baseline_run_id')}, "
|
f"baseline_run_id: 冻结={frozen_data.get('baseline_run_id')}, 当前={baseline_run_id}"
|
||||||
f"当前={baseline_run_id}"
|
|
||||||
)
|
)
|
||||||
if frozen_data.get("split_mode") != "per_category":
|
if frozen_data.get("split_mode") != "per_category":
|
||||||
mismatches.append(
|
mismatches.append(f"split_mode: 冻结={frozen_data.get('split_mode')}, 当前=per_category")
|
||||||
f"split_mode: 冻结={frozen_data.get('split_mode')}, 当前=per_category"
|
|
||||||
)
|
|
||||||
if mismatches:
|
if mismatches:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"per_category pools.json 与当前配置不一致:\n"
|
"per_category pools.json 与当前配置不一致:\n"
|
||||||
@@ -484,13 +474,13 @@ def build_or_load_pools(
|
|||||||
"FROM predictions WHERE run_id=?",
|
"FROM predictions WHERE run_id=?",
|
||||||
(baseline_run_id,),
|
(baseline_run_id,),
|
||||||
)
|
)
|
||||||
correctness = {
|
correctness = {r["question_id"]: r["prediction"] == r["answer"] for r in rows}
|
||||||
r["question_id"]: r["prediction"] == r["answer"]
|
|
||||||
for r in rows
|
|
||||||
}
|
|
||||||
|
|
||||||
new_cats = strategy.build_incremental(
|
new_cats = strategy.build_incremental(
|
||||||
sorted(new_types), questions, correctness, pool_config,
|
sorted(new_types),
|
||||||
|
questions,
|
||||||
|
correctness,
|
||||||
|
pool_config,
|
||||||
)
|
)
|
||||||
# 合并新类别到 categories
|
# 合并新类别到 categories
|
||||||
frozen_categories.update(new_cats)
|
frozen_categories.update(new_cats)
|
||||||
@@ -525,7 +515,8 @@ def build_or_load_pools(
|
|||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"per_category 增量追加 {} 个新类别: {}",
|
"per_category 增量追加 {} 个新类别: {}",
|
||||||
len(new_types), sorted(new_types),
|
len(new_types),
|
||||||
|
sorted(new_types),
|
||||||
)
|
)
|
||||||
|
|
||||||
return load_pools(pools_path)
|
return load_pools(pools_path)
|
||||||
@@ -596,7 +587,10 @@ class PerCategoryPoolStrategy:
|
|||||||
|
|
||||||
for task_type in sorted(groups.keys()):
|
for task_type in sorted(groups.keys()):
|
||||||
train, val = self._split_one_category(
|
train, val = self._split_one_category(
|
||||||
groups[task_type], correctness, config.train_ratio, rng,
|
groups[task_type],
|
||||||
|
correctness,
|
||||||
|
config.train_ratio,
|
||||||
|
rng,
|
||||||
)
|
)
|
||||||
all_train.extend(train)
|
all_train.extend(train)
|
||||||
all_val.extend(val)
|
all_val.extend(val)
|
||||||
@@ -609,9 +603,7 @@ class PerCategoryPoolStrategy:
|
|||||||
test = load_benchmark(config.test_questions_dir)
|
test = load_benchmark(config.test_questions_dir)
|
||||||
|
|
||||||
# Phase 5: 计算 baseline_val_accuracy
|
# Phase 5: 计算 baseline_val_accuracy
|
||||||
val_correct = sum(
|
val_correct = sum(1 for q in all_val if correctness.get(q.question_id, False))
|
||||||
1 for q in all_val if correctness.get(q.question_id, False)
|
|
||||||
)
|
|
||||||
baseline_val_accuracy = val_correct / len(all_val) if all_val else 0.0
|
baseline_val_accuracy = val_correct / len(all_val) if all_val else 0.0
|
||||||
|
|
||||||
return Pools(
|
return Pools(
|
||||||
@@ -654,9 +646,7 @@ class PerCategoryPoolStrategy:
|
|||||||
# 校验 correctness 完整性
|
# 校验 correctness 完整性
|
||||||
missing = [q.question_id for q in questions if q.question_id not in correctness]
|
missing = [q.question_id for q in questions if q.question_id not in correctness]
|
||||||
if missing:
|
if missing:
|
||||||
raise ValueError(
|
raise ValueError(f"correctness 缺失 {len(missing)} 题: {missing[:5]}")
|
||||||
f"correctness 缺失 {len(missing)} 题: {missing[:5]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
correct_qs = [q for q in questions if correctness[q.question_id]]
|
correct_qs = [q for q in questions if correctness[q.question_id]]
|
||||||
wrong_qs = [q for q in questions if not correctness[q.question_id]]
|
wrong_qs = [q for q in questions if not correctness[q.question_id]]
|
||||||
@@ -667,7 +657,9 @@ class PerCategoryPoolStrategy:
|
|||||||
label = "全部正确" if n_correct == n_total else "全部错误"
|
label = "全部正确" if n_correct == n_total else "全部错误"
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"类别 {} {} ({} 题),退化为非分层随机划分",
|
"类别 {} {} ({} 题),退化为非分层随机划分",
|
||||||
questions[0].task_type, label, n_total,
|
questions[0].task_type,
|
||||||
|
label,
|
||||||
|
n_total,
|
||||||
)
|
)
|
||||||
shuffled = list(questions)
|
shuffled = list(questions)
|
||||||
rng.shuffle(shuffled)
|
rng.shuffle(shuffled)
|
||||||
@@ -683,12 +675,8 @@ class PerCategoryPoolStrategy:
|
|||||||
train = correct_qs[:train_correct] + wrong_qs[:train_wrong]
|
train = correct_qs[:train_correct] + wrong_qs[:train_wrong]
|
||||||
val = correct_qs[train_correct:] + wrong_qs[train_wrong:]
|
val = correct_qs[train_correct:] + wrong_qs[train_wrong:]
|
||||||
|
|
||||||
assert len(train) == n_train, (
|
assert len(train) == n_train, f"train 数量不匹配: {len(train)} != {n_train}"
|
||||||
f"train 数量不匹配: {len(train)} != {n_train}"
|
assert len(val) == n_val, f"val 数量不匹配: {len(val)} != {n_val}"
|
||||||
)
|
|
||||||
assert len(val) == n_val, (
|
|
||||||
f"val 数量不匹配: {len(val)} != {n_val}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return train, val
|
return train, val
|
||||||
|
|
||||||
@@ -721,7 +709,10 @@ class PerCategoryPoolStrategy:
|
|||||||
|
|
||||||
for task_type in sorted(groups.keys()):
|
for task_type in sorted(groups.keys()):
|
||||||
train, val = self._split_one_category(
|
train, val = self._split_one_category(
|
||||||
groups[task_type], correctness, config.train_ratio, rng,
|
groups[task_type],
|
||||||
|
correctness,
|
||||||
|
config.train_ratio,
|
||||||
|
rng,
|
||||||
)
|
)
|
||||||
result[task_type] = {
|
result[task_type] = {
|
||||||
"train": [q.question_id for q in train],
|
"train": [q.question_id for q in train],
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from pathlib import Path
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from app.harness.pools import (
|
from app.harness.pools import (
|
||||||
PerCategoryPoolStrategy,
|
PerCategoryPoolStrategy,
|
||||||
@@ -22,10 +23,14 @@ from core.types import GeneratedQuestion, PoolConfig
|
|||||||
def _make_question(qid: str, task_type: str) -> GeneratedQuestion:
|
def _make_question(qid: str, task_type: str) -> GeneratedQuestion:
|
||||||
"""构造测试用 GeneratedQuestion。"""
|
"""构造测试用 GeneratedQuestion。"""
|
||||||
return GeneratedQuestion(
|
return GeneratedQuestion(
|
||||||
question_id=qid, video_id="v1", task_type=task_type,
|
question_id=qid,
|
||||||
|
video_id="v1",
|
||||||
|
task_type=task_type,
|
||||||
question=f"Q {qid}?",
|
question=f"Q {qid}?",
|
||||||
options=("A. a", "B. b", "C. c", "D. d"),
|
options=("A. a", "B. b", "C. c", "D. d"),
|
||||||
answer="A", source_nodes=("n1",), difficulty="medium",
|
answer="A",
|
||||||
|
source_nodes=("n1",),
|
||||||
|
difficulty="medium",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -35,10 +40,18 @@ class TestPerCategoryE2E:
|
|||||||
def test_full_flow(self, tmp_path: Path) -> None:
|
def test_full_flow(self, tmp_path: Path) -> None:
|
||||||
"""完整流程:12 类各 30 题 → 策略构建 → 冻结 → 加载 → 三池校验。"""
|
"""完整流程:12 类各 30 题 → 策略构建 → 冻结 → 加载 → 三池校验。"""
|
||||||
task_types = [
|
task_types = [
|
||||||
"Action Prediction", "Action Reasoning", "Action Recognition",
|
"Action Prediction",
|
||||||
"Action Sequence", "Causal Reasoning", "Event Reasoning",
|
"Action Reasoning",
|
||||||
"Object Interaction", "Object Reasoning", "Object Recognition",
|
"Action Recognition",
|
||||||
"Scene Understanding", "Spatial Reasoning", "Temporal Reasoning",
|
"Action Sequence",
|
||||||
|
"Causal Reasoning",
|
||||||
|
"Event Reasoning",
|
||||||
|
"Object Interaction",
|
||||||
|
"Object Reasoning",
|
||||||
|
"Object Recognition",
|
||||||
|
"Scene Understanding",
|
||||||
|
"Spatial Reasoning",
|
||||||
|
"Temporal Reasoning",
|
||||||
]
|
]
|
||||||
questions = []
|
questions = []
|
||||||
for tt in task_types:
|
for tt in task_types:
|
||||||
@@ -52,11 +65,17 @@ class TestPerCategoryE2E:
|
|||||||
correctness[q.question_id] = idx < 18
|
correctness[q.question_id] = idx < 18
|
||||||
|
|
||||||
config = PoolConfig(
|
config = PoolConfig(
|
||||||
task_types=None, seed=42, baseline_run_id="baseline_v2",
|
task_types=None,
|
||||||
diag_size=0, diag_correct_ratio=0.0,
|
seed=42,
|
||||||
val_size=0, val_correct_ratio=0.0,
|
baseline_run_id="baseline_v2",
|
||||||
test_size=0, eval_min_per_class=0,
|
diag_size=0,
|
||||||
train_ratio=20 / 30, test_questions_dir=None,
|
diag_correct_ratio=0.0,
|
||||||
|
val_size=0,
|
||||||
|
val_correct_ratio=0.0,
|
||||||
|
test_size=0,
|
||||||
|
eval_min_per_class=0,
|
||||||
|
train_ratio=20 / 30,
|
||||||
|
test_questions_dir=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
strategy = PerCategoryPoolStrategy()
|
strategy = PerCategoryPoolStrategy()
|
||||||
@@ -107,11 +126,17 @@ class TestPerCategoryE2E:
|
|||||||
correctness = {q.question_id: (i < 20) for i, q in enumerate(questions)}
|
correctness = {q.question_id: (i < 20) for i, q in enumerate(questions)}
|
||||||
|
|
||||||
config = PoolConfig(
|
config = PoolConfig(
|
||||||
task_types=("Object Recognition",), seed=42, baseline_run_id="bl",
|
task_types=("Object Recognition",),
|
||||||
diag_size=0, diag_correct_ratio=0.0,
|
seed=42,
|
||||||
val_size=0, val_correct_ratio=0.0,
|
baseline_run_id="bl",
|
||||||
test_size=0, eval_min_per_class=0,
|
diag_size=0,
|
||||||
train_ratio=20 / 30, test_questions_dir=None,
|
diag_correct_ratio=0.0,
|
||||||
|
val_size=0,
|
||||||
|
val_correct_ratio=0.0,
|
||||||
|
test_size=0,
|
||||||
|
eval_min_per_class=0,
|
||||||
|
train_ratio=20 / 30,
|
||||||
|
test_questions_dir=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
strategy = PerCategoryPoolStrategy()
|
strategy = PerCategoryPoolStrategy()
|
||||||
|
|||||||
Reference in New Issue
Block a user