qinghuan
← 回到知识探索

AI 直觉 / 动手实验

每个动作都估得不偏,挑最高分后为什么反而高估了?

枚举四张估值表和十六组交叉评价,亲手看到最大值偏差、Double Q 的作用与低估边界。

展开本文目录
  1. 下载完整练习包
  2. 先动手看看
  3. 把误差模型写清楚
  4. 先取最大值,再平均,顺序不能交换
  5. A 负责选,B 负责给选中动作打分
  6. 真值不同,仍可能选错动作并低估
  7. 偏差变小,不保证每次误差更小
  8. 用精确分数枚举,不靠碰运气
  9. 真正该核对的是谁选、谁评、误差怎样相关
  10. 记住这三点
  11. 别把例子用过头
  12. 自己试一小步
  13. 查原始资料
开始前,会这些就够了
  • 知道平均值和最大值的区别
  • 知道动作价值是未来收益的估计
  • 先读 MDP、Bellman 与策略梯度:先认识动作价值与 Bellman 更新,再理解最大估值为什么会被用于下一步学习。
遇到不熟悉的词,先看这 2 个解释
最大值偏差
从多份有误差的估计里挑最高分,会偏向挑中向上估错的结果。
交叉评价
用一份估计选动作,再用另一份估计给该动作打分。
打开完整术语小抄 ↗

两个动作的真实收益都为零。估计器对每个动作有时多报一分、有时少报一分,平均下来恰好正确。现在每次只挑报得最高的那个,最后的平均估值还是零吗?

答案是零点五。不是每个动作的估计器本身偷偷加了分,而是“挑最高”更容易挑中向上偏的误差。本篇用全部情况枚举,展示把选择和评价拆开后,哪些偏差消失,哪些问题仍在。

把例子带到自己的电脑上

下载完整练习包

需要 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 的目录打开终端。命令分别回车执行。独立有限噪声下的单次价值估计枚举;未实现或训练完整 Q-learning、Double Q-learning 或 Double DQN。

找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。

同一张表挑最大值,再用另一张表估价

正在准备实验控件…

真实最大值
0
直接 max A 的均值
0.50
B[argmax A] 的均值
0.00
直接估计的均方误差
1.00
分开选择/评价的均方误差
1.00

共 16 种等概率组合。均值更接近真值,均方误差却没有降低。

逐项检查全部组合
每行按 A 选项,再查看该项在 B 中的值
AB选中项max AB 的估价
-1, -1-1, -10-1-1
-1, -1-1, 10-1-1
-1, -11, -10-11
-1, -11, 10-11
-1, 1-1, -111-1
-1, 1-1, 1111
-1, 11, -111-1
-1, 11, 1111
1, -1-1, -101-1
1, -1-1, 101-1
1, -11, -1011
1, -11, 1011
1, 1-1, -101-1
1, 1-1, 101-1
1, 11, -1011
1, 11, 1011

两项真值是 (0,v)。每项误差等概率取 ±1,枚举所有离散组合;并列固定选第 0 项。独立 B 与 A 拷贝是两种教学假设,真实 Double Q 训练并不自动保证两张估计表完全独立。

把误差模型写清楚

先固定一个状态和两个动作,真实价值为 [0,0]。估值表 A 对每个动作分别加 −1 或 +1,四种组合等可能,动作之间的误差独立。

四张表依次是 [−1,−1]、[−1,+1]、[+1,−1]、[+1,+1]。对任意一个固定动作取平均,估值都是零,因此这里称每个动作估计无偏。

“无偏”说的是把这种实验重复许多次时的期望,不是每次都准确。本篇会直接枚举全部组合,不使用随机种子或有限次抽样来近似。

先取最大值,再平均,顺序不能交换

四张表各自的最大值是 −1、+1、+1、+1,平均为 0.5;真实价值先取最大则为零。E[max A] 与 max E[A] 不是一个运算。

直觉上,一次向下估错可能被另一个动作盖过去;只要有动作向上估错,它就更容易被选中。我们对“最高分”附带了一次偏爱正误差的筛选。

在 Q-learning 中,更新目标通常包含 r+gamma×max_a Q(s_next,a)。如果把带误差的估值直接放进这个最大值,偏差就可能进入后续学习。这里仅隔离其中的估计步骤,不运行完整的学习循环。

A 负责选,B 负责给选中动作打分

再造一张 B 表。B 的每个误差也独立取 −1 或 +1,并且整张 B 与 A 独立。先令 a*=argmax A,再读取 B[a*];不要在 B 里重新挑最大值。并列时固定选索引较小的动作。

四张 A 与四张 B 两两组合,一共十六种等可能情况。选中动作虽然受 A 的误差影响,但 B 没参与这次选择;在本例条件下,B 对选中动作仍平均给零。因此交叉评价的平均为零。

这个结论依赖 B 在给定选择结果后仍能无偏评价该动作。独立的无偏 B 是一个充分条件;把 A 复制成 B 不满足它。代码也验证复制后平均仍是 0.5。

真值不同,仍可能选错动作并低估

把真实价值改为 [0,1],保留同样独立的 ±1 误差。A 的四张表变成 [−1,0]、[−1,2]、[1,0]、[1,2]。第三张会误选真实价值为零的动作。

最大值法平均报 1.25,高于真正最大值 1;交叉评价平均报 0.75,低于 1。B 无偏地评价了“被选中的动作”,并不表示 A 一定选中了真正最好的动作。

一般说,在这些独立无偏假设下,交叉评价的期望是被选动作真实价值的加权平均。只有选到的动作都具有最大真实价值时,它才等于最大值;否则可以低估。

偏差变小,不保证每次误差更小

回到真值 [0,0]。最大值法有 0.5 的偏差,交叉评价偏差为零,但两者本例的均方误差都是 1。均方误差就是每次误差平方后再平均。

这说明“减少偏差”和“保证更小总误差”是两个不同命题。交叉评价可以改变方差,不能只展示均值更准,就宣称所有误差指标都变好。

Double DQN 把这一思路用于神经网络:在线网络选择动作,目标网络评价该动作。目标网络会周期性从在线网络复制参数,因此两者并不满足本篇完全独立的人工设定;不能把本例的零偏差原样套过去。

用精确分数枚举,不靠碰运气

保存为 demo.py,运行 python demo.py。只需要 Python 3.14 标准库。Fraction 让均值与断言使用精确分数,最后打印时才转成六位小数。

十个条件覆盖单个估计器无偏、并列选择、最大值高估、独立评价、复制反例、低估与均方误差。实验没有经验回放、环境交互、神经网络或训练收敛测试。

展开收起代码 · 小实验python · 64 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

小实验 · python
"""Enumerate finite estimator noise; this is not a Q-learning training loop."""
from fractions import Fraction
from itertools import product


def estimates(truth):
    return [tuple(value + error for value, error in zip(truth, errors))
            for errors in product((-1, 1), repeat=len(truth))]


def first_argmax(values):
    return max(range(len(values)), key=values.__getitem__)


def average(values):
    return sum(values, Fraction(0)) / len(values)


def evaluate(truth):
    tables = estimates(truth)
    target = max(truth)
    single = [max(a) for a in tables]
    # Every A table is paired with every independently generated B table.
    double = [b[first_argmax(a)] for a in tables for b in tables]
    copied = [a[first_argmax(a)] for a in tables]
    return {
        "truth": target,
        "single": average(single),
        "double": average(double),
        "copied": average(copied),
        "single_mse": average([(x - target) ** 2 for x in single]),
        "double_mse": average([(x - target) ** 2 for x in double]),
    }


def main():
    equal = evaluate((0, 0))
    unequal = evaluate((0, 1))
    tables = estimates((0, 0))
    checks = {
        "four_equiprobable_A_tables": len(tables) == 4,
        "each_estimator_unbiased": all(average([a[i] for a in tables]) == 0 for i in range(2)),
        "tie_breaks_toward_first": first_argmax((1, 1)) == 0,
        "max_has_positive_bias": equal["single"] == Fraction(1, 2),
        "independent_equal_value_evaluation_unbiased": equal["double"] == 0,
        "copied_table_keeps_bias": equal["copied"] == equal["single"],
        "single_mse_one": equal["single_mse"] == 1,
        "double_mse_not_lower_here": equal["double_mse"] == equal["single_mse"],
        "unequal_single_overestimates": unequal["single"] == Fraction(5, 4),
        "unequal_double_underestimates": unequal["double"] == Fraction(3, 4),
    }
    assert all(checks.values()), checks
    print("noise=independent +/-1; tie=first action; exact enumeration")
    print("A tables=4; independent A/B pairs=16")
    for truth, result in (((0, 0), equal), ((0, 1), unequal)):
        print(f"true_values={truth} true_max={result['truth']}")
        print(f"  single_mean={float(result['single']):.6f} double_mean={float(result['double']):.6f} copied_B_mean={float(result['copied']):.6f}")
        print(f"  single_bias={float(result['single'] - result['truth']):.6f} double_bias={float(result['double'] - result['truth']):.6f}")
    print(f"equal_values_MSE: single={float(equal['single_mse']):.6f} double={float(equal['double_mse']):.6f}")
    print(f"PASS {len(checks)} checks; estimator experiment only, no RL training")


if __name__ == "__main__":
    main()

怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-23 使用 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。独立有限噪声下的单次价值估计枚举;未实现或训练完整 Q-learning、Double Q-learning 或 Double DQN。

运行后对照结果
noise=independent +/-1; tie=first action; exact enumeration
A tables=4; independent A/B pairs=16
true_values=(0, 0) true_max=0
  single_mean=0.500000 double_mean=0.000000 copied_B_mean=0.500000
  single_bias=0.500000 double_bias=0.000000
true_values=(0, 1) true_max=1
  single_mean=1.250000 double_mean=0.750000 copied_B_mean=1.250000
  single_bias=0.250000 double_bias=-0.250000
equal_values_MSE: single=1.000000 double=1.000000
PASS 10 checks; estimator experiment only, no RL training

真正该核对的是谁选、谁评、误差怎样相关

读实现时先把动作索引 a* 和被读取的估值分开。max B、B[argmax A]、(A+B)/2 都是不同运算,不能因为出现了两张表就都叫同一算法。

本例刻意给出独立、等概率、零均值误差,便于证明。真实训练中的两个网络可能共享数据、参数来源和系统性误差,Double Q 也不保证永远无偏、永远不低估,或每次选对动作。

要验证实际算法,应另外报告估值误差、实际回报及训练稳定性。本篇只证明这个有限模型里选择与评价复用误差会发生什么,为理解真实算法提供一个可核验的局部环节。

记住这三点

  • 每个动作的估计无偏,不代表最大估计值无偏。
  • B[argmax A] 将选择和评价分开;复制 A 并不会产生独立评价。
  • 交叉评价仍可能因选错动作而低估,减少偏差也不保证降低均方误差。

别把例子用过头

  • 把 E[max A] 写成 max E[A]。
  • 在 B 中再做一次 argmax,误以为实现了交叉评价。
  • 把目标网络当成与在线网络完全独立的估计器。
  • 只看平均估值更接近真值,就承诺所有训练任务都获益。

自己试一小步

把真实价值改为 [0,3],仍使用 ±1 误差,交叉评价的均值和偏差会怎样?

  1. 先写出四张 A 表,标出每次选到的动作。
  2. 列出十六组独立 A/B 组合,分别计算最大值法与交叉评价。
  3. 再把 B 改成 A 的副本,比较这个新例子是否仍能显示相关误差的危害。

做到这些,就算完成

  • 固定并记录并列规则。
  • 分别计算偏差与均方误差。
  • 解释结果依赖真实动作差距和误差幅度,而非仅依赖是否有两张表。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。