File size: 6,300 Bytes
2a06dbe e000be4 ffaa3d1 2a06dbe ffaa3d1 2a06dbe d504def e000be4 2a06dbe | 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 143 144 145 146 147 148 149 150 151 152 | #!/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}")
|