From 4c1b1c971c4363098d4cc5c82e7e6f206f2548fd Mon Sep 17 00:00:00 2001 From: xiaoou Date: Mon, 13 Jul 2026 08:43:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=2020k=E5=B1=80/=E8=BD=AE=E6=97=A0?= =?UTF-8?q?=E9=99=90=E8=AE=AD=E7=BB=83=20+=20scan=5Fwins=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AD=98=E6=A1=A3=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/__pycache__/scan_wins.cpython-311.pyc | Bin 0 -> 6062 bytes .../training_orchestrator.cpython-311.pyc | Bin 0 -> 13150 bytes scripts/scan_wins.py | 12 +++ scripts/training_orchestrator.py | 96 ++++++------------ 4 files changed, 41 insertions(+), 67 deletions(-) create mode 100644 scripts/__pycache__/scan_wins.cpython-311.pyc create mode 100644 scripts/__pycache__/training_orchestrator.cpython-311.pyc diff --git a/scripts/__pycache__/scan_wins.cpython-311.pyc b/scripts/__pycache__/scan_wins.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d1edd1d840f6097f4ea119e11930c189dde934b GIT binary patch literal 6062 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 literal 0 HcmV?d00001 diff --git a/scripts/__pycache__/training_orchestrator.cpython-311.pyc b/scripts/__pycache__/training_orchestrator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fba292091e982c2b9eb1dd08ad7dec509d7d3d18 GIT binary patch literal 13150 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 F`)>lyG+qDz literal 0 HcmV?d00001 diff --git a/scripts/scan_wins.py b/scripts/scan_wins.py index fb18f3db..ebf48513 100644 --- a/scripts/scan_wins.py +++ b/scripts/scan_wins.py @@ -64,4 +64,16 @@ def scan(csv_dir='training_data'): if __name__ == '__main__': d = sys.argv[1] if len(sys.argv) > 1 else 'training_data' + # 如果指定了目录不存在, 可能是编号的round目录 + if not os.path.isdir(d): + # 尝试 training_rounds 下最新 + rounds = sorted(glob.glob('training_rounds/r*')) + if rounds: + d = os.path.join(rounds[-1], 'training_data') + if not os.path.isdir(d): + print(f"No training_data in {rounds[-1]}") + sys.exit(1) + else: + print("No data found") + sys.exit(1) scan(d) \ No newline at end of file diff --git a/scripts/training_orchestrator.py b/scripts/training_orchestrator.py index 46c3a3a7..b4bee57c 100644 --- a/scripts/training_orchestrator.py +++ b/scripts/training_orchestrator.py @@ -1,30 +1,23 @@ #!/usr/bin/env python3 """ -持续训练 — 10k首轮 + 5k后续轮 → 循环到08:00 -每轮: dotnet自对弈 → V训练 → ONNX导出 +持续训练: 20k局/轮, 无限循环 +每轮: dotnet自对弈 → V训练 → ONNX导出 → 存档 +胜率趋势由 scan_wins.py 每小时 cron 独立采集 """ -import subprocess, os, sys, time, shutil, glob, json +import subprocess, os, sys, time, shutil, glob from datetime import datetime HJHA_DIR = '/home/xiaoou/projects/hjha-server' TRAINER_DIR = '/home/xiaoou/projects/paodekuai-trainer' -DEADLINE = '08:00' LOG = os.path.join(HJHA_DIR, 'training_loop.log') -def now(): return datetime.now().strftime('%H:%M:%S') +def now(): return datetime.now().strftime('%m%d %H:%M:%S') def log(msg): line = f"[{now()}] {msg}" print(line, flush=True) with open(LOG, 'a') as f: f.write(line + '\n') -def mins_left(): - dt = datetime.now() - h, m = map(int, DEADLINE.split(':')) - dl = dt.replace(hour=h, minute=m, second=0, microsecond=0) - return max(0, (dl - dt).total_seconds() / 60) - -def run(cmd, cwd=HJHA_DIR, to=14400): - """返回 (ok, stdout+stderr)""" +def run(cmd, cwd=HJHA_DIR, to=86400): try: r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=to) ok = r.returncode == 0 @@ -35,7 +28,6 @@ def run(cmd, cwd=HJHA_DIR, to=14400): log(f" EXCEPTION: {e}") return False, str(e) -# ── 自对弈 ── def selfplay(games): log(f"── 自对弈 {games}局 ──") td = os.path.join(HJHA_DIR, 'training_data') @@ -45,8 +37,8 @@ def selfplay(games): env = os.environ.copy() env['PATH'] = f"/usr/lib/dotnet:{env.get('PATH','')}" - ok, out = run(['/usr/lib/dotnet/dotnet', 'run', '--project', 'hjha-console', - '--', '--mix', str(games)], to=max(14400, games*2)) + ok, out = run(['/usr/lib/dotnet/dotnet', 'run', '--project', 'hjha-console', '-c', 'Release', + '--', '--mix', str(games)], to=max(36000, games*2)) csv = len(glob.glob(os.path.join(td, 'game_*.csv'))) sf = os.path.join(HJHA_DIR, 'stress_summary.txt') @@ -55,7 +47,6 @@ def selfplay(games): log(f" {csv} CSV") return csv -# ── V训练 ── def train_v(): log(f"── V训练 ──") @@ -63,7 +54,6 @@ def train_v(): import numpy as np, torch from models.network import PdkNet - # load data csv_dir = os.path.join(HJHA_DIR, 'training_data') states, results = [], [] for fp in sorted(glob.glob(f'{csv_dir}/game_*.csv')): @@ -71,6 +61,7 @@ def train_v(): with open(fp) as f: f.readline() for line in f: + if line.startswith('#'): continue p = line.strip().split('"') if len(p) < 3: continue sf = [float(x) for x in p[1].split(',')] @@ -88,15 +79,11 @@ def train_v(): results = np.array(results, dtype=np.float32) log(f" {len(states)} samples from {len(glob.glob(f'{csv_dir}/game_*.csv'))} games") - # load or init model — NOTE: old model.pt may be Q-network (14-output) - # V-training needs 1-output. Always start fresh for V-training. - mp = os.path.join(TRAINER_DIR, 'data/model.pt') model = PdkNet() - log(" 新V模型 (从头训练)") - opt = torch.optim.Adam(model.parameters(), lr=0.001) loss_fn = torch.nn.MSELoss() n = len(states) + t0 = datetime.now() for ep in range(20): perm = np.random.permutation(n) @@ -105,18 +92,19 @@ def train_v(): idx = perm[i:i+128] x = torch.from_numpy(states[idx]).float() y = torch.from_numpy(results[idx]).float().unsqueeze(1) - model.train() - pred = model(x) + model.train(); pred = model(x) loss = loss_fn(pred, y) - opt.zero_grad() - loss.backward() - opt.step() + opt.zero_grad(); loss.backward(); opt.step() losses.append(loss.item()) if ep % 5 == 0: - log(f" ep{ep+1}/20 loss={np.mean(losses):.4f}") - log(f" done. final loss={np.mean(losses):.4f}") + dt = (datetime.now() - t0).total_seconds() + log(f" ep{ep+1}/20 loss={np.mean(losses):.4f} ({dt:.0f}s)") - # save pytorch + elapsed = (datetime.now() - t0).total_seconds() + log(f" done {elapsed:.0f}s, final loss={np.mean(losses):.4f}") + + # save pt + mp = os.path.join(TRAINER_DIR, 'data/model.pt') model.save(mp) # export ONNX @@ -129,14 +117,12 @@ def train_v(): dynamic_axes={'state': {0: 'batch'}, 'v': {0: 'batch'}}, opset_version=15) - # copy to hjha-server (include .data if external storage) dest = os.path.join(HJHA_DIR, 'model.onnx') shutil.copy(onnx_path, dest) data_src = onnx_path + '.data' if os.path.exists(data_src): shutil.copy(data_src, dest + '.data') - # archive tag = datetime.now().strftime('%m%d_%H%M') rd = os.path.join(HJHA_DIR, f'training_rounds/r{tag}') os.makedirs(rd, exist_ok=True) @@ -148,60 +134,36 @@ def train_v(): shutil.move(csv_dir, os.path.join(rd, 'training_data')) os.makedirs(csv_dir, exist_ok=True) - sz = os.path.getsize(dest) / 1024 - log(f" ONNX → {dest} ({sz:.0f}KB), 存档 → {rd}") + sz = os.path.getsize(dest) + os.path.getsize(dest + '.data') if os.path.exists(dest + '.data') else os.path.getsize(dest) + log(f" ONNX → {dest} ({sz//1024}KB), 存档 → {rd}") except Exception as e: - log(f" ONNX失败: {e}") + log(f" ONNX failed: {e}") return False return True -# ── 主循环 ── def main(): log("=" * 50) - log(f"持续训练 启动 | 截止 {DEADLINE}") - log("策略: 首轮10k → 后续5k/轮") + log("持续训练: 20k局/轮 | 无限循环") log("=" * 50) rn, total = 0, 0 while True: - ml = mins_left() - if ml < 3: - log(f"只剩 {ml:.0f}min, 停止") - break - rn += 1 - games = 10000 if rn == 1 else 5000 - est = games/60 + 15 # estimate minutes - - if est > ml - 3: - # shrink - games = max(500, int((ml - 18) * 60)) - if games < 500: - log(f"不够时间跑完整轮 ({ml:.0f}min), 停止") - break - log(f"缩减为 {games} 局 (剩{ml:.0f}min)") - - log(f"\n── 第{rn}轮 {games}局 (剩{ml:.0f}min) ──") + games = 20000 + log(f"\n── 第{rn}轮: {games}局 ──") csv = selfplay(games) if csv == 0: - log("自对弈失败! 跳过训练, 继续下一轮") + log("自对弈失败! 跳过") continue total += games if not train_v(): - log("训练失败! 继续下一轮") + log("训练失败! 跳过") continue - - log(f"\n{'='*40}") - log(f"结束: {rn}轮, {total}局") - mp = os.path.join(HJHA_DIR, 'model.onnx') - if os.path.exists(mp): - log(f"最终模型: {mp} ({os.path.getsize(mp)/1024:.0f}KB)") - rd = os.path.join(HJHA_DIR, 'training_rounds') - if os.path.exists(rd): - log(f"存档: {len(os.listdir(rd))} 轮 → {rd}") + + log(f"累计: {rn}轮, {total}局") if __name__ == '__main__': main() \ No newline at end of file