Upload elo_rank.py
Browse files- elo_rank.py +133 -0
elo_rank.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
import json
|
3 |
+
|
4 |
+
class EloRank:
|
5 |
+
def __init__(self, initial_rating=1000, k_factor=32):
|
6 |
+
"""
|
7 |
+
Initialize the EloRank class.
|
8 |
+
:param initial_rating: Initial ELO rating for each model.
|
9 |
+
:param k_factor: The K-factor that determines the sensitivity of rating changes.
|
10 |
+
"""
|
11 |
+
self.ratings = {}
|
12 |
+
self.initial_rating = initial_rating
|
13 |
+
self.k_factor = k_factor
|
14 |
+
self.wins = {}
|
15 |
+
|
16 |
+
def add_model(self, model_id):
|
17 |
+
"""
|
18 |
+
Add a new model with the initial rating.
|
19 |
+
:param model_id: Unique identifier for the model.
|
20 |
+
"""
|
21 |
+
self.ratings[model_id] = self.initial_rating
|
22 |
+
self.wins[model_id] = 0
|
23 |
+
|
24 |
+
def record_match(self, winner, loser):
|
25 |
+
"""
|
26 |
+
Update the ratings based on a match result.
|
27 |
+
:param winner: Model ID of the winner.
|
28 |
+
:param loser: Model ID of the loser.
|
29 |
+
"""
|
30 |
+
rating_winner = self.ratings[winner]
|
31 |
+
rating_loser = self.ratings[loser]
|
32 |
+
|
33 |
+
expected_winner = self.expected_score(rating_winner, rating_loser)
|
34 |
+
expected_loser = self.expected_score(rating_loser, rating_winner)
|
35 |
+
|
36 |
+
self.ratings[winner] += self.k_factor * (1 - expected_winner)
|
37 |
+
self.ratings[loser] += self.k_factor * (0 - expected_loser)
|
38 |
+
|
39 |
+
# Update win count
|
40 |
+
self.wins[winner] += 1
|
41 |
+
|
42 |
+
def expected_score(self, rating_a, rating_b):
|
43 |
+
"""
|
44 |
+
Calculate the expected score for a model.
|
45 |
+
:param rating_a: Rating of model A.
|
46 |
+
:param rating_b: Rating of model B.
|
47 |
+
:return: Expected score.
|
48 |
+
"""
|
49 |
+
return 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
|
50 |
+
|
51 |
+
def get_rating(self, model_id):
|
52 |
+
"""
|
53 |
+
Get the current rating of a model.
|
54 |
+
:param model_id: Unique identifier for the model.
|
55 |
+
:return: Current rating of the model.
|
56 |
+
"""
|
57 |
+
return self.ratings.get(model_id, None)
|
58 |
+
|
59 |
+
def get_wins(self, model_id):
|
60 |
+
"""
|
61 |
+
Get the number of wins of a model.
|
62 |
+
:param model_id: Unique identifier for the model.
|
63 |
+
:return: Number of wins of the model.
|
64 |
+
"""
|
65 |
+
return self.wins.get(model_id, 0)
|
66 |
+
|
67 |
+
def get_top_models(self, n=2):
|
68 |
+
"""
|
69 |
+
Get the top N models by rating.
|
70 |
+
:param n: Number of top models to retrieve.
|
71 |
+
:return: List of model IDs of the top models.
|
72 |
+
"""
|
73 |
+
return sorted(self.ratings, key=self.ratings.get, reverse=True)[:n]
|
74 |
+
|
75 |
+
def sample_next_match(self):
|
76 |
+
"""
|
77 |
+
Sample the next match based on the probability proportional to the current rating.
|
78 |
+
This approach helps accelerate the convergence of ranking.
|
79 |
+
:return: Tuple of two model IDs for the next match.
|
80 |
+
"""
|
81 |
+
model_ids = list(self.ratings.keys())
|
82 |
+
probabilities = [self.ratings[model_id] for model_id in model_ids]
|
83 |
+
total_rating = sum(probabilities)
|
84 |
+
probabilities = [rating / total_rating for rating in probabilities]
|
85 |
+
|
86 |
+
# Sample two different models for the next match
|
87 |
+
next_match = random.choices(model_ids, probabilities, k=2)
|
88 |
+
while next_match[0] == next_match[1]:
|
89 |
+
next_match = random.choices(model_ids, probabilities, k=2)
|
90 |
+
|
91 |
+
return tuple(next_match)
|
92 |
+
|
93 |
+
def process_match_records(self, file_path):
|
94 |
+
"""
|
95 |
+
Process match records from a JSON file and update ratings and win counts accordingly.
|
96 |
+
:param file_path: Path to the JSON file containing match records.
|
97 |
+
"""
|
98 |
+
with open(file_path, 'r') as file:
|
99 |
+
match_records = json.load(file)
|
100 |
+
|
101 |
+
for record in match_records:
|
102 |
+
winner = record['winner']
|
103 |
+
model_1 = record['model_1']
|
104 |
+
model_2 = record['model_2']
|
105 |
+
|
106 |
+
# Add models if they are not already added
|
107 |
+
if model_1 not in self.ratings:
|
108 |
+
self.add_model(model_1)
|
109 |
+
if model_2 not in self.ratings:
|
110 |
+
self.add_model(model_2)
|
111 |
+
|
112 |
+
# Record the match result
|
113 |
+
if winner == model_1:
|
114 |
+
self.record_match(model_1, model_2)
|
115 |
+
elif winner == model_2:
|
116 |
+
self.record_match(model_2, model_1)
|
117 |
+
|
118 |
+
# # Example Usage
|
119 |
+
# e = EloRank()
|
120 |
+
# e.add_model('model_A')
|
121 |
+
# e.add_model('model_B')
|
122 |
+
# e.add_model('model_C')
|
123 |
+
|
124 |
+
# e.record_match('model_A', 'model_B')
|
125 |
+
# print(e.get_rating('model_A')) # Should be greater than the initial rating
|
126 |
+
# print(e.get_rating('model_B')) # Should be less than the initial rating
|
127 |
+
|
128 |
+
# print(e.get_top_models(2)) # Get the top 2 models
|
129 |
+
# print(e.sample_next_match()) # Sample the next match based on ratings
|
130 |
+
|
131 |
+
# # Process match records from a JSON file
|
132 |
+
# e.process_match_records('match_records.json')
|
133 |
+
# print(e.get_wins('model_A')) # Get the number of wins for model_A
|