Deep CFR 로컬 metrics logging 추가

This commit is contained in:
2026-05-06 23:54:52 +09:00
parent fc5b3d9bc8
commit 76833109dc
3 changed files with 59 additions and 3 deletions
@@ -42,7 +42,7 @@ def train_command(args: argparse.Namespace) -> None:
trainer.load_checkpoint(args.resume)
metrics = trainer.train()
for item in metrics:
print(json.dumps(item.__dict__, sort_keys=True))
print(json.dumps(item.to_dict(), sort_keys=True))
def eval_command(args: argparse.Namespace) -> None:
@@ -1,5 +1,7 @@
from __future__ import annotations
import json
import time
from dataclasses import dataclass
from pathlib import Path
@@ -34,6 +36,22 @@ class IterationMetrics:
traversal_max_depth_reached: int
eval_metrics: dict[str, float | int]
def to_dict(self) -> dict[str, float | int]:
data = {
"iteration": self.iteration,
"advantage_samples": self.advantage_samples,
"strategy_samples": self.strategy_samples,
"advantage_loss": self.advantage_loss,
"strategy_loss": self.strategy_loss,
"traversal_nodes": self.traversal_nodes,
"traversal_terminals": self.traversal_terminals,
"traversal_depth_cutoffs": self.traversal_depth_cutoffs,
"traversal_node_limit_cutoffs": self.traversal_node_limit_cutoffs,
"traversal_max_depth_reached": self.traversal_max_depth_reached,
}
data.update(self.eval_metrics)
return data
class DeepCFRTrainer:
def __init__(
@@ -70,6 +88,10 @@ class DeepCFRTrainer:
self.strategy_memory = ReservoirMemory(self.config.strategy_memory_capacity)
self.rng = np.random.default_rng(self.config.seed + 101)
self.iteration = 0
self.run_dir = self.config.checkpoint_path
self.metrics_path = self.run_dir / "metrics.jsonl"
self.progress_path = self.run_dir / "runtime_progress.json"
self.log_path = self.run_dir / "train.log"
def checkpoint_payload(self, metrics: IterationMetrics | None = None) -> dict:
return {
@@ -84,7 +106,7 @@ class DeepCFRTrainer:
optimizer.state_dict() for optimizer in self.advantage_optimizers
],
"strategy_optimizer": self.strategy_optimizer.state_dict(),
"metrics": None if metrics is None else metrics.__dict__,
"metrics": None if metrics is None else metrics.to_dict(),
}
def save_checkpoint(self, path: str | Path, metrics: IterationMetrics | None = None) -> Path:
@@ -156,18 +178,48 @@ class DeepCFRTrainer:
)
def train(self) -> list[IterationMetrics]:
self._start_run_logging()
metrics: list[IterationMetrics] = []
start = self.iteration + 1
stop = self.iteration + self.config.iterations
for iteration in range(start, stop + 1):
started = time.perf_counter()
item = self.run_iteration(iteration)
elapsed = time.perf_counter() - started
metrics.append(item)
self._append_metrics(item, elapsed)
if self.config.save_every_iteration:
checkpoint_dir = self.config.checkpoint_path
checkpoint_dir = self.run_dir
self.save_checkpoint(checkpoint_dir / f"iteration_{iteration:05d}.pt", item)
self.save_checkpoint(checkpoint_dir / "latest.pt", item)
return metrics
def _start_run_logging(self) -> None:
self.run_dir.mkdir(parents=True, exist_ok=True)
config_path = self.run_dir / "config.json"
if not config_path.exists():
config_path.write_text(
json.dumps(self.config.to_dict(), indent=2, sort_keys=True),
encoding="utf-8",
)
if self.iteration == 0 and self.metrics_path.exists():
self.metrics_path.unlink()
with self.log_path.open("a", encoding="utf-8") as handle:
handle.write(f"Deep CFR run start iteration={self.iteration} seed={self.config.seed}\n")
def _append_metrics(self, metrics: IterationMetrics, iteration_seconds: float) -> None:
data = metrics.to_dict()
data["iteration_seconds"] = iteration_seconds
data["nodes_per_second"] = metrics.traversal_nodes / max(iteration_seconds, 1.0e-12)
with self.metrics_path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(data, sort_keys=True) + "\n")
self.progress_path.write_text(json.dumps(data, indent=2, sort_keys=True), encoding="utf-8")
with self.log_path.open("a", encoding="utf-8") as handle:
handle.write(
f"iteration={metrics.iteration} nodes={metrics.traversal_nodes} adv_loss={metrics.advantage_loss:.6f} "
f"strategy_loss={metrics.strategy_loss:.6f} seconds={iteration_seconds:.3f}\n"
)
def _evaluate(self, iteration: int) -> dict[str, float | int]:
if self.config.eval_every <= 0 or iteration % self.config.eval_every != 0:
return {}
@@ -188,5 +188,9 @@ def test_deep_cfr_trainer_saves_loads_and_evaluates_checkpoint(tmp_path) -> None
restored.load_checkpoint(latest)
assert latest.exists()
assert (checkpoint_dir / "config.json").exists()
assert (checkpoint_dir / "metrics.jsonl").exists()
assert (checkpoint_dir / "runtime_progress.json").exists()
assert (checkpoint_dir / "train.log").exists()
assert restored.iteration == 1
assert "eval_random_games" in metrics[0].eval_metrics