Skip to content

Commit 82c2a1c

Browse files
committed
Named params and temporal drift
1 parent 885985c commit 82c2a1c

File tree

7 files changed

+161
-74
lines changed

7 files changed

+161
-74
lines changed
Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
{
2-
"save_checkpoint": "../experiments/testing/codechef-state.json",
3-
"max_contests": 630,
4-
"mu_noob": 1600,
5-
"sig_noob": 280,
6-
"contest_source": "codechef",
7-
"system": {
8-
"method": "mmr-simple",
9-
"params": [0.2, 80, 1, 50, 0.1]
10-
}
11-
}
2+
"save_checkpoint": "../experiments/testing/codechef-state.json",
3+
"max_contests": 630,
4+
"mu_noob": 1600,
5+
"sig_noob": 280,
6+
"contest_source": "codechef",
7+
"system": {
8+
"method": "mmr-simple",
9+
"weight_limit": 0.2,
10+
"noob_delay": [0.6, 0.8],
11+
"sig_limit": 80,
12+
"drift_per_day": 0,
13+
"split_ties": true,
14+
"history_len": 50,
15+
"transfer_speed": 0.1
16+
}
17+
}
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
{
2-
"load_checkpoint": "../experiments/testing/codechef-state.json",
3-
"save_checkpoint": "../experiments/testing/codechef-state.json",
4-
"skip_contests": 630,
5-
"max_contests": 999,
6-
"mu_noob": 1500,
7-
"sig_noob": 280,
8-
"contest_source": "codechef",
9-
"system": {
10-
"method": "mmr-simple",
11-
"params": [0.2, 80, 1, 50, 0.1]
12-
}
13-
}
2+
"load_checkpoint": "../experiments/testing/codechef-state.json",
3+
"save_checkpoint": "../experiments/testing/codechef-state.json",
4+
"skip_contests": 630,
5+
"max_contests": 999,
6+
"mu_noob": 1500,
7+
"sig_noob": 280,
8+
"contest_source": "codechef",
9+
"system": {
10+
"method": "mmr-simple",
11+
"weight_limit": 0.2,
12+
"noob_delay": [0.6, 0.8],
13+
"sig_limit": 80,
14+
"drift_per_day": 0,
15+
"split_ties": true,
16+
"history_len": 50,
17+
"transfer_speed": 0.1
18+
}
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"max_contests": 9999,
3+
"mu_noob": 1500,
4+
"sig_noob": 350,
5+
"contest_source": "iaxcindividuals",
6+
"system": {
7+
"method": "mmr-simple",
8+
"weight_limit": 0.2,
9+
"noob_delay": [],
10+
"sig_limit": 0,
11+
"drift_per_day": 50,
12+
"split_ties": false,
13+
"history_len": 999,
14+
"transfer_speed": 0.1
15+
}
16+
}

multi-skill/src/experiment_config.rs

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,41 @@ use std::path::Path;
1515
#[derive(Deserialize, Debug)]
1616
#[serde(tag = "method", rename_all = "kebab-case")]
1717
pub enum SystemParams {
18-
Glicko { params: Vec<f64> },
19-
Bar { params: Vec<f64> },
20-
Endure { params: Vec<f64> },
21-
Cfsys { params: Vec<f64> },
22-
Tcsys { params: Vec<f64> },
23-
Trueskill { params: Vec<f64> },
24-
Mmx { params: Vec<f64> },
25-
Mmr { params: Vec<f64> },
26-
MmrSimple { params: Vec<f64> },
18+
Glicko {
19+
params: Vec<f64>,
20+
},
21+
Bar {
22+
params: Vec<f64>,
23+
},
24+
Endure {
25+
params: Vec<f64>,
26+
},
27+
Cfsys {
28+
params: Vec<f64>,
29+
},
30+
Tcsys {
31+
params: Vec<f64>,
32+
},
33+
Trueskill {
34+
params: Vec<f64>,
35+
},
36+
Mmx {
37+
params: Vec<f64>,
38+
},
39+
Mmr {
40+
params: Vec<f64>,
41+
},
42+
// Experimental support for named config params
43+
// TODO: find nice ways to use defaults and interface with constructor
44+
MmrSimple {
45+
weight_limit: f64,
46+
noob_delay: Vec<f64>,
47+
sig_limit: f64,
48+
drift_per_day: f64,
49+
split_ties: bool,
50+
history_len: usize,
51+
transfer_speed: f64,
52+
},
2753
}
2854

2955
fn usize_zero() -> usize {
@@ -119,7 +145,7 @@ impl Experiment {
119145
weight_limit: params[0],
120146
noob_delay: vec![], // TODO: add this to the config spec
121147
sig_limit: params[1],
122-
drift_per_sec: 0.,
148+
drift_per_day: 0.,
123149
split_ties: params[2] > 0.,
124150
subsample_size: params[3] as usize,
125151
subsample_bucket: params[4],
@@ -129,20 +155,28 @@ impl Experiment {
129155
weight_limit: params[0],
130156
noob_delay: vec![], // TODO: add this to the config spec
131157
sig_limit: params[1],
132-
drift_per_sec: 0.,
158+
drift_per_day: 0.,
133159
split_ties: params[2] > 0.,
134160
subsample_size: params[3] as usize,
135161
subsample_bucket: params[4],
136162
variant: EloMMRVariant::Logistic(params[5]),
137163
}),
138-
SystemParams::MmrSimple { params } => Box::new(SimpleEloMMR {
139-
weight_limit: params[0],
140-
noob_delay: vec![0.6, 0.8], // TODO: add this to the config spec
141-
sig_limit: params[1],
142-
drift_per_sec: 0.,
143-
split_ties: params[2] > 0.,
144-
history_len: params[3] as usize,
145-
transfer_speed: params[4],
164+
SystemParams::MmrSimple {
165+
weight_limit,
166+
noob_delay,
167+
sig_limit,
168+
drift_per_day,
169+
split_ties,
170+
history_len,
171+
transfer_speed,
172+
} => Box::new(SimpleEloMMR {
173+
weight_limit,
174+
noob_delay,
175+
sig_limit,
176+
drift_per_day,
177+
split_ties,
178+
history_len,
179+
transfer_speed,
146180
}),
147181
};
148182

multi-skill/src/systems/elo_mmr.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Elo-R system details: https://arxiv.org/abs/2101.00400
2-
use super::{Player, Rating, RatingSystem, TanhTerm};
2+
use super::{Player, Rating, RatingSystem, TanhTerm, SECS_PER_DAY};
33
use crate::data_processing::ContestRatingParams;
44
use crate::numerical::{solve_newton, standard_normal_cdf, standard_normal_pdf};
55
use core::ops::Range;
@@ -146,8 +146,8 @@ pub struct EloMMR {
146146
// each contest participation adds an amount of drift such that, in the absence of
147147
// much time passing, the limiting skill uncertainty's square approaches this value
148148
pub sig_limit: f64,
149-
// additional variance per second, from a drift that's continuous in time
150-
pub drift_per_sec: f64,
149+
// additional variance per day, from a drift that's continuous in time
150+
pub drift_per_day: f64,
151151
// whether to count ties as half a win plus half a loss
152152
pub split_ties: bool,
153153
// maximum number of opponents and recent events to use, as a compute-saving approximation
@@ -195,20 +195,32 @@ impl EloMMR {
195195
weight_limit,
196196
noob_delay,
197197
sig_limit,
198-
drift_per_sec: 0.,
198+
drift_per_day: 0.,
199199
split_ties,
200200
subsample_size,
201201
subsample_bucket,
202202
variant,
203203
}
204204
}
205205

206-
fn sig_perf_and_drift(&self, mut contest_weight: f64, n: usize) -> (f64, f64) {
206+
fn compute_weight(&self, mut contest_weight: f64, n: usize) -> f64 {
207207
contest_weight *= self.weight_limit;
208-
contest_weight *= self.noob_delay.get(n).unwrap_or(&1.);
209-
let sig_perf = (1. + 1. / contest_weight).sqrt() * self.sig_limit;
210-
let sig_drift_sq = contest_weight * self.sig_limit * self.sig_limit;
211-
(sig_perf, sig_drift_sq)
208+
if let Some(delay_factor) = self.noob_delay.get(n) {
209+
contest_weight *= delay_factor;
210+
}
211+
contest_weight
212+
}
213+
214+
fn compute_sig_perf(&self, weight: f64) -> f64 {
215+
let discrete_perf = (1. + 1. / weight) * self.sig_limit * self.sig_limit;
216+
let continuous_perf = self.drift_per_day / weight;
217+
(discrete_perf + continuous_perf).sqrt()
218+
}
219+
220+
fn compute_sig_drift(&self, weight: f64, delta_secs: f64) -> f64 {
221+
let discrete_drift = weight * self.sig_limit * self.sig_limit;
222+
let continuous_drift = self.drift_per_day * delta_secs / SECS_PER_DAY;
223+
(discrete_drift + continuous_drift).sqrt()
212224
}
213225

214226
fn subsample(
@@ -252,10 +264,9 @@ impl RatingSystem for EloMMR {
252264
let mut base_terms: Vec<(Rating, usize)> = standings
253265
.par_iter_mut()
254266
.map(|(player, lo, _)| {
255-
let (sig_perf, discrete_drift) =
256-
self.sig_perf_and_drift(params.weight, player.times_played_excl());
257-
let continuous_drift = self.drift_per_sec * player.delta_time as f64;
258-
let sig_drift = (discrete_drift + continuous_drift).sqrt();
267+
let weight = self.compute_weight(params.weight, player.times_played_excl());
268+
let sig_perf = self.compute_sig_perf(weight);
269+
let sig_drift = self.compute_sig_drift(weight, player.delta_time as f64);
259270
match self.variant {
260271
// if transfer_speed is infinite or the prior is Gaussian, the logistic
261272
// weights become zero so this special-case optimization clears them out
@@ -318,7 +329,8 @@ impl RatingSystem for EloMMR {
318329
);
319330
}
320331
let bounds = (-6000.0, 9000.0);
321-
let (sig_perf, _) = self.sig_perf_and_drift(params.weight, player.times_played_excl());
332+
let weight = self.compute_weight(params.weight, player.times_played_excl());
333+
let sig_perf = self.compute_sig_perf(weight);
322334

323335
match self.variant {
324336
EloMMRVariant::Gaussian => {

multi-skill/src/systems/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub use simple_elo_mmr::SimpleEloMMR;
2121
pub use topcoder_sys::TopcoderSys;
2222
pub use true_skill::TrueSkillSPb;
2323

24+
pub static SECS_PER_DAY: f64 = 86_400.;
25+
2426
// TODO: add a version that can take parameters, like in experiment_config but polymorphic
2527
pub fn get_rating_system_by_name(
2628
system_name: &str,

multi-skill/src/systems/simple_elo_mmr.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! This version has fewer features and optimizations than elo_mmr.rs, more
22
//! closely matching the pseudocode in https://arxiv.org/abs/2101.00400
3-
use super::{Player, Rating, RatingSystem, TanhTerm};
3+
use super::{Player, Rating, RatingSystem, TanhTerm, SECS_PER_DAY};
44
use crate::data_processing::ContestRatingParams;
55
use crate::numerical::solve_newton;
66
use rayon::prelude::*;
@@ -29,8 +29,8 @@ pub struct SimpleEloMMR {
2929
// each contest participation adds an amount of drift such that, in the absence of
3030
// much time passing, the limiting skill uncertainty's square approaches this value
3131
pub sig_limit: f64,
32-
// additional variance per second, from a drift that's continuous in time
33-
pub drift_per_sec: f64,
32+
// additional variance per day, from a drift that's continuous in time
33+
pub drift_per_day: f64,
3434
// whether to count ties as half a win plus half a loss
3535
pub split_ties: bool,
3636
// maximum number of recent contests to store, must be at least 1
@@ -43,9 +43,9 @@ impl Default for SimpleEloMMR {
4343
fn default() -> Self {
4444
Self {
4545
weight_limit: 0.2,
46-
noob_delay: vec![0.6, 0.8], // TODO: make the default empty once it's configurable
46+
noob_delay: vec![],
4747
sig_limit: 80.,
48-
drift_per_sec: 0.,
48+
drift_per_day: 0.,
4949
split_ties: false,
5050
history_len: usize::MAX,
5151
transfer_speed: 1.,
@@ -54,21 +54,32 @@ impl Default for SimpleEloMMR {
5454
}
5555

5656
impl SimpleEloMMR {
57-
fn sig_perf_and_drift(&self, mut contest_weight: f64, n: usize) -> (f64, f64) {
57+
fn compute_weight(&self, mut contest_weight: f64, n: usize) -> f64 {
5858
contest_weight *= self.weight_limit;
59-
contest_weight *= self.noob_delay.get(n).unwrap_or(&1.);
60-
let sig_perf = (1. + 1. / contest_weight).sqrt() * self.sig_limit;
61-
let sig_drift_sq = contest_weight * self.sig_limit * self.sig_limit;
62-
(sig_perf, sig_drift_sq)
59+
if let Some(delay_factor) = self.noob_delay.get(n) {
60+
contest_weight *= delay_factor;
61+
}
62+
contest_weight
63+
}
64+
65+
fn compute_sig_perf(&self, weight: f64) -> f64 {
66+
let discrete_perf = (1. + 1. / weight) * self.sig_limit * self.sig_limit;
67+
let continuous_perf = self.drift_per_day / weight;
68+
(discrete_perf + continuous_perf).sqrt()
69+
}
70+
71+
fn compute_sig_drift(&self, weight: f64, delta_secs: f64) -> f64 {
72+
let discrete_drift = weight * self.sig_limit * self.sig_limit;
73+
let continuous_drift = self.drift_per_day * delta_secs / SECS_PER_DAY;
74+
(discrete_drift + continuous_drift).sqrt()
6375
}
6476
}
6577

6678
impl RatingSystem for SimpleEloMMR {
6779
fn individual_update(&self, params: ContestRatingParams, player: &mut Player, mu_perf: f64) {
68-
let (sig_perf, discrete_drift) =
69-
self.sig_perf_and_drift(params.weight, player.times_played_excl());
70-
let continuous_drift = self.drift_per_sec * player.delta_time as f64;
71-
let sig_drift = (discrete_drift + continuous_drift).sqrt();
80+
let weight = self.compute_weight(params.weight, player.times_played_excl());
81+
let sig_perf = self.compute_sig_perf(weight);
82+
let sig_drift = self.compute_sig_drift(weight, player.delta_time as f64);
7283
player.add_noise_best(sig_drift, self.transfer_speed);
7384

7485
player.update_rating_with_logistic(
@@ -92,10 +103,9 @@ impl RatingSystem for SimpleEloMMR {
92103
let tanh_terms: Vec<TanhTerm> = standings
93104
.par_iter_mut()
94105
.map(|(player, _, _)| {
95-
let (sig_perf, discrete_drift) =
96-
self.sig_perf_and_drift(params.weight, player.times_played_excl());
97-
let continuous_drift = self.drift_per_sec * player.delta_time as f64;
98-
let sig_drift = (discrete_drift + continuous_drift).sqrt();
106+
let weight = self.compute_weight(params.weight, player.times_played_excl());
107+
let sig_perf = self.compute_sig_perf(weight);
108+
let sig_drift = self.compute_sig_drift(weight, player.delta_time as f64);
99109
player.add_noise_best(sig_drift, self.transfer_speed);
100110
player.approx_posterior.with_noise(sig_perf).into()
101111
})
@@ -116,7 +126,8 @@ impl RatingSystem for SimpleEloMMR {
116126
.fold((0., 0.), |(s, sp), (v, vp)| (s + v, sp + vp))
117127
};
118128
let mu_perf = solve_newton(bounds, f).min(params.perf_ceiling);
119-
let (sig_perf, _) = self.sig_perf_and_drift(params.weight, player.times_played_excl());
129+
let weight = self.compute_weight(params.weight, player.times_played_excl());
130+
let sig_perf = self.compute_sig_perf(weight);
120131
player.update_rating_with_logistic(
121132
Rating {
122133
mu: mu_perf,

0 commit comments

Comments
 (0)