게시판바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기예제 소스

예제 소스

바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기 · 파일 169개

dataset_moves_katago.py

#!/usr/bin/env python3
"""대국 국면을 KataGo 최선수로 라벨한 수 학습 데이터 (서버 수 프롬프트 그대로).

dataset_coords_game.py 와 같은 레코드 형식(sys/usr/expect/board)이라 eval_coords_game.py 로 평가한다.
  kmove        일반 수 프롬프트
  kmove_retry  금지 좌표(모델이 그 국면에서 실제로 고른 이미 돌 있는 칸)가 붙은 재시도 프롬프트

  python scripts/dataset_moves_katago.py --source logs/x/matches.sqlite --jsonl logs/x/gemma4.jsonl \
      --out sft_data/gemma_kmove_r1
"""

from __future__ import annotations

import argparse
import json
import random
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from scripts.dataset_coords_game import HINTS, board_hash, load_bad_picks, load_positions  # noqa: E402
from vlm_server.dataset_baduk import assistant_text, read_pick_answer, read_pick_answer2  # noqa: E402
from vlm_server.dataset_onpolicy import analyze  # noqa: E402
from vlm_server.server import (  # noqa: E402
    SYSTEM_PROMPT_ALIGNED,
    SYSTEM_PROMPT_ALIGNED_READ,
    SYSTEM_PROMPT_ALIGNED_READ2,
    build_prompt,
)

N = 19


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--source", action="append", required=True)
    ap.add_argument("--jsonl", action="append", default=[])
    ap.add_argument("--out", required=True)
    ap.add_argument("--color", default="W", choices=["B", "W", "BW"])
    ap.add_argument("--retry-frac", type=float, default=0.3)
    ap.add_argument("--max-positions", type=int, default=2400)
    ap.add_argument("--val-n", type=int, default=120)
    ap.add_argument("--visits", type=int, default=64)
    ap.add_argument("--seed", type=int, default=31)
    ap.add_argument("--board-repr", default="aligned", choices=["aligned", "aligned_read", "aligned_read2"])
    ap.add_argument("--holdout-source", action="append", default=[],
                    help="이 대국 국면은 val 로만 쓴다 (지정하면 게임 단위 무작위 val 분할 대신)")
    args = ap.parse_args()

    from vlm_server.dataset_ladder import _KataGo

    rng = random.Random(args.seed)
    sides = {"B": {1}, "W": {2}, "BW": {1, 2}}[args.color]

    def uniq_positions(sources: list[str]) -> list[dict]:
        seen: set[str] = set()
        out = []
        for s in sources:
            for p in load_positions(Path(s)):
                h = board_hash(p["cells"], p["side"])
                if p["side"] in sides and h not in seen:
                    seen.add(h)
                    out.append(p)
        rng.shuffle(out)
        return out

    uniq = uniq_positions(args.source)[: args.max_positions]
    bad = load_bad_picks([Path(p) for p in args.jsonl])
    if args.holdout_source:
        held = uniq_positions(args.holdout_source)[: args.val_n]
        uniq += held
        val_games = {p["src"].rsplit(":", 1)[0] for p in held}
    else:
        games = sorted({p["src"].rsplit(":", 1)[0] for p in uniq})
        rng.shuffle(games)
        val_games = set(games[: max(1, len(games) // 10)])
    system = {
        "aligned_read": SYSTEM_PROMPT_ALIGNED_READ,
        "aligned_read2": SYSTEM_PROMPT_ALIGNED_READ2,
    }.get(args.board_repr, SYSTEM_PROMPT_ALIGNED)

    kg = _KataGo(args.visits, 6.5)
    train, val = [], []
    t0 = time.time()
    for i, pos in enumerate(uniq, 1):
        cells, side = pos["cells"], pos["side"]
        v = analyze(kg, cells, side, 3)
        if not v["top"] or v["top_is_pass"]:
            continue
        best = tuple(v["top"][0])
        retry = rng.random() < args.retry_frac
        forbidden: list[tuple[int, int]] = []
        hint = None
        if retry:
            occ = sorted({q for q in bad.get(board_hash(cells, side), []) if cells[q[0] * N + q[1]]})
            if not occ:
                own = [(k // N, k % N) for k, x in enumerate(cells) if x == side]
                occ = rng.sample(own, min(len(own), rng.randint(1, 3))) if own else []
            forbidden = [q for q in occ if q != best][:6]
            hint = rng.choice(HINTS) if forbidden else None
        user = build_prompt(bytes(cells), side, forbidden=forbidden or None, illegal_hint=hint,
                            board_repr=args.board_repr)
        task = "kmove_retry" if forbidden else "kmove"
        if args.board_repr == "aligned_read2":
            answer = read_pick_answer2(bytes(cells), best[0], best[1])
        elif args.board_repr == "aligned_read":
            answer = read_pick_answer(bytes(cells), best[0], best[1])
        else:
            answer = assistant_text(best)
        rec = {
            "task": task,
            "src": pos["src"],
            "sys": system,
            "usr": user,
            "expect": {"row": best[0], "col": best[1], "side": side, "forbidden": forbidden,
                       "top": v["top"], "lead": v["lead"]},
            "board": "".join(".XO"[x] for x in cells),
            "images": [],
            "messages": [
                {"role": "user", "content": [{"type": "text", "text": f"{system}\n\n{user}"}]},
                {"role": "assistant", "content": [{"type": "text", "text": answer}]},
            ],
        }
        (val if pos["src"].rsplit(":", 1)[0] in val_games else train).append(rec)
        if i % 200 == 0:
            print(f"[kmove] {i}/{len(uniq)} train={len(train)} val={len(val)} {time.time() - t0:.0f}s", flush=True)

    rng.shuffle(val)
    val = val[: args.val_n]
    out = Path(args.out)
    out.mkdir(parents=True, exist_ok=True)
    for name, rows in (("train", train), ("val", val)):
        with (out / f"{name}.jsonl").open("w", encoding="utf-8") as f:
            for r in rows:
                f.write(json.dumps(r, ensure_ascii=False) + "\n")
    summary = {
        "positions": len(uniq),
        "train": len(train),
        "train_retry": sum(1 for r in train if r["task"] == "kmove_retry"),
        "val": len(val),
        "seconds": round(time.time() - t0),
    }
    (out / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(f"[kmove] {summary}", flush=True)
    return 0 if (train or val) else 1


if __name__ == "__main__":
    raise SystemExit(main())