开始前,会这些就够了
- 能算三个数的平均值
- 知道二分类概率p的另一类概率是1−p
- 先读 Softmax 分类与数值稳定的损失函数:先理解logit如何变成概率,再比较平均发生在变换之前还是之后。
- 先读 平均概率都一样,为什么一组模型比另一组更有分歧?:分清成员预测与融合结果,避免把融合规则和成员分歧混为一谈。
三个分类器对类别1分别给出0.8、0.8、0.01。直接平均,得到0.536667,超过一半;先把概率变成二分类logit,再平均并转回概率,却得到0.352627,低于一半。
这里没有换模型、样本或权重,只是换了融合规则。实验没有真实标签,因此只能证明两种规则会给出不同选择,不能据此宣布其中一种更准确。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- demo.py:完整可运行源码
- README.md:运行步骤与练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和运行验证记录
- SHA256SUMS.txt:文件摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B demo.py解压后,在含 demo.py 的目录打开终端。命令分别回车执行。代码只对给定概率计算两种融合方式,没有导入PyTorch或scikit-learn,没有训练分类器。18组数值用独立的几何平均表达式核对;另检查相同输入、决策边界与无效输入。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
同三份预测,先平均概率还是先平均 logit?
正在准备实验控件…
二分类、三个等权输入、类别顺序一致;每个数是类别1的概率。二分类logit指 log(p/(1−p)),等于两类原始logit之差。这里只计算两种融合公式,没有训练模型或调用PyTorch/scikit-learn。限制概率严格在0与1之间;不比较哪种规则总是更好。
先平均概率,相当于直接平均三个答案分布
把每个分类器的完整输出写出来:类别1概率是p,类别0概率是1−p。本例依次是[0.2,0.8]、[0.2,0.8]、[0.99,0.01],两列的类别顺序一致。
每列直接求平均,类别1得到(0.8+0.8+0.01)/3=0.536667,类别0得到0.463333。若用最大概率选类,就选类别1。scikit-learn 的 soft voting 文档描述的就是基于加权概率和进行选择;这里采用三个相等权重。
它可以解释成先等概率选择一个分类器,再按那个分类器的分布看结果时,最终得到的混合分布。这是对给定概率的数学解释,不是在说三个分类器真的被随机调用。
先平均logit,实际平均的是对数胜算
二分类logit写成z=ln(p/(1−p))。p=0.8对应ln4≈1.386294,p=0.01对应ln(1/99)≈−4.595120。三个logit的平均值约为−0.607510。
再用p=1/(1+exp(−z))转回概率,得到0.352627。第三个输入对类别1的强烈反对,在对数胜算坐标下与前两个输入的支持共同参与平均。
这里的z是二分类的logit差值。若模型输出两类原始分数z₀、z₁,softmax给出的对数胜算等于z₁−z₀;不是把任意一个类别的原始分数直接当作这个二分类z。
把公式展开,会得到归一化的几何平均
对M个输入,令a=(∏pₘ)^(1/M),b=(∏(1−pₘ))^(1/M)。平均对数胜算再转回概率,结果正好是a/(a+b)。它比较的是两类各自的几何平均,并且最后还要归一化。
更一般地,若各模型使用相同类别顺序,pₘ,c=softmax(zₘ)的第c项,那么softmax(平均zₘ)的第c项,正比于∏pₘ,c^(1/M)。每个模型softmax的归一化常数,对所有类别相同,在最后一步会抵消。这个等式由softmax定义直接推得。
因此“平均概率”和“平均logit”是两种不同的定义,通常不相等。前者是算术平均,后者是归一化几何平均。不要只看到两边都写着mean,就把它们当成可以交换顺序的同一步操作。
运行完整小例子,再与独立公式核对
代码只对给定概率计算两种融合方式,没有导入PyTorch或scikit-learn,没有训练分类器。18组数值用独立的几何平均表达式核对;另检查相同输入、决策边界与无效输入。
本轮实际使用 Windows / Python 3.14.0 运行。保存为 demo.py,执行 python demo.py;只用标准库。检查使用显式 AssertionError,python -O 不会删除这些检查。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Two equal-weight pooling rules, using Python's standard library only."""
from math import exp, isclose, isfinite, log, log1p, prod
def require(condition, message):
if not condition:
raise AssertionError(message)
def pool(probabilities):
if not isinstance(probabilities, (list, tuple)) or not 1 <= len(probabilities) <= 10:
raise ValueError("need 1 to 10 probabilities")
if any(type(p) not in (int, float) or not isfinite(p) or not 0 < p < 1
for p in probabilities):
raise ValueError("probabilities must be finite and strictly between 0 and 1")
logits = [log(p) - log1p(-p) for p in probabilities]
mean_probability = sum(probabilities) / len(probabilities)
mean_logit = sum(logits) / len(logits)
# Stable inverse logit, also for very negative finite inputs.
logit_probability = (1 / (1 + exp(-mean_logit)) if mean_logit >= 0
else exp(mean_logit) / (1 + exp(mean_logit)))
return mean_probability, logit_probability
def main():
for values in ([0.8, 0.8, 0.01], [0.8, 0.8, 0.8], [0.8, 0.8, 0.1]):
arithmetic, logarithmic = pool(values)
print(f"p={values}; mean_probability={arithmetic:.6f}; mean_logit_then_sigmoid={logarithmic:.6f}")
a, b = pool([0.8, 0.8, 0.01])
print(f"opposite_decisions: probability_pool={int(a > 0.5)}; logit_pool={int(b > 0.5)}")
count = 0
for p in (0.001, 0.01, 0.1, 0.5, 0.8, 0.999):
for q in (0.01, 0.2, 0.9):
values = [p, p, q]
yes = prod(values) ** (1 / 3)
no = prod(1 - value for value in values) ** (1 / 3)
require(isclose(pool(values)[1], yes / (yes + no), abs_tol=1e-12),
"normalized geometric mean oracle")
count += 1
require(isclose(pool([p, p, p])[1], p, abs_tol=1e-12), "identical models")
require(isclose(pool([0.8, 0.8, 1 / 17])[1], 0.5, abs_tol=1e-12), "decision boundary")
for bad in ([], [0], [1], [True], [float('nan')]):
try:
pool(bad)
except ValueError:
pass
else:
raise AssertionError("invalid input accepted")
print(f"PASS: {count} geometric-mean checks, identical inputs, threshold and invalid inputs")
if __name__ == '__main__':
main()
怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-26 使用 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。代码只对给定概率计算两种融合方式,没有导入PyTorch或scikit-learn,没有训练分类器。18组数值用独立的几何平均表达式核对;另检查相同输入、决策边界与无效输入。
运行后对照结果
p=[0.8, 0.8, 0.01]; mean_probability=0.536667; mean_logit_then_sigmoid=0.352627
p=[0.8, 0.8, 0.8]; mean_probability=0.800000; mean_logit_then_sigmoid=0.800000
p=[0.8, 0.8, 0.1]; mean_probability=0.566667; mean_logit_then_sigmoid=0.547801
opposite_decisions: probability_pool=1; logit_pool=0
PASS: 18 geometric-mean checks, identical inputs, threshold and invalid inputs哪些情况下会相同,哪些细节需要先说清?
如果三个输入都给0.8,两条路径都得到0.8。相同是可以发生的,但一个相同例子不能证明两条规则处处等价。保持前两个0.8时,logit融合恰好在第三个概率为1/17时打平两类,可以作为单独的边界检查。
本页限制0<p<1,避免把无穷logit或互相冲突的零概率支持混入这个有限数值实验。互动控件进一步限制在0.001到0.999。没有对原始logit做额外温度缩放,也没有改变模型权重。
实际集成要说明类别是否对齐、概率是否校准、融合权重和温度如何选,并在独立验证数据上比较。这里不要求模型彼此独立,也没有用独立性假设证明某一种融合总是最优。站内关于平均预测熵与模型分歧的旧文研究另一问题:平均后的分布相同,成员之间的分歧仍可不同。
记住这三点
- 非线性变换前后各做一次平均,通常得到不同结果。
- 平均logit对应归一化几何平均,而不是概率的算术平均。
- 先固定类别顺序、权重、温度与评价数据,再比较融合规则。
别把例子用过头
- 把二分类对数胜算与单个原始类别logit混为一谈。
- 看到两个支持票就认定任何概率融合都一定支持同一类。
- 把一个反例改写成“平均概率永远更好”或“平均logit永远更好”。
自己试一小步
固定前两个输入,能否手算第三个输入让logit融合恰好打平的位置?
- 把前两个概率设成0.7,写出它们的胜算。
- 令三个胜算的乘积等于1,解出第三个概率。
- 先预测概率平均的方向,再用完整程序核对两条路径。
做到这些,就算完成
- 先写清类别1和类别0的对应关系。
- 说明是否用了等权与相同温度。
- 只比较规则输出,不凭空添加准确率结论。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- VotingClassifier — scikit-learn 1.9 (新标签页)↗
soft voting依据预测概率和选择类别;weights控制平均前的权重。
- Softmax — PyTorch 2.14 (新标签页)↗
softmax的指数归一化定义;本文的几何平均关系由该定义自行推导。