算法 /
从 Elo 到 Glicko:训练分系统的数学骨架
SWUCPC 的训练分使用自研的 Glicko 变体。这篇文章整理从 Elo 到 Glicko 的数学直觉、一次批量更新的公式,以及定级赛在工程上如何落位。
训练分不是奖杯。它更像一个温度计:用尽可能少的假设,描述一个选手当前的水平,以及这个描述本身有多不确定。
SWUCPC 的训练分用了自研的 Glicko 变体,支持定级赛。这篇笔记记录设计时用到的数学骨架,以及几个工程上必须做的取舍。完整的参数、阈值和实现细节不在这里展开。
Elo 的核心:只记录一个期望
Elo 用分数差描述两名选手的预期胜率。设 A、B 的分数分别为 、,A 的期望得分为:
一局结束后,分数按“结果减期望”的方向移动:
其中 是实际得分, 控制单局更新的幅度。
这个模型足够简单,但它有一个隐含假设:分数已经足够可信。新队员和已经打了两年的人,用的是同一套更新力度。显然不对。
Glicko 多了一个“可信度”
Glicko 为每位选手增加一个评分偏差(rating deviation,)。 越大,表示系统越不确定这名选手的真实水平。
对局质量也和对手的 有关。对手分数很不可信时,这场比赛不应该被赋予太高权重。常用的权重函数是:
于是 A 对 B 的期望得分从固定的 400 分差,变成了带不确定度的形式:
接下来是两个同时更新的量。为了表达方便,把一场比赛记为 ,其中 是实际得分。
先计算信息量:
然后得到新的分数与偏差:
直观上,这就是一次带精度权重的贝叶斯更新:已有的分数带着自身的可信度,新比赛带来新的信息量,两者合并。打得越多、越稳定, 越小,后续单场变化也会随之收敛。
Glicko 的完整版本还会对波动率 做迭代估计。上面的三段式是一个足够解释“为什么这样更新”的简化版本。
把更新写成代码
实际系统一般会按一个评分周期批量处理多场比赛。下面的 Python 片段抓住核心的两个状态量,省去了波动率更新:
from dataclasses import dataclass
import math
Q = math.log(10) / 400
@dataclass(frozen=True)
class Rating:
r: float
rd: float
def g(rd: float) -> float:
return 1 / math.sqrt(1 + 3 * Q**2 * rd**2 / math.pi**2)
def expected(player: Rating, opponent: Rating) -> float:
return 1 / (1 + 10 ** (-g(opponent.rd) * (player.r - opponent.r) / 400))
def update(player: Rating, games: list[tuple[Rating, float]]) -> Rating:
"""games: [(对手, 实际得分)],得分为 0 / 0.5 / 1。"""
weighted = [(g(o.rd), expected(player, o), score) for o, score in games]
precision = 1 / player.rd**2 + Q**2 * sum(
weight**2 * exp * (1 - exp) for weight, exp, _ in weighted
)
new_r = player.r + (Q / precision) * sum(
weight * (score - exp) for weight, exp, score in weighted
)
new_rd = math.sqrt(1 / precision)
return Rating(new_r, min(new_rd, 350))
代码里有两个细节:
precision是 ,不是 。- 给 设上限,避免长期不活跃的账号把不确定性膨胀到没有意义。
定级赛放在哪
定级赛的难点不是公式,而是产品表达。
新用户的初始 很大,系统对这名用户的判断很弱。定级赛的任务不是“快速给出一个好看的分数”,而是用有限的场次收集足够的信息,让后续训练分进入一个相对稳定的区间。
工程上可以把它拆成三层:
- 状态层:初始 取高值;定级赛期间仍然按同一套公式更新。
- 展示层:定级赛期间展示区间或“定级中”,不完全隐藏分数,也不把它包装成确定结论。
- 策略层:定级赛结束后再开启更细的段位或标签,避免用早期噪声定义长期身份。
这三层分开之后,数学逻辑不需要为“新手任务”做特殊分支,产品也能诚实地告诉用户:现在只是估计,不是判决。
三个工程取舍
第一,评分不是能力本身。 题目难度、比赛类型、队友配合都会影响结果。训练分只应该作为观察训练状态的一个信号。
第二,更新频率比公式细节更重要。 一场比赛后立即更新,和累积一周再更新,用户的感受可能完全不同。系统需要明确自己的评分周期。
第三,不要把分数做成目标。 一个训练系统的目标是让薄弱项变得可见,并推动下一次训练,而不是让分数曲线好看起来。
这套数学骨架的价值,不是把一个数字算得更准,而是让“不确定”也成为一个可计算、可展示的对象。
参考资料
- Mark E. Glickman, The Glicko system。
- Arpad E. Elo, The Rating of Chessplayers, Past and Present。