This commit is contained in:
2026-09-11 17:24:07 +08:00
parent 4699b0d45f
commit 30df22e8b2
240 changed files with 3391 additions and 64 deletions
+27 -4
View File
@@ -17,9 +17,12 @@ from __future__ import annotations
import argparse
import json
import time
from dataclasses import asdict
import hashlib
from pathlib import Path
from stable_baselines3 import PPO
from sb3_contrib import MaskablePPO
from stable_baselines3.common.vec_env import DummyVecEnv
from mcm26b.config import ProblemConfig
@@ -46,11 +49,13 @@ def evaluate(model, problem: int, *, episodes: int = 24,
total_reward = 0.0
successes = 0
steps = []
costs = []
for episode in range(episodes):
obs, _info = env.reset(seed=1_000_000 + episode)
episode_reward = 0.0
for step in range(env.max_episode_steps + 2):
action, _state = model.predict(obs, deterministic=True)
kwargs = {'action_masks': env.action_masks()} if isinstance(model, MaskablePPO) else {}
action, _state = model.predict(obs, deterministic=True, **kwargs)
obs, reward, terminated, truncated, info = env.step(int(action))
episode_reward += float(reward)
if terminated or truncated:
@@ -59,12 +64,16 @@ def evaluate(model, problem: int, *, episodes: int = 24,
cleared = bool(info.get("cleared"))
successes += int(cleared)
steps.append(step + 1)
costs.append(info['virtual_cost_s'])
env.close()
return {
"episodes": episodes,
"success_rate": successes / episodes,
"mean_steps": sum(steps) / episodes,
"mean_episode_reward": total_reward / episodes,
"successes": successes,
"mean_virtual_cost_s": sum(costs) / episodes,
"max_virtual_cost_s": max(costs),
}
@@ -87,6 +96,8 @@ def main(argv=None) -> int:
"加载后继续 PPO 微调)")
parser.add_argument("--eval-episodes", type=int, default=24)
parser.add_argument("--verbose", type=int, default=1)
parser.add_argument('--algorithm', choices=('ppo', 'maskable_ppo'), default='maskable_ppo')
parser.add_argument('--torch-threads', type=int, default=1)
args = parser.parse_args(argv)
directional_probability = (args.directional_probability
if args.directional_probability is not None
@@ -95,6 +106,7 @@ def main(argv=None) -> int:
parser.error("--directional-probability 须在 [0,1]")
import torch
torch.set_num_threads(args.torch_threads)
if args.device == "auto":
device = "cuda" if torch.cuda.is_available() else "cpu"
else:
@@ -111,20 +123,24 @@ def main(argv=None) -> int:
}, ensure_ascii=False))
config = ProblemConfig()
model_cls = MaskablePPO if args.algorithm == 'maskable_ppo' else PPO
vec_env = DummyVecEnv(
[_make_env(args.problem, args.seed + rank, config,
directional_probability)
for rank in range(max(1, args.envs))])
if args.init:
model = PPO.load(args.init, env=vec_env, device=device)
metadata = json.loads(Path(args.init).with_suffix('.json').read_text(encoding='utf-8'))
if metadata.get('algorithm', 'ppo') != args.algorithm or metadata.get('feature_version') != 2:
parser.error('初始化模型的算法/特征版本与当前训练不符,请重训或提供v2初始化模型')
model = model_cls.load(args.init, env=vec_env, device=device)
else:
model = PPO(
model = model_cls(
"MlpPolicy", vec_env,
policy_kwargs={"net_arch": [128, 128]},
n_steps=512,
batch_size=128,
n_epochs=6,
gamma=0.99,
gamma=1.0,
gae_lambda=0.95,
learning_rate=3e-4,
clip_range=0.2,
@@ -158,12 +174,19 @@ def main(argv=None) -> int:
"wall_seconds": round(elapsed_s, 1),
"model": str(model_path),
"init": args.init,
"algorithm": args.algorithm,
"feature_version": 2,
"reward": 'negative_virtual_cost_div100_success20_failure100',
"gamma": 1.0,
"config": asdict(config),
"model_sha256": hashlib.sha256(model_path.read_bytes()).hexdigest(),
**evaluation,
}
report_path = model_path.with_suffix(".json")
report_path.write_text(json.dumps(report, ensure_ascii=False, indent=2),
encoding="utf-8")
print(json.dumps(report, ensure_ascii=False, indent=2))
vec_env.close()
return 0