よっしーの私的空間

機械学習を中心に興味のあることについて更新します

多人数対戦におけるレーティング方法(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

使い方は以下の通り。

  1. setPlayer関数でplayerオブジェクトを生成して、それらをリストとして格納する。
  2. 各playerの順位をリストとして格納する。
  3. 上記1,2のリストをcalcRatings関数に代入する。
  4. 各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等が更新される。
f:id:t-yoshi-book:20220213224138p:plain