feat: add video-split config knobs and reproducible script
This commit is contained in:
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# 结果驱动视频级切分 —— capstone 可复现实验(离线管线最后一步)
|
||||
# ----------------------------------------------------------------------------
|
||||
# 把整条离线管线串成零参可复现实验:诊断信号 → 贪心联合约束选择 → 冻结 pools.json。
|
||||
# 全部旋钮写死在 config/video_split.yaml,零参即可复现(GPU 卡号除外)。
|
||||
#
|
||||
# 用法:
|
||||
# CUDA_VISIBLE_DEVICES=0 bash scripts/build_video_split.sh
|
||||
#
|
||||
# ============================================================================
|
||||
# 两阶段流程
|
||||
# ----------------------------------------------------------------------------
|
||||
# ① 离线诊断(Task 4,run_baseline_diagnosis)——【LLM 重活,需单独跑一次】
|
||||
# 对 baseline run(infer_adhoc,workspaces/default/harness.db 内 236 道错题)
|
||||
# 逐题跑诊断瀑布,把 defect/lapse/infra/degraded 投影为 tier 信号,逐行 upsert
|
||||
# 到 harness.db 的 baseline_diagnosis 表(主键含 diag_fingerprint,断点续跑幂等)。
|
||||
# 诊断依赖完整 harness 装配(GovernedLLMClient / 树数据 / 诊断 prompt),
|
||||
# 不在本脚本内联,由诊断编排单独执行;本脚本前置校验诊断信号已落库,缺失即 fail loud。
|
||||
#
|
||||
# ② 冻结切分(Task 11,build_split)——【本脚本执行】
|
||||
# 读 canonical 基线预测 + 诊断信号 → 构建全视频画像 → 贪心选择 trainval/test →
|
||||
# 视频组原子切三池 → 原子冻结 pools.json + 溯源 manifest → 六条防御断言。
|
||||
#
|
||||
# ============================================================================
|
||||
# 标定程序(诊断跑完 → 用真实 T2 分布把 config/video_split.yaml 的占位值定死)
|
||||
# ----------------------------------------------------------------------------
|
||||
# 1. floor_k : 读 baseline_diagnosis 各 task_type 的 T2(defect) 计数,
|
||||
# 每个高信号题型取克制值 floor = min(可用 defect 数, 3),
|
||||
# 避免把该类信号全抽进 trainval(会让 test 失去代表性)。
|
||||
# 2. n_trainval : trainval 目标视频数,取 ~100(总 300 视频的约 1/3),
|
||||
# 给多样性阶段留足填充预算,同时保证 test ≥ 2/3 保代表性。
|
||||
# 3. epsilon : test 相对全局的最大分布偏差,取 0.1(题型/难度逐桶容忍 10%)。
|
||||
# 4. val_wrong_min : validation 池最少错题数,取 McNemar 检验功效阈 ≈ 20;
|
||||
# 切分后 val 错题不足此值 → 验证信号不可靠 → fail loud。
|
||||
# 5. report_floor : per-type 报告门限,题数 ≥ 27 的 task_type 才入 ε 约束(滤长尾)。
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-0}"
|
||||
export CUDA_VISIBLE_DEVICES
|
||||
|
||||
# 日志即时输出,不缓存,便于实时排错。
|
||||
export PYTHONUNBUFFERED=1
|
||||
|
||||
# 工程配置(DB 路径、LLM 凭证)走 .env,不写死在脚本。
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
|
||||
PYTHON="$(conda run -n Video-Tree-TRM which python)"
|
||||
|
||||
CONFIG="config/video_split.yaml"
|
||||
HARNESS_DB="workspaces/default/harness.db"
|
||||
QUESTIONS_DIR="store/questions/benchmarks/Video-MME"
|
||||
OUT_DIR="workspaces/video-split"
|
||||
|
||||
echo "=== 结果驱动视频级切分:冻结 pools.json(读 ${CONFIG})==="
|
||||
|
||||
# 诊断口径指纹的代码分量 = 当前 git 短 SHA(诊断代码变则指纹变,旧信号不被覆盖)。
|
||||
CODE_VERSION="$(git rev-parse --short HEAD)"
|
||||
export CODE_VERSION HARNESS_DB QUESTIONS_DIR OUT_DIR CONFIG
|
||||
|
||||
"${PYTHON}" -c "
|
||||
import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
from adapters.baseline_diagnosis_store import SqliteDiagnosisSignalStore
|
||||
from app.harness.build_split import SplitBuildConfig, build_split
|
||||
from app.harness.split_selection import diag_fingerprint
|
||||
|
||||
config_path = Path(os.environ['CONFIG'])
|
||||
cfg = yaml.safe_load(config_path.read_text(encoding='utf-8'))
|
||||
vs = cfg['video_split']
|
||||
dg = cfg['diag']
|
||||
|
||||
# 诊断口径指纹:诊断 prompt 版本 + 模型名 + 代码短 SHA(三分量任一变则换指纹)。
|
||||
fingerprint = diag_fingerprint(dg['prompt_version'], dg['model'], os.environ['CODE_VERSION'])
|
||||
logger.info('诊断口径指纹 diag_fingerprint={} (prompt={} model={} code={})',
|
||||
fingerprint, dg['prompt_version'], dg['model'], os.environ['CODE_VERSION'])
|
||||
|
||||
harness_db = Path(os.environ['HARNESS_DB'])
|
||||
questions_dir = Path(os.environ['QUESTIONS_DIR'])
|
||||
out_dir = Path(os.environ['OUT_DIR'])
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
store = SqliteDiagnosisSignalStore(str(harness_db))
|
||||
try:
|
||||
# 前置校验(P5 fail loud):本指纹下必须已有诊断信号(Phase 1 诊断先跑完)。
|
||||
signals = store.load(vs['baseline_run_id'], fingerprint)
|
||||
if not signals:
|
||||
raise SystemExit(
|
||||
f'诊断信号为空:baseline={vs[\"baseline_run_id\"]} fingerprint={fingerprint} '
|
||||
f'在 {harness_db} 的 baseline_diagnosis 表无任何行。'
|
||||
'请先跑离线诊断(Phase 1 run_baseline_diagnosis)再执行本脚本。'
|
||||
)
|
||||
logger.info('诊断信号就绪:{} 行,开始冻结切分。', len(signals))
|
||||
|
||||
result = build_split(
|
||||
db_path=harness_db,
|
||||
baseline_run_id=vs['baseline_run_id'],
|
||||
signal_store=store,
|
||||
diag_fingerprint=fingerprint,
|
||||
questions_dir=questions_dir,
|
||||
config=SplitBuildConfig(
|
||||
n_trainval=vs['n_trainval'],
|
||||
floor_k=vs['floor_k'],
|
||||
epsilon=vs['epsilon'],
|
||||
report_floor=vs['report_floor'],
|
||||
select_seed=vs['seed'],
|
||||
val_ratio=vs['val_ratio'],
|
||||
split_seed=vs['seed'],
|
||||
),
|
||||
out_path=out_dir / 'pools.json',
|
||||
manifest_path=out_dir / 'split_manifest.json',
|
||||
generated_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
)
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
# McNemar 功效护栏(P5 fail loud):build_split 按 Task 11 契约不带 val_wrong_min,
|
||||
# 冻结后在 capstone 层校验 validation 池错题数是否达 McNemar 功效阈。
|
||||
pools = result['pools']
|
||||
val_wrong = sum(1 for q in pools.validation if not pools.correctness[q.question_id])
|
||||
val_wrong_min = vs['val_wrong_min']
|
||||
if val_wrong < val_wrong_min:
|
||||
raise SystemExit(
|
||||
f'validation 池错题数 {val_wrong} < val_wrong_min={val_wrong_min},'
|
||||
'验证信号不足以支撑可靠比较(McNemar 检验功效不够)。'
|
||||
'请放大 val_ratio / 调整旋钮后重跑,勿静默放行。'
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'切分冻结完成:pools={} manifest={} val错题={}/{}(阈)',
|
||||
out_dir / 'pools.json',
|
||||
out_dir / 'split_manifest.json',
|
||||
val_wrong,
|
||||
val_wrong_min,
|
||||
)
|
||||
"
|
||||
|
||||
echo "=== 完成:冻结产物在 ${OUT_DIR}/ (pools.json + split_manifest.json) ==="
|
||||
Reference in New Issue
Block a user