メインコンテンツまでスキップ

評価とベンチマーク

本記事では、囲碁AIの棋力とパフォーマンスの評価方法を、Eloレーティングシステム、対局テスト手法、標準ベンチマークを含めて紹介します。


Eloレーティングシステム

基本概念

Eloレーティングは相対的な棋力を測定する標準的な方法です:

期待勝率 E_A = 1 / (1 + 10^((R_B - R_A) / 400))

新Elo = 旧Elo + K × (実際の結果 - 期待結果)

Elo差と勝率対照表

Elo差強者の勝率
050%
10064%
20076%
40091%
80099%

実装

def expected_score(rating_a, rating_b):
"""AのBに対する期待スコアを計算"""
return 1 / (1 + 10 ** ((rating_b - rating_a) / 400))

def update_elo(rating, expected, actual, k=32):
"""Eloレーティングを更新"""
return rating + k * (actual - expected)

def calculate_elo_diff(wins, losses, draws):
"""対戦結果からElo差を計算"""
total = wins + losses + draws
win_rate = (wins + 0.5 * draws) / total

if win_rate <= 0 or win_rate >= 1:
return float('inf') if win_rate >= 1 else float('-inf')

return 400 * math.log10(win_rate / (1 - win_rate))

対局テスト

テストフレームワーク

class MatchTester:
def __init__(self, engine_a, engine_b):
self.engine_a = engine_a
self.engine_b = engine_b
self.results = {'a_wins': 0, 'b_wins': 0, 'draws': 0}

def run_match(self, num_games=400):
"""対戦テストを実行"""
for i in range(num_games):
# 先後を交代
if i % 2 == 0:
black, white = self.engine_a, self.engine_b
a_is_black = True
else:
black, white = self.engine_b, self.engine_a
a_is_black = False

# 対局を実施
result = self.play_game(black, white)

# 結果を記録
if result == 'black':
if a_is_black:
self.results['a_wins'] += 1
else:
self.results['b_wins'] += 1
elif result == 'white':
if a_is_black:
self.results['b_wins'] += 1
else:
self.results['a_wins'] += 1
else:
self.results['draws'] += 1

return self.results

def play_game(self, black_engine, white_engine):
"""1局の対局を実施"""
game = Game()

while not game.is_terminal():
if game.current_player == 'black':
move = black_engine.get_move(game.state)
else:
move = white_engine.get_move(game.state)

game.play(move)

return game.get_winner()

統計的有意性

テスト結果に統計的意味があることを確認:

from scipy import stats

def calculate_confidence_interval(wins, total, confidence=0.95):
"""勝率の信頼区間を計算"""
p = wins / total
z = stats.norm.ppf((1 + confidence) / 2)
margin = z * math.sqrt(p * (1 - p) / total)

return (p - margin, p + margin)

# 例
wins, total = 220, 400
ci_low, ci_high = calculate_confidence_interval(wins, total)
print(f"勝率: {wins/total:.1%}, 95% CI: [{ci_low:.1%}, {ci_high:.1%}]")

推奨対局数

予想Elo差推奨対局数信頼度
>10010095%
50-10020095%
20-5040095%
<201000以上95%

SPRT(逐次確率比検定)

概念

対局数を固定せず、累積結果に基づいて動的に停止を判断:

def sprt(wins, losses, elo0=0, elo1=10, alpha=0.05, beta=0.05):
"""
逐次確率比検定

elo0: 帰無仮説のElo差(通常0)
elo1: 対立仮説のElo差(通常5-20)
alpha: 偽陽性率
beta: 偽陰性率
"""
if wins + losses == 0:
return 'continue'

# 対数尤度比を計算
p0 = expected_score(elo1, 0) # H1での期待勝率
p1 = expected_score(elo0, 0) # H0での期待勝率

llr = (
wins * math.log(p0 / p1) +
losses * math.log((1 - p0) / (1 - p1))
)

# 決定境界
lower = math.log(beta / (1 - alpha))
upper = math.log((1 - beta) / alpha)

if llr <= lower:
return 'reject' # H0が棄却、新モデルは弱い
elif llr >= upper:
return 'accept' # H0が受容、新モデルは強い
else:
return 'continue' # テストを継続

KataGoベンチマーク

ベンチマークの実行

# 基本テスト
katago benchmark -model model.bin.gz

# 訪問回数を指定
katago benchmark -model model.bin.gz -v 1000

# 詳細出力
katago benchmark -model model.bin.gz -v 1000 -t 8

出力の解釈

KataGo Benchmark Results
========================

Configuration:
Model: kata-b18c384.bin.gz
Backend: CUDA
Threads: 8
Visits: 1000

Performance:
NN evals/second: 2847.3
Playouts/second: 4521.8
Avg time per move: 0.221 seconds

Memory:
GPU memory usage: 2.1 GB
System memory: 1.3 GB

Quality metrics:
Policy accuracy: 0.612
Value accuracy: 0.891

主要指標

指標説明良好な値
NN evals/secニューラルネットワーク評価速度>1000
Playouts/secMCTSシミュレーション速度>2000
GPU使用率GPU使用効率>80%

棋力評価

人間の棋力対照

AI Elo人間の棋力
~1500アマチュア初段
~2000アマチュア5段
~2500プロ初段
~3000プロ五段
~3500世界チャンピオン級
~4000以上人間を超越

主要AIのElo

AIElo(推定)
KataGo (最新)~5000
AlphaGo Zero~5000
Leela Zero~4500
絶芸~4800

テスト対照

def estimate_human_rank(ai_model, test_positions):
"""AIの人間相当の棋力を推定"""
# 標準テスト問題を使用
correct = 0
for pos in test_positions:
ai_move = ai_model.get_best_move(pos['state'])
if ai_move == pos['best_move']:
correct += 1

accuracy = correct / len(test_positions)

# 正確率対照表
if accuracy > 0.9:
return "プロ級"
elif accuracy > 0.7:
return "アマチュア5段以上"
elif accuracy > 0.5:
return "アマチュア1-5段"
else:
return "アマチュア級以下"

パフォーマンス監視

継続的監視

import time
import psutil
import GPUtil

class PerformanceMonitor:
def __init__(self):
self.metrics = []

def sample(self):
"""現在のパフォーマンス指標をサンプリング"""
gpus = GPUtil.getGPUs()

self.metrics.append({
'timestamp': time.time(),
'cpu_percent': psutil.cpu_percent(),
'memory_percent': psutil.virtual_memory().percent,
'gpu_util': gpus[0].load * 100 if gpus else 0,
'gpu_memory': gpus[0].memoryUsed if gpus else 0,
})

def report(self):
"""レポートを生成"""
if not self.metrics:
return

avg_cpu = sum(m['cpu_percent'] for m in self.metrics) / len(self.metrics)
avg_gpu = sum(m['gpu_util'] for m in self.metrics) / len(self.metrics)

print(f"平均CPU使用率: {avg_cpu:.1f}%")
print(f"平均GPU使用率: {avg_gpu:.1f}%")

パフォーマンスボトルネック診断

症状考えられる原因解決策
CPU 100%、GPU低い探索スレッド不足numSearchThreadsを増やす
GPU 100%、出力が遅いバッチが小さすぎるnnMaxBatchSizeを増やす
メモリ不足モデルが大きすぎるより小さいモデルを使用
速度が不安定温度が高すぎる冷却を改善

自動化テスト

CI/CD統合

# .github/workflows/benchmark.yml
name: Benchmark

on:
push:
branches: [main]

jobs:
benchmark:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Run benchmark
run: |
./katago benchmark -model model.bin.gz -v 500 > results.txt

- name: Check performance
run: |
playouts=$(grep "Playouts/second" results.txt | awk '{print $2}')
if (( $(echo "$playouts < 1000" | bc -l) )); then
echo "Performance regression detected!"
exit 1
fi

回帰テスト

def regression_test(new_model, baseline_model, threshold=0.95):
"""新モデルにパフォーマンス回帰がないかチェック"""
# 精度をテスト
new_accuracy = test_accuracy(new_model)
baseline_accuracy = test_accuracy(baseline_model)

if new_accuracy < baseline_accuracy * threshold:
raise Exception(f"精度回帰: {new_accuracy:.3f} < {baseline_accuracy:.3f}")

# 速度をテスト
new_speed = benchmark_speed(new_model)
baseline_speed = benchmark_speed(baseline_model)

if new_speed < baseline_speed * threshold:
raise Exception(f"速度回帰: {new_speed:.1f} < {baseline_speed:.1f}")

print("回帰テスト合格")

関連記事