hpce-dev / scripts /_dataset_common.py
μ΄λ™ν˜„
[P3] κ²°ν•© Q9 μž¬μ„€κ³„: λͺ¨λ°”일 μ™Έ 톡신 μ§€μΆœ 직접 질문 + λͺ¨λ°”μΌλ§Œ 쑰건뢀 λΉ„ν™œμ„±
ffaa3d1
Raw
History Blame Contribute Delete
6.3 kB
#!/usr/bin/env python3
"""
페λ₯΄μ†Œλ‚˜ 기반 seed_dataset 생성 곡톡 ν”„λ ˆμž„μ›Œν¬ (μ‹œλ‚˜λ¦¬μ˜€ 무관).
각 μ‹œλ‚˜λ¦¬μ˜€ 슀크립트(build_persona/bundle/worker_dataset.py)λŠ” PERSONASΒ·μ—”μ§„Β·νŒŒλΌλ―Έν„°λ§Œ
μ£Όμž…ν•˜κ³  build_samples()λ₯Ό ν˜ΈμΆœν•œλ‹€. λ™μž‘μ€ κΈ°μ‘΄ 3개 μŠ€ν¬λ¦½νŠΈμ™€ byte-identical.
핡심:
- build_samples(): persona μƒ˜ν”Œλ§ β†’ λ‹΅λ³€ β†’ batch β†’ 행동 μ‹œν€€μŠ€ β†’ pattern/event β†’ 라벨 (RNG μˆœμ„œ 보쑴)
- tree_action_resolver(): behavior_id 2단 트리 β†’ actions (CS/bundle)
- app_action_resolver(): μ•± entity μ‹œν€€μŠ€ β†’ actions (worker λ“± 단일선택)
"""
from __future__ import annotations
import argparse
import json
import random
import sys
from collections import Counter
from pathlib import Path
from typing import Any, Callable
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.extractor import get_extractor # noqa: E402
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--n", type=int, default=500)
parser.add_argument("--seed", type=int, default=42)
return parser.parse_args()
# ── 행동 μ‹œν€€μŠ€ β†’ actions λ³€ν™˜ ────────────────────────────────
def tree_action_resolver(behaviors_data: dict) -> Callable[[list[str]], list[dict]]:
"""behavior_id 2단 트리(step1 + step2.by_parent + step2.common) β†’ resolver(seq)->actions."""
by_id = {b["id"]: b for b in behaviors_data["step1"]["behaviors"]}
for items in behaviors_data["step2"]["by_parent"].values():
for b in items:
by_id[b["id"]] = b
for b in behaviors_data["step2"]["common"]:
by_id[b["id"]] = b
def resolve(seq: list[str]) -> list[dict]:
out = []
for bid in seq:
b = by_id.get(bid)
if b is None:
continue
out.append({"behavior_id": b["id"], "event_type": b["event_type"], "entity": b["entity"]})
return out
return resolve
def app_action_resolver(seq: list[str]) -> list[dict]:
"""μ•± entity μ‹œν€€μŠ€ β†’ actions (event_type=app_open 단일화)."""
return [{"behavior_id": f"app-{e}", "event_type": "app_open", "entity": e} for e in seq]
def _scalar(d: dict) -> dict:
"""슀칼라 feature만(ν‚€ μ •λ ¬). list/dictΒ·wall-clock(*_at, λͺ¨λΈ feature μ•„λ‹˜) μ œμ™Έ.
μ—”μ§„μ˜ set 기반 ν‚€ μˆœμ„œ(예: bundle event 트리거)κΉŒμ§€ μ •λ ¬λ‘œ 흑수 β†’ μ™„μ „ 결정적 좜λ ₯."""
return {k: d[k] for k in sorted(d)
if not isinstance(d[k], (list, dict)) and not k.endswith("_at")}
# ── μƒ˜ν”Œ 생성 루프 (RNG 호좜 μˆœμ„œ κ³ μ •) ───────────────────────
def build_samples(
*,
n: int,
seed: int,
personas: list[dict],
engine: Any, # build_batch_features/pattern_features/event_features 보유 λͺ¨λ“ˆ
seq_key: str, # "action_seqs" | "app_seqs"
action_resolver: Callable[[list[str]], list[dict]],
entity_intents: dict[str, list[str]],
cust_prefix: str, # "C" | "BC" | "WC"
behavior_labels: bool = True, # Falseλ©΄ λͺ¨λΈ 라벨에 행동 μ‹ ν˜Έ intent μ œμ™Έ (행동→intentλŠ” ranker λ‹΄λ‹Ή)
answer_fixup: Callable[[dict], None] | None = None, # μƒ˜ν”Œ λ‹΅λ³€ μ •ν•©μ„± 보정(in-place). 예: 쑰건뢀 λ¬Έν•­
) -> list[dict]:
rng = random.Random(seed)
ex = get_extractor()
rows = []
for i in range(n):
persona = rng.choices(personas, weights=[p["weight"] for p in personas], k=1)[0]
answers = {qid: rng.choices(list(d), weights=list(d.values()), k=1)[0]
for qid, d in persona["answer_dist"].items()}
if answer_fixup:
answer_fixup(answers)
batch = engine.build_batch_features(answers)
seq = rng.choice(persona[seq_key])
actions = action_resolver(seq)
# 행동 μ‹œν€€μŠ€λ₯Ό 곡유 μ €μž₯μ†Œμ— ν†΅κ³Όμ‹œμΌœ Pattern/Event Feature μ‚°μΆœ(μΆ”λ‘  μ‹œμ κ³Ό 동일 경둜)
sid = f"{cust_prefix}{i + 1:05d}"
ex.reset(sid)
for a in actions:
ex.add_event(sid, a["event_type"], a["entity"])
pattern = engine.pattern_features(sid)
event = engine.event_features(sid) if actions else {}
ex.reset(sid)
# μ–‘μ„± 라벨: persona expected_intents (+ behavior_labels=Trueλ©΄ 행동 μ‹ ν˜Έ intent도)
positives = set(persona["expected_intents"])
if behavior_labels:
for a in actions:
positives.update(entity_intents.get(a["entity"], []))
labels = {iid: 1 for iid in sorted(positives)} # μ •λ ¬ β†’ 결정적 ν‚€ μˆœμ„œ
rows.append({
"cust_id": sid,
"persona_id": persona["id"],
"persona_name": persona["name"],
"survey_answers": answers,
"batch_features": _scalar(batch),
"pattern_features": _scalar(pattern),
"event_features": _scalar(event),
"actions": actions,
"intent_labels": labels,
})
return rows
def write_dataset(out_path: Path, rows: list[dict], scenario_id: str, n_personas: int, seed: int) -> None:
with open(out_path, "w", encoding="utf-8") as f:
json.dump({
"scenario_id": scenario_id,
"n_samples": len(rows),
"n_personas": n_personas,
"seed": seed,
"samples": rows,
}, f, ensure_ascii=False, indent=2)
print(f"Wrote {out_path} ({len(rows)} samples)")
def print_label_stats(rows: list[dict], *, total: int | None = None, top_n: int | None = None) -> None:
label_counts = Counter()
for r in rows:
for iid in r["intent_labels"]:
label_counts[iid] += 1
title = f"μ–‘μ„± 라벨 Top {top_n}" if top_n else "μ–‘μ„± 라벨"
print(f"\n── {title} ──")
items = label_counts.most_common(top_n) if top_n else label_counts.most_common()
for iid, c in items:
print(f" {iid}: {c} ({c / len(rows) * 100:.1f}%)")
suffix = f" / {total}" if total else ""
print(f"\n 총 μ–‘μ„± 라벨 Intent μ’…λ₯˜: {len(label_counts)}{suffix}")