Skip to content

Latest commit

ย 

History

History
1367 lines (1033 loc) ยท 55.2 KB

File metadata and controls

1367 lines (1033 loc) ยท 55.2 KB

๐Ÿชž Measurement Mirror โ€” ํ”„๋กœ๋ธŒ ์™„์ „ ๊ฐ€์ด๋“œ

๋Œ€์ƒ ๋…์ž: ๊ฐ ํ”„๋กœ๋ธŒ๊ฐ€ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€, ์–ธ์ œ ์จ์•ผ ํ•˜๋Š”์ง€, ์ถœ๋ ฅ์„ ์–ด๋–ป๊ฒŒ ์ฝ์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ๊ณ  ์‹ถ์€ ์—ฐ๊ตฌ์žยทML ์—”์ง€๋‹ˆ์–ดยท๋ฆฌ๋ทฐ์–ด.

๊ด€๋ จ ํŒŒ์ผ: README_KO (API ๋ ˆํผ๋Ÿฐ์Šค) ยท CHANGELOG ยท CHRONICLE (๊ฐœ๋ฐœ ์—ฐ๋Œ€๊ธฐ)

English version: GUIDE.md

๋ฒ”์œ„: ์ด ๊ฐ€์ด๋“œ๋Š” 28๊ฐœ ํ”„๋กœ๋ธŒ ์ „์ฒด๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์•„๋ž˜์— ๋‚˜์˜ค๋Š” "4๊ฐœ ํ”„๋กœ๋ธŒ" ๊ฐ™์€ ํ‘œํ˜„์€ ํŠน์ • ๊ทธ๋ฃน์˜ ๋ถ€๋ถ„ ์ˆ˜์น˜์ด์ง€ ์ด๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.


์ฒ ํ•™: ์–‘๋ฐฉํ–ฅ ๊ฑฐ์šธ

๋Œ€๋ถ€๋ถ„์˜ ํ‰๊ฐ€ ๋ฌด๊ฒฐ์„ฑ ๋„๊ตฌ๋Š” ๊ฑฐ์ง“์–‘์„ฑ๋งŒ ์žก์Šต๋‹ˆ๋‹ค โ€” ์‹ค์ œ๋ณด๋‹ค ์ข‹์•„ ๋ณด์ด๋Š” ๊ฒฐ๊ณผ. Measurement Mirror๋Š” ์–‘๋ฐฉํ–ฅ์„ ๋ชจ๋‘ ์žก์Šต๋‹ˆ๋‹ค:

๋ฐฉํ–ฅ ์‹คํŒจ ์˜ˆ์‹œ ๊ฑฐ์šธ์˜ ๋ฐ˜์‘
๊ฑฐ์ง“์–‘์„ฑ n=9, acc=55.6%๋ฅผ ํš๊ธฐ์  ์„ฑ๊ณผ๋กœ ๋ณด๊ณ  โ‘ฃa Wilson CI๊ฐ€ ์šฐ์—ฐ ์ˆ˜์ค€์ž„์„ ์ ๋ฐœ
๊ฑฐ์ง“์Œ์„ฑ ์‹คํ—˜ 1ํšŒ ์‹คํŒจ โ†’ "์ด ์ ‘๊ทผ๋ฒ•์€ ์ฃฝ์—ˆ๋‹ค" โ‘ฌ negative_audit๊ฐ€ ๋…๋ฆฝ ๊ฐ๋„ โ‰ฅ3๊ฐœ ์š”๊ตฌ
ํŒ์ •์ž ์ฐฉ์‹œ LLM ํŒ์ •์ž๊ฐ€ ํ•ญ์ƒ ์ฒซ ๋ฒˆ์งธ ์‘๋‹ต์„ ์„ ํƒ โ‘ฎ judge_bias_check๊ฐ€ ์œ„์น˜ ํŽธํ–ฅ ์ ๋ฐœ

์„ฑ๊ธ‰ํ•œ ์Œ์„ฑ ์ข…๊ฒฐ์€ ์กฐ์ž‘๋œ ์–‘์„ฑ๋งŒํผ ๋‚˜์ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์—ฐ๊ตฌ ์ž์›์„ ๋‚ญ๋น„ํ•˜๊ณ  ๋ถ„์•ผ ์ „์ฒด๋ฅผ ์ž˜๋ชป๋œ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋„๋Š” ํ—ˆ์ƒ์ž…๋‹ˆ๋‹ค.


์ฃผ์žฅ ์ƒ์• ์ฃผ๊ธฐ

โ‘  preregister               โ† ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ ์ „์— ๊ธฐ์ค€์„ ๋ด‰์ธ
        โ”‚
        โ–ผ
ใ‰— prereg_lint               โ† ์—ฐ์‚ฐ์„ ์“ฐ๊ธฐ ์ „์— ๋ด‰์ธ์˜ ํ’ˆ์งˆ์„ ๋ฆฐํŠธ
        โ”‚                       (kill-condition ๋ˆ„์ˆ˜ ยท ์„ ์–ธ์šฐ์—ฐ ์ดํ•˜ ๋ฐ” ยท
        โ–ผ                        ๋น„๊ตฌ์กฐํ™” kill ยท ์ € n ยท ์ฒดํฌ ๋ฏธ์„ ์–ธ)
    ์‹คํ—˜ ์‹คํ–‰
        โ”‚
        โ–ผ
audit / full_audit           โ† ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ ํ›„ ์ „์ฒด ํ”„๋กœ๋ธŒ ์‹คํ–‰
  โ”œโ”€ โ‘ฃa ํ†ต๊ณ„ ์œ ํšจ์„ฑ
  โ”œโ”€ โ‘   ์‚ฌ์ „๋“ฑ๋ก ํ™•์ธ
  โ”œโ”€ โ‘ฉ  GRIM ์‚ฐ์ˆ  ํ™•์ธ
  โ”œโ”€ โ‘ช  ๋ฐ˜์ฆ๊ฐ€๋Šฅ์„ฑ / kill-condition
  โ””โ”€ โ‘ซ  ์ฒ ํšŒ cascade
        โ”‚
        โ”œโ”€โ”€ ์–‘์„ฑ ๊ฒฐ๋ก  โ†’ publish + anchor (์™ธ๋ถ€ ํ•ด์‹œ ๋ณด๊ด€)
        โ”‚
        โ”œโ”€โ”€ ์Œ์„ฑ ๊ฒฐ๋ก  โ†’ negative_audit (โ‘ฌ) ๋กœ ์ข…๊ฒฐ ๊ฒŒ์ดํŠธ
        โ”‚                 ๋…๋ฆฝ ๊ฐ๋„ โ‰ฅ min_angles ํ•„์š”
        โ”‚                 โ”‚
        โ”‚                 โ–ผ
        โ”‚              ๋‚˜์ค‘์— ๋ฌดํšจํ™” ์‹œ: retract() โ†’ cascade_check()
        โ”‚
        โ””โ”€โ”€ LLM judge ํ‰๊ฐ€ โ†’ judge_run()์ด โ‘ญโ‘ฎโ‘ฏโ‘ฐ ์ž๋™ ๋ฐœํ™”
                              ์ฒด์ธ ์—ฐ๊ฒฐ ์—”ํŠธ๋ฆฌ๋ฅผ ์›์žฅ์— ๋ด‰์ธ

๊ฒ€์ฆ 3๋‹จ๊ณ„

๋‹จ๊ณ„ ๋ฐฉ๋ฒ• ์šฉ๋„
ํ’€ ๊ฒ€์ฆ mm.verify(ledger, data) / mm verify --file data.json ์›์ƒท ๊ฐ์‚ฌ โ€” data์— ์ž…๋ ฅ์ด ์žˆ๋Š” ๋ชจ๋“  ํ”„๋กœ๋ธŒ ์‹คํ–‰
๊ทธ๋ฃน๋‹จ์œ„ ๊ฒ€์ฆ mm.verify(ledger, data, groups=["judge"]) / --groups judge ํ•˜๋‚˜์˜ ๊ฒ€์ฆ ๊ด€์‹ฌ์‚ฌ์— ์ง‘์ค‘
๊ฐœ๋ณ„๊ฒ€์ฆ mm.grim_check(...), mm.judge_swap_check(...) ๋“ฑ ์ •๋ฐ€ ์ œ์–ด, ์ปค์Šคํ…€ ํŒŒ์ดํ”„๋ผ์ธ

๊ฒ€์ฆ ๊ทธ๋ฃน (mm verify --list-groups ๋˜๋Š” mm.GROUPS):

๊ทธ๋ฃน ํ”„๋กœ๋ธŒ ๋‹ตํ•˜๋Š” ์งˆ๋ฌธ
ledger โ‘  โ‘ซ + ์ฒด์ธ ์‚ฌ์ „๋“ฑ๋ก ๊ธฐ๋ก์ด ๋ฌด๊ฒฐํ•˜๊ณ  ์ฒ ํšŒ๋˜์ง€ ์•Š์•˜๋‚˜?
stats โ‘ฃ โ‘ค โ‘ฆ โ‘ง โ‘จ โ‘ฉ ์ˆซ์ž๊ฐ€ ํ†ต๊ณ„์ ์œผ๋กœ ์ง„์งœ์ธ๊ฐ€?
design โ‘ก โ‘ข โ‘ฅ โ‘ช ์‹คํ—˜์ด ๊ณต์ •ํ•˜๊ฒŒ ์„ค๊ณ„๋๋‚˜?
negative โ‘ฌ ์ด ์Œ์„ฑ ์ข…๊ฒฐ์ด ์„ฑ๊ธ‰ํ•˜์ง€ ์•Š๋‚˜?
judge โ‘ญ โ‘ฎ โ‘ฏ โ‘ฐ โ‘ฑ LLM ํŒ์ •์ž๊ฐ€ ์‹ ๋ขฐํ•  ๋งŒํ•œ๊ฐ€?
ranking โ‘ฒ โ‘ณ ๋ฆฌ๋”๋ณด๋“œ๊ฐ€ ์ง„์งœ์ธ๊ฐ€?

verify()๋Š” ์ž…๋ ฅ ์ฃผ๋„ํ˜•์ž…๋‹ˆ๋‹ค: data ๋”•์…”๋„ˆ๋ฆฌ์— ํ‚ค๊ฐ€ ์žˆ๋Š” ํ”„๋กœ๋ธŒ๋งŒ ์‹คํ–‰๋˜๋ฏ€๋กœ, ํ’€ ๊ฒ€์ฆ์€ ์ž…๋ ฅ ๋ˆ„๋ฝ์œผ๋กœ ์—๋Ÿฌ๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค โ€” ๋ฐ์ดํ„ฐ๊ฐ€ ์ง€์›ํ•˜๋Š” ๋งŒํผ๋งŒ ๋•๋‹ˆ๋‹ค. group_of(finding)์œผ๋กœ ์–ด๋–ค Finding์ด๋“  ์†Œ์† ๊ทธ๋ฃน์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํ”„๋กœ๋ธŒ ๋ ˆํผ๋Ÿฐ์Šค

ํ”„๋กœ๋ธŒ๋Š” ์žก์•„๋‚ด๋Š” ๋ฌด๊ฒฐ์„ฑ ์‹คํŒจ์˜ ์ข…๋ฅ˜๋ณ„๋กœ ๋ฌถ์—ˆ์Šต๋‹ˆ๋‹ค.


Group 1 โ€” ์‚ฌ์ „๋“ฑ๋ก & ์›์žฅ ๋ฌด๊ฒฐ์„ฑ

โ‘  preregister / audit

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์‚ฌํ›„ ์ง€ํ‘œ ๊ต์ฒด ยท ํ‘œ๋ณธ ๋ฏธ๋‹ฌ ยท ์กฐ์ž‘๋œ ๊ธฐ์ค€ ยท pass-threshold ๋ฏธ๋‹ฌ

์‚ฌ์ „๋“ฑ๋ก์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ ์ „์— ํ‰๊ฐ€ ๊ณ„ํš์„ ๋ด‰์ธํ•ฉ๋‹ˆ๋‹ค. SHA-256 ๋ด‰์ธ๊ณผ ์ฒด์ธ ๋งํฌ๊ฐ€ ์œ„๋ณ€์กฐ๋ฅผ ํƒ์ง€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค: ์‚ฌํ›„์— ์–ด๋–ค ํ•„๋“œ๋“  ๋ณ€๊ฒฝํ•˜๋ฉด ๊ฐ์ง€๋ฉ๋‹ˆ๋‹ค.

# ์‹คํ—˜ ์ „
mm.preregister("ledger.jsonl", "my_model",
               metric="acc",          # ์ปค๋ฐ‹ํ•˜๋Š” ๋‹จ ํ•˜๋‚˜์˜ ์ง€ํ‘œ
               min_n=200,             # ํ—ˆ์šฉ๋˜๋Š” ์ตœ์†Œ ํ‘œ๋ณธ ํฌ๊ธฐ
               baseline=0.5,          # ๊ณต์ •ํ•œ ๋น„๊ต ๊ธฐ์ค€์„ 
               pass_threshold=0.60)   # ์„ฑ๊ณต์„ ์ฃผ์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ตœ์†Œ ๊ธฐ์ค€

# ์‹คํ—˜ ํ›„
findings = mm.audit("ledger.jsonl", "my_model",
                    reported_metric="acc",   # ๋“ฑ๋ก๋œ ์ง€ํ‘œ์™€ ์ผ์น˜ํ•ด์•ผ ํ•จ
                    reported_acc=0.72,
                    n=500)
mm.report("my_model", findings)

audit() ์ถœ๋ ฅ ๋ ˆ๋ฒจ:

  • FAIL [โ‘  pre-registration(metric-swap)] โ€” acc ๋“ฑ๋ก ํ›„ f1 ๋ณด๊ณ 
  • FAIL [โ‘  pre-registration(min_n)] โ€” n=9 < ๋“ฑ๋ก๋œ min_n=200
  • FAIL [โ‘  pre-registration(pass-threshold)] โ€” ์Šค์Šค๋กœ ์„ค์ •ํ•œ ๊ธฐ์ค€ ๋ฏธ๋‹ฌ
  • FAIL [โ‘  seal-tamper] โ€” ๋“ฑ๋ก ํ›„ ์›์žฅ ํŒŒ์ผ ์ˆ˜์ •๋จ

ํ”ํ•œ ์‹ค์ˆ˜: metric="acc"๋กœ ๋“ฑ๋ก ํ›„, ๋‚˜์ค‘์— ๋” ์ข‹์•„ ๋ณด์ด๋Š” ์ง€ํ‘œ(f1, auc)๋ฅผ ๋ณด๊ณ . ๋ด‰์ธ์ด ๋ช‡ ๋‹ฌ ํ›„์—๋„ ์ด๊ฒƒ์„ ์žก์•„๋ƒ…๋‹ˆ๋‹ค.


โ‘  verify_chain

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์‚ญ์ œ๋œ ์—”ํŠธ๋ฆฌ ยท ์‚ฝ์ž…๋œ ์—”ํŠธ๋ฆฌ ยท ์–ด๋–ค ์—”ํŠธ๋ฆฌ๋“  ๋‚ด์šฉ ์ˆ˜์ •

findings = mm.verify_chain("ledger.jsonl")
mm.report("์›์žฅ ๋ฌด๊ฒฐ์„ฑ", findings)

์ฒด์ธ์ด ์ž‘๋™ํ•˜๋Š” ์›๋ฆฌ: ๋ชจ๋“  ์—”ํŠธ๋ฆฌ๊ฐ€ ์ž์‹ ์˜ ๋ด‰์ธ์„ ๊ณ„์‚ฐํ•˜๊ธฐ ์ „์— ์ด์ „ ์—”ํŠธ๋ฆฌ์˜ SHA-256์ธ prev_seal์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. N๋ฒˆ ์—”ํŠธ๋ฆฌ๋ฅผ ์‚ญ์ œํ•˜๋ฉด N+1๋ฒˆ ์—”ํŠธ๋ฆฌ์—์„œ ์ฒด์ธ์ด ๋Š๊น๋‹ˆ๋‹ค. ๊ฐ€์งœ ์—”ํŠธ๋ฆฌ๋ฅผ ์‚ฝ์ž…ํ•ด๋„ ๋Š๊น๋‹ˆ๋‹ค.

์‹คํ–‰ ์‹œ์ : ๋ชจ๋“  ์‹คํ—˜ ํ›„ CI์—์„œ, ๊ทธ๋ฆฌ๊ณ  ๊ฒฐ๊ณผ ๊ฒŒ์žฌ ์ „์—.


anchor (์œ ํ‹ธ๋ฆฌํ‹ฐ)

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์›์žฅ ํŒŒ์ผ ํ†ต์งธ ๊ต์ฒด โ€” ์ฒด์ธ ํ•ด์‹œ๊ฐ€ ํ˜ผ์ž์„œ๋Š” ์žก์ง€ ๋ชปํ•˜๋Š” ์œ ์ผํ•œ ๊ณต๊ฒฉ

์ฒด์ธ ํ•ด์‹œ๋Š” ํŒŒ์ผ ์•ˆ์˜ ์ˆ˜์ •์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ˆ„๊ตฐ๊ฐ€ ํŒŒ์ผ์„ ํ†ต์งธ๋กœ ์‚ญ์ œํ•˜๊ณ  ์ƒˆ๋กœ ์‹œ์ž‘ํ•˜๋ฉด, ๊ธฐ์ˆ ์ ์œผ๋กœ ์ฒด์ธ์€ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค(์ƒˆ genesis). anchor_hash(์ „์ฒด ํŒŒ์ผ ๋ฐ”์ดํŠธ์˜ SHA-256)๊ฐ€ ์ด๊ฒƒ์„ ์žก์•„๋ƒ…๋‹ˆ๋‹ค.

# ๋ณ€์กฐ ๋ฐฉ์ง€ ์Šค๋ƒ…์ƒท ์ถœ๋ ฅ โ€” ์‹ ๋ขฐํ•˜๋Š” ๊ณณ์— ํŒŒ์ดํ”„
a = mm.anchor("ledger.jsonl")
# โ†’ {"_type": "anchor", "anchor_hash": "sha256hex...", "chain_ok": true, ...}
# ๊ฒŒ์žฌ ์ „ ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ํŒŒ์ดํ”„
mm anchor | gh gist create -               # GitHub Gist ํƒ€์ž„์Šคํƒฌํ”„
mm anchor >> ~/Dropbox/mm_anchors.jsonl    # ๋กœ์ปฌ ๋ฐฑ์—…
mm anchor --pretty                          # ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ

๊ถŒ์žฅ ์‚ฌํ•ญ: ๊ฒฐ๊ณผ๋ฅผ ๊ฒŒ์žฌํ•˜๊ธฐ ์ง์ „์— mm anchor๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”. ์™ธ๋ถ€ ํƒ€์ž„์Šคํƒฌํ”„๊ฐ€ ๊ฒŒ์žฌ ์‹œ์ ์— ์›์žฅ์ด ๋ฌด์—‡์„ ๋‹ด๊ณ  ์žˆ์—ˆ๋Š”์ง€๋ฅผ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.

ใ‰— prereg_lint โ€” ์—ฐ์‚ฐ ์ „, ๋ด‰์ธ์˜ ํ’ˆ์งˆ

falsifiability_check(โ‘ช)๋Š” kill-condition์ด ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ๋ฌป์Šต๋‹ˆ๋‹ค. prereg_lint๋Š” ๋ด‰์ธ์ด ์ž๋™ ๊ฒ€์‚ฌ๊ฐ€ ๋ฐœํ™”ํ•  ๋งŒํผ ์ œ๋Œ€๋กœ ๋๋Š”์ง€, ๋ฐ”๊ฐ€ ์˜๋ฏธ ์žˆ๋Š”์ง€๋ฅผ ๋ฌป์Šต๋‹ˆ๋‹ค โ€” ๋ฌด์˜๋ฏธ ์—ฐ์‚ฐ์ด ์ƒˆ๋Š” ๊ฒฐํ•จ ํด๋ž˜์Šค๋“ค:

๋ ˆ๋ฒจ ๊ฒฐํ•จ
FAIL kill-condition ๋ฌธ์žฅ์ด metric ํ•„๋“œ๋กœ ๋ˆ„์ˆ˜(์ž˜๋ชป๋œ ํ˜ธ์ถœ โ€” ์‚ฌ๋žŒ ๋ˆˆ์—” ๊ธฐ์ค€์ด ๋ณด์ด๋‚˜ ํŒŒ์„œ์—” ์—†์Œ)
FAIL pass ๋ฐ”๊ฐ€ ์„ ์–ธ๋œ ์šฐ์—ฐ(chance) ์ดํ•˜(๋„˜์–ด๋„ ์•„๋ฌด๊ฒƒ๋„ ์ฆ๋ช… ๋ชป ํ•จ; chance= ํ•„์š” โ€” baseline๋งŒ์œผ๋ก  ๋ฐ”๋‹ฅ ์•„๋‹˜)
WARN ์ •๋Ÿ‰ kill์„ ๊ตฌ์กฐํ™”๋œ kill_threshold ์—†์ด ์ž์œ ํ…์ŠคํŠธ๋กœ๋งŒ โ†’ ์˜์›ํžˆ ์ž๋™ํ‰๊ฐ€ ๋ถˆ๊ฐ€
WARN min_n์ด ์†Œํ‘œ๋ณธ ๋ฐ”๋‹ฅ(20) ๋ฏธ๋งŒ
INFO pre_seal_checks ๋ฏธ์„ ์–ธ(reachability-smoke ยท mass-balance-audit ยท neutral-control ยท manipulation-check ยท positive-control)
mm.preregister("ledger.jsonl", "my_model",
               metric="acc", min_n=240, baseline=0.5, pass_threshold=0.60,
               kill_threshold={"metric": "acc", "threshold": 0.55, "direction": "below"},
               pre_seal_checks=["reachability-smoke", "neutral-control"])

for f in mm.prereg_lint("ledger.jsonl", "my_model"):   # claim_id=None โ†’ ์ „ ์ฃผ์žฅ ๋ฆฐํŠธ
    print(f)

์˜๋„์ ์œผ๋กœ verify() ์šฐ์‚ฐยท๊ทธ๋ฃน์— ๋„ฃ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: ์ด๊ฒƒ์€ ์—ฐ์‚ฐ ์ „ ์ฒดํฌ์ด๊ณ , verify()/audit()์€ ๋ณด๊ณ  ์‹œ์ ์— ๋•๋‹ˆ๋‹ค. MCP mm_register ์•ˆ์—์„œ ์ž๋™ ๋ฐœํ™”ํ•˜๋ฉฐ(์‘๋‹ต์— lint ๋™๋ด‰), mirror-stack compute ๊ฒŒ์ดํŠธ๋Š” ใ‰— FAIL์— BLOCKํ•ฉ๋‹ˆ๋‹ค. FAIL์ด๋ฉด ์ƒˆ claim_id๋กœ ๊ณ ์ณ์„œ ์žฌ๋ด‰์ธ โ€” first-write-wins๋ผ ์ด๋ฏธ ๋ด‰์ธ๋œ ๊ฒƒ์€ ๊ณ ์น  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


Group 2 โ€” ํ†ต๊ณ„ ์œ ํšจ์„ฑ

โ‘ฃa Wilson CI (audit ๋‚ด๋ถ€)

์žก์•„๋‚ด๋Š” ๊ฒƒ: ํ†ต๊ณ„์ ์œผ๋กœ ์šฐ์—ฐ๊ณผ ๊ตฌ๋ณ„ ๋ถˆ๊ฐ€ํ•œ ๊ฒฐ๊ณผ (์†Œํ‘œ๋ณธ ์‹ ๊ธฐ๋ฃจ)

Wilson ์Šค์ฝ”์–ด ์‹ ๋ขฐ๊ตฌ๊ฐ„์€ ์ž‘์€ n์—์„œ ์ •๊ทœ๊ทผ์‚ฌ๋ณด๋‹ค ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. 95% CI๊ฐ€ ๊ธฐ์ค€์„ ์„ ํฌํ•จํ•˜๋ฉด ๊ฒฐ๊ณผ๋Š” ํ†ต๊ณ„์ ์œผ๋กœ ๋ฌด์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

# audit() ๋‚ด๋ถ€์—์„œ ์ž๋™ ์‹คํ–‰
findings = mm.audit("ledger.jsonl", "my_model",
                    reported_metric="acc", reported_acc=0.72, n=15)
# โš ๏ธ  [โ‘ฃa small-sample CI] n=15, acc=0.72 โ†’ 95%CI [0.467, 0.887]
#     โŠƒ baseline(0.5). ์šฐ์—ฐ๊ณผ ๊ตฌ๋ณ„ ๋ถˆ๊ฐ€.

๊ฒฝํ—˜ ๋ฒ•์น™: n=15์—์„œ๋Š” acc=0.72๋„ 0.5์™€ ๊ตฌ๋ณ„๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +10pp ๊ฐœ์„ ์„ ์œ ์˜๋ฏธํ•˜๊ฒŒ ๋ณด์ด๋ ค๋ฉด nโ‰ฅ200์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


โ‘ง power_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๊ด€์‹ฌ ์žˆ๋Š” ์ตœ์†Œ ํšจ๊ณผ๋ฅผ ํƒ์ง€ํ•˜๊ธฐ์— n์ด ๋„ˆ๋ฌด ์ž‘์Œ (๊ฑฐ์ง“์Œ์„ฑ ๊ฐ€๋“œ)

์ด๊ฒƒ์ด ๊ฑฐ์šธ์˜ ํ•ต์‹ฌ ๊ฑฐ์ง“์Œ์„ฑ ํ”„๋กœ๋ธŒ์ž…๋‹ˆ๋‹ค. ํ†ต๊ณ„์  ๊ฒ€์ •๋ ฅ์ด ๋ถ€์กฑํ•œ ์‹คํ—˜์—์„œ ๋‚˜์˜จ ์Œ์„ฑ ๊ฒฐ๊ณผ๋Š” ์˜๋ฏธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค โ€” ์ง„์งœ ํšจ๊ณผ๋ฅผ ๋†“์ณค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

f = mm.power_check(n=50, baseline=0.5, min_detectable_effect=0.05)
# โš ๏ธ  [โ‘ง power] n=50์€ 80% ๊ฒ€์ •๋ ฅ์œผ๋กœ ฮ”=+0.05 ํƒ์ง€์— ๋ถ€์กฑ.
#     ํ•„์š” nโ‰ฅ388. (ฮฑ=0.05, power=0.80)

# ๋‹ค์–‘ํ•œ ํšจ๊ณผ ํฌ๊ธฐ์—์„œ 80% ๊ฒ€์ •๋ ฅ์— ํ•„์š”ํ•œ n:
# ฮ”=+0.20 โ†’ nโ‰ˆ50  |  ฮ”=+0.10 โ†’ nโ‰ˆ200  |  ฮ”=+0.05 โ†’ nโ‰ˆ388

# full_audit์—์„œ ํ™œ์„ฑํ™”
findings = mm.full_audit("ledger.jsonl", "my_model", ...,
                          min_detectable_effect=0.05)

ํ”ํ•œ ์‹ค์ˆ˜: n=30์œผ๋กœ ฮ”=+0.05์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธํ•œ ํ›„ "์ด ๋ฐฉ๋ฒ•์€ ๋„์›€์ด ์•ˆ ๋œ๋‹ค" (์Œ์„ฑ ๊ฒฐ๋ก )๋ฅผ ์ข…๊ฒฐ. ๊ฒ€์ •๋ ฅ์ด 14%์˜€์œผ๋ฏ€๋กœ ์ง„์งœ ํšจ๊ณผ๋ฅผ ๋†“์ณค์„ ํ™•๋ฅ ์ด 86%์ž…๋‹ˆ๋‹ค.


โ‘จ multiple_comparisons_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๊ฐ™์€ ์›์žฅ์—์„œ k>1 ์‹คํ—˜์„ ํ•  ๋•Œ์˜ ๋‹ค์ค‘๋น„๊ต ๋ฌธ์ œ

5๊ฐœ ์‹คํ—˜์„ ๋Œ๋ ค ๊ฐ€์žฅ ์ข‹์€ ๊ฒƒ์„ ๊ณ ๋ฅด๋ฉด, ์‹คํšจ ฮฑ๋Š” 0.05๊ฐ€ ์•„๋‹ˆ๋ผ 1 โˆ’ (1โˆ’0.05)^5 โ‰ˆ 0.23์ž…๋‹ˆ๋‹ค. Bonferroni ๊ต์ •์€ ํ…Œ์ŠคํŠธ๋‹น ฮฑ/k๋ฅผ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค.

f = mm.multiple_comparisons_check("ledger.jsonl", alpha=0.05)
# โš ๏ธ  [โ‘จ multiple-comparisons] ์›์žฅ์— k=5๊ฐœ ์‹คํ—˜.
#     Bonferroni ๊ต์ • ฮฑ = 0.010 (0.05๊ฐ€ ์•„๋‹˜). ๋” ์—„๊ฒฉํ•œ ๊ธฐ์ค€ ์‚ฌ์šฉ.

# full_audit์—์„œ ํ™œ์„ฑํ™”
findings = mm.full_audit("ledger.jsonl", "my_model", ...,
                          check_multiplicity=True)

์ฐธ๊ณ : ๊ฐ™์€ claim_id์˜ ์žฌ๋“ฑ๋ก์€ k=1๋กœ ์นด์šดํŠธ(first-write-wins ์ •์ฑ…๊ณผ ์ผ๊ด€). ์„œ๋กœ ๋‹ค๋ฅธ claim_id๋งŒ ์นด์šดํŠธ๋ฉ๋‹ˆ๋‹ค.


Group 3 โ€” ๋น„๊ต ์ •์ง์„ฑ

โ‘ก baseline_fairness

์žก์•„๋‚ด๋Š” ๊ฒƒ: ํ—ˆ์•ฝํ•œ ๊ธฐ์ค€์„  ยท ๋™์  ๊ฒฐ๊ณผ ยท ์—ญ์ „๋œ ๋น„๊ต

์˜๋„์ ์œผ๋กœ ์•ฝํ•œ ๊ธฐ์ค€์„  ๋Œ€๋น„ "๊ฐ•ํ•œ ๊ฐœ์„ "์€ ๊ฐœ์„ ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์˜ค์ฐจ ๋ฒ”์œ„ ์•ˆ์— ๋“œ๋Š” "๋” ์ข‹์€" ๊ฒฐ๊ณผ๋Š” ๋™์ ์ž…๋‹ˆ๋‹ค.

# ํ—ˆ์•ฝํ•œ ๊ธฐ์ค€์„ : ๋‚ด ๋ชจ๋ธ์ด ๋ง๊ฐ€์ง„ ๊ฒฝ์Ÿ์ž๋ฅผ ์ด๊น€
f = mm.baseline_fairness("random_baseline", 0.60, 0.50)   # OK โ€” ๋ช…ํ™•ํ•œ ์Šน๋ฆฌ
f = mm.baseline_fairness("vs_gru_ode",      0.998, 0.996)  # FAIL โ€” ๋™์  (ฮ”=0.002)

# ์—ญ์ „: ๋‚ด๊ฐ€ ์ง
f = mm.baseline_fairness("strong_model", 0.72, 0.86)  # FAIL โ€” ๊ธฐ์ค€์„  ์Šน

# ์ด์ง„์ด ์•„๋‹Œ ์ง€ํ‘œ (๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ, ์˜ˆ: MSE)
f = mm.baseline_fairness("vs_baseline_mse", 0.12, 0.15, higher_better=False)

๋ ˆ๋ฒจ:

  • FAIL [โ‘ก fair-baseline] X wins โ€” ๊ธฐ์ค€์„ ์ด ๋‚˜๋ฅผ ๋Šฅ๊ฐ€
  • FAIL [โ‘ก fair-baseline] Tied โ€” ฮ” < margin (๊ธฐ๋ณธ๊ฐ’ 0.01)
  • OK โ€” ๋ช…ํ™•ํ•œ ์Šน๋ฆฌ

โ‘ฆ too_good_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์ถ”๊ฐ€ ๊ฒ€ํ† ๊ฐ€ ํ•„์š”ํ•œ ์˜์‹ฌ์Šค๋Ÿฝ๊ฒŒ ํฐ ๊ฐœ์„ 

"๋„ˆ๋ฌด ์ข‹์•„ ๋ณด์ด๋ฉด, ๊ทธ๊ฒŒ ๋ณดํ†ต ์‚ฌ์‹ค์ž…๋‹ˆ๋‹ค": ๋ฐ์ดํ„ฐ ๋ˆ„์ˆ˜, ํ‰๊ฐ€์…‹ ์˜ค์—ผ, ๋ณด์ƒ/์ง€ํ‘œ ์ •๋ ฌ ๋ฒ„๊ทธ๊ฐ€ ํ”ํ•œ ์›์ธ์ž…๋‹ˆ๋‹ค.

f = mm.too_good_check("my_model", claimed=0.95, baseline=0.50)
# โš ๏ธ  [โ‘ฆ too-good] ๊ธฐ์ค€์„  ๋Œ€๋น„ ฮ”=+0.45 โ€” ์˜์‹ฌ์Šค๋Ÿฝ๊ฒŒ ํผ.
#     ์กฐ์‚ฌ: ๋ฐ์ดํ„ฐ ๋ˆ„์ˆ˜? ๋ณด์ƒ ํ•ดํ‚น? ์ง€ํ‘œ ์ •๋ ฌ ๋ฒ„๊ทธ?

๊ธฐ๋ณธ ์ž„๊ณ„๊ฐ’: ฮ” > 0.30์ด๋ฉด WARN. full_audit() ๋‚ด๋ถ€์—์„œ ํ•ญ์ƒ ์ž๋™ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.


Group 4 โ€” ๋ฐ์ดํ„ฐ & ์ง€ํ‘œ ๋ฌด๊ฒฐ์„ฑ

โ‘ข gaming_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ํ›ˆ๋ จ ๋ณด์ƒ/์†์‹ค์— ํ‰๊ฐ€ ์ง€ํ‘œ๊ฐ€ ์ง์ ‘ ํฌํ•จ๋จ

ํ‰๊ฐ€ ์ง€ํ‘œ๋ฅผ ์ง์ ‘ ์ตœ์ ํ™”ํ•˜๋ฉด ๊ฒฐ๊ณผ๋Š” ์ž๊ธฐ์ถฉ์กฑ์ ์ž…๋‹ˆ๋‹ค: ๋ชจ๋ธ์€ ๊ธฐ์ € ๊ณผ์ œ๋ฅผ ํ•™์Šตํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด ์•„๋‹ˆ๋ผ, ์ง€ํ‘œ๋ฅผ ์ตœ์ ํ™”ํ•˜๋„๋ก ํ›ˆ๋ จ๋๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

f = mm.gaming_check(metric="accuracy",
                    reward_terms=["cross_entropy", "accuracy"])
# ๐Ÿ”ด [โ‘ข gaming] 'accuracy'๊ฐ€ reward_terms์— ํฌํ•จ๋จ.
#    ๊ฒฐ๊ณผ๋Š” ์ž๊ธฐ์ถฉ์กฑ์  โ€” ์ง€ํ‘œ๊ฐ€ ํ›ˆ๋ จ ๋ชฉ์ ํ•จ์ˆ˜์— ์ง์ ‘ ์žˆ์Œ.

f = mm.gaming_check(metric="bleu", reward_terms=["rl_reward", "fluency"])
# โœ… OK โ€” bleu๊ฐ€ ๋ณด์ƒ์— ์—†์Œ

โ‘ฃa leakage_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ํ›ˆ๋ จ/ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ค‘๋ณต (๋ฐ์ดํ„ฐ ์˜ค์—ผ)

์†Œ๋Ÿ‰์˜ ์ค‘๋ณต๋„ ์ •ํ™•๋„๋ฅผ ํฌ๊ฒŒ ๋ถ€ํ’€๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ดํ…œ์„ ํ•ด์‹ฑํ•˜์—ฌ ๊ต์ง‘ํ•ฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค โ€” ํ•ด์‹œ ๊ฐ€๋Šฅํ•œ ์•„์ดํ…œ ํƒ€์ž…์ด๋ฉด ๋ชจ๋‘ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

# ๋ฌธ์ž์—ด ์•„์ดํ…œ
train = ["๋ฌธ์žฅ A", "๋ฌธ์žฅ B", "๋ฌธ์žฅ C"]
test  = ["๋ฌธ์žฅ C", "๋ฌธ์žฅ D", "๋ฌธ์žฅ E"]
f = mm.leakage_check(train, test)
# ๐Ÿ”ด [โ‘ฃa leakage] ํ…Œ์ŠคํŠธ ์•„์ดํ…œ์˜ 1/3 (33.3%)์ด ํ›ˆ๋ จ์…‹์— ์žˆ์Œ.

# ์ •์ˆ˜, ํŠœํ”Œ, ํ•ด์‹œ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ํƒ€์ž… ์ž‘๋™
f = mm.leakage_check(list(range(100)), list(range(50, 150)))  # 50% ์ค‘๋ณต โ†’ FAIL

โ‘ค multiseed_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์‹œ๋“œ ๊ฐ„ ๋ถˆ์•ˆ์ •ํ•œ ์‹ ํ˜ธ ยท ๊ธฐ์ค€์„ ์ด ์‹œ๋“œ ๋ฒ”์œ„ ์•ˆ์— ๋“ค์–ด์˜ด

acc=0.48~0.72 ๋ฒ”์œ„๋กœ ์‹œ๋“œ๋งˆ๋‹ค ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€๋ฉด ์‹ ๋ขฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ์ค€์„ ์ด ์‹œ๋“œ ๋ฒ”์œ„ ์•ˆ์— ์žˆ์œผ๋ฉด, ๋‹ค๋ฅธ ์ดˆ๊ธฐํ™”์—์„œ ๊ฒฐ๊ณผ๊ฐ€ ์šฐ์—ฐ๊ณผ ๊ตฌ๋ณ„๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

f = mm.multiseed_check([0.48, 0.72, 0.65], baseline=0.5)
# ๐Ÿ”ด [โ‘ค multi-seed] ๊ธฐ์ค€์„  0.500์ด ์‹œ๋“œ ๋ฒ”์œ„ [0.480, 0.720] ์•ˆ์— ์žˆ์Œ.
#    ์šฐ์—ฐ ์ด์ƒ์˜ ์‹ ํ˜ธ๊ฐ€ ๊ฐ•๊ฑดํ•˜์ง€ ์•Š์Œ.

f = mm.multiseed_check([0.68, 0.71, 0.72], baseline=0.5)   # OK
f = mm.multiseed_check([0.70, 0.85, 0.75], baseline=0.5,
                        cv_threshold=0.05)  # CV ์ž„๊ณ„๊ฐ’ ์กฐ์ •

๊ทœ์น™: CV(๋ณ€๋™๊ณ„์ˆ˜) > 10%์ด๋ฉด ๊ธฐ๋ณธ์œผ๋กœ WARN.


โ‘ฅ scope_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๊ฒ€์ฆ๋œ ๋ฒ”์œ„๋ณด๋‹ค ๋„“๊ฒŒ ์ฃผ์žฅ (๊ณผ๋Œ€ ์ผ๋ฐ˜ํ™”)

"๊ณผ์ œ A์—์„œ ์ž‘๋™" โ‰  "์ผ๋ฐ˜ ์ถ”๋ก ". claimed_scope๋Š” tested_scope์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์ด๊ฑฐ๋‚˜ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

f = mm.scope_check(claimed_scope=["reasoning", "math"],
                   tested_scope=["musr_task_a"])
# ๐Ÿ”ด [โ‘ฅ scope] ๊ณผ๋Œ€์ฃผ์žฅ: {'reasoning', 'math'} ๋ฏธ๊ฒ€์ฆ.
#    ๊ฒ€์ฆ๋จ: {'musr_task_a'} ๋ฟ.

f = mm.scope_check(claimed_scope=["task_a"],
                   tested_scope=["task_a", "held_out_b"])  # OK

โ‘ฉ grim_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์‚ฐ์ˆ ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•œ ์ •ํ™•๋„/ํ‰๊ท  ๊ฐ’ โ€” ์กฐ์ž‘๋˜์—ˆ๊ฑฐ๋‚˜ n์„ ์ž˜๋ชป ๊ธฐ์žฌํ•œ ๊ฐ€๋Šฅ์„ฑ

GRIM (Granularity-Related Inconsistency of Means): acc = k/N์ด ์–ด๋–ค ์ •์ˆ˜ k์— ๋Œ€ํ•ด ์„ฑ๋ฆฝํ•œ๋‹ค๋ฉด(N = nยทitems), round(k/N, d) == acc๊ฐ€ ๋ฐ˜๋“œ์‹œ ์„ฑ๋ฆฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋งŒ์กฑํ•˜๋Š” ์ •์ˆ˜ k๊ฐ€ ์—†๋‹ค๋ฉด ๊ทธ ๊ฐ’์€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋น„์œจยทํผ์„ผํŠธยท์ •์ˆ˜(๋ฆฌ์ปคํŠธ ๋“ฑ) ๋ฐ์ดํ„ฐ์˜ ํ‰๊ท ์— ๋ชจ๋‘ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

f = mm.grim_check(reported_acc=0.33, n=10)
# ๐Ÿ”ด [โ‘ฉ GRIM] acc=0.33์€ n=10์—์„œ ์‚ฐ์ˆ ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅ.
#    round(k/10, 2) = 0.33์„ ๋งŒ์กฑํ•˜๋Š” ์ •์ˆ˜ k ์—†์Œ.
#    (ํ›„๋ณด: k=3 โ†’ 0.30, k=4 โ†’ 0.40). ์ˆ˜์น˜ ์กฐ์ž‘ ๋˜๋Š” n ์˜ค๊ธฐ์žฌ.

f = mm.grim_check(reported_acc=0.30, n=10)   # OK โ€” round(3/10, 2) = 0.30

# ๋‹ค๋ฌธํ•ญ ์ฒ™๋„์˜ ํ‰๊ท : granularity๋Š” nยทitems
f = mm.grim_check(5.90, n=40, items=3)        # N=120

# ์†Œ์ˆ˜์  ์ž๋ฆฌ์ˆ˜ ์ž๋™ ์ถ”๋ก ; n_decimals๋กœ ์žฌ์ •์˜ ๊ฐ€๋Šฅ
f = mm.grim_check(0.333, n=10, n_decimals=3)

audit() ๋‚ด๋ถ€์—์„œ ์ž๋™ ์‹คํ–‰ โ€” FAIL๋งŒ ์ถ”๊ฐ€, OK๋Š” ์กฐ์šฉํžˆ ํ†ต๊ณผ.

๋ฒ”์œ„ โ€” ์†Œํ‘œ๋ณธ ์ „์šฉ. GRIM์˜ ํž˜์€ granularity์—์„œ ๋‚˜์˜ต๋‹ˆ๋‹ค: N์ด ์ž‘์œผ๋ฉด ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๊ฐ’์ด ๋ช‡ ๊ฐœ๋ฟ์ด๋ผ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ’์ด ํŠ€์–ด๋‚˜์˜ต๋‹ˆ๋‹ค. N์ด ์ปค์ง€๋ฉด ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๊ฐ’์ด ๋นฝ๋นฝ์ด ์ฑ„์›Œ์ ธ GRIM์€ ๋ˆˆ์ด ๋ฉ‰๋‹ˆ๋‹ค โ€” d์ž๋ฆฌ๋กœ ๋ณด๊ณ ๋œ ๊ฐ’์€ N โ‰ณ 10^d(์˜ˆ: 2์ž๋ฆฌ ํ‰๊ท ์—์„œ n โ‰ฅ 100)๋ถ€ํ„ฐ ์•„๋ฌด๊ฒƒ๋„ ๋ชป ์žก์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฐ์ˆ ์  ๋ถˆ๊ฐ€๋Šฅ๋งŒ ์žก์ง€ ๋ถ„ํฌ์  ์กฐ์ž‘์€ ๋ชป ์žก์Šต๋‹ˆ๋‹ค: ๋Œ€๊ทœ๋ชจ ์กฐ์ž‘ ๋ฐ์ดํ„ฐ์…‹(์˜ˆ: AI ์ƒ์„ฑ)์€ ๋ณดํ†ต GRIM์„ ํ†ต๊ณผํ•˜๊ณ , ๋Œ€์‹  ๋””์ง€ํŠธ ํŒจํ„ด/๋ถ„ํฌ ํฌ๋ Œ์‹์— ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค(์—ฌ๊ธฐ ๋ฒ”์œ„ ๋ฐ–). ์‹ค์ฆ ํ™•์ธ: ๋ฌด์ž‘์œ„ 2์ž๋ฆฌ ํ‰๊ท ์€ n=20์—์„œ ~79%๊ฐ€ GRIM-๋ถˆ๊ฐ€๋Šฅ์ด์ง€๋งŒ nโ‰ฅ100์—์„œ๋Š” ~0%. GRIM์€ ์†Œํ‘œ๋ณธ ์‚ฐ์ˆ  ๊ฒŒ์ดํŠธ์ด์ง€ ๋ฒ”์šฉ ์‚ฌ๊ธฐ ํƒ์ง€๊ธฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.


Group 5 โ€” ์ฃผ์žฅ ์ƒ์• ์ฃผ๊ธฐ

์ด ์„ธ ํ”„๋กœ๋ธŒ๊ฐ€ "์ฃผ์žฅ ์ƒ์• ์ฃผ๊ธฐ ๋ฌด๊ฒฐ์„ฑ" ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ์…‹์„ ํ•ฉ์น˜๋ฉด Measurement Mirror๊ฐ€ "ํ†ต๊ณ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ"์—์„œ ๋ฌด์—‡์ด ์ฃผ์žฅ๋๊ณ , ๋ฌด์—‡์ด ๊ทธ ์ฃผ์žฅ์„ ์ฃฝ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ธฐ๋ฐ˜์ด ๋ฌด๋„ˆ์กŒ๋Š”์ง€๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ฐ์‚ฌ ์ธํ”„๋ผ๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค.


โ‘ช falsifiability_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๋ฐ˜์ฆ ๋ถˆ๊ฐ€๋Šฅํ•œ ์ฃผ์žฅ (kill-condition ์—†์Œ) ยท ์ด๋ฏธ ์ž๊ธฐ๋ถ€์ •ํ•œ ์ฃผ์žฅ

kill-condition ์—†๋Š” ์ฃผ์žฅ์€ ์›์น™์ ์œผ๋กœ ํ‹€๋ ธ์Œ์„ ์ฆ๋ช…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค โ€” ๋ฐ˜์ฆ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋“ฑ๋ก๋œ kill_threshold๋ฅผ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœํ™”์‹œํ‚ค๋Š” ์ฃผ์žฅ์€ ๊ฒŒ์žฌ ์‹œ์ ์— ์ž๊ธฐ๋ถ€์ •๋ฉ๋‹ˆ๋‹ค.

# ์‹คํ—˜ ์ „ kill-condition ๋“ฑ๋ก
mm.preregister("ledger.jsonl", "my_model",
               metric="acc", min_n=200, baseline=0.5, pass_threshold=0.60,
               # ์‚ฌ๋žŒ์ด ์ฝ๋Š” ์„ค๋ช… (์„ ํƒ์‚ฌํ•ญ์ด์ง€๋งŒ ๊ถŒ์žฅ)
               kill_condition="held-out ํ…Œ์ŠคํŠธ์—์„œ ์ •ํ™•๋„ 0.55 ๋ฏธ๋งŒ",
               # ๊ตฌ์กฐํ™” ํ˜•ํƒœ: audit ์‹œ์ ์— ์ž๋™ ํ‰๊ฐ€ (๊ถŒ์žฅ)
               kill_threshold={"metric": "acc",
                                "threshold": 0.55,
                                "direction": "below"})

# audit ์‹œ โ€” โ‘ช ์ž๋™ ์‹คํ–‰
findings = mm.audit("ledger.jsonl", "my_model",
                    reported_metric="acc", reported_acc=0.50, n=500)
# ๐Ÿ”ด [โ‘ช falsifiability] Kill condition triggered: acc=0.5 < 0.55.
#    Claim 'my_model' is falsified by its own pre-registered criterion.

# ๋‹จ๋… ํ™•์ธ (์ „์ฒด audit ์ „ ๋˜๋Š” ํŠน์ • ์ฟผ๋ฆฌ์šฉ)
f = mm.falsifiability_check("ledger.jsonl", "my_model", reported_acc=0.50)

๋ ˆ๋ฒจ:

  • FAIL โ€” kill_threshold ๋“ฑ๋ก๋จ AND reported_acc๊ฐ€ ๋ฐœํ™”
  • WARN โ€” kill-condition ์•„์˜ˆ ์—†์Œ ("๋ฐ˜์ฆ๋ถˆ๊ฐ€๋Šฅ") OR threshold ์„ค์ •๋์ง€๋งŒ ๊ฒฐ๊ณผ ๋ฏธ์ œ๊ณต
  • OK โ€” threshold ๋ฏธ๋ฐœํ™”, ๋˜๋Š” ํ…์ŠคํŠธ ์ „์šฉ ์กฐ๊ฑด ๋“ฑ๋ก๋จ

direction ํŒŒ๋ผ๋ฏธํ„ฐ:

  • "below": reported_acc < threshold์ผ ๋•Œ FAIL (์ •ํ™•๋„ํ˜•, ๋†’์„์ˆ˜๋ก ์ข‹์Œ)
  • "above": reported_acc > threshold์ผ ๋•Œ FAIL (์˜ค๋ฅ˜ํ˜•, ์˜ˆ: MSE, ๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ)

CLI:

mm register my_model --metric acc --min-n 200 --baseline 0.5 --pass 0.60 \
  --kill "์ •ํ™•๋„ 0.55 ๋ฏธ๋งŒ์ด๋ฉด ์‚ฌ๋ง" \
  --kill-threshold 0.55 --kill-direction below

โ‘ซ cascade_check + retract

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์ฒ ํšŒ๋œ ๊ธฐ๋ฐ˜ ์œ„์— ์„ธ์›Œ์ง„ ์ฃผ์žฅ (์˜ค๋ž˜๋œ ์ „์ด ์˜์กด์„ฑ)

์—ฐ๊ตฌ๋Š” ๋ˆ„์ ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์žฅ B๋Š” ์ฃผ์žฅ A ์œ„์— ์„ธ์›Œ์ง€๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ฃผ์žฅ A๊ฐ€ ์ฒ ํšŒ๋˜๋ฉด (๋ฐ์ดํ„ฐ์…‹ ์˜ค์—ผ ๋ฐœ๊ฒฌ, ๋ฐฉ๋ฒ•๋ก  ๊ฒฐํ•จ ๋ฐœ๊ฒฌ), A์— ์˜์กดํ•˜๋Š” ๋ชจ๋“  ์ฃผ์žฅ์€ ์ž๋™์œผ๋กœ STALE๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค โ€” ์„ค๋ น ์ˆ˜๋…„ ํ›„์— ๊ฒŒ์žฌ๋œ ๊ฒƒ์ด๋ผ๋„.

# ์‹คํ—˜ ์ „ ์˜์กด ๊ด€๊ณ„ ๋“ฑ๋ก
mm.preregister("ledger.jsonl", "dataset_v1",
               metric="quality_score", min_n=100, baseline=0.0, pass_threshold=0.8)
mm.preregister("ledger.jsonl", "v1๋กœ_ํ›ˆ๋ จ๋œ_๋ชจ๋ธ",
               metric="acc", min_n=200, baseline=0.5, pass_threshold=0.60,
               depends_on=["dataset_v1"])            # โ† ์˜์กด์„ฑ ๋ด‰์ธ
mm.preregister("ledger.jsonl", "๋…ผ๋ฌธ_๊ฒฐ๊ณผ",
               metric="acc", min_n=500, baseline=0.5, pass_threshold=0.70,
               depends_on=["v1๋กœ_ํ›ˆ๋ จ๋œ_๋ชจ๋ธ"])       # โ† ์ „์ด ์ฒด์ธ

# ๋‚˜์ค‘์—: ๋ฐ์ดํ„ฐ์…‹ ์˜ค์—ผ ๋ฐœ๊ฒฌ
mm.retract("ledger.jsonl", "dataset_v1",
           reason="์ „์ฒ˜๋ฆฌ ๋‹จ๊ณ„์—์„œ ํ›ˆ๋ จ/ํ…Œ์ŠคํŠธ 12% ์ค‘๋ณต ๋ฐœ๊ฒฌ")

# Cascade check โ€” ์˜์กด์„ฑ ์ฒด์ธ์„ ์ง์ ‘ ์•Œ ํ•„์š” ์—†์Œ
f = mm.cascade_check("ledger.jsonl", "๋…ผ๋ฌธ_๊ฒฐ๊ณผ")
# โš ๏ธ  [โ‘ซ retraction-cascade] Claim '๋…ผ๋ฌธ_๊ฒฐ๊ณผ' is STALE:
#     depends (transitively) on retracted claim(s): 'dataset_v1'

f = mm.cascade_check("ledger.jsonl", "dataset_v1")
# ๐Ÿ”ด [โ‘ซ retraction-cascade] Claim 'dataset_v1' has been retracted.

cascade_check ๋ ˆ๋ฒจ:

  • FAIL โ€” ์ฃผ์žฅ ์ž์ฒด๊ฐ€ ์ฒ ํšŒ๋จ
  • WARN โ€” ์ฃผ์žฅ์ด STALE (์ „์ด ์˜์กด์„ฑ์ด ์ฒ ํšŒ๋จ)
  • OK โ€” ์ฒ ํšŒ ์œ„ํ—˜ ์—†์Œ

ํ•ต์‹ฌ ํŠน์„ฑ:

  • ์ฒ ํšŒ ์—”ํŠธ๋ฆฌ๋Š” ์ฒด์ธ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค โ€” ์ฒ ํšŒ ๊ธฐ๋ก ์‚ญ์ œ ์‹œ verify_chain()์ด ๊ฐ์ง€. ์กฐ์šฉํžˆ ์ฒ ํšŒ๋ฅผ ์—†์• ๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ์ „ํŒŒ๋Š” ๊ฒŒ์žฌ ์ˆœ์„œ์™€ ๋ฌด๊ด€ โ€” 2019๋…„ ๋ฐ์ดํ„ฐ์…‹์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ 2020๋…„ ๋…ผ๋ฌธ์ด 2024๋…„์— ์ฒ ํšŒ๋˜๋ฉด ์ฆ‰์‹œ STALE๋กœ ํ‘œ์‹œ.
  • audit() ๋‚ด๋ถ€์—์„œ ์ž๋™ ์‹คํ–‰ (WARN/FAIL๋งŒ ์ถ”๊ฐ€).

CLI:

# ์˜์กด์„ฑ ํฌํ•จ ๋“ฑ๋ก
mm register model_v2 --metric acc --min-n 200 --baseline 0.5 --pass 0.60 \
  --depends-on dataset_v1 baseline_eval

# ์ฒ ํšŒ
mm retract dataset_v1 --reason "ํ›ˆ๋ จ/ํ…Œ์ŠคํŠธ ์ค‘๋ณต ๋ฐœ๊ฒฌ"

โ‘ฌ negative_audit

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์„ฑ๊ธ‰ํ•œ ์Œ์„ฑ ์ข…๊ฒฐ (๊ฐ๋„๊ฐ€ ๋ถ€์กฑํ•œ Resolved-Negative)

์—ฐ๊ตฌ์—์„œ ๊ฐ€์žฅ ์žก๊ธฐ ์–ด๋ ค์šด ๊ฑฐ์ง“์Œ์„ฑ: ๋‹จ ํ•œ ๋ฒˆ์˜ ์Œ์„ฑ ์‹คํ—˜ ํ›„ "X๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค" ์„ ์–ธ. ๋‹จ์ผ ์‹คํŒจ๋Š” frame ๊ฒฐํ•จ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ณดํŽธ์  ๋ฒฝ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์Œ์„ฑ ๊ฒฐ๋ก ์ด ์‹ ๋ขฐ๋ฐ›์œผ๋ ค๋ฉด ์—ฌ๋Ÿฌ ๋…๋ฆฝ ๊ฐ๋„๊ฐ€ ์ˆ˜๋ ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

# ๊ฐ ๊ฐ๋„๋Š” ๋™์ผํ•œ ๊ฐ€์„ค์„ ๋‹ค๋ฅธ ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ณ„๊ฐœ์˜ ๋…๋ฆฝ ์‹คํ—˜
# (๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ์…‹, ๋ฐฉ๋ฒ•๋ก , ์กฐ๊ฑด)
for angle_id in ["oee_wave_ode", "oee_bilinear_coev", "oee_alife_sim",
                  "oee_gray_scott", "oee_hp_folding"]:
    mm.preregister("ledger.jsonl", angle_id,
                   metric="oee_score", min_n=50, baseline=0.5, pass_threshold=0.0)

# ๋ชจ๋“  ๊ฐ๋„๊ฐ€ ์Œ์„ฑ์œผ๋กœ ์ˆ˜๋ ดํ•œ ํ›„ โ€” ์ข…๊ฒฐ ๊ฒŒ์ดํŠธ
f = mm.negative_audit("ledger.jsonl",
                      angles=["oee_wave_ode", "oee_bilinear_coev",
                               "oee_alife_sim", "oee_gray_scott", "oee_hp_folding"],
                      min_angles=3)
# โœ… [โ‘ฌ negative-audit] 5/5 independent pre-registered angle(s) verified โ€”
#    negative conclusion is supported.

# ์„ ํƒ์‚ฌํ•ญ: ์Œ์„ฑ ๊ฒฐ๋ก ์ด ๊ณผ๋Œ€ ์ผ๋ฐ˜ํ™”๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๋ฒ”์œ„ ํ™•์ธ
f = mm.negative_audit("ledger.jsonl",
                      angles=["oee_wave_ode", "oee_bilinear_coev", "oee_alife_sim"],
                      conclusion_scope=["all_substrates", "all_ALife"],
                      tested_scope=["in_silico_digital"])
# ๐Ÿ”ด [โ‘ฌ negative-audit] conclusion scope includes untested domain(s):
#    ['all_substrates', 'all_ALife'].

๋ ˆ๋ฒจ:

  • FAIL โ€” min_angles๋ณด๋‹ค ๊ฐ๋„ ์ˆ˜๊ฐ€ ์ ์Œ (์„ฑ๊ธ‰์ข…๊ฒฐ ์œ„ํ—˜)
  • FAIL โ€” ๊ฐ๋„๊ฐ€ ์‚ฌ์ „๋“ฑ๋ก๋˜์ง€ ์•Š์Œ (๋…๋ฆฝ ์ฆ๊ฑฐ๋กœ ์‹ ๋ขฐ ๋ถˆ๊ฐ€)
  • FAIL โ€” conclusion_scope โŠ„ tested_scope (๊ณผ๋Œ€ ์ผ๋ฐ˜ํ™”๋œ ์Œ์„ฑ)
  • WARN โ€” ๊ฐ๋„ ์ˆ˜๋Š” ์ถฉ๋ถ„ํ•˜์ง€๋งŒ ์ผ๋ถ€ ์ฒ ํšŒ๋จ (์•ฝํ™”๋œ ์ผ€์ด์Šค)
  • OK โ€” ์ „์ฒด ํ†ต๊ณผ

CLI:

mm negative \
  --angles oee_wave_ode oee_bilinear_coev oee_alife_sim \
  --min-angles 3

full_audit()๋ฅผ ํ†ตํ•œ ํ™œ์„ฑํ™”:

findings = mm.full_audit("ledger.jsonl", "main_claim", ...,
                          angles=["exp1", "exp2", "exp3"])
# โ‘ฌ ๊ฒฐ๊ณผ๊ฐ€ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋จ

๊ทธ๋ฃน 6 โ€” LLM-as-a-Judge ํ”„๋กœ๋ธŒ โ‘ญโ‘ฎโ‘ฏโ‘ฐ

์ด 4๊ฐœ ํ”„๋กœ๋ธŒ๋Š” ํ‰๊ฐ€๋ฐ›๋Š” ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ ํŒ์ •์ž ์ž์ฒด๋ฅผ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. LLM ํŒ์ •์ž๋Š” ์ˆซ์ž ์ง€ํ‘œ๋กœ๋Š” ์žก์„ ์ˆ˜ ์—†๋Š” ๊ณ ์œ ํ•œ ์‹คํŒจ ํŒจํ„ด์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค: ํ™•๋ฅ ์  ๋’ค์ง‘๊ธฐ, ์œ„์น˜ ํŽธํ–ฅ, ๋Ÿฐ ๊ฐ„ ๋ถˆ์ผ์น˜, ํ‡ดํ™” ์ ์ˆ˜ ๋ถ„ํฌ.

4๊ฐœ ํ”„๋กœ๋ธŒ๋Š” ๋ชจ๋‘ mm.py์— ์žˆ์–ด ์˜์กด์„ฑ์ด ์—†์œผ๋ฉฐ ์ ์ˆ˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ง์ ‘ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์„ ํƒ ๋ชจ๋“ˆ judge.py๋Š” LLM ํ˜ธ์ถœ๊ณผ 4๊ฐœ ํ”„๋กœ๋ธŒ ์—ฐ๊ฒฐ์„ ์ž๋™ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์„ค์น˜:

pip install "measure-mirror[judge]"   # openai ยท anthropic ํŒจํ‚ค์ง€ ์ถ”๊ฐ€

โ‘ญ judge_consistency_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์žฌ์‹คํ–‰ ์‹œ ๋™์ผ ์•„์ดํ…œ์— ๋‹ค๋ฅธ ํŒ์ •์„ ๋‚ด๋ฆฌ๋Š” ํ™•๋ฅ ์  ํŒ์ •์ž.

๋™์ผ ์•„์ดํ…œ์— ํŒ์ •์ž๋ฅผ ๋‘ ๋ฒˆ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ๋’ค์ง‘๊ธฐ ๋น„์œจ์ด ๋†’์œผ๋ฉด ํŒ์ •์ž ์ถœ๋ ฅ์€ ๋…ธ์ด์ฆˆ์ž…๋‹ˆ๋‹ค. ๋…ธ์ด์ฆˆ ์ ์ˆ˜๋กœ ๋งŒ๋“  ์ˆœ์œ„๋Š” ์ง‘๊ณ„ ์ˆ˜์น˜๊ฐ€ ์•ˆ์ •์ ์œผ๋กœ ๋ณด์—ฌ๋„ ์˜๋ฏธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

# score_pairs: [(๋Ÿฐ1 ์ ์ˆ˜, ๋Ÿฐ2 ์ ์ˆ˜), ...] โ€” ๋™์ผ ์•„์ดํ…œ 2ํšŒ ํŒ์ •
# pairwise: 0 = A ์Šน, 1 = B ์Šน
# rating: ์ •์ˆ˜ ์ ์ˆ˜

score_pairs = [(1, 1), (0, 0), (1, 1), (0, 0), (1, 0)]  # 1ํšŒ ๋’ค์ง‘๊ธฐ / 5
f = mm.judge_consistency_check(score_pairs, flip_threshold=0.20)
# โœ… [โ‘ญ judge-consistency] Judge flip rate 20.0% โ‰ค 20.0% (1/5 flips). Consistent.

score_pairs_bad = [(1, 0), (0, 1), (1, 0), (0, 1), (1, 0)]  # ์ „๋ถ€ ๋’ค์ง‘ํž˜
f = mm.judge_consistency_check(score_pairs_bad, flip_threshold=0.20)
# ๐Ÿ”ด [โ‘ญ judge-consistency] Judge flip rate 100.0% > 20.0%.
#    Judge is unreliable โ€” scores cannot be trusted.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
score_pairs ํ•„์ˆ˜ [(๋Ÿฐ1, ๋Ÿฐ2), ...] โ€” ์•„์ดํ…œ๋‹น 1์Œ
flip_threshold 0.20 ํ—ˆ์šฉ ์ตœ๋Œ€ ๋’ค์ง‘๊ธฐ ๋น„์œจ

๋ ˆ๋ฒจ:

  • FAIL โ€” flip_rate > flip_threshold
  • WARN โ€” ๋นˆ score_pairs (ํŒ๋‹จ ๋ถˆ๊ฐ€)
  • OK โ€” flip_rate โ‰ค flip_threshold

โ‘ฎ judge_bias_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๋‚ด์šฉ์— ๊ด€๊ณ„์—†์ด A ๋˜๋Š” B ์œ„์น˜๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ์„ ํ˜ธํ•˜๋Š” ํŒ์ •์ž.

pairwise ํ‰๊ฐ€์—์„œ ํŒ์ •์ž๋Š” ๊ณ ์ •๋œ ์ˆœ์„œ(A, B)๋กœ ์‘๋‹ต์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ํŽธํ–ฅ๋œ ํŒ์ •์ž๋Š” "์ฒซ ๋ฒˆ์งธ ๋‹ต์ด ๋” ์ข‹๋‹ค"๋ผ๋Š” ์ง€๋ฆ„๊ธธ์„ ์”๋‹ˆ๋‹ค. ์ด๋Š” ์„ ํ˜ธ๋˜๋Š” ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•˜๋Š” ํ›„๋ณด๋ฅผ ๋ถ€๋‹นํ•˜๊ฒŒ ์œ ๋ฆฌํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

# pairwise_results: [0, 1, 0, ...] โ€” 0 = A ์Šน, 1 = B ์Šน

results = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]  # 50/50 โ€” ํŽธํ–ฅ ์—†์Œ
f = mm.judge_bias_check(results)
# โœ… [โ‘ฎ judge-bias] Position A win rate 50.0% โ€” no significant position bias.

results = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]  # A๊ฐ€ 90% ์Šน
f = mm.judge_bias_check(results, bias_threshold=0.60)
# ๐Ÿ”ด [โ‘ฎ judge-bias] Position A win rate 90.0% > 60.0%.
#    Strong position bias detected (9/10 items favor A).

์™„ํ™” ๋ฐฉ๋ฒ•: ๊ฐ ์Œ์„ ์–‘๋ฐฉํ–ฅ(AB, BA)์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ  ํ‰๊ท ๋ƒ…๋‹ˆ๋‹ค. ํŽธํ–ฅ ์—†๋Š” ํŒ์ •์ž๋ผ๋ฉด ์ˆœ์„œ ๋ณ€๊ฒฝ ์‹œ ํŒ์ • ๋นˆ๋„๊ฐ€ ๋ฐ˜์ „๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
pairwise_results ํ•„์ˆ˜ [0, 1, ...] โ€” ๋น„๊ต๋‹น 1๊ฒฐ๊ณผ
bias_threshold 0.60 ์œ„์น˜ ํŽธํ–ฅ ํ”Œ๋ž˜๊ทธ ์ž„๊ณ„๊ฐ’

๋ ˆ๋ฒจ:

  • FAIL โ€” A ๋˜๋Š” B ์Šน๋ฅ  > bias_threshold
  • WARN โ€” ๋นˆ results
  • OK โ€” ์–‘๋ฐฉํ–ฅ ์Šน๋ฅ  ๋ชจ๋‘ ์ž„๊ณ„๊ฐ’ ์ดํ•˜

โ‘ฏ inter_rater_agreement

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๋‘ ํŒ์ •์ž(๋˜๋Š” ๋™์ผ ํŒ์ •์ž์˜ ๋‘ ๋Ÿฐ)๊ฐ€ ์šฐ์—ฐ ์ˆ˜์ค€์„ ๋„˜์–ด ๋ถˆ์ผ์น˜.

Cohen's ฮบ๋Š” ๋ฌด์ž‘์œ„ ์šฐ์—ฐ์œผ๋กœ ๊ธฐ๋Œ€๋˜๋Š” ์ผ์น˜ ์ด์ƒ์˜ ์ผ์น˜๋ฅผ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค. ฮบ โ‰ˆ 0์ด๋ฉด ๋‘ ํ‰๊ฐ€์ž๋Š” ์‚ฌ์‹ค์ƒ ๋…๋ฆฝ์ ์ธ ๋‚œ์ˆ˜ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค โ€” ์ ์ˆ˜๋ฅผ ํ‰๊ท ๋‚ด๊ฑฐ๋‚˜ ๋‹จ์ผ ์‹ ํ˜ธ๋กœ ๋ณด๊ณ ํ•˜๋Š” ๊ฒƒ์ด ์˜๋ฏธ๋ฅผ ์žƒ์Šต๋‹ˆ๋‹ค.

ฮบ ํ•ด์„
< 0.20 ๋ถˆ๋Ÿ‰ โ€” ์‚ฌ์‹ค์ƒ ๋ฌด์ž‘์œ„
0.20 โ€“ 0.40 ๋ณดํ†ต
0.40 โ€“ 0.60 ์ค‘๊ฐ„ (๊ธฐ๋ณธ ์ž„๊ณ„๊ฐ’)
0.60 โ€“ 0.80 ์–‘ํ˜ธ
> 0.80 ๊ฑฐ์˜ ์™„๋ฒฝ
# ratings_matrix: [(ํŒ์ •์ž1_์ ์ˆ˜, ํŒ์ •์ž2_์ ์ˆ˜), ...] โ€” ์•„์ดํ…œ๋‹น 1ํ–‰

# ์™„์ „ ์ผ์น˜
matrix = [(1, 1), (0, 0), (1, 1), (0, 0), (1, 1)]
f = mm.inter_rater_agreement(matrix)
# โœ… [โ‘ฏ inter-rater] Cohen's ฮบ=1.000 โ‰ฅ 0.40 โ€” acceptable inter-rater agreement.

# ๋ณดํ†ต ์ผ์น˜ (ฮบ โ‰ˆ 0.33 < 0.40)
matrix = [(0, 0), (0, 1), (0, 0), (1, 1), (1, 0), (1, 1)]
f = mm.inter_rater_agreement(matrix, min_kappa=0.40)
# โš ๏ธ [โ‘ฏ inter-rater] Cohen's ฮบ=0.333 < 0.40 โ€” fair agreement only.

# ๋ถˆ๋Ÿ‰ ์ผ์น˜ โ†’ FAIL
matrix = [(0, 1), (1, 0), (0, 1), (1, 0), (0, 1)]
f = mm.inter_rater_agreement(matrix)
# ๐Ÿ”ด [โ‘ฏ inter-rater] Cohen's ฮบ=-1.000 < 0.20 โ€” poor agreement.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
ratings_matrix ํ•„์ˆ˜ [(r1, r2), ...] โ€” โ‰ฅ 3 ์•„์ดํ…œ ํ•„์š”
min_kappa 0.40 ํ—ˆ์šฉ ์ตœ์†Œ ฮบ

๋ ˆ๋ฒจ:

  • FAIL โ€” ฮบ < 0.20 ๋˜๋Š” ์•„์ดํ…œ < 3
  • WARN โ€” 0.20 โ‰ค ฮบ < min_kappa
  • OK โ€” ฮบ โ‰ฅ min_kappa

โ‘ฐ judge_score_sanity

์žก์•„๋‚ด๋Š” ๊ฒƒ: ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒƒ์— ๋™์ผํ•œ ์ ์ˆ˜๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ํ‡ดํ™” ํŒ์ •์ž.

ํŒ๋ณ„๋ ฅ์ด ์‚ฌ์‹ค์ƒ ์—†๋Š” ํŒ์ •์ž๋Š” ์‹ ํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ์ ์ˆ˜๋กœ ๋งŒ๋“  ์ˆœ์œ„๋Š” ๋ฌด์ž‘์œ„ ์ˆœ์œ„์™€ ๋™๋“ฑํ•ฉ๋‹ˆ๋‹ค.

# scores: [8, 7, 8, 9, ...] โ€” ํ•œ ํŒ์ •์ž์˜ ๋ชจ๋“  ์ ์ˆ˜

# ๊ฑด๊ฐ•ํ•œ ๋ถ„ํฌ
scores = [3, 7, 5, 8, 4, 6, 9, 2, 7, 5, 3, 8, 6, 4, 7]
f = mm.judge_score_sanity(scores)
# โœ… [โ‘ฐ judge-score-sanity] 8 distinct values across 15 scores (53.3% unique).

# ํ‡ดํ™”: ์ „์› ๋™์ 
scores = [8] * 20
f = mm.judge_score_sanity(scores)
# ๐Ÿ”ด [โ‘ฐ judge-score-sanity] All 20 scores identical (8).
#    Judge is not discriminating โ€” scores are meaningless.

# ๊ทผ์‚ฌ ํ‡ดํ™”: 95%๊ฐ€ 8
scores = [8] * 19 + [7]
f = mm.judge_score_sanity(scores)
# โš ๏ธ [โ‘ฐ judge-score-sanity] 95% of scores are '8' โ€” near-degenerate distribution.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
scores ํ•„์ˆ˜ ํ•œ ํŒ์ •์ž ๋Ÿฐ์˜ ๋ชจ๋“  ์ ์ˆ˜ ๋ชฉ๋ก
min_unique_ratio 0.10 ์ด ์ ์ˆ˜ ๋Œ€๋น„ ๊ณ ์œ ๊ฐ’ ๋น„์œจ ์ตœ์†Ÿ๊ฐ’

๋ ˆ๋ฒจ:

  • FAIL โ€” ์ „์› ๋™์ 
  • WARN โ€” ์ตœ๋‹ค ๊ฐ’ ์ง‘์ค‘ > 90% ๋˜๋Š” ๊ณ ์œ  ๋น„์œจ < min_unique_ratio
  • OK โ€” ๋ถ„ํฌ ์–‘ํ˜ธ

โ‘ฑ judge_swap_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ํŒ์ •์ด ์Šฌ๋กฏ์„ ๋”ฐ๋ฅด๊ณ  ๋‚ด์šฉ์„ ๋”ฐ๋ฅด์ง€ ์•Š๋Š” ํŒ์ •์ž โ€” ๊ฐ€์žฅ ์–ด๋ ค์šด ์ผ€์ด์Šค์ธ "๊ฒฐ์ •๋ก ์ ยท๊ท ํ˜•์ ยท๋‚ด์šฉ ๋ฌด์‹œ" ํŒ์ •์ž(๋‹ค๋ฅธ ๋ชจ๋“  ํ”„๋กœ๋ธŒ ํ†ต๊ณผ) ํฌํ•จ.

๊ฐ ์Œ์„ ๋‘ ๋ฒˆ ํŒ์ •ํ•ฉ๋‹ˆ๋‹ค: (A, B) ์ˆœ์„œ๋กœ ํ•œ ๋ฒˆ, ์œ„์น˜๋ฅผ ๊ตํ™˜ํ•œ (B, A)๋กœ ํ•œ ๋ฒˆ. ๋‚ด์šฉ์„ ์ฝ๋Š” ํŒ์ •์ž๋Š” ํŒ์ •์„ ๋’ค์ง‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค โ€” ๊ฐ™์€ ์‘๋‹ต์ด ์–ด๋А ์Šฌ๋กฏ์—์„œ๋“  ์ด๊ฒจ์•ผ ํ•˜๋‹ˆ๊นŒ. ํŒ์ •์ด ์Šฌ๋กฏ์— ๋จธ๋ฌด๋ฅด๋ฉด ๋‚ด์šฉ์ด ์•„๋‹Œ ์œ„์น˜๋ฅผ ์ฝ๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

lock = forward[i] == swapped[i]   (๊ฐ™์€ ์Šฌ๋กฏ์ด ๋‘ ๋ฒˆ ๋‹ค ์Šน๋ฆฌ)

lock_rate โ‰ˆ 0.0  โ†’ ๋‚ด์šฉ ์ฃผ๋„   (ํŒ์ •์ด ์‘๋‹ต์„ ๋”ฐ๋ผ๊ฐ)   โ†’ OK
lock_rate โ‰ˆ 0.5  โ†’ ๋…ธ์ด์ฆˆ      (์–ด๋А ์ชฝ๋„ ์•ˆ ๋”ฐ๋ผ๊ฐ)    โ†’ WARN
lock_rate โ‰ˆ 1.0  โ†’ ์œ„์น˜ ๊ณ ์ฐฉ   (ํŒ์ •์ด ์Šฌ๋กฏ์„ ๋”ฐ๋ผ๊ฐ)   โ†’ FAIL

์Šน๋ฅ  ์ง‘๊ณ„(โ‘ฎ)๋กœ ๋ถ€์กฑํ•œ ์ด์œ  โ€” ์‘๋‹ต์„ ์ „ํ˜€ ์•ˆ ์ฝ๋Š” ๊ฒฐ์ •๋ก ์  ํŒ์ •์ž(์˜ˆ: ํ”„๋กฌํ”„ํŠธ๋งŒ ๋ณด๊ณ  ๊ฒฐ์ •)๋Š” ์™„๋ฒฝํžˆ ์ผ๊ด€๋˜๊ณ (โ‘ญ OK), ์Šน๋ฅ ๋„ ๊ท ํ˜•์ด๊ณ (โ‘ฎ OK), ์ž๊ธฐ ์ž์‹ ๊ณผ ์ผ์น˜ํ•˜๊ณ  (โ‘ฏ ฮบ=1.0), ์ ์ˆ˜๋„ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค(โ‘ฐ OK). ์˜ค์ง ์Šค์™‘๋งŒ์ด ์ •์ฒด๋ฅผ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค:

# ๋‚ด์šฉ ์ฃผ๋„ ํŒ์ •์ž: ์Šค์™‘ํ•˜๋ฉด ๋ชจ๋“  ํŒ์ •์ด ๋’ค์ง‘ํž˜
forward = [0, 1, 0, 1, 0, 1]
swapped = [1, 0, 1, 0, 1, 0]
f = mm.judge_swap_check(forward, swapped)
# โœ… [โ‘ฑ judge-swap] Position-lock rate 0.0% โ‰ค 35.0%. Content-driven.

# ๋‚ด์šฉ ๋ฌด์‹œ ํŒ์ •์ž: ์–‘๋ฐฉํ–ฅ ํŒ์ •์ด ๋™์ผ
forward = [0, 1, 0, 1, 0, 1]
swapped = [0, 1, 0, 1, 0, 1]
f = mm.judge_swap_check(forward, swapped)
# ๐Ÿ”ด [โ‘ฑ judge-swap] Position-lock rate 100.0% > 65.0%.
#    ํŒ์ •์ž๊ฐ€ ๋‚ด์šฉ์ด ์•„๋‹Œ ์œ„์น˜๋ฅผ ์ฝ๊ณ  ์žˆ์Œ.

python examples/demo_judge.py๋ฅผ ์‹คํ–‰ํ•˜๋ฉด mock ๋‚ด์šฉ-๋ฌด์‹œ ํŒ์ •์ž๊ฐ€ โ‘ญโ‘ฎโ‘ฏโ‘ฐ์„ ์ „๋ถ€ ํ†ต๊ณผํ•˜๊ณ  โ‘ฑ์—๋งŒ ์ ๋ฐœ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค โ€” API ํ‚ค ๋ถˆํ•„์š”.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
forward_results ํ•„์ˆ˜ [0, 1, ...] โ€” ์›๋ž˜ (A, B) ์ˆœ์„œ ์Šน์ž
swapped_results ํ•„์ˆ˜ [0, 1, ...] โ€” ๊ตํ™˜๋œ (B, A) ์ˆœ์„œ ์Šน์ž
position_lock_threshold 0.65 lock_rate ์ดˆ๊ณผ ์‹œ FAIL
noise_threshold 0.35 lock_rate ์ดˆ๊ณผ ์‹œ WARN

{0, 1} ๋ฐ–์˜ ๊ฐ’(-1 ํŒŒ์‹ฑ ์‹คํŒจ ๋“ฑ)์ด ํฌํ•จ๋œ ์Œ์€ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค.

๋ ˆ๋ฒจ:

  • FAIL โ€” lock_rate > position_lock_threshold ๋˜๋Š” ๊ธธ์ด ๋ถˆ์ผ์น˜
  • WARN โ€” ๋…ธ์ด์ฆˆ ๋ฐด๋“œ ๋‚ด lock_rate ๋˜๋Š” ์œ ํšจ ์Œ ์—†์Œ
  • OK โ€” lock_rate โ‰ค noise_threshold

judge_run โ€” ์ž๋™ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ (judge.py)

judge_run์€ ์ ์ˆ˜ ์ˆ˜์ง‘์˜ ๋ฒˆ๊ฑฐ๋กœ์›€์„ ์—†์• ์ค๋‹ˆ๋‹ค. judge ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , 4๊ฐœ ํ”„๋กœ๋ธŒ๋ฅผ ์ž๋™์œผ๋กœ ๋ฐœํ™”ํ•œ ๋’ค, ์ฒด์ธ ์—ฐ๊ฒฐ๋œ _type: judge_run ์—”ํŠธ๋ฆฌ๋ฅผ ์›์žฅ์— ๋ด‰์ธํ•ฉ๋‹ˆ๋‹ค.

from measure_mirror.judge import anthropic_judge, openai_judge, judge_run

# 1๋‹จ๊ณ„: judge callable ์ƒ์„ฑ
judge_fn = anthropic_judge(
    model="claude-opus-4-8",
    system_prompt="๋‹น์‹ ์€ ์—„๊ฒฉํ•˜๊ณ  ๊ณต์ •ํ•œ ํ‰๊ฐ€์ž์ž…๋‹ˆ๋‹ค.",
    pairwise=True,   # True = A/B ๋น„๊ต; False = 1-10 ์ ์ˆ˜
)

# 2๋‹จ๊ณ„: ์•„์ดํ…œ ์ค€๋น„
pairs = [
    {"prompt": "๊ฒฝ์‚ฌํ•˜๊ฐ•๋ฒ•์„ ์„ค๋ช…ํ•˜์„ธ์š”",
     "a": "๋ชจ๋ธ A์˜ ์‘๋‹ต", "b": "๋ชจ๋ธ B์˜ ์‘๋‹ต"},
    ...
]

# 3๋‹จ๊ณ„: ์‹คํ–‰ + ์ž๋™ ํ”„๋กœ๋ธŒ
result = judge_run(
    "mm_ledger.jsonl",   # ์›์žฅ ๊ฒฝ๋กœ โ€” ์—”ํŠธ๋ฆฌ๊ฐ€ ์ฒด์ธ ์—ฐ๊ฒฐยท๋ด‰์ธ๋จ
    "my_llm_eval_v1",    # claim_id โ€” ์‚ฌ์ „๋“ฑ๋ก ์—”ํŠธ๋ฆฌ์™€ ์—ฐ๊ฒฐ
    judge_fn=judge_fn,
    items=pairs,
    runs=2,               # ์•„์ดํ…œ๋‹น 2ํšŒ ํ˜ธ์ถœ โ†’ โ‘ญ + โ‘ฏ ํ™œ์„ฑํ™”
    pairwise=True,        # โ‘ฎ ํŽธํ–ฅ ๊ฒ€์‚ฌ ํ™œ์„ฑํ™”
    swap_positions=True,  # ABโ†’BA ์ถ”๊ฐ€ ํŒจ์Šค โ†’ โ‘ฑ ์Šค์™‘ ๊ฒ€์‚ฌ ํ™œ์„ฑํ™”
)

for f in result["findings"]:
    print(f"  {f.level}  [{f.probe}]  {f.msg}")

print(result["scores"])        # ๋Ÿฐ-1 ์ ์ˆ˜ (์•„์ดํ…œ๋‹น 1๊ฐœ)
print(result["score_pairs"])   # (๋Ÿฐ1, ๋Ÿฐ2) ์Œ โ€” runs=1์ด๋ฉด None
print(result["ledger_entry"])  # ๋ด‰์ธ๋œ ์›์žฅ ์—”ํŠธ๋ฆฌ

๋ฐ˜ํ™˜๊ฐ’ ํ‚ค:

ํ‚ค ํƒ€์ž… ๋‚ด์šฉ
findings list[Finding] ํ”„๋กœ๋ธŒ ๊ฒฐ๊ณผ (โ‘ญโ‘ฎโ‘ฏโ‘ฐโ‘ฑ + judge-parse)
scores list[int] ๋Ÿฐ-1 ์›์‹œ ์ ์ˆ˜ (์•„์ดํ…œ๋‹น 1๊ฐœ, -1 ํฌํ•จ ๊ฐ€๋Šฅ)
score_pairs list[tuple] or None (๋Ÿฐ1, ๋Ÿฐ2) ์Œ; runs=1์ด๋ฉด None
swap_scores list[int] or None ๊ตํ™˜ ์ˆœ์„œ ์ ์ˆ˜; swap_positions ์•„๋‹ˆ๋ฉด None
parse_failures int ํŒŒ์‹ฑ ๋ถˆ๊ฐ€๋กœ ์ œ์™ธ๋œ ์•„์ดํ…œ ์ˆ˜
n_items int ํ‰๊ฐ€๋œ ์•„์ดํ…œ ์ˆ˜
runs int ๋ฐ˜๋ณต ํšŸ์ˆ˜
pairwise bool pairwise ๋ชจ๋“œ ์—ฌ๋ถ€
ledger_entry dict ์›์žฅ์— ์ถ”๊ฐ€๋œ ์ฒด์ธ ์—ฐ๊ฒฐ ์—”ํŠธ๋ฆฌ

์กฐ๊ฑด๋ณ„ ํ™œ์„ฑ ํ”„๋กœ๋ธŒ:

ํ”„๋กœ๋ธŒ ์กฐ๊ฑด
โ‘ญ judge_consistency_check runs โ‰ฅ 2์ผ ๋•Œ ํ•ญ์ƒ
โ‘ฎ judge_bias_check pairwise=True์ผ ๋•Œ ํ•ญ์ƒ
โ‘ฏ inter_rater_agreement ์ž๋™๋ฐœํ™” ์•ˆ ํ•จ โ€” ๋‹จ๋… ์ „์šฉ, ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ํŒ์ •์ž์šฉ (๊ฐ™์€ ํŒ์ •์ž ์žฌ์‹คํ–‰์€ โ‘ญ์˜ ๋ชซ)
โ‘ฐ judge_score_sanity ํ•ญ์ƒ
โ‘ฑ judge_swap_check swap_positions=True์ผ ๋•Œ (pairwise ์ „์šฉ)
judge-parse ํŒŒ์‹ฑ ์‹คํŒจ์œจ >10% ์‹œ WARN; ์ „๋ถ€ ์‹คํŒจ ์‹œ FAIL

ํŒŒ์‹ฑ ์‹คํŒจ ์ฒ˜๋ฆฌ โ€” ํŒŒ์‹ฑ ๋ถˆ๊ฐ€ ์‘๋‹ต์€ -1๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. ์–ด๋А ๋Ÿฐ์—์„œ๋“  -1์ด ๋‚˜์˜จ ์•„์ดํ…œ์€ ๋ชจ๋“  ํ”„๋กœ๋ธŒ์—์„œ ์ œ์™ธ๋˜์–ด, ํŒŒ์‹ฑ ๋…ธ์ด์ฆˆ๊ฐ€ โ‘ฎ ํŽธํ–ฅ์ด๋‚˜ โ‘ฐ ๊ฑด์ „์„ฑ ๊ฒฐ๊ณผ๋ฅผ ์™œ๊ณกํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ œ์™ธ ์ˆ˜๋Š” ์›์žฅ ์—”ํŠธ๋ฆฌ(parse_failures)์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.


๊ทธ๋ฃน 7 โ€” ์ˆœ์œ„ ๋ฌด๊ฒฐ์„ฑ โ‘ฒโ‘ณ

ํŒ์ •์ž ํ”„๋กœ๋ธŒ(๊ทธ๋ฃน 6)๋Š” ๊ฐœ๋ณ„ ํŒ์ •์„ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๋‘ ํ”„๋กœ๋ธŒ๋Š” ๊ทธ ํŒ์ •์œผ๋กœ ๋งŒ๋“  ์ˆœ์œ„ ์ž์ฒด๋ฅผ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค โ€” ๋Œ€๋ถ€๋ถ„์˜ ๋ฐœํ‘œ ์ฃผ์žฅ์ด ์‹ค์ œ๋กœ ์‚ฌ๋Š” ๋ฆฌ๋”๋ณด๋“œ ๋ ˆ์ด์–ด์ž…๋‹ˆ๋‹ค.


โ‘ฒ judge_transitivity_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: pairwise ํ† ๋„ˆ๋จผํŠธ์˜ ์ˆœํ™˜ ์„ ํ˜ธ(A>B>C>A) โ€” ์ผ๊ด€๋œ ํ’ˆ์งˆ ์ฒ™๋„๊ฐ€ ์—†๋Š” ํŒ์ •์ž.

ํŒ์ •์ž๊ฐ€ ์…‹ ์ด์ƒ์˜ ๋ชจ๋ธ์„ ์Œ๋ณ„ ๋น„๊ต๋กœ ์ˆœ์œ„ ๋งค๊ธธ ๋•Œ, ์ง‘๊ณ„๋œ ์„ ํ˜ธ๋Š” ์ดํ–‰์  ์ˆœ์„œ๋ฅผ ์ด๋ค„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ˆœํ™˜์ด ์žˆ์œผ๋ฉด ๊ทธ ํŒ์ •์œผ๋กœ ๋งŒ๋“  ๋ฆฌ๋”๋ณด๋“œ๋Š” ๋Œ€์ง„ ์ˆœ์„œ์˜ ์‚ฐ๋ฌผ์ž…๋‹ˆ๋‹ค: ๋ธŒ๋ž˜ํ‚ท์„ ๋‹ค๋ฅธ ์ˆœ์„œ๋กœ ๋Œ๋ฆฌ๋ฉด ๋‹ค๋ฅธ ์ฑ”ํ”ผ์–ธ์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.

# matches: [(๋ชจ๋ธa, ๋ชจ๋ธb, ์Šน์ž), ...] โ€” ์Šน์ž 0 = ์ฒซ์งธ, 1 = ๋‘˜์งธ
# ๊ฐ™์€ ์Œ์˜ ๋ฐ˜๋ณต ๋Œ€์ง„์€ ๋‹ค์ˆ˜๊ฒฐ๋กœ ์ง‘๊ณ„๋ฉ๋‹ˆ๋‹ค.

matches = [("gpt", "claude", 0),     # gpt > claude
           ("claude", "llama", 0),   # claude > llama
           ("gpt", "llama", 0)]      # gpt > llama โ€” ์ดํ–‰์  โœ“
f = mm.judge_transitivity_check(matches)
# โœ… [โ‘ฒ judge-transitivity] 3๊ฐœ ๋ชจ๋ธ ์„ ํ˜ธ ๊ทธ๋ž˜ํ”„๊ฐ€ ๋น„์ˆœํ™˜ โ€” ์ผ๊ด€๋œ ์ˆœ์œ„ ์กด์žฌ.

matches = [("gpt", "claude", 0),
           ("claude", "llama", 0),
           ("llama", "gpt", 0)]      # llama > gpt โ€” ์ˆœํ™˜!
f = mm.judge_transitivity_check(matches)
# ๐Ÿ”ด [โ‘ฒ judge-transitivity] ์ˆœํ™˜ ์„ ํ˜ธ ์ ๋ฐœ: gpt > claude > llama > gpt.
#    ํŒ์ •์ž์—๊ฒŒ ์ผ๊ด€๋œ ํ’ˆ์งˆ ์ฒ™๋„๊ฐ€ ์—†์Œ.

์ •ํ™•ํžˆ ๋™๋ฅ ์ธ ์Œ(์–‘๋ฐฉํ–ฅ ๋™์ˆ˜ ์Šน๋ฆฌ)์€ ์—ฃ์ง€๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์•„ ๊ฑฐ์ง“ ์ˆœํ™˜์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์—†์œผ๋ฉฐ, OK ๋ฉ”์‹œ์ง€์— ์ œ์™ธ๋œ ๋™๋ฅ  ์ˆ˜๊ฐ€ ๋ณด๊ณ ๋ฉ๋‹ˆ๋‹ค.

๋ ˆ๋ฒจ:

  • FAIL โ€” ์ˆœํ™˜ 1๊ฐœ ์ด์ƒ (์˜ˆ์‹œ ๊ฒฝ๋กœ ํ‘œ์‹œ)
  • WARN โ€” ๋ชจ๋ธ 3๊ฐœ ๋ฏธ๋งŒ ๋˜๋Š” ๋Œ€์ง„ ์—†์Œ
  • OK โ€” ์„ ํ˜ธ ๊ทธ๋ž˜ํ”„ ๋น„์ˆœํ™˜

โ‘ณ ranking_stability_check

์žก์•„๋‚ด๋Š” ๊ฒƒ: ์ˆœ์œ„ ์‹ ๊ธฐ๋ฃจ โ€” ๊ฐ™์€ ํฌ๊ธฐ ํ‘œ๋ณธ์„ ๋‹ค์‹œ ๋ฝ‘์œผ๋ฉด ๋’ค์ง‘ํžˆ๋Š” "๋ชจ๋ธ A๊ฐ€ B๋ฅผ ์ด๊น€" ์ฃผ์žฅ.

๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๋ฆฌ์ƒ˜ํ”Œ๋ง: ์•„์ดํ…œ ์ธ๋ฑ์Šค๋ฅผ ๋ณต์›์ถ”์ถœ๋กœ n_bootํšŒ ๋‹ค์‹œ ๋ฝ‘์•„ ๊ด€์ธก๋œ ์Šน์ž๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ž์ฃผ ์Šน์ž๋กœ ์œ ์ง€๋˜๋Š”์ง€ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ์ •๋ก ์ (์‹œ๋“œ ๊ณ ์ • RNG) โ€” ๊ฐ™์€ ์ž…๋ ฅ์€ ํ•ญ์ƒ ๊ฐ™์€ Finding์„ ๋‚ด์–ด ๊ฑฐ์šธ์˜ ์žฌํ˜„์„ฑ ๊ทœ์œจ์„ ์ง€ํ‚ต๋‹ˆ๋‹ค.

# ๊ฐ™์€ ์•„์ดํ…œ์— ๋Œ€ํ•œ ๋‘ ๋ชจ๋ธ์˜ ์•„์ดํ…œ๋ณ„ ์ ์ˆ˜ (์ธ๋ฑ์Šค๋กœ ์ง์ง€์Œ)
scores_a = [9, 8, 9, 9, 8, 9, 8, 9]   # ์ผ๊ด€๋˜๊ฒŒ ๋†’์Œ
scores_b = [3, 2, 3, 2, 3, 2, 3, 2]   # ์ผ๊ด€๋˜๊ฒŒ ๋‚ฎ์Œ
f = mm.ranking_stability_check(scores_a, scores_b)
# โœ… [โ‘ณ ranking-stability] ์ˆœ์œ„ 'A > B'๊ฐ€ 1000ํšŒ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ค‘ 100.0% ์œ ์ง€ (n=8).

scores_a = [5, 9, 1, 8, 2, 7, 3]      # ๊ณ ๋ถ„์‚ฐ,
scores_b = [6, 1, 9, 2, 8, 3, 7]      # ํ•ฉ๊ณ„ ๊ฑฐ์˜ ๋™๋ฅ 
f = mm.ranking_stability_check(scores_a, scores_b)
# ๐Ÿ”ด [โ‘ณ ranking-stability] ์ˆœ์œ„๊ฐ€ 1000ํšŒ ์ค‘ 52.4%๋งŒ ์œ ์ง€ (n=7). ์ˆœ์œ„๋Š” ๋…ธ์ด์ฆˆ.

ํŒŒ๋ผ๋ฏธํ„ฐ:

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
scores_a, scores_b ํ•„์ˆ˜ ์ง์ง€์–ด์ง„ ์•„์ดํ…œ๋ณ„ ์ ์ˆ˜; ๋™์ผ ๊ธธ์ด, โ‰ฅ 5 ์•„์ดํ…œ
n_boot 1000 ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ํšŸ์ˆ˜
seed 0 RNG ์‹œ๋“œ (๊ฒฐ์ •๋ก )
min_stability 0.95 ์š”๊ตฌ๋˜๋Š” ์Šน์ž ์œ ์ง€ ๋น„์œจ

๋ ˆ๋ฒจ:

  • FAIL โ€” ๊ธธ์ด ๋ถˆ์ผ์น˜ ยท ํ•ฉ๊ณ„ ๋™๋ฅ  ยท ์•ˆ์ •์„ฑ < 0.80
  • WARN โ€” ์•„์ดํ…œ 5๊ฐœ ๋ฏธ๋งŒ ยท 0.80 โ‰ค ์•ˆ์ •์„ฑ < min_stability
  • OK โ€” ์•ˆ์ •์„ฑ โ‰ฅ min_stability

์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ ˆํผ๋Ÿฐ์Šค

calibrate (๋ณด์ •)

์ž๊ฐ€ ํ…Œ์ŠคํŠธ: 5๊ฐ€์ง€ ํ•ฉ์„ฑ ์•Œ๋ ค์ง„-์ข‹์Œ/๋‚˜์จ ์ผ€์ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆ. ์‹ค์ œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์‚ฌํ•˜๊ธฐ ์ „์— ๊ฑฐ์šธ ์ž์ฒด์— ํšŒ๊ท€๊ฐ€ ์—†์Œ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

findings = mm.calibrate()
mm.report("๊ฑฐ์šธ ๊ฑด๊ฐ• ์ƒํƒœ", findings)
# โœ… [โš™ calibrate] 5/5 synthetic cases correct โ€” mirror is calibrated.
mm calibrate

witness() ์ „ ๋˜๋Š” CI์—์„œ ๋„๊ตฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์‹คํ–‰.


witness (์ฆ์ธ ์‹คํ–‰)

์ปค๋งจ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ์ถœ๋ ฅ์„ ์บก์ฒ˜ํ•˜๋ฉฐ, ๋ณ€์กฐ ๋ฐฉ์ง€ ์‹คํ–‰ ๊ธฐ๋ก์„ ๋ด‰์ธํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ์ปค๋งจ๋“œ๊ฐ€, ์–ธ์ œ, ์ •ํ™•ํžˆ ๋ฌด์—‡์„ ์ƒ์„ฑํ–ˆ๋Š”์ง€๋ฅผ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.

entry = mm.witness("ledger.jsonl", "my_model",
                   ["python", "evaluate.py", "--model", "my_model"])
# stdout/stderr/returncode๊ฐ€ ๋ฐ”๋€Œ๋ฉด entry["output_hash"]๊ฐ€ ๋‹ฌ๋ผ์ง
# ์—”ํŠธ๋ฆฌ๋Š” ์ฒด์ธ ์—ฐ๊ฒฐ๋จ โ€” ์‚ญ์ œ ์‹œ verify_chain()์ด ๊ฐ์ง€
# CLI: ๋จผ์ € ๋ณด์ • ํ›„ ์‹คํ–‰ ๋ฐ ๋ด‰์ธ (--no-calibrate๋กœ ๋ณด์ • ๊ฑด๋„ˆ๋œ€)
mm run my_model -- python evaluate.py --model my_model

ํ™œ์šฉ: ๊ฒฐ๊ณผ ๊ฒŒ์žฌ ์ „์— ํ‰๊ฐ€ ์Šคํฌ๋ฆฝํŠธ์˜ ์ •ํ™•ํ•œ ์ถœ๋ ฅ์„ ๋ด‰์ธ. ๋ˆ„๊ตฐ๊ฐ€ ์ˆ˜์น˜์— ์˜๋ฌธ์„ ์ œ๊ธฐํ•˜๋ฉด, ์ฆ์ธ ๊ธฐ๋ก์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋ฌด์—‡์„ ์ƒ์„ฑํ–ˆ๋Š”์ง€ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.


retract (์ฒ ํšŒ)

์ฒด์ธ ์—ฐ๊ฒฐ ์ฒ ํšŒ ์—”ํŠธ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์œ„์˜ โ‘ซ cascade_check๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.


certificate (์ธ์ฆ์„œ)

์ฃผ์žฅ์˜ ์ „์ฒด ๋ฌด๊ฒฐ์„ฑ ์ƒํƒœ๋ฅผ ๋…ผ๋ฌธยทREADMEยท๋ฆด๋ฆฌ์Šค ๋…ธํŠธ์— ์‚ฝ์ž… ๊ฐ€๋Šฅํ•œ ํ•˜๋‚˜์˜ ๊ฒ€์ฆ ๊ฐ€๋Šฅ ์‚ฐ์ถœ๋ฌผ๋กœ ์••์ถ•ํ•ด ๋ด‰์ธ๋œ ์ธ์ฆ์„œ๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค.

# ๊ตฌ์กฐ ์ธ์ฆ์„œ (์‚ฌ์ „๋“ฑ๋ก ๋ด‰์ธ + ์ฒด์ธ + ์ฒ ํšŒ ์ƒํƒœ)
cert = mm.certificate("ledger.jsonl", "my_model")

# ์™„์ „ ์ธ์ฆ์„œ โ€” ๊ฐ์‚ฌ ๊ฒฐ๊ณผ๊นŒ์ง€ ํฌํ•จ
findings = mm.audit("ledger.jsonl", "my_model",
                    reported_metric="acc", reported_acc=0.72, n=500)
cert = mm.certificate("ledger.jsonl", "my_model", findings=findings)
mm certify my_model --pretty                  # ๊ตฌ์กฐ๋งŒ
mm certify my_model --acc 0.72 --n 500        # + ๊ฐ์‚ฌ ๊ฒฐ๊ณผ ํฌํ•จ
mm certify my_model | gh gist create -        # ์™ธ๋ถ€ ๊ณต๊ฐœ
ํŒ์ • ๋ฐœ๋™ ์กฐ๊ฑด
REJECTED ์ฒด์ธ ํŒŒ์† ยท ๋ด‰์ธ ๋ณ€์กฐ ยท ์ฒ ํšŒ๋จ ยท FAIL ์กด์žฌ
UNVERIFIED ์‚ฌ์ „๋“ฑ๋ก ์—†์Œ
CERTIFIED-WITH-WARNINGS ์˜ค๋ž˜๋œ ์˜์กด์„ฑ ๋˜๋Š” WARN ์กด์žฌ
CERTIFIED ๋ชจ๋“  ๊ฒ€์‚ฌ ํ†ต๊ณผ

ํ•ต์‹ฌ ์†์„ฑ:

  • ์›์žฅ์˜ anchor_hash๋ฅผ ํฌํ•จ โ€” ์ธ์ฆ์„œ๋Š” ํŠน์ • ์›์žฅ ์ƒํƒœ ํ•˜๋‚˜๋ฅผ ๋ณด์ฆ. ์›์žฅ ๋ณ€๊ฒฝ ํ›„์—๋Š” ์žฌ๋ฐœํ–‰ ํ•„์š”.
  • ์ธ์ฆ์„œ ์ž์ฒด๊ฐ€ ๋ด‰์ธ๋จ(SHA-256) โ€” ํ•„๋“œ ์ˆ˜์ •์€ ์ฆ‰์‹œ ํƒ์ง€.
  • ์›์žฅ์— ์ถ”๊ฐ€๋˜์ง€ ์•Š์Œ; anchor()์ฒ˜๋Ÿผ ์ถœ๋ ฅ ์‚ฐ์ถœ๋ฌผ.

badge (๋ฐฐ์ง€)

์ธ์ฆ์„œ๋ฅผ ์ž„๋ฒ ๋“œ ๊ฐ€๋Šฅํ•œ ๋ฐฐ์ง€๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค โ€” verdict์˜ ์‹œ๊ฐํ™”.

cert = mm.certificate("ledger.jsonl", "my_model")
mm.badge(cert)                 # markdown โ€” README์šฉ shields.io ์ด๋ฏธ์ง€
mm.badge(cert, fmt="svg")      # ์ž์ฒด์™„๊ฒฐ SVG, ์˜คํ”„๋ผ์ธ ์ž‘๋™
mm certify my_model --badge markdown >> README.md
mm certify my_model --badge svg > badge.svg
ํŒ์ • ์ƒ‰์ƒ
CERTIFIED brightgreen
CERTIFIED-WITH-WARNINGS yellow
UNVERIFIED lightgrey
REJECTED red

SVG ๋ฒ„์ „์€ ์ธ์ฆ์„œ seal๊ณผ anchor-hash ์ ‘๋‘์‚ฌ๋ฅผ <title> ํˆดํŒ์— ๋‚ด์žฅํ•ฉ๋‹ˆ๋‹ค โ€” ๋ชจ๋“  ๋ฐฐ์ง€๋Š” ์ž์‹ ์ด ๋ Œ๋”๋งํ•œ ๋ด‰์ธ ์ธ์ฆ์„œ๋กœ ์ถ”์  ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. SVG ํ˜•์‹์€ ์™ธ๋ถ€ ์„œ๋น„์Šค๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.


์›Œํฌํ”Œ๋กœ์šฐ

์›Œํฌํ”Œ๋กœ์šฐ 1: ์ •์งํ•œ ์—ฐ๊ตฌ ๋…ผ๋ฌธ

๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ์—”๋“œ-ํˆฌ-์—”๋“œ ํ๋ฆ„.

from measure_mirror import mm

LEDGER = "experiment_ledger.jsonl"

# โ”€โ”€โ”€ 1. ์‹คํ—˜ ์ „ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
mm.preregister(LEDGER, "bert_sentiment",
               metric="acc",
               min_n=500,
               baseline=0.5,
               pass_threshold=0.70,
               kill_condition="held-out์—์„œ ์ •ํ™•๋„ 0.65 ๋ฏธ๋งŒ",
               kill_threshold={"metric": "acc",
                                "threshold": 0.65,
                                "direction": "below"},
               depends_on=["sst2_dataset_v3"])   # ๋ฐ์ดํ„ฐ์…‹ ์˜์กด์„ฑ

# โ”€โ”€โ”€ 2. ์‹คํ–‰ ๋ฐ ์ฆ์ธ ๊ธฐ๋ก (์„ ํƒ์‚ฌํ•ญ์ด์ง€๋งŒ ๊ถŒ์žฅ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
mm.witness(LEDGER, "bert_sentiment",
           ["python", "train_and_eval.py", "--dataset", "sst2"])

# โ”€โ”€โ”€ 3. ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ ํ›„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
findings = mm.full_audit(
    LEDGER, "bert_sentiment",
    reported_metric="acc", reported_acc=0.78, n=872,
    baseline=0.5,
    competing_name="LogReg ๊ธฐ์ค€์„ ", competing_acc=0.73,     # โ‘ก
    reward_terms=["cross_entropy"],                          # โ‘ข
    train_items=train_ids, test_items=test_ids,              # โ‘ฃa ๋ˆ„์ˆ˜
    seed_results=[0.77, 0.78, 0.79],                         # โ‘ค
    claimed_scope=["sentiment"],                             # โ‘ฅ
    tested_scope=["sst2", "yelp_polarity"],
    min_detectable_effect=0.03,                              # โ‘ง
    check_multiplicity=True,                                 # โ‘จ
)
mm.report("BERT ๊ฐ์„ฑ๋ถ„์„", findings)

# โ”€โ”€โ”€ 4. ์ œ์ถœ ์ „ ์•ต์ปค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import subprocess
subprocess.run(["mm", "anchor", "--pretty"], check=True)
# โ†’ gist, s3, dropbox์— ํŒŒ์ดํ”„ํ•˜์—ฌ ํƒ€์ž„์Šคํƒฌํ”„๋œ ์™ธ๋ถ€ ์ฆ๋ช… ์ƒ์„ฑ

์›Œํฌํ”Œ๋กœ์šฐ 2: pytest๋ฅผ ์ด์šฉํ•œ CI ๊ฒŒ์ดํŠธ

# conftest.py
pytest_plugins = ["measure_mirror.pytest_plugin"]

# test_eval_integrity.py
from measure_mirror import mm
from measure_mirror.pytest_plugin import assert_clean

LEDGER = "production_ledger.jsonl"

def test_model_integrity():
    findings = mm.audit(LEDGER, "prod_model_v3",
                        reported_metric="acc", reported_acc=0.78, n=1000)
    assert_clean(findings)   # FAIL findings โ†’ pytest ์‹คํŒจ โ†’ CI ๋นจ๊ฐ„๋ถˆ

def test_ledger_chain():
    findings = mm.verify_chain(LEDGER)
    assert_clean(findings)

def test_mirror_health():
    findings = mm.calibrate()
    assert_clean(findings)

์›Œํฌํ”Œ๋กœ์šฐ 3: Resolved-Negative ๊ฒฐ๋ก  ์ข…๊ฒฐ

LEDGER = "oee_research_ledger.jsonl"

# ๊ฐ ๋…๋ฆฝ ๊ฐ๋„๋ฅผ ์‹คํ—˜ ์ „ ๋“ฑ๋ก
angles = [
    ("oee_angle_wave",     "wave ODE โ€” ์—ฐ์† ์žฅ"),
    ("oee_angle_bilinear", "bilinear ๊ณต์ง„ํ™”"),
    ("oee_angle_alife",    "๋””์ง€ํ„ธ ALife (DISHTINY ์Šคํƒ€์ผ)"),
    ("oee_angle_folding",  "HP ๋‹จ๋ฐฑ์งˆ ํด๋”ฉ ๊ฒฝ๊ด€"),
    ("oee_angle_gs",       "Gray-Scott ๋ฐ˜์‘-ํ™•์‚ฐ"),
]
for cid, desc in angles:
    mm.preregister(LEDGER, cid,
                   metric="oee_score", min_n=50, baseline=0.5, pass_threshold=0.0,
                   kill_condition=f"{desc}: OEE๊ฐ€ ์ž„๊ณ„๊ฐ’ ์ด์ƒ")

# ... 5๊ฐœ ์‹คํ—˜ ๋ชจ๋‘ ์‹คํ–‰, ๋ชจ๋‘ ์Œ์„ฑ์œผ๋กœ ์ˆ˜๋ ด ...

# ์Œ์„ฑ ์ข…๊ฒฐ ๊ฒŒ์ดํŠธ
f = mm.negative_audit(LEDGER,
                      angles=[cid for cid, _ in angles],
                      min_angles=3,
                      conclusion_scope=["in_silico_์ž์ƒ_OEE"],
                      tested_scope=["digital_field", "alife_sim",
                                    "protein_HP", "reaction_diffusion"])
mm.report("OEE Resolved-Negative", [f])

# ์ตœ์ข… ์Œ์„ฑ ๊ฒฐ๋ก  ์•ต์ปค
import subprocess, json
snap = json.loads(subprocess.check_output(["mm", "anchor", "--ledger", LEDGER]))
print(f"์•ต์ปค๋จ: {snap['anchor_hash'][:12]}...")

์›Œํฌํ”Œ๋กœ์šฐ 4: ์ฒ ํšŒ ๋ฐ cascade ์ •๋ฆฌ

LEDGER = "shared_lab_ledger.jsonl"

# โ”€โ”€โ”€ ๊ธฐ์ค€ ๋ฐ์ดํ„ฐ์…‹์—์„œ ์˜ค์—ผ ๋ฐœ๊ฒฌ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
mm.retract(LEDGER, "imagenet_baseline_v1",
           reason="์ „์ฒ˜๋ฆฌ ๋‹จ๊ณ„์—์„œ ํ›ˆ๋ จ/ํ…Œ์ŠคํŠธ 12% ์ค‘๋ณต ๋ฐœ๊ฒฌ")

# โ”€โ”€โ”€ ์ด์ œ ์˜ค๋ž˜๋œ(STALE) ๊ฒŒ์žฌ ๊ฒฐ๊ณผ ํ™•์ธ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
published_claims = ["vit_paper_2023", "resnet_ablation", "downstream_nlp"]

for claim_id in published_claims:
    f = mm.cascade_check(LEDGER, claim_id)
    if f.level != "OK":
        print(f"โš ๏ธ  {claim_id}: {f.msg}")

# ์ถœ๋ ฅ:
# โš ๏ธ  vit_paper_2023: Claim 'vit_paper_2023' is STALE: depends (transitively)
#     on retracted claim(s): 'imagenet_baseline_v1'
# โš ๏ธ  downstream_nlp: Claim 'downstream_nlp' is STALE: ...

์›Œํฌํ”Œ๋กœ์šฐ 5: MCP ์—์ด์ „ํŠธ ์—ฐ๋™

MCP ํ˜ธํ™˜ AI(Claude Code, Cursor, Windsurf ๋“ฑ)๊ฐ€ ๋ชจ๋“  ํ”„๋กœ๋ธŒ๋ฅผ ๋Œ€ํ™” ์ค‘์— ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ์ฃผ์žฅ์„ ๊ฐ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// .mcp.json
{
  "mcpServers": {
    "measure-mirror": {
      "command": "python",
      "args": ["-m", "measure_mirror.mcp_server"],
      "cwd": "/path/to/measure-mirror"
    }
  }
}

์—์ด์ „ํŠธ ๋Œ€ํ™” ์˜ˆ์‹œ:

์‚ฌ์šฉ์ž: "๋‚ด ๋ชจ๋ธ์ด n=200์—์„œ SQuAD 87.3% ๋‚˜์™”์–ด์š”. ๋ฏฟ์„ ์ˆ˜ ์žˆ๋‚˜์š”?"

์—์ด์ „ํŠธ: [mm_register, mm_audit ํ˜ธ์ถœ]
โ†’ โš ๏ธ  [โ‘ฃa small-sample CI] n=200, acc=0.873 โ†’ 95%CI [0.820, 0.915]
   ๊ธฐ์ค€์„ (0.5) ์ดˆ๊ณผ. OK.
โ†’ โš ๏ธ  [โ‘ฆ too-good] ๊ธฐ์ค€์„  ๋Œ€๋น„ ฮ”=+0.373 โ€” ์˜์‹ฌ์Šค๋Ÿฝ๊ฒŒ ํผ.
   ์กฐ์‚ฌ: ๋ฐ์ดํ„ฐ ๋ˆ„์ˆ˜? ๋ณด์ƒ ํ•ดํ‚น?

[mm_grim_check ํ˜ธ์ถœ: reported_acc=0.873, n=200]
โ†’ ๐Ÿ”ด FAIL โ€” n=200์—์„œ acc=0.873์„ ๋งŒ์กฑํ•˜๋Š” ์ •์ˆ˜ k ์—†์Œ.
   round(175/200, 3) = 0.875 โ‰  0.873.

"87.3%์€ n=200์—์„œ GRIM ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ’์ž…๋‹ˆ๋‹ค. n ๋˜๋Š” acc ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ž˜๋ชป ๋ณด๊ณ ๋์Šต๋‹ˆ๋‹ค."

๋น ๋ฅธ ์ฐธ์กฐํ‘œ

# ํ•จ์ˆ˜ ์žก์•„๋‚ด๋Š” ๊ฒƒ audit() ์ž๋™ ์‹คํ–‰?
โ‘  preregister/audit ์ง€ํ‘œ ๊ต์ฒด, min_n, pass ๊ธฐ์ค€, ๋ด‰์ธ ์œ„๋ณ€์กฐ โœ…
โ‘  verify_chain ์—”ํŠธ๋ฆฌ ์‚ญ์ œ/์‚ฝ์ž…/์œ„๋ณ€์กฐ ์ˆ˜๋™
โ‘ก baseline_fairness ํ—ˆ์•ฝ/๋™์ /์—ญ์ „๋œ ๊ธฐ์ค€์„  full_audit
โ‘ข gaming_check ์ง€ํ‘œ๊ฐ€ ๋ณด์ƒ/์†์‹ค์— ์ง์ ‘ ํฌํ•จ full_audit
โ‘ฃa Wilson CI (๋‚ด๋ถ€) ์†Œํ‘œ๋ณธ ์šฐ์—ฐ ์ˆ˜์ค€ ๊ฒฐ๊ณผ โœ…
โ‘ฃa direction (๋‚ด๋ถ€) ๊ธฐ์ค€์„ ๋ณด๋‹ค ๋‚˜์จ (์—ญ์‹ ํ˜ธ) โœ…
โ‘ฃa leakage_check ํ›ˆ๋ จโˆฉํ…Œ์ŠคํŠธ ์ค‘๋ณต full_audit
โ‘ค multiseed_check ๋ถˆ์•ˆ์ •ํ•œ ์‹œ๋“œ, ๊ธฐ์ค€์„ ์ด ๋ฒ”์œ„ ์•ˆ full_audit
โ‘ฅ scope_check ๊ณผ๋Œ€ ์ผ๋ฐ˜ํ™”๋œ ์ฃผ์žฅ full_audit
โ‘ฆ too_good_check ์˜์‹ฌ์Šค๋Ÿฝ๊ฒŒ ํฐ ฮ” full_audit
โ‘ง power_check n์ด ํšจ๊ณผ ํƒ์ง€์— ๋ถ€์กฑ full_audit
โ‘จ multiple_comparisons_check k>1 ์‹คํ—˜ Bonferroni ๊ฒฝ๋ณด full_audit
โ‘ฉ grim_check ์‚ฐ์ˆ  ๋ถˆ๊ฐ€๋Šฅ ์ˆ˜์น˜ โœ… (FAIL๋งŒ)
โ‘ช falsifiability_check kill-condition ์—†์Œ; ๋ฐœํ™”๋œ kill threshold โœ… (์‚ฌ์ „๋“ฑ๋ก ์œ ํšจ ์‹œ)
ใ‰— prereg_lint ๊ธฐํ˜• ๋ด‰์ธ: kill-condition ๋ˆ„์ˆ˜, ์„ ์–ธ์šฐ์—ฐ ์ดํ•˜ ๋ฐ”, ๋น„๊ตฌ์กฐํ™” kill, ์ € n, ์ฒดํฌ ๋ฏธ์„ ์–ธ ๋…๋ฆฝ ์‹คํ–‰ (์—ฐ์‚ฐ ์ „; MCP mm_register์—์„œ ์ž๋™)
โ‘ซ cascade_check ์ฒ ํšŒ๋œ ์ฃผ์žฅ ๋˜๋Š” ์˜ค๋ž˜๋œ ์˜์กด์„ฑ โœ… (WARN/FAIL๋งŒ)
โ‘ฌ negative_audit ์„ฑ๊ธ‰ํ•œ ์Œ์„ฑ ์ข…๊ฒฐ; ๋ฒ”์œ„ ์ดˆ๊ณผ full_audit(angles=...)
โ‘ญ judge_consistency_check LLM ํŒ์ •์ž ๋’ค์ง‘๊ธฐ์œจ ์ดˆ๊ณผ (์‹ ๋ขฐ ๋ถˆ๊ฐ€ ํŒ์ •์ž) ๋‹จ๋…
โ‘ฎ judge_bias_check ํŒ์ •์ž๊ฐ€ A ๋˜๋Š” B ์œ„์น˜๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ์„ ํ˜ธ ๋‹จ๋…
โ‘ฏ inter_rater_agreement Cohen's ฮบ ๋ฏธ๋‹ฌ (ํ‰๊ฐ€์ž ๊ฐ„ ์ผ์น˜๋„ ๋ถ€์กฑ) ๋‹จ๋…
โ‘ฐ judge_score_sanity ํŒ์ •์ž๊ฐ€ ๋™์ผ/๊ทผ์‚ฌ ์ ์ˆ˜๋งŒ ๋ถ€์—ฌ (ํ‡ดํ™” ๋ถ„ํฌ) ๋‹จ๋…
โ‘ฑ judge_swap_check ํŒ์ •์ด ๋‚ด์šฉ ์•„๋‹Œ ์Šฌ๋กฏ์„ ๋”ฐ๋ฆ„ (ABโ†’BA ์Šค์™‘) judge_run(swap_positions=True)
โ‘ฒ judge_transitivity_check ํ† ๋„ˆ๋จผํŠธ์˜ A>B>C>A ์ˆœํ™˜ ์„ ํ˜ธ ๋‹จ๋… / mm judge
โ‘ณ ranking_stability_check ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๋ฆฌ์ƒ˜ํ”Œ๋ง์—์„œ ๋’ค์ง‘ํžˆ๋Š” ์ˆœ์œ„ ๋‹จ๋… / mm judge
โ€” anchor ์›์žฅ ํŒŒ์ผ ์™„์ „ ๊ต์ฒด ์ˆ˜๋™ (๊ฒŒ์žฌ ์ „)
โ€” calibrate ๊ฑฐ์šธ ์ž์ฒด์˜ ํšŒ๊ท€ ์ˆ˜๋™ (witness ์ „)
โ€” witness ์‹คํ–‰ ๊ธฐ๋ก: ๋ฌด์—‡์ด ์‹คํ–‰๋๊ณ , ์–ธ์ œ, ์ถœ๋ ฅ ํ•ด์‹œ ์ˆ˜๋™
โ€” retract ์ฒ ํšŒ ๊ธฐ๋ก ์ƒ์„ฑ (์ฒด์ธ ์—ฐ๊ฒฐ) ์ˆ˜๋™
โ€” certificate ์ฃผ์žฅ๋‹น ๋ด‰์ธ๋œ ํŒ์ • ์‚ฐ์ถœ๋ฌผ (anchor ๊ณ ์ •) ์ˆ˜๋™ (๊ฒŒ์žฌ ์ „)
โ€” badge ์ž„๋ฒ ๋“œ ๊ฐ€๋Šฅํ•œ verdict ๋ฐฐ์ง€ (markdown / SVG) ์ˆ˜๋™ (mm certify --badge)

์ฝ”๋“œ๋ฒ ์ด์Šค ์ „์ฒด ์‹ฌ๊ฐ๋„ ์ •์ฑ…:

  • FAIL โ€” ํ•˜๋“œ ์Šคํ†ฑ; ๊ฒฐ๊ณผ๊ฐ€ ๋ฌดํšจ์ด๊ฑฐ๋‚˜ ์ž๊ธฐ๋ชจ์ˆœ
  • WARN โ€” ์ฃผ์˜ ํ•„์š”; ๊ฒฐ๊ณผ๊ฐ€ ์œ ํšจํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ฒ€ํ†  ํ•„์š”
  • OK โ€” ์ด ํ•ญ๋ชฉ์€ ํ†ต๊ณผ; ์ด ์ฐจ์›์€ ๊นจ๋—ํ•จ

์œ„ํ˜‘ ๋ชจ๋ธ โ€” ์ธก์ •๊ฑฐ์šธ์ด ๋ชป ์žก๋Š” ๊ฒƒ

์ธก์ •๊ฑฐ์šธ์„ ๊ณ ์˜ ์กฐ์ž‘์— ๋Œ€ํ•ด ๋ ˆ๋“œํŒ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์„ค๊ณ„์ƒ ์ง‘๋‹ˆ๋‹ค. ์ด ์„น์…˜์€ ๊ทธ ํŒจ๋ฐฐ์˜ ์ •์งํ•œ ์ง€๋„์ž…๋‹ˆ๋‹ค โ€” OK๋ฅผ ๋ฏฟ๊ธฐ ์ „์— ์ฝ์œผ์„ธ์š”.

๊ทผ๋ณธ ํ•œ๊ณ„: ์ธก์ •๊ฑฐ์šธ์€ ๋‚ด์  ์ผ๊ด€์„ฑ์„ ๊ฒ€์‚ฌํ•˜์ง€ ์›์ฒœ ์ง„์‹ค์„ฑ์„ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณด๊ณ ๋œ ์ˆซ์ž๋“ค์ด ์„œ๋กœ/์‚ฐ์ˆ ๊ณผ ๋ชจ์ˆœ๋˜์ง€ ์•Š๋Š”์ง€๋Š” ๊ฒ€์ฆํ•˜์ง€๋งŒ, ๊ทธ ์ˆซ์ž๊ฐ€ ์ง„์งœ ์‹คํ—˜์—์„œ ๋‚˜์™”๋Š”์ง€๋Š” ๊ฒ€์ฆ ๋ชป ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์ˆซ์ž๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์ง€์–ด๋‚ด๋ฉด ์ „์ฒด ๊ฐ์‚ฌ๊ฐ€ OK๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑด ๊ณ ์น  ์ˆ˜ ์žˆ๋Š” ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค โ€” ์กฐ์ž‘์ด ๋‚ด์ ์œผ๋กœ ์ผ๊ด€๋˜๋ฉด ์–ด๋–ค ๋„๊ตฌ๋„ ๋ณด๊ณ ๋œ ์ˆซ์ž๋งŒ์œผ๋กœ๋Š” ์‚ฌ๊ธฐ๋ฅผ ํƒ์ง€ ๋ชป ํ•ฉ๋‹ˆ๋‹ค.

๊ฒ€์ฆ๋œ ๋ ˆ๋“œํŒ€ ๊ฒฐ๊ณผ:

๊ณต๊ฒฉ ๊ฒฐ๊ณผ
GRIM ํ†ต๊ณผํ•˜๋Š” ๊ฐ€์งœ ํ‰๊ท  (ํฐ n, ๊ฐ’์„ k/N์— ์Šค๋ƒ…) ํ†ต๊ณผ โ€” GRIM์€ ์†ŒN ๊ฒŒ์ดํŠธ(โ‘ฉ ๋ฒ”์œ„ ์ฐธ์กฐ)
์™„์ „ ์กฐ์ž‘ ์—ฐ๊ตฌ: ์‚ฌ์ „๋“ฑ๋กยทํฐ nยท๊ณต์ • baselineยทkill-conditionยท์•ˆ์ • ์‹œ๋“œ full_audit OK ํ†ต๊ณผ โ€” ๋ชจ๋“  ์ˆซ์ž๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์ง€์–ด๋ƒ„
๊ฑฐ์ง“๋งํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ witness() ํ†ต๊ณผ โ€” witness๋Š” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋ผ ์ด ์ถœ๋ ฅ์„ ๋ƒˆ๋‹ค๋ฅผ ๋ด‰์ธํ•˜์ง€, ์ถœ๋ ฅ์ด ์ง„์‹ค์ž„์„ ๋ด‰์ธ ๋ชป ํ•จ

๋ชจ๋“  ๋ฐฉ์–ด(์‚ฌ์ „๋“ฑ๋กยทauditยทwitnessยทanchor)๋Š” ๋ฐ์ดํ„ฐยท์ฝ”๋“œ๊ฐ€ ์ •์งํ•˜๋‹ค๋Š” ๊ฐ€์ • ์œ„์— ์„ญ๋‹ˆ๋‹ค. ์ธก์ •๊ฑฐ์šธ์€ *๊ธฐ๋ก(paper trail)*์„ ๋‹จ๋‹จํ•˜๊ฒŒ ํ•˜์ง€ ๊ทผ์ €์˜ ์ง„์‹ค์„ ๋‹จ๋‹จํ•˜๊ฒŒ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜๋„ ์‚ฌ๋Š” ๊ฒƒ โ€” ์‚ฌ๊ธฐ๊ฐ€ ๊ฒธ์†ํ•˜๋„๋ก ๊ฐ•์š”๋ฉ๋‹ˆ๋‹ค. ํ†ต๊ณผํ•˜๋ ค๋ฉด ๊ฑฐ์ง“๋ง์ด ๊ฒธ์†ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์š•์‹ฌ์„ ๋ถ€๋ฆฌ๋ฉด ํ”„๋กœ๋ธŒ๊ฐ€ ์žก์Šต๋‹ˆ๋‹ค:

์š•์‹ฌ๋‚ธ ๊ฑฐ์ง“๋ง ์žก๋Š” ํ”„๋กœ๋ธŒ
baseline ๋Œ€๋น„ ์˜์‹ฌ์Šค๋Ÿฝ๊ฒŒ ํฐ ฮ” โ‘ฆ too_good_check
์ž‘์€ ํ‘œ๋ณธ โ‘ฉ grim_check, โ‘ฃa Wilson CI
๋ถˆ์•ˆ์ •/๋น„ํ˜„์‹ค์ ์œผ๋กœ ์™„๋ฒฝํ•œ ์‹œ๋“œ โ‘ค multiseed_check
๊ฒ€์ฆ๋ณด๋‹ค ๋„“์€ ์ฃผ์žฅ โ‘ฅ scope_check

๊ทธ๋Ÿฌ๋‹ˆ ์ธก์ •๊ฑฐ์šธ์˜ ์ง„์งœ ์ผ์€ ์ž‘์ •ํ•œ ์‚ฌ๊ธฐ๋ฅผ ์žก๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ(๋“œ๋ฌผ๊ณ  ํ’€ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ) ์ •์งํ•œ ์ž๊ธฐ๊ธฐ๋งŒ์„ ์žก๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค โ€” p-ํ•ดํ‚น, ์ฒด๋ฆฌํ”ฝ, ์†Œn ๊ณผ์‹ , ์„ฑ๊ธ‰ํ•œ ์ข…๊ฒฐ. ์ด๊ฒŒ ์—ฐ๊ตฌ ๋ถ€์ •์ง์˜ ํ”ํ•œ ํ˜•ํƒœ์ด๊ณ , ๋„๊ตฌ๊ฐ€ ๊ทธ๊ฑธ ์žก์Šต๋‹ˆ๋‹ค. OK๋Š” *"์ˆซ์ž๊ฐ€ ๋‚ด์ ์œผ๋กœ ์ •์งํ•˜๊ณ  ๊ณผ๋Œ€ํ•˜์ง€ ์•Š๋‹ค"*๋กœ ์ฝ์œผ์„ธ์š”, ์ ˆ๋Œ€ *"์ด๊ฑด ์ง„์‹ค์ด๋‹ค"*๋กœ ์ฝ์ง€ ๋งˆ์„ธ์š”.

(์ด ์ง€๋„๋Š” ๋„๊ตฌ๊ฐ€ ๊นจ์งˆ ๋•Œ๊นŒ์ง€ ๋ ˆ๋“œํŒ€ํ•ด์„œ ๊ทธ๋ ธ์Šต๋‹ˆ๋‹ค โ€” ์šฐ๋ฆฌ๊ฐ€ ๋„๊ตฌ์— ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์ธก์ •๊ฑฐ์šธ๋‹ค์šด ์ผ์ด์—ˆ์Šต๋‹ˆ๋‹ค.)


์Šค์Šค๋กœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ •์งํ•˜๊ฒŒ ์ฃฝ์ด๋Š” ๊ณผ์ •์—์„œ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ œ์ž‘์ž๋“ค์ด ์ž์‹ ๋“ค์—๊ฒŒ ๋จผ์ € ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
โ†’ ๊ฐœ๋ฐœ ์—ฐ๋Œ€๊ธฐ