如题,我们更新到了更先进的 Glicko-2 Rating 系统!
目测现在大家的 Rating 都很合理。
同时,Glicko-2 给我们带来了除了 Rating 外的两个参数:RD 和 Vol。
RD 可以理解为方差,一个人的 Perf 有 95% 的可能在 [Rating - 2RD, Rating + 2RD] 内。
Vol 表示这个人最近表现的波动程度。Vol 大的人,最近表现的特别好,或者特别烂。
我们还添加了一个 Rating 分布图像,在每个人的首页,Rating 曲线下面可以看到。非常有趣。
另外,格言已经回滚。但由于备份文件损坏,回滚到的是 7 月 4 日下午 14:00 的版本。
论文地址:Glicko-2 PDF
代码:
function g2_g($x) {
return 1 / sqrt(1 + 3 * $x * $x / M_PI / M_PI);
}
function g2_E($u, $uj, $pj) {
return 1 / (1 + exp(-g2_g($pj) * ($u - $uj)));
}
function g2_f($x, $delta, $phi, $v, $a, $tau) {
return (exp($x) * ($delta * $delta - $phi * $phi - $v - exp($x))
/ (2 * pow($phi * $phi + $v + exp($x), 2)))
- ($x - $a) / $tau / $tau;
}
function calcRatingGlicko2($standings, &$new_rating, &$new_rd, &$new_vol) {
$TAU = 0.6;
$n = count($standings);
$rating = array();
$rd = array();
$vol = array();
for ($i = 0; $i < $n; ++$i) {
$rating[$i] = ($standings[$i][2][1] - 1500) / 173.7178;
$rd[$i] = $standings[$i][2][2] / 173.7178;
$vol[$i] = ($standings[$i][2][3] ?? 0.06);
if ($vol[$i] <= 0) $vol[$i] = 0.06;
}
if ($n == 1) {
$new_rating[0] = $rating[0];
$new_rd[0] = $rd[0];
$new_vol[0] = $vol[0];
}
for ($i = 0; $i < $n; $i++) {
$delta = 0;
$v = 0;
for ($j = 0; $j < $n; $j++) if ($i != $j) {
$s = 0;
if ($standings[$i][3] > $standings[$j][3])
$s = 0;
else if ($standings[$i][3] == $standings[$j][3])
$s = 0.5;
else
$s = 1;
$e = g2_E($rating[$i], $rating[$j], $rd[$j]);
$v += pow(g2_g($rd[$j]), 2) * $e * (1 - $e);
$delta += g2_g($rd[$j]) * ($s - $e);
}
if ($v < 1e-20) $v = 1e-20;
$v = 1 / $v;
$delta *= $v;
if ($delta > 10) $delta = 10;
if ($delta < -10) $delta = -10;
$a = 2 * log($vol[$i]);
$A = $a;
if ($delta * $delta > $rd[$i] * $rd[$i] + $v)
$B = log($delta * $delta - $rd[$i] * $rd[$i] - $v);
else {
for ($k = 1; $k < 100 && g2_f($a - $k * $TAU, $delta, $rd[$i], $v, $a, $TAU) < 0; ++$k)
;
$B = $a - $k * $TAU;
}
$fa = g2_f($A, $delta, $rd[$i], $v, $a, $TAU);
$fb = g2_f($B, $delta, $rd[$i], $v, $a, $TAU);
while ($fa * $fb > 0) {
$B -= $TAU;
$fb = g2_f($B, $delta, $rd[$i], $v, $a, $TAU);
}
$k = 0;
while (abs($B - $A) > 1e-6 && $k < 100) {
if (!is_finite($fa) || !is_finite($fb)) break;
if (abs($fa - $fb) < 1e-15) break;
$C = $A + ($A - $B) * $fa / ($fb - $fa);
$fc = g2_f($C, $delta, $rd[$i], $v, $a, $TAU);
if ($fc * $fb <= 0) {
$A = $B;
$fa = $fb;
}
else
$fa = $fa / 2;
$B = $C;
$fb = $fc;
++$k;
}
$new_vol[$i] = exp(($A + $B) / 4);
$new_rd[$i] = sqrt($rd[$i] * $rd[$i] + $new_vol[$i] * $new_vol[$i]);
$new_rd[$i] = 1 / sqrt(1 / $v + 1 / $new_rd[$i] / $new_rd[$i]);
$new_rating[$i] = $rating[$i] + $delta / $v * $new_rd[$i] * $new_rd[$i];
$new_rating[$i] = $new_rating[$i] * 173.7178 + 1500;
$new_rd[$i] *= 173.7178;
}
}
原帖:
WyOJ 现在切换到了新的 和 Codeforces 算法相同的 Rating 系统。
具体算法:
写的很着急,如果有问题可以跟帖。
同时,所有 Rating 已经重新计算。现在的 Rating 应该是非常合理的。
新的 Rating 计算函数,采用 Codeforces 算法。
具体算法是:
设参赛前每人的 Rating 是 $r_i$, 比赛的最终排名是 $R_i$,那么
首先承认:在先前 Rating 的基础上,i 战胜 j 的概率是 $P_ij = \dfrac{1}{1 + 10^{\dfrac{r_j - r_i}{400}}}$
立即得到 i 的期望排名 $E_i = \sum_{j \ne i} P_{ji} + 1$
显然,真正的排名和期望排名之间的差距,就是 rating delta 的来源。
我们取 $S_i = \sqrt{E_i R_i}$,二分出一个 u 使得计算出的 $S_i = E_i$,rating 应当向 u 的方向变化。
取 $\delta = \dfrac{u + r_i}{2}$,随后我们对 $\delta$ 进行微调。
第一次微调:取 $d = \dfrac{-1 - \sum_{i} \delta_i}{n}$,将所有人的 \delta 加上 d。
第二次微调:取排名前 $s = \min{n, 4\sqrt n}$ 高的人,让这些人的 rating 变化量之和为零,得到 d。
随后再将所有人的 $\delta$ 加上 d,得到最终的 rating。
/*
* 新的 Rating 计算函数,采用 Codeforces 算法。
* 具体算法是:
* 设参赛前每人的 Rating 是 r_i, 比赛的最终排名是 R_i,那么
* 首先承认:在先前 Rating 的基础上,i 战胜 j 的概率是 P_ij = \dfrac{1}{1 + 10^{\dfrac{r_j - r_i}{400}}}
* 立即得到 i 的期望排名 E_i = \sum_{j \ne i} P_ji + 1
* 显然,真正的排名和期望排名之间的差距,就是 rating delta 的来源。
* 我们取 S_i = \sqrt{E_i R_i},二分出一个 u 使得计算出的 S_i = E_i,rating 应当向 u 的方向变化。
* 取 \delta = \dfrac{u + r_i}{2},随后我们对 \delta 进行微调。
* 第一次微调:取 d = \dfrac{-1 - \sum_{i} \delta_i}{n},将所有人的 \delta 加上 d。
* 第二次微调:取排名前 s = \min{n, 4\sqrt n} 高的人,让这些人的 rating 变化量之和为零,得到 d。
* 随后再将所有人的 \delta 加上 d,得到最终的 rating。
* */
function calcRatingX($standings) {
$n = count($standings);
$rating = array();
$delta = array();
$ds = 0;
for ($i = 0; $i < $n; ++$i)
$rating[$i] = $standings[$i][2][1];
for ($i = 0; $i < $n; ++$i) {
$l = 0;
$r = 4000;
while ($l <= $r) {
$mid = ($l + $r) / 2;
$e = 1;
for ($j = 0; $j < $n; ++$j)
if ($i != $j)
$e += 1 / (1 + pow(10, ($mid - $rating[$j]) / 400));
$s = sqrt($e * ($i + 1));
if ($e <= $s)
$r = $mid - 1;
else
$l = $mid + 1;
}
$delta[$i] = (($l + $r) / 2 - $rating[$i]) / 2;
$ds += $delta[$i];
}
$ds = (-1 - $ds) / $n;
for ($i = 0; $i < $n; ++$i)
$delta[$i] += $ds;
$s = min($n, 4 * sqrt($n));
$ds = 0;
for ($i = 0; $i < $s; ++$i)
$ds += $delta[$i];
$ds = -$ds / $s;
for ($i = 0; $i < $s; ++$i)
$delta[$i] += $ds;
$R = array();
for ($i = 0; $i < $n; ++$i)
$R[$i] = ceil($rating[$i] + $delta[$i]);
return $R;
}
