| |
| """ |
| κ°μ μλ λ°μ΄ν°μ
(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 |
| from data.seed import load_intents_catalog |
| from core.engines import config, formula |
|
|
| _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) |
|
|
| |
| 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) |
|
|
| |
| 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}κ°") |
|
|
| |
| 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)}") |
|
|
| |
| 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() |
|
|