多人数対戦におけるレーティング方法(glicko2 rating systemを用いて)
競走馬の強さをレーティングをしたくて、レーティングシステムについて調べたところ、glicko-2 rating systemというのが良さそうでした。本記事ではこれの直感的な理解やPythonによる実装方法についてまとめます。
Glicko-2 rating systemの計算式について知りたい場合は、以下の記事がよくまとまっています。以下の記事に書いてあったのですが、glicko-2 rating systemというのはsplatoon2(スプラトゥーン2)でも使用されているようです。
レーティングについてと,グリコ2レーティング(Glicko-2 System)におけるレーティング算出方法 - 機械学習、たまにゲーム
なお、本記事は執筆にあたり、以下を参考にしています。
1.glicko-2 rating systemの直感的な理解
Glicko-2のパラメータは以下3つで構成されていて、対戦毎にこれらのパラメータが更新されます。
- rating(レーティング):r(初期値:1500)
- rating deviation(レーティング偏差):RD(初期値:350)
- rating volatility(レーティング変動率):σ(初期値は適用対象による。論文では0.06と設定。)
ratingは解説するまでもないですが、高ければ高いほど強いことを表し、初期値は1500です。対戦で強い相手に勝つほどratingスコアが高くなります。逆に負けるとスコアが下がります。
rating deviationはratingスコアの振れ幅を表します。ratingは単一の値で表現されますが、これが本当に真の強さを表現できているとは限りません。そこで、スコアの振れ幅を設定して真の強さが収まる範囲を設定します。論文では、ratingが1850、rating deviationが50のとき、真の強さの95%信頼区間は1750~1950と言えると記載されていました。これはつまり、ratingスコアが正規分布に従うと仮定していて、その幅を以下の通り定義しているものと思われます。
r-1.96RD ≦ 真の強さ ≦ r+1.96RD
最後にrating volatilityについてですが、これは強さが安定しているかを表す指標です。恒常的に同じパフォーマンスを発揮している場合はこの値が低くなり、安定していない場合はこの値が高くなるようです。
2.glicko-2 rating systemの実装
実装に当たり、deepy/glicko2を使用しました。論文執筆者Mark Glickmanの実装(ryankirkman/pyglicko2)も試してみたのですが、稀にエラーが発生するので、今回はdeepy/glicko2を使用しています。
さっそくですが、実装したコードは以下の通りです。
# ライブラリのインポート from glicko2 import glicko2 import copy # プレイヤーのパラメータを設定する関数 ## 変数rating, rd, volはそれぞれrating、rating deviation、rating volatilityの初期値 ## playerオブジェクトを1つ返す def setPlayer(rating=1500, rd=350, vol=0.06): player = glicko2.Player() player.rating=rating player.rd=rd player.vol=vol return player # 複数プレイヤーのrating等パラメータを計算する関数 ## 変数playersは複数のplayerオブジェクトを格納したリスト ## 変数ranksは複数のplayerの対戦時における順位を格納したリスト ## 各playerのrating等を更新してplayersリストを返す def calcRatings(players,ranks): newPlayers=[] for i, (target_player,target_rank) in enumerate(zip(players,ranks)): new_target_player = copy.deepcopy(target_player) ratings=[] rds=[] outcomes=[] for j, (player, rank) in enumerate(zip(players,ranks)): if not i==j: ratings.append(player.rating) rds.append(player.rd) if rank>target_rank: outcomes.append(1) elif rank<target_rank: outcomes.append(0) elif rank==target_rank: outcomes.append(0.5) new_target_player.update_player(ratings, rds, outcomes) newPlayers.append(new_target_player) return newPlayers
使い方は以下の通り。
- setPlayer関数でplayerオブジェクトを生成して、それらをリストとして格納する。
- 各playerの順位をリストとして格納する。
- 上記1,2のリストをcalcRatings関数に代入する。
- 各playerのrating, rating deviation, rating volatilityが更新されたリストが返ってくる。
上記の手順を試しに実装すると以下の通り。
player1 = setPlayer(1400,30,0.06) player2 = setPlayer(1550,100,0.06) player3 = setPlayer(1700,300,0.06) player4 = setPlayer(1500,200,0.06) players=[player1,player2,player3,player4] ranks=[4,2,1,3] players=calcRatings(players,ranks)
上記を実行した際の結果は以下の通り各プレイヤーのrating等が更新される。