QOJDoudizhuModel / evaluate.py
xcx0902's picture
Upload folder using huggingface_hub
e579420 verified
Raw History Blame Contribute Delete
4.39 kB
"""Paired, role-balanced tests; bootstrap units are complete seeded deals."""
import argparse
import json
import statistics
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if __package__ in (None, ''):
sys.path.insert(0, str(ROOT))
from qojbot.history import write_json
from qojbot.model_benchmark import run, _bootstrap
from QOJDoudizhuModel.policy import QOJPolicy
from QOJDoudizhuModel.storage import digest, source_hashes, teacher_hashes
def evaluate(policy, deals, seed, modes=('fixed', 'bidding'), opponent=None):
opponent = opponent or policy.teacher
result = run({'lstm': opponent, 'resnet': policy}, deals=deals, seed=seed, modes=modes)
# Reuse the tested paired evaluator without presenting the new network as ResNet.
result['labels'] = {'lstm': type(opponent).__name__, 'resnet': 'QOJNet'}
result['selection_score'] = statistics.mean(
metrics['resnet']['mean_score'] for metrics in result['summary'].values())
clusters = {}
for record in result['records']:
clusters.setdefault(record['seed'], []).extend(
record['deltas'][seat] for seat, name in enumerate(record['lineup']) if name == 'resnet')
result['selection_score_95pct_cluster_bootstrap'] = _bootstrap(
[statistics.mean(values) for values in clusters.values()])
result['positive_score_supported'] = (
deals >= 100 and result['selection_score_95pct_cluster_bootstrap'][0] > 0 and
all(metrics['resnet']['mean_score'] > 0 for metrics in result['summary'].values()))
result['teacher_sha256'] = teacher_hashes(policy.teacher)
if hasattr(opponent, 'paths'):
result['opponent'] = {'model_type': opponent.model_type,
'paths': {key: str(path) for key, path in opponent.paths.items()},
'sha256': teacher_hashes(opponent)}
result['source_sha256'] = source_hashes()
result['protocol'] = {'lineups_per_deal_per_mode': 6,
'selection': 'validation only; test seed reserved',
'score': 'QOJ bid * (1+bombs+spring), landlord x2',
'confidence': 'bootstrap by original seed; few deals yield unreliable intervals',
'information': 'own hand and public information only',
'limitation': 'not online rating or six-game match placement'}
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--checkpoint', type=Path, required=True)
parser.add_argument('--device', choices=('cpu', 'auto', 'mps', 'cuda'), default='mps')
parser.add_argument('--deals', type=int, default=100)
parser.add_argument('--seed', type=int, default=40261002)
parser.add_argument('--modes', nargs='+', choices=('fixed', 'bidding'), default=['fixed', 'bidding'])
parser.add_argument('--opponent', choices=('douzero', 'search'), default='douzero')
parser.add_argument('--opponent-dir', type=Path,
help='explicit DouZero benchmark weights, independent of the frozen prior')
parser.add_argument('--teacher-dir', type=Path,
help='frozen prior used by the checkpoint')
parser.add_argument('--output', type=Path, default=Path('QOJDoudizhuModel/results/test.json'))
args = parser.parse_args()
if args.opponent == 'search' and args.opponent_dir:
parser.error('--opponent-dir requires --opponent douzero')
policy = QOJPolicy(args.checkpoint, device=args.device, teacher_dir=args.teacher_dir)
opponent = None
if args.opponent == 'search':
from QOJDoudizhuModel.learning import SearchOpponent
opponent = SearchOpponent(.1)
elif args.opponent_dir:
from qojbot.douzero import DouZeroPolicy
opponent = DouZeroPolicy(args.opponent_dir, device=str(policy.device), batch_size=256)
report = evaluate(policy, args.deals, args.seed, tuple(args.modes), opponent)
path = args.checkpoint / 'best.pt' if args.checkpoint.is_dir() else args.checkpoint
report['checkpoint_sha256'] = digest(path)
args.output.parent.mkdir(parents=True, exist_ok=True)
write_json(args.output, report)
print(json.dumps({key: report[key] for key in ('games', 'selection_score', 'summary')}, indent=2))
if __name__ == '__main__':
main()