์ด๋ํ
[FEAT] cs ์๋๋ฆฌ์ค Intent ๊ณ ๋ํ โ ๋ฒ์ฉ intent ์ต์ + ํ๋ฅด์๋ ์ปค๋ฒ๋ฆฌ์ง ๊ฐ์
df7aa3a | 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) | |