File size: 5,576 Bytes
a248495 97cf40f a248495 97cf40f a248495 97cf40f a248495 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | #!/usr/bin/env python3
"""
κ°μ μλ λ°μ΄ν°μ
(seed_dataset.json)μΌλ‘ Rule μΆλ ₯μ ν©λ¦¬μ± κ²μ¦.
μ°μΆ:
1. Rule μΆλ ₯ vs μμ± λΌλ²¨: ROC-like ν΅κ³ (μμ± νκ· score vs λΉμμ± νκ· score)
2. νλ₯΄μλλ³ Top 5 Rule Intent λΆν¬ β μλλ Intentκ° μμμ μ€λμ§
3. Ruleμ΄ μ μλ Intentμ baseline(0.05)λ§ λ°ννλ Intent λΉμ¨
μ€ν:
cd roadshow-server-v3
python scripts/analyze_rule_validation.py
"""
from __future__ import annotations
import json
import sys
from collections import Counter, defaultdict
from pathlib import Path
from statistics import mean
sys.path.insert(0, str(Path(__file__).parent.parent))
from data.executor import init_db, get_executor # noqa: E402
from data.seed import load_intents_catalog # noqa: E402
from core.engines import config, formula # noqa: E402
_CS_RULE_SPEC = config.get_rule_spec("cs-myk-v3")
def _load_dataset(path: Path) -> list[dict]:
with open(path, encoding="utf-8") as f:
return json.load(f)["samples"]
def _score_with_rules(features: dict, intents: list[dict]) -> dict[str, float]:
f = dict(features)
if isinstance(f.get("κ²°ν© μ¬λΆ"), bool):
f["κ²°ν© μ¬λΆ"] = 1 if f["κ²°ν© μ¬λΆ"] else 0
out = {}
for intent in intents:
if intent["inference_type"] != "Rule":
continue
out[intent["id"]] = formula.rule_predict(_CS_RULE_SPEC, intent["id"], f)
return out
def main() -> None:
init_db()
intents = load_intents_catalog()
intent_meta = {i["id"]: i for i in intents}
rule_intent_ids = [i["id"] for i in intents if i["inference_type"] == "Rule"]
dataset_path = Path(__file__).parent.parent / "scenarios" / "cs-myk-v3" / "seed_dataset.json"
samples = _load_dataset(dataset_path)
# ββ 1. μμ±/λΉμμ± νκ· score λΉκ΅ ββββββββββββββββββββββββ
print("=" * 72)
print(" Rule κ²μ¦: μμ± λΌλ²¨ vs λΉμμ± λΌλ²¨μ νκ· Rule score")
print("=" * 72)
pos_scores: dict[str, list[float]] = defaultdict(list)
neg_scores: dict[str, list[float]] = defaultdict(list)
for s in samples:
scores = _score_with_rules(s["batch_features"], intents)
positives = set(s["intent_labels"].keys())
for iid, sc in scores.items():
if iid in positives:
pos_scores[iid].append(sc)
else:
neg_scores[iid].append(sc)
# μμ± λΉλκ° μΆ©λΆν Intentλ§
rows = []
for iid in rule_intent_ids:
pos = pos_scores.get(iid, [])
neg = neg_scores.get(iid, [])
if len(pos) < 5:
continue
pos_mean = mean(pos)
neg_mean = mean(neg) if neg else 0.0
rows.append((iid, len(pos), pos_mean, neg_mean, pos_mean - neg_mean))
rows.sort(key=lambda r: r[4], reverse=True)
print(f"\n {'Intent':10s} {'n(+)':>5s} {'pos_avg':>8s} {'neg_avg':>8s} {'gap':>7s} L3")
print(f" {'-'*10} {'-'*5} {'-'*8} {'-'*8} {'-'*7} {'-'*30}")
good, bad = 0, 0
for iid, n_pos, p, n, gap in rows[:30]:
name = intent_meta[iid]["name"]
flag = "β" if gap > 0.05 else ("Β·" if gap > 0 else "β")
if gap > 0.05: good += 1
elif gap <= 0: bad += 1
print(f" {iid:10s} {n_pos:>5d} {p:>8.3f} {n:>8.3f} {gap:>+7.3f} {flag} {name}")
print(f"\n (μ 체 {len(rows)}κ° μ€) Ruleμ΄ μμ±μ μ ꡬλΆ: {good}κ°, μμ±βλΉμμ± κ²©μ°¨ λΆμ‘± λλ μμ : {bad}κ°")
# ββ 2. νλ₯΄μλλ³ Rule Top 5 μΌμΉλ βββββββββββββββββββββββ
print("\n" + "=" * 72)
print(" νλ₯΄μλλ³ Rule Top 5 Intent β μλλ Intentκ° μμμ μ€λκ°?")
print("=" * 72)
persona_top: dict[str, Counter] = defaultdict(Counter)
persona_labels: dict[str, Counter] = defaultdict(Counter)
persona_hits: dict[str, list[int]] = defaultdict(list)
for s in samples:
scores = _score_with_rules(s["batch_features"], intents)
top5 = [iid for iid, _ in sorted(scores.items(), key=lambda kv: kv[1], reverse=True)[:5]]
positives = set(s["intent_labels"].keys())
hit = sum(1 for iid in top5 if iid in positives)
pid = s["persona_id"]
persona_hits[pid].append(hit)
for iid in top5:
persona_top[pid][iid] += 1
for iid in positives:
persona_labels[pid][iid] += 1
for pid in sorted(persona_top.keys()):
top_intents = persona_top[pid].most_common(5)
avg_hit = mean(persona_hits[pid]) if persona_hits[pid] else 0
top_labels = persona_labels[pid].most_common(5)
print(f"\n [{pid}] νκ· Top5 hit (μμ±κ³Ό μΌμΉ): {avg_hit:.2f}/5")
print(f" Rule Top5 : {', '.join(f'{iid}({c})' for iid, c in top_intents)}")
print(f" μμ± Top5 : {', '.join(f'{iid}({c})' for iid, c in top_labels)}")
# ββ 3. Rule μ μ vs baseline-only λΆν¬ ββββββββββββββββββββ
print("\n" + "=" * 72)
print(" Rule μ μ ν΅κ³")
print("=" * 72)
defined = len(_CS_RULE_SPEC)
rule_n = len(rule_intent_ids)
print(f"\n Rule Intent μ΄: {rule_n} (Model μ μΈ)")
print(f" λͺ
μμ Rule ν¨μ μ μ: {defined}κ°")
print(f" baseline(0.05)λ§ λ°ν: {rule_n - defined}κ°")
print(f" β ν₯ν ~{rule_n - defined}κ° Intentμ λͺ
μμ Rule μΆκ° λλ Modelλ‘ μ ν κ²ν ")
if __name__ == "__main__":
main()
|