Files
C.E.L_Slide_test2/tests/matching/pipeline_18_slot_axis_ablation.py
KyeongminandClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:03:42 +09:00

196 lines
8.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pipeline Step 18 — V4 slot 축 ablation (실증).
질문: V4 의 5축 중 slot 축 (W_SLOT=0.15) 이 frame 매칭에 실제로 기여하는가?
방법:
1. 기존 v4_full32_result (W_SLOT=0.15 유지) 를 baseline 으로
2. W_SLOT=0 으로 두고 나머지 4축 (anchor/cardinality/relation/content) 가중치
재정규화 — sum=1.0 유지 → ablated 결과 산출
3. 32 frame × 7 MDX 섹션 confidence 재계산
4. 비교:
- TARGET 4개의 정답 frame 라벨/순위 변화
- Top-3 매칭 변화
- 라벨 분포 변화 (use_as_is/light_edit/restructure/reject 카운트)
5. 결론: slot 축이 의미 있나?
출력: V4_SLOT_ABLATION.md
"""
import sys
from pathlib import Path
import yaml
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from template_fit import route
OUT_PATH = HERE / 'V4_SLOT_ABLATION.md'
# Baseline (현재): 0.25 + 0.20 + 0.20 + 0.15 + 0.20 = 1.00
W_BASELINE = {'anchor': 0.25, 'cardinality': 0.20, 'relation': 0.20, 'slot': 0.15, 'content': 0.20}
# Ablated: slot=0, 나머지 4축 비례 재정규화 → sum=1.00
# 4축 합 = 0.85, 각 축 / 0.85
W_ABLATED = {
'anchor': 0.25 / 0.85, # 0.2941
'cardinality': 0.20 / 0.85, # 0.2353
'relation': 0.20 / 0.85, # 0.2353
'slot': 0.0,
'content': 0.20 / 0.85, # 0.2353
}
def recompute_confidence(axes, weights, penalty):
base = sum(weights[k] * axes.get(k, 0) for k in weights)
return max(0.0, base - penalty)
def main():
v4 = yaml.safe_load((HERE / 'v4_full32_result.yaml').read_text(encoding='utf-8'))
answer_map = v4['meta']['answer_map']
holdout = set(v4['meta']['holdout_sections'])
md = []
md.append('# V4 slot 축 ablation — 실증\n')
md.append('## 질문\n')
md.append('V4 confidence 계산식의 slot 축 (W_SLOT=0.15) 이 frame 매칭에 실제로 기여하는가?\n')
md.append('## 방법\n')
md.append('- **Baseline**: 5축 가중치 그대로 — 0.25 anchor + 0.20 card + 0.20 rel + 0.15 slot + 0.20 content\n')
md.append('- **Ablated**: W_SLOT=0, 나머지 4축 비례 재정규화 (sum=1.0)\n')
md.append(' - anchor: 0.294, cardinality: 0.235, relation: 0.235, content: 0.235\n')
md.append('- 32 frame × 7 MDX 섹션 confidence 재계산 → label 재할당 → 비교\n\n')
section_results = {}
for sid, sec in v4['mdx_sections'].items():
is_target = sid not in holdout
ans_fn = answer_map.get(sid)
# Baseline 결과 (이미 v4_full32 에 있음)
baseline = sorted(sec['judgments_full32'], key=lambda x: -x['confidence'])
# Ablated 결과 재계산
ablated = []
for j in sec['judgments_full32']:
new_conf = recompute_confidence(j['axes'], W_ABLATED, j['penalty'])
new_label = route(new_conf)
ablated.append({
'frame_number': j['frame_number'],
'frame_id': j['frame_id'],
'baseline_conf': j['confidence'],
'baseline_label': j['label'],
'ablated_conf': round(new_conf, 4),
'ablated_label': new_label,
})
ablated.sort(key=lambda x: -x['ablated_conf'])
# 정답 frame 변화
ans_baseline = next((x for x in baseline if x['frame_number'] == ans_fn), None) if ans_fn else None
ans_ablated = next((x for x in ablated if x['frame_number'] == ans_fn), None) if ans_fn else None
# 순위 비교
baseline_top3 = [(x['frame_number'], x['confidence'], x['label']) for x in baseline[:3]]
ablated_top3 = [(x['frame_number'], x['ablated_conf'], x['ablated_label']) for x in ablated[:3]]
# 라벨 분포 비교
from collections import Counter
base_lbl = Counter(x['label'] for x in baseline)
abl_lbl = Counter(x['ablated_label'] for x in ablated)
section_results[sid] = {
'is_target': is_target,
'ans_fn': ans_fn,
'baseline_top3': baseline_top3,
'ablated_top3': ablated_top3,
'ans_baseline': ans_baseline,
'ans_ablated': ans_ablated,
'base_lbl': base_lbl,
'abl_lbl': abl_lbl,
}
# ─── TARGET 정답 매칭 변화 ───
md.append('## 결과 1 — TARGET 4 섹션의 정답 frame 라벨/순위 변화\n')
md.append('| MDX | 정답 Frame | Baseline conf / 라벨 / 순위 | Ablated conf / 라벨 / 순위 | 변화 |\n')
md.append('|---|---|---|---|---|\n')
target_sids = [sid for sid in section_results if section_results[sid]['is_target']]
correct_baseline = 0
correct_ablated = 0
for sid in target_sids:
r = section_results[sid]
ans_fn = r['ans_fn']
ab = r['ans_baseline']
aa = r['ans_ablated']
base_rank = next((i+1 for i, x in enumerate(sorted(v4['mdx_sections'][sid]['judgments_full32'], key=lambda x: -x['confidence'])) if x['frame_number'] == ans_fn), '?')
# ablated rank
ablated_sorted = sorted(
[{'fn': j['frame_number'], 'c': recompute_confidence(j['axes'], W_ABLATED, j['penalty'])}
for j in v4['mdx_sections'][sid]['judgments_full32']],
key=lambda x: -x['c']
)
abl_rank = next((i+1 for i, x in enumerate(ablated_sorted) if x['fn'] == ans_fn), '?')
# 정답률 — baseline / ablated 의 Top-1 이 정답인가
if v4['mdx_sections'][sid]['judgments_full32']:
base_top1_fn = sorted(v4['mdx_sections'][sid]['judgments_full32'], key=lambda x: -x['confidence'])[0]['frame_number']
if base_top1_fn == ans_fn:
correct_baseline += 1
abl_top1_fn = ablated_sorted[0]['fn']
if abl_top1_fn == ans_fn:
correct_ablated += 1
change = '동일' if (ab['label'] == aa['ablated_label'] and base_rank == abl_rank) else '⚠️ 변화'
md.append(f'| {sid} | Frame {ans_fn} | '
f'{ab["confidence"]:.3f} / {ab["label"]} / 순위 {base_rank} | '
f'{aa["ablated_conf"]:.3f} / {aa["ablated_label"]} / 순위 {abl_rank} | {change} |\n')
md.append(f'\n**TARGET Top-1 정답률**: Baseline {correct_baseline}/4, Ablated {correct_ablated}/4\n\n')
# ─── Top-3 변화 ───
md.append('## 결과 2 — 각 섹션 Top-3 매칭 변화\n')
for sid, r in section_results.items():
tag = 'TARGET' if r['is_target'] else 'Holdout'
ans = f'(정답 Frame {r["ans_fn"]})' if r['ans_fn'] else '(holdout)'
md.append(f'\n### [{sid}] {tag} {ans}\n')
md.append('| 순위 | Baseline | Ablated |\n|---|---|---|\n')
for i in range(3):
b = r['baseline_top3'][i] if i < len(r['baseline_top3']) else (None, 0, '')
a = r['ablated_top3'][i] if i < len(r['ablated_top3']) else (None, 0, '')
b_mark = ' 🎯' if r['ans_fn'] and b[0] == r['ans_fn'] else ''
a_mark = ' 🎯' if r['ans_fn'] and a[0] == r['ans_fn'] else ''
md.append(f'| {i+1} | Frame {b[0]}{b_mark} ({b[1]:.3f}, {b[2]}) | Frame {a[0]}{a_mark} ({a[1]:.3f}, {a[2]}) |\n')
# ─── 라벨 분포 변화 ───
md.append('\n## 결과 3 — 라벨 분포 변화 (32 frame 기준)\n')
md.append('| MDX | use_as_is | light_edit | restructure | reject |\n|---|---|---|---|---|\n')
for sid, r in section_results.items():
b = r['base_lbl']; a = r['abl_lbl']
def fmt(lbl):
bv = b.get(lbl, 0); av = a.get(lbl, 0)
if bv == av: return f'{bv}'
return f'{bv}{av}'
md.append(f'| {sid} | {fmt("use_as_is")} | {fmt("light_edit")} | {fmt("restructure")} | {fmt("reject")} |\n')
# ─── 결론 ───
md.append('\n## 결론\n')
same_top1 = sum(1 for sid, r in section_results.items()
if r['baseline_top3'] and r['ablated_top3']
and r['baseline_top3'][0][0] == r['ablated_top3'][0][0])
same_top3 = sum(1 for sid, r in section_results.items()
if r['baseline_top3'] and r['ablated_top3']
and set(x[0] for x in r['baseline_top3']) == set(x[0] for x in r['ablated_top3']))
md.append(f'- **Top-1 일치 섹션 수**: {same_top1}/{len(section_results)}\n')
md.append(f'- **Top-3 멤버 일치 섹션 수**: {same_top3}/{len(section_results)}\n')
md.append(f'- **TARGET Top-1 정답률**: Baseline {correct_baseline}/4 → Ablated {correct_ablated}/4\n')
OUT_PATH.write_text(''.join(md), encoding='utf-8')
print('=' * 70)
print(f'V4 slot 축 ablation 완료: {OUT_PATH}')
print('=' * 70)
print(f'TARGET Top-1 정답률: Baseline {correct_baseline}/4, Ablated {correct_ablated}/4')
print(f'Top-1 동일 섹션: {same_top1}/{len(section_results)}')
print(f'Top-3 멤버 동일 섹션: {same_top3}/{len(section_results)}')
if __name__ == '__main__':
main()