hpce-dev / scripts /sim_cs.py
์ด๋™ํ˜„
[FEAT] cs ์‹œ๋‚˜๋ฆฌ์˜ค Intent ๊ณ ๋„ํ™” โ€” ๋ฒ”์šฉ intent ์–ต์ œ + ํŽ˜๋ฅด์†Œ๋‚˜ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฐœ์„ 
df7aa3a
Raw
History Blame Contribute Delete
6.23 kB
from __future__ import annotations
"""
CS(cs-myk-v3) Intent ๋ถ„ํฌ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ (๋กœ์ง ๊ณ ๋„ํ™”์šฉ).
113๊ฐœ Intent ๊ทœ๋ชจ. ํŽ˜๋ฅด์†Œ๋‚˜๊ฐ€ ์„ค๋ฌธ ์‘๋‹ต + ๋Œ€ํ‘œ ํ–‰๋™ ์‹œํ€€์Šค(behavior tree)๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ
๊ธฐ๋Œ€ intent(expected_intents)๊ฐ€ ์ƒ์œ„ ๋ถ„ํฌ์— ๋œจ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๋ฒ”์šฉ Intent(AI ์ถ”์ฒœ ๋“ฑ)๊ฐ€
๊ณผ๋„ํ•˜๊ฒŒ ์ƒ์œ„๋ฅผ ์ ์œ ํ•˜์ง€ ์•Š๋Š”์ง€ ํ‰๊ฐ€ํ•œ๋‹ค.
์ง€ํ‘œ:
- cov@5 / cov@10 : expected_intents ์ค‘ final top-5/top-10 ๋น„์œจ
- avg_rank : expected_intents ํ‰๊ท  final ์ˆœ์œ„ (๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ, /113)
- ๋ฒ”์šฉ Intent top-5 ์ ์œ ์œจ : ๋ฌด์ž‘์œ„ ์‘๋‹ต์—์„œ GENERIC intent๊ฐ€ top-5์— ๋“œ๋Š” ๋น„์œจ
์‹คํ–‰: python scripts/sim_cs.py [--behavior] [--dist]
--behavior : ๋Œ€ํ‘œ ํ–‰๋™ ์‹œํ€€์Šค๊นŒ์ง€ ๋ฐ˜์˜
--dist : ๋ฌด์ž‘์œ„ ์‘๋‹ต ๊ธฐ์ค€ top-1/top-5 ๋ถ„ํฌ + ๋ฒ”์šฉ intent ์ ์œ ์œจ ์ง„๋‹จ
"""
import argparse
import random
import sys
from collections import Counter
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.engines import config # noqa: E402
from core.extractor import get_extractor # noqa: E402
from core.inference import infer_batch, infer_with_behavior # noqa: E402
from scripts.build_cs_dataset import PERSONAS # noqa: E402
SID = "cs-myk-v3"
N_INTENTS = len(config.get_taxonomy(SID)["intents"])
# ๋„ˆ๋ฌด ๋ฒ”์šฉ์ ์ด๋ผ ์‹œ์—ฐ์—์„œ ์ƒ์œ„ ๋…ธ์ถœ์„ ์ง€์–‘ํ•  Intent (AI ์ถ”์ฒœ๋ฅ˜)
GENERIC_INTENTS = {"INT-4310", "INT-4320", "INT-4330"}
def _sample_answers(answer_dist: dict, rng: random.Random) -> dict[str, str]:
return {qid: rng.choices(list(d), weights=list(d.values()), k=1)[0] for qid, d in answer_dist.items()}
def _behavior_map() -> dict[str, tuple[str, str]]:
bc = config.get_behaviors(SID)
m: dict[str, tuple[str, str]] = {}
for b in bc["step1"]["behaviors"]:
m[b["id"]] = (b["event_type"], b["entity"])
for items in bc["step2"]["by_parent"].values():
for b in items:
m[b["id"]] = (b["event_type"], b["entity"])
for b in bc["step2"].get("common", []):
m[b["id"]] = (b["event_type"], b["entity"])
m.setdefault("BACK", ("navigate_back", "back_to_step1"))
m.setdefault("EXIT", ("app_exit", "session_end"))
return m
def _names() -> dict[str, str]:
return {i["id"]: i["name"] for i in config.get_taxonomy(SID)["intents"]}
def _rank_of(scores: list, intent_id: str) -> int:
for s in scores:
if s.intent_id == intent_id:
return s.rank
return 999
def _rand_answers(rng: random.Random) -> dict[str, str]:
return {q["id"]: rng.choice([o["code"] for o in q["options"]])
for q in config.get_survey(SID)["questions"]}
def run(use_behavior: bool, k: int = 30, seed: int = 7) -> None:
bmap = _behavior_map()
ext = get_extractor()
rng = random.Random(seed)
p5, p10, rank_all = [], [], []
gen_in5 = 0 # expected ํ‰๊ฐ€ ํ‘œ๋ณธ์—์„œ ๋ฒ”์šฉ intent๊ฐ€ top-5 ์ ์œ ํ•œ ํšŸ์ˆ˜
n_samples = 0
for p in PERSONAS:
expected = p["expected_intents"]
c5s, c10s, rks = [], [], []
for j in range(k):
answers = _sample_answers(p["answer_dist"], rng)
if use_behavior:
seq = rng.choice(p["action_seqs"])
sess = f"__cs__{p['id']}_{j}"
ext.reset(sess)
for bid in seq:
et, ent = bmap.get(bid, (None, None))
if et:
ext.add_event(sess, et, ent)
_, scores = infer_with_behavior(answers, sess, SID)
ext.reset(sess)
else:
_, scores = infer_batch(answers, SID)
top = [s.intent_id for s in sorted(scores, key=lambda s: s.final_score, reverse=True)]
t5, t10 = set(top[:5]), set(top[:10])
c5s.append(sum(1 for e in expected if e in t5) / len(expected))
c10s.append(sum(1 for e in expected if e in t10) / len(expected))
rks.extend(_rank_of(scores, e) for e in expected)
gen_in5 += len(GENERIC_INTENTS & t5); n_samples += 1
cov5, cov10 = sum(c5s) / k, sum(c10s) / k
p5.append(cov5); p10.append(cov10); rank_all.extend(rks)
print(f" {p['id']} {p['name'][:24]:24} cov@5={cov5:.2f} cov@10={cov10:.2f} avg_rank={sum(rks)/len(rks):5.1f}")
n = len(PERSONAS)
print("=" * 70)
print(f" ์ „์ฒด ํ‰๊ท  cov@5={sum(p5)/n:.3f} cov@10={sum(p10)/n:.3f} "
f"avg_rank={sum(rank_all)/len(rank_all):.1f}/{N_INTENTS} "
f"({'ํ–‰๋™๋ฐ˜์˜' if use_behavior else '์„ค๋ฌธ๋งŒ'}, k={k})")
print(f" ๋ฒ”์šฉ intent(AI ์ถ”์ฒœ๋ฅ˜) top-5 ํ‰๊ท  ์ ์œ : {gen_in5/n_samples:.2f}๊ฐœ/ํ‘œ๋ณธ")
print("=" * 70)
def dist(m: int = 400, seed: int = 3) -> None:
"""๋ฌด์ž‘์œ„ ์‘๋‹ต์—์„œ top-1/top-5 ๋ถ„ํฌ + ๋ฒ”์šฉ intent ์ ์œ ์œจ ์ง„๋‹จ."""
names = _names()
rng = random.Random(seed)
top1 = Counter()
top5 = Counter()
gen5 = 0
for _ in range(m):
_, scores = infer_batch(_rand_answers(rng), SID)
top = [s.intent_id for s in sorted(scores, key=lambda s: s.final_score, reverse=True)]
top1[top[0]] += 1
for iid in top[:5]:
top5[iid] += 1
gen5 += len(GENERIC_INTENTS & set(top[:5]))
print(f"โ”€โ”€ ๋ฌด์ž‘์œ„ ์‘๋‹ต {m}๊ฑด ๋ถ„ํฌ ์ง„๋‹จ โ”€โ”€")
print(f" ์„œ๋กœ ๋‹ค๋ฅธ top-1 intent: {len(top1)}์ข…")
print(" top-1 ์ตœ๋นˆ 10:")
for iid, c in top1.most_common(10):
flag = " โš ๏ธ๋ฒ”์šฉ" if iid in GENERIC_INTENTS else ""
print(f" {iid} {names.get(iid,'')[:18]:18} {c/m*100:4.1f}%{flag}")
print(f" ๋ฒ”์šฉ intent(AI ์ถ”์ฒœ๋ฅ˜) top-5 ์ ์œ : {gen5/m:.2f}๊ฐœ/์‘๋‹ต "
f"(= ํ‰๊ท  {gen5/m/5*100:.0f}% of top-5 slots)")
print(" ๋ฒ”์šฉ intent๋ณ„ top-5 ๋“ฑ์žฅ๋ฅ :")
for iid in sorted(GENERIC_INTENTS):
print(f" {iid} {names.get(iid,'')[:18]:18} {top5[iid]/m*100:4.1f}%")
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--behavior", action="store_true")
ap.add_argument("--dist", action="store_true")
args = ap.parse_args()
if args.dist:
dist()
else:
run(args.behavior)