生成式 AI 模型的排位算法
Bradley-Terry 模型和 Elo 积分调整算法
对 chatbot-arena-leaderboard (聊天机器人竞技排行榜)中 AI 模型得分计算方法的一些梳理。
TOP5 模型(20250217) | 竞技得分 |
---|---|
Gemini-2.0-Flash-Thinking-Exp-01-21 | 1384 |
Gemini-2.0-Pro-Exp-02-05 | 1379 |
ChatGPT-4o-latest (2025-01-29) | 1377 |
DeepSeek-R1 | 1361 |
Gemini-2.0-Flash-001 | 1355 |
图灵测试和人类偏好
历史上第一次尝试对机器智能进行定义时提出了图灵测试,这是一种给机器设计的 模仿游戏,它的现代版描述是: 请人类测试者在特定聊天软件上同时和两个“网友“聊天,其中一个是人类,另一个是机器人,在聊天过程中,如果人类测试者无法区分哪个是人类,哪个是机器人,那么就认为那台机器具备了人类智能(图灵定义的智能基准线是:机器成功欺骗了 30% 以上的测试者)。
+------------------------------------------------------------+
| (Human or Robot?) |
| +---------------------+ +---------------------+ |
| | [Screen 1] | | [Screen 2] | |
| | | | | |
| | : 我叫刘梓轩 | | : 我是陈梓涵 | |
| | | | | |
| +---------------------+ +---------------------+ |
| |
| [Person] |
| >_ 你叫什么名字? |
| |
+------------------------------------------------------------+
由于图灵测试的目标是“欺骗人类”,那么机器也许可以用一些话语技巧来取胜,比如一个巧舌如簧的模型可能通过了测试,拥有了语言技巧上的智能,但它却没有更实际的用途(缺乏知识和推理等能够帮助人类解决具体问题的能力),因此学术上检查模型的能力一般还是采用的静态的针对不同领域的测试基准,比如 MMLU (全称是 Massive Multitask Language Understanding, 可以译为大规模多科目语言理解能力测验集,其中包括 57 种任务,涵盖小学数学,计算机科学,法律,思想品德等等 这类似人类通识教育试题,偏向于知识测试), AIME(人类数学竞赛题,考查数学推理能力), ARC-AGI (考查归纳和类比推理能力)等。
静态测试集的缺点在于容易饱和,也就是说可能推出不久后,新发布的模型就刷到了接近满分, 比如 MMLU 的推出就是因为更早之前的 GLUE 测试集在推出一年后饱和,接着推出的 superGLUE 又在一年后被 GPT3 推向饱和。
准确率 (%)
100 |
| █████ █████
90 | █████ █████ █████
| █████ █████ █████
80 | █████ █████ █████
| █████ █████ █████
70 | █████ █████ █████
| █████ █████ █████
60 | █████ █████ █████ █████
|---------------------------------------
GLUE superGLUE MMLU ARC-AGI(2024)
对战类(Arena)榜单最初来自于国际象棋等运动,目的是给竞技选手在胜负历史(各类胜率)之外赋予一个绝对的数值作为选手得分,但聊天机器人如何对战呢?即便可以让两个机器人互相辩论,又如何评判二者孰胜孰负呢?
把这种竞技评分机制引入到 AI 模型评估的时候,吸收了图灵测试的特点,它的做法是:
用户在网页上和 AI 对话时,系统会将用户的输入同时喂给两个随机选择的 AI 模型并返回二者的回答,用户从中选择更好的一个。
这和图灵测试很相似,只不过目的不是区分哪个是人类哪个是机器。(这不是一般用户所关心的问题,用户更多是想获取信息去解决各种各样的问题,但网络服务提供商可以用某种特定的图灵测试去检测爬虫)
+------------------------------------------------------------+
| (你偏好哪个回答?) |
| +---------------------+ +---------------------+ |
| | [Screen 1] | | [Screen 2] | |
| | | | | |
| | : 🤣 | | : 🙂 | |
| | | | | |
| +---------------------+ +---------------------+ |
| |
| [Person] |
| >_ 给我一个代表开心表情 |
| |
+------------------------------------------------------------+
用户的每一次选择就决定了两个模型之间的“对战”结果,根据这些结果用后文介绍的算法就可以得到一个绝对得分,然后对 AI 模型进行排序。
这种”对战”不同于棋类游戏下按固定规则自动评判得到的结果,它是基于人类裁判的偏好,因此它可以推广到对任何生成(AIGC)模型的排位上,比如音乐,图片,视频模型等。
在和 ChatGPT 或其他一些聊天服务对话时,有时候也会弹出两个回答让用户选择,其目标可能是为了搜集人类偏好去训练回报模型或者用强化学习(或其他算法)继续训练提高模型的能力,这和本文关注的对模型现有能力的排序的目标和算法是不同的,但搜集数据的过程是类似的。
根据 Chatbot Arena Leaderboard 的介绍,其最初使用的是 Elo 积分更新算法,之后切换到 Bradley-Terry 模型(Transition from online Elo rating system to Bradley-Terry model),这种说法其实有点误导性,因为 Elo 本身可以看作是 Bradley-Terry 模型里的一种具体计算对战比分的方法,也属于 Bradley-Terry 模型。因此其真正意思是从 Elo 算法换到了 Bradley-Terry 模型里另一种计算积分的算法。
Bradley-Terry 模型
尽管叫作 Bradley-Terry 模型,但实际这最初是由 Ernst Zermelo 在 1929 年首次提出的,Bradley and Terry 在 1952 重新发现并命名。
为了区分 Bradley-Terry 模型中的“模型”和对话 AI 模型中的“模型”,后文用 "选手" 来指代 AI 模型。
Bradley-Terry 模型假设每个选手都对应了一个绝对的分值 \( r_i \) 代表它的能力,再假设任意选手 i 和 j 之间的比赛可以用伯努利随机变量 \( Y_{ij} \)表示,而且所有 \( Y_{ij} \) 相互独立。
伯努利随机变量意味着比赛的结果只有两种可能:要么选手 i 胜利,要么选手 j 胜利。用参数 \( p_{ij} \) 表示选手 \( i \) 战胜选手 \( j \) 的概率。
在该假设下,首先要解决的就是平局问题,比如,如果 DeepSeekR1 和 GPT-4o 对战了 100 次,其中 40 胜,30 负,30 平局,那么一般会把平局次数平分到胜负中去,即 DeepSeekR1 55 胜,45 负(也可以丢弃平局数据)。
为了继续把比赛获胜的概率和选手得分绑定,Bradley-Terry 模型继续假设:该概率只和选手之间实力的差值有关, 即 \( p_{ij} \) 由 \(r_{i}-r_{j}\) 决定。
更具体的,如果二者实力相等(实力差为 0), \( p_{ij} \) 就应该是 0.5, 如果选手 i 实力高于选手 j ,则 \( p_{ij} \) 应该大于 0.5 ,否则小于 0.5 。
这几句话至少对 \( p_{ij} \) 和 \(r_{i}-r_{j}\) 的关系 \( p_{ij}=f(r_{i}-r_{j}) \) 有了多一点的定量的约束,即 f(0)=0.5, 且 x 越大则 f(x) 越接近 1 (但不可能超过 1), x 越小 f(x) 越接近 0 。这种规律实际和 S 形曲线是相匹配的,而 S 形曲线里公式相对简单、性质又很好的一个具体实例是 sigmoid 函数:
AI 模型绘制的 sigmoid 函数 ASCII 图(手动调整后)
def plot_sigmoid(width=60, height=20):
# 定义 x 的范围
x_min, x_max = -3.5, 3.5
x_step = (x_max - x_min) / width
# 定义 y 的范围
y_min, y_max = 0.0, 1.0
y_step = (y_max - y_min) / height
# 初始化画布
canvas = [[" " for _ in range(width + 1)] for _ in range(height + 1)]
# 绘制坐标轴
for j in range(width):
canvas[height - 1][j + 1] = "-"
for i in range(height - 1):
canvas[i][width // 2] = "|"
canvas[height//2][width // 2] = "0.5"
# 计算 Sigmoid 曲线并绘制
for j in range(width):
x = x_min + j * x_step
y = 1 / (1 + math.exp(-x))
row = int((y - y_min) * (height - 1))
row = min(max(row, 0), height - 1) # 确保不越界
canvas[height - 1 - row][j + 1] = "/"
# 打印画布
for row in canvas:
print("".join(row))
plot_sigmoid()
| | ///// | ////// | //// | /// | /// | // | // | // | // 0.5/ // // | // | // | /// | /// | //// | ////// | //////------------------------------------------------------
Logistic 回归的特例
sigmoid 函数来自于对对数几率(log-odds)的线性建模,也就是 logistic 回归,假设样本 x 有三个特征 x1,x2,x3 ,对每条样本进行 0/1 二分类,用 logistic 回归建模的话,x 被分类为 1 的概率是:
\[ p=\frac{1}{1+e^{-(w_1x1+2_2x2+w_3x3+b)}} \]
整理后:
\[ \ln (\frac{p}{1-p})=w_1x1+w_2x2+w_3x3+b \]
左边的 p/(1-p) 就是几率,它表示分类为 1 的概率与分类为 0 概率的比值,放在更经典的概率场景里就是:赌赢的概率和输的概率的比值 – 赔率,取对数就被称为“对数几率”。
在 Bradley-Terry 模型下,右边线性方程被替换成了两个选手的实力差,分类概率 p 对应的是 i 战胜 j 的概率 \( p_{ij} \) :
\[ \ln \frac{p_{ij}}{1 - p_{ij}} = r_i - r_j \]
注意它和 logistic 回归的公式在语义上没有任何差别,只不过这里是对单个具体样本(比赛)的关系描述。
假设只有 3 个选手,可以把它写成更一般形式:
\[ \ln \frac{p}{1 - p} = r_1x_1 + r_2x_2 + r_3x_3 + 0 \tag{1} \]
对比一般 logisitic 公式(把权重由常见的 w 符号替换成 r):
\[ \ln \frac{p}{1-p}=r_1x1+r_2x2+r_2x3+b \tag{2} \]
可以看到 Bradley-Terry 模型只是模型权重和数据采样过程做了一些限制:
- 偏置 b 先验为 0
数据集里仅包括满足以下条件的数据(或者理解为采样的时候只筛选这类数据作为合法数据进行建模):
只有两个非 0 特征,且一个为 1 另一个为 -1, 自然也有 sum([x1,x2,…xn])=0 。
因此 Bradley-Terry 模型只是 Logistic 回归模型的一种特殊情况,完全可以放在一般 logistic 模型下进行求解。但这些限制使得公式经过变换后可以得到更具体的解释视角,也可以设计出与现实需求更贴合的优化算法,这才有后文的故事。
----------------------------
/ \
/ Logistic Regression \
/ ----------------------- \
/ / \ \
/ / Bradley-Terry Model \ \
/ / \ \
/ ------------------------------\ \
/ \
---------------------------------------------
比如对公式
\[ \ln \frac{p_{ij}}{1 - p_{ij}} = r_i - r_j \]
整理后:
\[ p_{ij} = \frac{1}{1 + e^{-(r_i - r_j)}} = \frac{e^{r_i - r_j}}{1 + e^{r_i - r_j}} = \frac{e^{r_i}}{e^{r_i} + e^{r_j}} \]
这可以解释成:在已知选手 i,j 的实力得分情况下,选手 i 和选手 j 对战时,获胜的概率就是对二者的实力值进行 softmax 归一化后的结果。因为有以上表达式,可以定义 \( \pi_{i} = e^{r_{i}} \) 也作为选手实力值(后文称为强度,strength),这样就有:
\[ p_{ij}=\frac{\pi_{i}}{\pi_{i}+\pi_{j}} \]
后文中,有些排位估计算法是去估计 \( \pi_{i} \) ,有些算法则是直接去估计 \( r_{i} \) ,但二者仅仅只是对同一个量在对数和指数尺度下的不同观测。
过参数化问题
以上模型是过度参数化的,意味着如果我们对所有 \( r_i \) 加上一个相同的常数 \( c \),模型的预测结果不会改变,因为两个队伍之间的差异 \( r_i - r_j \) 仍然保持不变。
为了避免这个问题,可以在计算出结果后将某个选手的实力值校准到特定得分上(比如最低分设置为 1000 分),或者把选手的平均水平校准到特定得分,比如取所有 \( r_{i} \) 的算术平均 \( \frac{1}{N}\sum_{i=1}^{N}r_{i} \) 为 0 ,对应到 \( \pi_{i} \) 上,就是所有 \( \pi_{i} \) 的几何平均 \( (\prod_{i=1}^{N}\pi_{i})^{\frac{1}{N}} \) 为 1.
问题是,从 logistic 回归模型层面,是什么限制使得 logistic 模型变得过参数化了? 是权重 b=0 的先验吗?看上去不是,提前解出了一个参数(或者固定某些参数进行微调),并不会导致其他参数变得冗余。那么只可能是数据采样机制限制导致的:还是以三个选手为例,数据集的每个样本 (x1,x2,x3) 满足 sum(x1,x2,x3)=0 (当然实际限制更强,三个数只能取 -1,1 和 0 的某个排列,但对于本文问题和为 0 的限制就足够解释了), 它如何影响到模型参数呢? 因为 r1x1+r2x2+r3x3 可以写成 r1x1+r2x2+r3(-x1-x2)= (r1-r3)x1+(r2-r3)x2, 因此独立变量只有 (r1-r3) 和 (r2-r3), 或者说是输入数据某个特征本身是线性冗余的,放到线性模型里,对应特征也冗余了。
总之,对于 N 个选手,要估计的并不是 N 个未知参数,而是 N-1 个。
接下来介绍的两个算法是估计 \( \pi_{i} \) 的。
Zermelo 算法
有了对每场比赛胜负的概率表示,那么当看到具体的 m 场比赛结果之后,可以用极大似然估计来计算出决定概率的那些参数了。
假设对任意选手 i 和选手 j (j!=i), i 战胜 j 的次数是 \( w_{ij} \) ,那么似然函数就是:
\[ L(r_1, r_2, \dots, r_n) = \prod_{(i,j)} p_{ij}^{w_{ij}} \]
注意,如果要按一般的 logistic 回归里似然函数的形式如下(并非正确):
\[ L(r_1, r_2, \dots, r_n) = \prod_{(i,j)} p_{ij}^{w_{ij}}(1-p_{ij})^{l_{ij}} \]
这里 \( l_{ij} \) 表示 i 输给 j 的次数,但实际 \( w_{ji} \) 就表示 i 输给 j 的次数(在对所有 i,j 遍历的时候已经覆盖了),因此我们只需要考虑对“赢”建模,这其实是 Bradley-Terry 模型是一般 logistic 模型的特例在似然函数上的体现。
由于 logistic 回归的似然函数是凸的,有唯一最优解,因此以上函数自然也有该性质。
继续计算其对数似然函数为: \[ \ln L(r_1, r_2, \dots, r_n) = \sum_{(i,j)} w_{ij} \ln p_{ij} \]
展开 \( p_{ij} \) 得到:
\[ \ln L(r_1, r_2, \dots, r_n) = \sum_{(i,j) } \left[ w_{ij} \ln \left( \frac{\pi _{i}}{\pi_{i} + \pi_{j}} \right) \right] = \sum_{(i,j) } \left[ w_{ij} \ln \pi_i-w_{ij}\ln (\pi_{i}+\pi_{j}) \right] \]
为了求最大值,可以对每个 \( \pi_k \) 求偏导数,用 \( \pi_{k} \) 表示任意某个选手得分是因为对于固定的 k, 以上式子可以拆分成两个:
\[ \ln L(r_1, r_2, \dots, r_n) = \sum_{(i=k,j) } \left[ w_{kj} \ln \pi_k-w_{kj}\ln (\pi_{k}+\pi_{j}) \right] + \sum_{(i,j=k) } \left[ w_{ik} \ln \pi_i-w_{ik}\ln (\pi_{i}+\pi_{k}) \right] \]
因此可以更清晰求导:
\[ \frac{\partial \ln L}{\partial \pi_k} = \sum_{j} \left[ w_{kj} \left( \frac{1}{\pi_{k}} \right) - w_{kj} \left( \frac{1}{\pi_k + \pi_j} \right) \right] + \sum_{i} \left[ - w_{ik} \left( \frac{1}{\pi_i + \pi_k} \right) \right] \]
此时式子的 i 可以写成 j, 与前一个式子合并:
\[ \frac{\partial \ln L}{\partial \pi_k} = \sum_{j} \left[ w_{kj} \left( \frac{1}{\pi_{k}} \right) - (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) \right] \]
可以用随机梯度下降或其他基于梯度的优化算法来估计最优解,解出之后在用上一节提到的方法校准。
Bradley–Terry model - Wikipedia 提到了 Zermelo 迭代法,思路是先将以上梯度置为 0 ,得到一个递归的关于 \( \pi_{k} \) 的式子: \[ \pi_{k} = \frac{\sum_{j}w_{kj}}{\sum_{j}(w_{kj}+w_{jk})/(\pi_{k}+\pi_{j})} \]
然后随机初始化所有 \( \pi_{k} \), 每轮迭代用以上式子计算出一组新的 \( \pi_{k} \) ,然后几何平均归一化: \[ \pi_{k} = \frac{\pi_{k}}{(\prod_{i}\pi_{i})^{\frac{1}{n}}} \]
不断迭代就可以逐渐逼近最优解。(如何证明见下节的说明)
更快的迭代算法 – Newman 算法
以上迭代算法是 1929 年 Zermelo 发现的,缺点是收敛效率低。
在近 100 年后的 2023 年,Newman 在 Efficient Computation of Rankings from Pairwise Comparisons 论文中发现对求偏导后置 0 的式子做一点变换:
\[ \frac{\partial \ln L}{\partial \pi_k} = \sum_{j} \left[ w_{kj} \left( \frac{1}{\pi_{k}} \right) - (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) \right] = 0 \]
\[ \sum_{j} w_{kj} \frac{1}{\pi_{k}} - \sum_{j} (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) = 0 \]
等价于:
\[ \frac{1}{\pi_{k}} \sum_{j} w_{kj}\left( \frac{\pi_k + \pi_j}{\pi_k + \pi_j} \right) - \sum_{j} (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) = 0 \]
\[ \frac{1}{\pi_{k}} \sum_{j} w_{kj}\left( \frac{\pi_k}{\pi_k + \pi_j} \right) + \frac{1}{\pi_{k}} \sum_{j} w_{kj}\left( \frac{\pi_j}{\pi_k + \pi_j} \right) - \sum_{j} (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) = 0 \]
\[ \sum_{j} \left( \frac{w_{kj}}{\pi_k + \pi_j} \right) + \frac{1}{\pi_{k}} \sum_{j} w_{kj}\left( \frac{\pi_j}{\pi_k + \pi_j} \right) - \sum_{j} (w_{kj} + w_{jk}) \left( \frac{1}{\pi_k + \pi_j} \right) = 0 \]
\[ \frac{1}{\pi_{k}} \sum_{j} w_{kj}\left( \frac{\pi_j}{\pi_k + \pi_j} \right) - \sum_{j} w_{jk} \left( \frac{1}{\pi_k + \pi_j} \right) = 0 \]
最终得到
\[ \pi_{k} = \frac{\sum_{j}w_{kj}\pi_{j}/(\pi_{k}+\pi_{j})}{\sum_{j}(w_{jk})/(\pi_{k}+\pi_{j})} \]
然后同样对所有 \( \pi_{k} \) 的几何平均归一到 1
\[ \pi_{k}=\frac{\pi_{k}}{(\prod_{j}^{n}\pi_{k})^{\frac{1}{n}}} \]
论文证明这个迭代方式会收敛到最优解(实际证明了一系列迭代算法的收敛性,包括 Zermelo 算法,并在经验上展示该算法在一些迭代算法中收敛速度是最快的)
Elo 算法
Elo等级分制度(英语:Elo rating system)是指由匈牙利裔美国物理学家Arpad Elo创建的一个衡量各类对弈活动水平的评价方法,是当今对弈水平评估公认的权威标准,且被广泛用于国际象棋、围棋、足球、篮球等运动。网络游戏的竞技对战系统也常采用此分级制度。
Elo 算法所要计算的选手得分的模型和 Bradley-Terry 是一样的, 但 Zermelo 和 Newman 算法是直接去优化最大似然函数,而似然函数里依赖于 \( w_{ij} \) 这个批量统计参数(所有比赛里 i 战胜 j 的次数),因此必须在所有比赛都结束了才能开始算分,这可以看作一种线下(offline)批量迭代算法。 此外这两个算法估计的是强度 \( \pi_{i} \),而不是能力值 \( r_{i} \) 。
Elo 算法则追求即时更新,即在选手 i 和选手 j 每比完一场比赛后就对 ri 和 rj 进行更新, 可以看作一种在线的随机梯度下降法。这更适合现实中人类参与的竞赛场景(其最初就是为了国际象棋比赛而设计的), 同样也适合于在线对战游戏,用户每打完一场就可以直观看到分数变化,即时奖励更容易影响用户的多巴胺,也方便游戏或赛事主办方对相似积分的玩家进行匹配,势均力敌提升观赏性、选手对战体验以及从注意力经济角度增加每场的对战时长(参考 ELO算法的原理及应用 - 知乎)。
但对于 AI 模型,一方面 AI 本身不会对自己评分的波动感到兴奋或失落,因此不需要实时更新机制;另一方面, AI 的对战都是大规模并行的,每分钟可能有很多用户同时和 AI 对话,对话时间相对人类比赛也短的多,因此一天搜集到的数据就非常多,没有必要用单场比赛的更新算法;最后,Bradley-Terry 批量更新的方式更精确和稳定。因此 聊天机器人竞技场 在使用 Elo 算法几个月后就切换到了 Bradley-Terry 模型下的其他算法了(不是用的 Newman 或 Zermelo 算法,而是直接用通用的 logistic 回归求解方法,见后文案例部分)。
实际场景中的解释
前文只是说选手 i,j 的得分差和胜率的对数几率是线性的,这和 logistic 回归类似,但没有解释直觉上这意味着什么, Why does the Elo rating system work? - Mathematics Stack Exchange 中有一个回答对此做了解释,个人觉得可以作为参考:
Elo 的假定是:
- 如果选手 i 击败选手 j 的概率是选手 j 击败选手 i 的概率的 10 倍,那么选手 i 的 Elo 排名应该比选手 j 高 400 分。
- 同理,假设选手 i 击败选手 j 的概率是选手 j 击败选手 k 的概率的 10 倍,那么选手 i 击败选手 k 的概率将是选手 k 击败选手 i 的 100 倍。因此,选手 i 的 Elo 排名应该比选手 k 高 800 分。
具体地,如果两个玩家的 Elo 排名差距为 400 分,那么这会导致玩家间的胜负概率比例为 10:1 ,写成公式就是:
\[ \text{Odds(i > j)} = 10^{(r_i - r_j) / 400} \]
\[ p_{ij} = \frac{1}{1 + 10^{(r_j - r_i) / 400}} - \frac{1}{1 + e^{-(r_i - r_j)\ln 10 / 400}} \]
因此这和 Bradley-Terry 模型的假定是一样的,只不过有更具体的 ln10/400 这种缩放因子(或者可以说是 temperature)。
单样本局部参数更新算法
前文说到,Elo 算法的关键特点是局部的随机梯度下降,这种算法下,我们不需要考虑多场比赛的胜负集合,而只需要关心当前比赛,也就是以下一个式子:
\[ p_{ij} = \frac{1}{1 + 10^{(r_j - r_i) / 400}} \]
这个函数就是单次比赛结果下的似然函数,为了更方便计算其对数似然的梯度,先引入一些符号:
定义 \( \sigma(x)=\frac{1}{1+e^{-x}} \) , 这是标准的 sigmoid 函数,它有一些比较好的方便计算梯度的性质:
- \( \sigma \) 对 x 的的导数是 \( \sigma (1-\sigma) \),
- \( 1-\sigma(x)=\sigma(-x) \)
再定义: \[ \sigma'(x)=\frac{1}{1+10^{-x/400}}=\sigma(\frac{\ln10}{400}x)=\sigma(Zx) \]
这里用 Z 来表示 (ln10)/400 , \( \sigma ' \) 的性质是:
- \( \sigma' \) 函数对 x 的导数是 \( Z\sigma' (1-\sigma') \),
- \( 1-\sigma'(x)=\sigma'(-x) \)
那么单场比赛的概率可以写成: \( p_{ij}=\sigma'(r_{i}-r_{j}) \)
如果 i 赢了, \(\ln L= \log( \sigma'(r_{i}-r_{j})) \) 就是这场比赛结果的对数似然,计算梯度有:
\[\frac{\partial \ln L}{\partial r_i}= Z(1-\sigma'(r_{i}-r_{j}))\]
\[ \frac{\partial \ln L}{\partial r_j}=-Z\sigma'(r_{j}-r_{i}) \]
利用梯度提升(也就是对负对数似然 -L 做梯度下降),选手 i 的得分一次更新之后就是: \[ r_{i} = r_{i} + \eta Z(1- \sigma'(r_{i}-r_{j})) \]
选手 j 的得分一次更新之后就是:
\[ r_{j} = r_{j}+\eta Z(0- \sigma' (r_{j}-r_{i}))\]
以上区分了 i 和 j 的,但如果令 \( S_{i}=1 \) 表示 i 胜利, \( S_{i}=0 \) 表示 i 失败(j 胜利),并且 \( K=\eta Z \), 更新规则就可以只用一个行紧凑地表示:
\[ r_{i} = r_{i} + K(S_{i}- \sigma'(r_{i}-r_{j})) \]
注意如果去做多步梯度下降,那么就纯粹在单个样本上优化,它不等同于对多场比赛的全局负对数似然进行优化,但如果只朝着以上目标走一小步,那么这一步的方向大致上也是指向全局最优的方向,所以它实质就是随机梯度下降法。
和神经网络的随机梯度下降的一个不同点在于,Elo 中一个样本只能更新两个参数: \( r_i \) 和 \( r_j \) ,这是因为每个样本的特征是稀疏的,即只有 \( r_{i} \) 和 \( r_{j} \) 对应了 1 和 -1, 其他特征都是 0,但神经网络即便只有一个样本,也会更新所有参数(除模型内故意为之的随机 dropout 或者 lora 等微调部分参数的方案)
以下是其他参考资料里更常见的分数调整公式: \[ R' = R + K (S - E) \]
其中:
- \( E = \sigma'(r_{i}-r_{j}) \)
- \( K \) 是动态调整的常数,对于参加比赛少的新手,K 的值会更大,比如 32, 而参加的比赛越多,分数应该更趋于稳定,因此更新幅度会更低,比如 16,这类似于神经网络训练里的学习率衰减策略。
- \( S \) 是实际的比赛结果,1 代表胜利,0.5 代表平局,0 代表失败,这里把平局的情况也用线性插值给加进来了。
由于 K 的选择完全是经验性的,而且 ELo 本身就是对全局似然最大化的近似算法,按比赛的不同顺序去更新计算得分得到的结果也会不同, 因此这个算法的稳定性比 Bradley-Terry 批量迭代算法更差。
在一个例子上的计算
这是来自 Bradley–Terry model - Wikipedia 的例子,假设有 4 支队伍,它们之间总共进行了 21 场比赛。 每支队伍的胜场数以表格的行表示,对手则以列表示。
import numpy as np
results = np.array(
[[0, 2, 0, 1],
[3, 0, 5, 0],
[0, 3, 0, 1],
[4, 0, 3, 0]])
这表示:
- A 队赢了 B 队两次;
- A 队输给了 B 队三次;
- A 队没有和 C 队进行过比赛;
- A 队对 D 队的比赛中赢了一次,输了四次。
Bradley–Terry model - Wikipedia 给出各选手的最终强度得分是: \( \pi \)= [0.640, 1.043, 0.660, 2.270]. (这是几何平均归一化后的结果)
统计胜率
def count_rank(results):
win_count = np.sum(results, axis=1)
fail_count = np.sum(results, axis=0)
win_rate = win_count / (win_count + fail_count)
return win_rate
count_rank(results)
array([0.3 , 0.61538462, 0.33333333, 0.77777778])
这里的排序是: D>B>C>A
这种做法可能遇到类似田忌赛马的问题,比如假设 B 是最弱的,A 比 B 强,C,D 实力远高于 A,B,那么如果 A 同 B 对战了 100 次且每次都是 A 胜利,A 同 C,D 分别比赛了 3 次且都输了,除此之外都是 C 和 D 的比赛,胜负比例是 1:1, 那么这种情况 C 和 D 的胜率都是 50% 多一点,而 A 的胜率接近 100% 。 换句话说,这种算法使得 A 对战 C,D 失败的信息没法传递到 A 对和 B 的对战信息上,使得 A 仅靠着欺负弱小的胜率而排到了第一。(这也有点类似辛普森悖论,在局部统计上,C,D 强于 A, 但综合统计 A 强于 C,D)
当然,如果模型之间比赛的匹配是相对均匀的,那么这种传递性问题在数据层面就被消除了(类似随机控制试验消除因果混淆?),用该方法预估得分并排序是可行的,在一些足球联赛中,每个球队和其他球队都有固定的交手次数,最后统计胜率就可以了(平局后再统计净胜球)。 但在 AI 评估场景下,由于每天搜集到很多数据,并且新模型频繁发布,旧模型也可能下线, 那么模型之间的交战次数无法保证均匀(考虑到用户体验,也会尽可能少地匹配较差的模型给用户),而且这种方式要求每次构造一个大矩阵重新计算,在榜单经常更新的情况下计算量也很大。
Elo
def elo_rank(results, k=4, reversed=False):
n = len(results)
order = []
for i in range(n):
for j in range(n):
order.extend([(i, j)] * results[i][j])
if reversed:
order = order[::-1]
ratings = np.ones(n) * 1000
for i, j in order:
e = 1 / (1 + 10 ** ((ratings[j] - ratings[i]) / 400))
ratings[i] += k * (1 - e)
ratings[j] += k * (0 - (1 - e))
print(ratings)
delta = ratings[0] - 1000
ratings -= delta
return ratings
elo_rank(results, k=4, reversed=False)
[ 991.90359033 1005.53773269 992.7547053 1009.80397168] array([1000. , 1013.63414236, 1000.85111497, 1017.90038135])
以上算法更新中固定 K 的值,并把 A 的得分校准到了 1000, 得到的排序是 D>B>C>A, 这和上一种计算方法得到结果是一样的(说明之前对战安排相对是公平的)
Elo 的不稳定性在于 K 的选择和比赛顺序:
比如修改 k 值,得分就不会不一样(尽管有时候总体排序还是一样)
elo_rank(results, k=10, reversed=False)
array([1000. , 1032.7481022 , 1004.99167616, 1044.40869968])
以下修改比赛顺序,排序就发生了变化: D>B>A>C
elo_rank(results, k=4, reversed=True)
array([1000. , 1012.61767187, 999.27397405, 1016.50208449])
不过 A,C 水平本身是接近的。
参数 K 和比赛顺序对 Elo 的稳定性详细分析可以参考: NeurIPS Poster Elo Uncovered: Robustness and Best Practices in Language Model Evaluation
Zermelo 算法 (1929)
更新公式为:
\[ \pi_{k} = \frac{\sum_{j}w_{kj}}{\sum_{j}(w_{kj}+w_{jk})/(\pi_{k}+\pi_{j})} \]
然后每一轮更细后要对所有 \( \pi_{k} \) 的几何平均归一到 1 \[ \pi_{k}=\frac{\pi_{k}}{(\prod_{j}^{n}\pi_{j})^{\frac{1}{n}}} \]
其实现为:
def bt_rank_1929(results, epochs=20):
n = len(results)
ratings = np.ones(n)
for step in range(epochs):
for k in range(n):
numerator = sum(results[k])
denominator = 0
for j in range(n):
if j == k:
continue
denominator += (results[k][j] + results[j][k]) / (
ratings[k] + ratings[j]
)
ratings[k] = numerator / denominator
ratings = ratings / np.exp(np.mean(np.log(ratings)))
return ratings
print(bt_rank_1929(results, epochs=3))
print(bt_rank_1929(results, epochs=20))
print(bt_rank_1929(results, epochs=30))
print(bt_rank_1929(results, epochs=40))
[0.63693151 1.21910418 0.69658672 1.84880562] [0.63987084 1.0440904 0.65996953 2.26801384] [0.63983646 1.04334968 0.65981745 2.27026909] [0.63983489 1.04331601 0.65981053 2.27037173]
排序结果是 D>B>C>A, 且 30 轮以上收敛到最优结果
引入 normalize2elo 函数,根据 \( \sigma'(x)=\frac{1}{1+10^{-x/400}}=\sigma(\frac{\ln10}{400}x) \) 且 \( \pi=e^{r} \) 这层关系将得分转到 Elo 尺度,并且将 A 的得分固定为 1000:
def normalize2elo(ratings):
elo_scores = 400 / np.log(10) * ratings
delta = elo_scores[0] - 1000
elo_scores -= delta
return elo_scores
print(normalize2elo(np.log(bt_rank_1929(results, epochs=40))))
[1000. 1084.9391811 1005.34052848 1220.01162127]
Newman 算法 (2023)
\[ \pi_{k} = \frac{\sum_{j}w_{kj}\pi_{j}/(\pi_{k}+\pi_{j})}{\sum_{j}(w_{jk})/(\pi_{k}+\pi_{j})} \]
\[ \pi_{k}=\frac{\pi_{k}}{(\prod_{j}^{n}\pi_{k})^{\frac{1}{n}}} \]
def bt_rank_2023(results, epochs=20):
n = len(results)
ratings = np.ones(n)
for step in range(epochs):
for k in range(n):
numerator = 0
denominator = 0
for j in range(n):
if j == k:
continue
numerator += results[k][j] * ratings[j] / (ratings[k] + ratings[j])
denominator += results[j][k] / (ratings[k] + ratings[j])
ratings[k] = numerator / denominator
ratings = ratings / np.exp(np.mean(np.log(ratings)))
return ratings
print(bt_rank_2023(results, epochs=2))
print(bt_rank_2023(results, epochs=3))
print(bt_rank_2023(results, epochs=4))
print(bt_rank_2023(results, epochs=10))
[0.67734685 1.03368552 0.62447816 2.28708969] [0.64996562 1.02624863 0.65621883 2.28459032] [0.63867658 1.04248289 0.661457 2.27064091] [0.63983459 1.04331496 0.65981021 2.2703762 ]
这里第 3-4 轮就接近收敛了,转成 Elo 得分
print(normalize2elo(np.log(bt_rank_2023(results, epochs=5))))
[1000. 1085.24912639 1005.59298743 1220.12534776]
对似然拆分后拟合
再尝试一种近似计算方法,以前文提到的例子说明,假设 DeepSeekR1 vs GPT-4o 55 胜,45 负的情况下,针对 \( Y_{ij} \) 的极大似然估计直接就就可以得到 DeepSeekR1 和 GPT-4o 的 \( p_{ij}=0.55 \) 。
那么我们有: log(0.55/0.45)= \( r_{i}-r_{j} \) (对应的是 DeepSeekR1 和 GPT-4o 的得分差)
通过统计所有模型两两对战的结果,N 个模型会搜集到 C(N,2)=N(N-1)/2 对,那么就有 N(N-1)/2 组线性方程, 要解 N-1 个变量,方程数远大于变量数,那么这线性方程几乎是无解的,不过可以用最小二乘求近似。
这种做法相当于把似然函数多个乘积拆分成不同因子,然后单独对各个子因子进行最大似然估计,再找一个近似的可能最大化各个子因子的解,它和以上提到的优化全局似然是不同的,但这种拆分成多个子因子的思路和 Elo 算法有类似之处。
def local_bt_rank(results):
n = len(results)
rows = n * (n - 1) // 2
X = np.zeros((rows, n))
y = np.zeros(rows)
row = 0
for i in range(n):
for j in range(i + 1, n):
if (results[i][j] + results[j][i]) == 0 or results[i][j] == 0:
continue
p_ij = results[i][j] / (results[i][j] + results[j][i])
log_odds = np.log(p_ij / (1 - p_ij))
X[row, i] = 1
X[row, j] = -1
y[row] = log_odds
row += 1
# solve the least squares problem
ratings = np.linalg.lstsq(X, y, rcond=None)[0]
return ratings
res = local_bt_rank(results)
print(res)
print(normalize2elo(res))
[-0.47073006 0.03299569 -0.37956928 0.81730365] [1000. 1087.50612634 1015.83624921 1223.75437381]
这也得到了 D>B>C>A 的排序,并且结果和上一节的迭代算法结果 [1000., 1085.25, 1005.6, 1220.13] 也较为接近。
logistic 回归计算方法
上一节的算法是把每对选手的胜率统计出来,联立多个 log(p/(1-p))=ri-rj 方程,这种情况下如果 p 估计出来很不精确,比如两个选手比赛很少,就会增大误差。
然而,由于每场比赛都遵循 \( p_{ij}=\sigma (r_{i}-r_{j}) \) ,并且可以观察到胜或者负的标签(对应 1 和 0),那么直接将其看作二分类问题,用封装好的 logistic 回归的计算方法(比如 lbfgs)即可:
from sklearn.linear_model import LogisticRegression
def logistic_mle_rank(results, epoches=10):
n = results.shape[0] # 模型数量
X, Y, sample_weights = [], [], []
for i in range(n):
for j in range(n):
row = np.zeros(n)
row[i], row[j] = 1, -1
X.append(row)
Y.append(1.0)
sample_weights.append(results[i][j])
row = np.zeros(n)
row[i], row[j] = -1, 1
X.append(row)
Y.append(0.0)
sample_weights.append(results[i][j])
X = np.array(X)
Y = np.array(Y)
sample_weights = np.array(sample_weights)
# 训练逻辑回归模型
lr = LogisticRegression(
fit_intercept=False, penalty='none', tol=1e-6,
max_iter=epoches, solver="lbfgs"
)
lr.fit(X, Y, sample_weight=sample_weights)
return lr.coef_[0]
print(logistic_mle_rank(results, 8))
print(normalize2elo(logistic_mle_rank(results, 8)))
[-0.44654166 0.04240325 -0.41580551 0.81994392] [1000. 1084.93843084 1005.33941574 1220.0110788 ]
chatbot-arena-leaderboard 所采用的是这种计算方式,参考 Chatbot Arena Leaderboard Calculation (Bradley-Terry model) - Colab
Bayes 视角:把实力值看作分布
还有偏差
前文里始终是假定选手 i(在特定时期)有一个固定的实力得分 \( r_{i} \) ,然后建立其得分差和比赛胜负的概率的关系,再用最大化似然作为目标,要么是迭代求解,要么是用近似随机梯度下降。 这可以看作是一种典型的所谓“频率主义”的参数估计范式。
但也可以假定选手的得分不是一个固定的值,而是分布,比如正态分布,logistics 分布,这种添加先验的 Bayes 主义视角能提供更多概率上的解释(从而基于此做出更通用的扩展,比如考虑平局的情况等等),在 Elo 的百科里也提到:
Elo模型原先采用正态分布。但是实践表明棋手的表现并非呈正态分布,所以现在的等级分计分系统通常使用的是对数分布。
先考虑经典抛硬币的例子,我们不知道硬币为正的概率是多少,于是抛一次硬币,发现为正面,频率主义下假设 p 是一个未知的实数,如果直接最大化这一次观测的似然 p, 那么估计出硬币为正的概率就是 1, 这当然过于武断, 不过随着抛硬币的次数越来越多,统计出的出现正面的频率会在大数定律加持下接近“真实”的正面概率值 p。
而如果是 Bayes 视角,那么硬币为正的概率是 p 就不再被视为一个未知的固定实数,而是 0 到 1 之间的随机变量。 可以看到,为了规定出先验分布,我们需要有其他额外的先验知识,比如 p 是 0 到 1 之间的,在频率主义方法上不需要这个知识,极大似然估计解出的值自然是 0 到 1 ,因为似然函数是概率模型里的对象,它的解的范围已经被概率公理限定了, 但 Bayes 视角下, p 的范围不是概率范围,而是随机变量的取值范围,而随机变量的取值是任意的(比如色子的点数是 1 到 6),因此必须用人类自己的知识去限定它。
于是,可以假设 p 是在 0-1 之间的均匀分布,或者以 0.5 为中心,标准差为 0.1 的取值在 0 到 1 之间带截断的高斯分布(需要额外的方差或者均值先验作为约束)。考虑到计算简单性,对于有限范围的随机变量大部分都是用均匀分布作为先验(最大熵原则,同时不依赖额外均值方差先验)。
好了,现在回到选手积分估计的模型场景,要给每个选手的实力 r 或者强度 \( \pi \) 一个先验分布,第一个问题是,它的取值范围是多少?
前文提到,该模型本身就是过参数化的,即便是最优解,也可以有任意选择,因此需要选一个数值去作为基准,那么一种做法是,每个选手得分的先验分布是高斯分布,其均值是需要估计的 \( r_{i} \) 但方差得根据选择的得分基准去选择,比如是 \( \sqrt{\frac{400}{ln10}} \), 这实际是一个需要经验验证的超参数。
由抛硬币的例子可以知道,如果先验的目标是概率的话,我们自然就知道它的范围是 0-1, 并且均匀分布是相对合理的。
假设我们已经定义选手 i 和选手 j 对战的概率:
\[ p_{ij}=\frac{\pi_{i}}{\pi_{i}+\pi_{j}} \]
由于过参数化,一般会对 \( \pi_{i} \) 的几何平均设置为 1, 而几何平均可以看作是所有选手的平均强度,因此对强度为 \( \pi \) 的选手,它战胜某个“虚拟”的平均水平选手的概率 \( p=\frac{\pi}{\pi+1} \)。
由于没有额外的信息,那么假设该概率的先验分布是在 0 到 1 之间的均匀分布,即 p~U
所以这里我们是通过限定 p 间接地去给 \( \pi \) 做先验限定,由于 \( \pi=e^{r} \) (我们是做验证,因此这里直接拿结论过来),那么 r 的分布就是 p 通过 \( \pi \) 再对 r 求导(逆变换),即 \( \frac{1}{(\pi+1)^{2}} e^{r} \) 。
根据符号统一性偏好,它可以写成两种形式:
只留下强度符号 \( \pi \):
\[ \frac{\pi}{(\pi+1)^{2}} = (1-\frac{1}{\pi+1})\frac{1}{\pi+1} \]
其解释是,某个选手的强度的先验概率就是该选手和平均水平选手进行了两场比赛,一场胜利且一场失败的概率。 注意这是一个形式为 (1-x)x 的式子,开口向下的抛物线, x=0.5 的时候取最大值 0.25 ,对应 \( \pi=1 \) 取 0.25。 如果一个没有任何背景的选手加入比赛,那么人们会以 0.25 的高概率认为它是平均水平。
只留下实力符号 \( r \):
\[ \frac{e^{r}}{(e^{r}+1)^{2}} = \frac{1}{(e^{r}+1)(e^{-r}+1)}\]
这个式子实际是均值为 0, 缩放系数为 1 的 logistic 分布的概率密度函数。
这表明,如果选手的实力值 r 遵循该分布,那么它和 Bradley–Terry 模型里的 \( \pi=e^{r} \) 以及 \( p_{ij}=\frac{\pi_{i}}{\pi_{i}+\pi_{j}} \) 假设是自洽的。
此外还可以参考 【RLxLLM 基础】Preference Learning: Bradley–Terry, The Goodhart's Law, RLHF, ESPD与科研BoN策略(x - 知乎 以及 Efficient Computation of Rankings from Pairwise Comparisons 中的第五章
模拟
有了先验分布,我们实际可以用 MonteCarlo 方式生成样本来模拟出对战情况,然后再用以上提到的算法去估计,对比一下高斯分布和 logistic 分布的情况:
def monte_carlo_bt_rank(dist):
dev = 400 / np.log(10)
if dist == np.random.logistic:
dev *= np.sqrt(3)/np.pi
n = 4
scores = np.array([1000, 1085, 1005., 1220])
results = np.zeros((n, n))
for _ in range(10000):
i, j = np.random.choice(n, 2, replace=False)
si, sj = dist(scores[i], dev), dist(scores[j], dev)
if si > sj:
results[i][j] += 1
else:
results[j][i] += 1
return normalize2elo(logistic_mle_rank(results, 8))
注意对于 np.random.logistic(mu, scale), 它的第二个参数是 scale 它和标准差的关系是 \( \sigma=\frac{s\pi}{\sqrt{3}} \)
print(monte_carlo_bt_rank(np.random.normal))
print(monte_carlo_bt_rank(np.random.logistic))
[1000. 1103.80268873 1011.547248 1260.18306746] [1000. 1105.27856832 1001.77999902 1271.48526012]
但这里似乎把分值作为高斯分布,用 Bradley-Terry 模型中方法估计出来的分数更接近原始值,为什么?