5D Library › 概率判断、布莱尔分数校准与群体智慧的统计理论 · English
第八章 实践与预注册试验
第一部分 四步概率校准核查表
这份核查表供任何经常需要做预测的人(项目负责人、研究者、编辑、学生)在一个季度里使用。它不需要任何特殊工具,只需要一份按时间记录的表格。
- 用数字替代模糊词。 每一个「可能」「很可能」「问题不大」都写成 0–100% 之间的一个数。同一个词在不同人、甚至同一个人的不同场合,代表的概率差别很大 (Wallsten et al. 1986);不写成数字,就无法打分。(第二章)
- 先找外部参考类别的基础率。 在看个案细节之前,先问:这类事情在类似情况下通常有多大比例发生?把基础率写在预测旁边,再写下你为什么要偏离它、偏离多少。(第三章、第五章)
- 事前写下更新的触发条件。 对每个预测写下:「如果出现某某消息,我会把概率调到多少。」消息出现时按约定小步更新,不因为一条新闻大起大落,也不因为不愿认错而不动。(第一章)
- 季度布莱尔分数复盘。 季度结束、问题揭晓后,计算自己的布莱尔分数,并与「只报基础率」的分数比较;把说 70% 以上的预测单独拿出来,看实际发生了几成。(第二章)
推导
把核查表用在一位虚构预测者一个季度的 12 个问题上。问题、数字与结果都是编造的。第 7 题涉及一次利率决定,只用于演示打分,不构成任何市场判断或投资建议。
推导
# Four-step checklist on one FICTIONAL forecaster's quarter (12 invented questions).
# Question 7 (interest rates) is a scoring exercise, not a market view or investment
# advice. Columns: question, vague word, base rate, first forecast, trigger fired,
# final, outcome.
Q = [("project A delivered by 30 June", "likely", 0.35, 0.75, True, 0.55, 0),
("new hire accepts the offer", "probably", 0.70, 0.80, False, 0.80, 1),
("supplier price rise above 5 %", "might", 0.30, 0.40, True, 0.65, 1),
("app passes review first time", "likely", 0.60, 0.85, False, 0.85, 1),
("conference paper accepted", "good chance", 0.25, 0.60, False, 0.60, 0),
("quarterly sales above last quarter", "probably", 0.55, 0.70, True, 0.50, 0),
("central bank raises its rate", "unlikely", 0.20, 0.15, True, 0.40, 1),
("office move completed in May", "almost sure", 0.40, 0.95, True, 0.70, 0),
("survey response rate above 2/3", "doubtful", 0.15, 0.10, False, 0.10, 0),
("grant renewed", "likely", 0.50, 0.70, False, 0.70, 1),
("pilot customer signs contract", "might", 0.30, 0.45, True, 0.30, 0),
("website redesign live before July", "probably", 0.45, 0.80, True, 0.60, 1)]
def brier(fs, os_):
return sum((f - o) ** 2 for f, o in zip(fs, os_)) / len(fs)
if __name__ == "__main__":
o = [q[6] for q in Q]
words = {}
for q in Q:
words.setdefault(q[1], []).append(q[3])
print("step 1 - the same word, different numbers:")
for w, v in words.items():
if len(v) > 1:
print(f" '{w}': " + ", ".join(f"{x:.0%}" for x in v))
gap = [q[3] - q[2] for q in Q]
print(f"step 2 - first forecast minus base rate: mean {sum(gap) / len(gap):+.2f};"
f" above the base rate in {sum(g > 0 for g in gap)} of 12")
half = [q[2] + 0.5 * (q[3] - q[2]) for q in Q]
print(f" had each first forecast been moved halfway back to its base rate: Brier"
f" {brier(half, o):.3f}")
fired = [q for q in Q if q[4]]
right = sum((q[5] - q[3]) * (q[6] - 0.5) > 0 for q in fired)
print(f"step 3 - triggers fired in {len(fired)} questions;"
f" the update moved towards the outcome in {right}")
bs_base, bs_first, bs_final = (brier([q[i] for q in Q], o) for i in (2, 3, 5))
print(f"step 4 - Brier: base rates only {bs_base:.3f}; first forecasts {bs_first:.3f};"
f" final forecasts {bs_final:.3f}")
sk1, sk2 = 1 - bs_first / bs_base, 1 - bs_final / bs_base
print(f" skill vs base rates (1 - BS / BS_base): first {sk1:+.0%}, final {sk2:+.0%}")
hi = [q for q in Q if q[3] >= 0.7]
mh = sum(q[3] for q in hi) / len(hi)
print(f" first forecasts of 70 % or more: {len(hi)}, mean {mh:.0%},"
f" happened {sum(q[6] for q in hi)} of {len(hi)}")
# step 1 - the same word, different numbers:
# 'likely': 75%, 85%, 70%
# 'probably': 80%, 70%, 80%
# 'might': 40%, 45%
# step 2 - first forecast minus base rate: mean +0.21; above the base rate in 10 of 12
# had each first forecast been moved halfway back to its base rate: Brier 0.252
# step 3 - triggers fired in 7 questions; the update moved towards the outcome in 6
# step 4 - Brier: base rates only 0.224; first forecasts 0.317; final forecasts 0.191
# skill vs base rates (1 - BS / BS_base): first -41%, final +15%
# first forecasts of 70 % or more: 7, mean 79%, happened 4 of 7这份虚构的记录显示了几件事。第一步:同一个「很可能」(likely),这位预测者有时指 70%,有时指 85%;不写数字,这种不一致根本看不出来。第二步:他最初的直觉数字平均比基础率高 0.21,12 题中有 10 题高于基础率——典型的内部视角。第三步:7 个问题中事先写下的触发条件被触发,其中 6 次更新的方向是对的。第四步:只报基础率的布莱尔分数为 0.224,最初的直觉数字是 0.317,比基础率还差 41%;按触发条件更新后的最终预测为 0.191,比基础率好 15%。说 70% 以上的 7 个最初预测平均为 79%,实际只发生了 4 个。
还有一行值得注意:如果把每个最初预测机械地往基础率方向拉回一半,分数是 0.252,比直觉好,却仍然不如只报基础率。12 个问题太少,任何一个季度的分数都含有大量运气;核查表的用处在于积累多个季度的记录,而不是根据一个季度的结果下结论。
第二部分 预注册随机对照试验方案
问题
一次 15 分钟的「校准微训练」(自然频率转换 + 外部参考类别锚定 + 布莱尔分数与区间命中反馈),与花同样时间阅读一份常规的逻辑思维材料相比,能否使成年人在之后的概率判断与区间估计任务中校准得更好?
主要结局的选择
主要结局只设一项:区间校准误差 , 是后测 20 道题中自己给出的 80% 区间包含正确答案的比例。理由是:过于精确是最稳定的一种过度自信(第二章),名义水平为 80% 的区间任务可以用一个数直接测量它,而且基础率题与数量估计题都能纳入;而按个人的概率判断计算墨菲可靠性 REL,在每人只有 20 次判断时严重偏高(第二章:完全校准者平均约 0.063),还取决于分箱。 也有抽样下限(完全校准者在 20 题上期望约 0.070,见样本量程序),但这一下限在两组相同,不依赖分箱。
设计
| 要素 | 规定 |
|---|---|
| 参与者 | 18–70 岁成年人,能流利阅读测验语言;通过在线研究参与者平台招募,不从作者的社群、学员、客户或牌卡用户中招募 |
| 排除标准 | 具有统计学或数学学位;参加过预测锦标赛或任何概率校准训练;参加过本研究的预试验 |
| 流程 | 知情同意 → 前测(10 道题,平行版本)→ 随机分组 → 15 分钟模块 → 后测(20 道题)→ 两周后在线随访(10 道题,平行版本) |
| 测验题 | 每题要求一个点估计和一个 80% 区间。后测 20 题中,10 道为含基础率陷阱的贝叶斯推理题(以概率格式呈现,正确答案为按贝叶斯定理计算的后验概率),10 道为答案可以核实的数量估计题(适合用参考类别推断)。题目与练习题互不重叠,事先经预试验,平行版本在参与者之间平衡 |
| 随机分组 | 前测结束后由计算机按 1:1 随机分配,可变区组,按有无大学学历分层;分配序列在前测结束前对参与者与研究人员隐藏 |
| A 组(校准微训练) | 15 分钟,三个部分:① 自然频率转换(约 5 分钟):把概率题改写成「1,000 人」频率树,做两道练习;② 外部参考类别锚定(约 4 分钟):先写下同类事件的基础率,再按个案信息调整;③ 反馈(约 6 分钟):8 道区间练习题,每题后立即显示是否命中与累计命中率(对照 80% 的目标),以及 4 道概率练习题的布莱尔分数 |
| B 组(积极对照) | 同样 15 分钟的常规逻辑思维阅读(演绎论证、常见的非形式谬误),练习题数量相同,每题后同样显示对错;不涉及概率、基础率或区间估计 |
| 盲法 | 参与者只被告知研究比较两种简短的「思维技能」模块,不告知假设;计分由程序自动完成;分析者在揭盲前只看到编码后的组别 |
| 主要结局 | 后测 20 题的区间校准误差 |
| 次要结局 | 有方向的校准差 (正值为过于精确);10 道基础率题的点估计与正确答案的平均绝对误差(百分点);忽略基础率的答案比例(点估计在灵敏度 ±5 个百分点以内);区间宽度的中位数(按题目标准化);两周随访的 |
| 分析 | 协方差分析:后测 为因变量,自变量为组别,协变量为前测 与分层变量;双侧 。可分析人数定义为完成后测并通过注意力检查者;另做把排除者计入的敏感性分析 |
样本量
推导
# Sample size. Primary outcome CE = |H - 0.80| over 20 items (H = share of 80 %
# intervals that hit). ANCOVA on pre-test CE (correlation rho); alpha 0.05 two-sided,
# power 0.90. SD of CE from a simulated control arm: hit chance ~ Beta(mean 0.50, SD
# 0.15), an assumption.
import math, random
from statistics import NormalDist, mean, stdev
rng = random.Random(15)
N = NormalDist()
def beta_ab(m, sd):
k = m * (1 - m) / sd ** 2 - 1
return m * k, (1 - m) * k
def ce_sample(n, m, sd=0.15, items=20):
a, b = beta_ab(m, sd)
out = []
for _ in range(n):
h = rng.betavariate(a, b)
hits = sum(rng.random() < h for _ in range(items))
out.append(abs(hits / items - 0.80))
return out
def per_arm(delta, sigma, rho, alpha=0.05, power=0.90):
z = N.inv_cdf(1 - alpha / 2) + N.inv_cdf(power)
return math.ceil(2 * z * z * sigma ** 2 * (1 - rho ** 2) / delta ** 2) + 1
if __name__ == "__main__":
ctrl, trt = ce_sample(20000, 0.50), ce_sample(20000, 0.57)
sigma = stdev(ctrl)
print(f"simulated control: mean CE {mean(ctrl):.3f}, SD {sigma:.3f};"
f" hit chance 0.57 gives mean CE {mean(trt):.3f}")
print("analysable participants per arm | rho = 0.0 | 0.3 | 0.5")
for d in (0.05, 0.06, 0.08):
ns = [per_arm(d, sigma, r) for r in (0.0, 0.3, 0.5)]
print(f" CE difference {d:.2f} (d = {d / sigma:.2f}) |"
f" {ns[0]:9d} | {ns[1]:3d} | {ns[2]:3d}")
n = per_arm(0.06, sigma, 0.5)
rec = math.ceil(n / 0.90)
print(f"chosen: 0.06, rho = 0.5 -> {n} per arm")
print(f" allowing 10 % loss (incomplete sessions, failed attention checks):")
print(f" recruit {rec} per arm, {2 * rec} in all")
n0, reps, hits = per_arm(0.06, sigma, 0.0), 1000, 0
for _ in range(reps):
a, b = ce_sample(n0, 0.50), ce_sample(n0, 0.57)
se = math.sqrt(stdev(a) ** 2 / n0 + stdev(b) ** 2 / n0)
hits += abs(mean(a) - mean(b)) / se > N.inv_cdf(0.975)
print(f"check without covariate: {n0} per arm, simulated power {hits / reps:.2f}")
pk = [math.comb(20, k) * 0.8 ** k * 0.2 ** (20 - k) for k in range(21)]
floor = sum(p * abs(k / 20 - 0.8) for k, p in enumerate(pk))
print(f"floor: a perfectly calibrated person (h = 0.80) has expected CE {floor:.3f}"
" on 20 items")
# simulated control: mean CE 0.304, SD 0.173; hit chance 0.57 gives mean CE 0.243
# analysable participants per arm | rho = 0.0 | 0.3 | 0.5
# CE difference 0.05 (d = 0.29) | 253 | 231 | 190
# CE difference 0.06 (d = 0.35) | 176 | 161 | 133
# CE difference 0.08 (d = 0.46) | 100 | 91 | 75
# chosen: 0.06, rho = 0.5 -> 133 per arm
# allowing 10 % loss (incomplete sessions, failed attention checks):
# recruit 148 per arm, 296 in all
# check without covariate: 176 per arm, simulated power 0.93
# floor: a perfectly calibrated person (h = 0.80) has expected CE 0.070 on 20 items模拟的对照组中, 的平均值约为 0.304,标准差约为 0.173(假设对照组 80% 区间的平均命中概率为 50%,个体间标准差 0.15);把平均命中概率提高到约 0.57, 的平均值降到 0.243,相差约 0.06。以 0.06(标准化效应约 0.35)为最小关注效应、前后测 的相关为 0.5、90% 把握度计算,每组需要 133 名可分析的参与者;考虑约 10% 的未完成与未通过注意力检查,每组招募 148 人,共 296 人。不用协变量时每组需要 176 人,模拟得到的把握度约为 0.93(模拟中的差异略大于 0.06)。前后测相关 0.5 是作者的假设,预试验应当估计它;如果实际相关更低,样本量要相应增加(表中列出相关为 0 与 0.3 时的数字)。
判定规则
推导
# Decision rule fixed in advance. D = arm A minus arm B in post-test CE (ANCOVA-adjusted);
# negative = better calibrated in A; SESOI 0.06. Manipulation check: A has lower base-rate
# error than B (one-sided p < 0.05). Integrity: at most 15 % excluded.
SESOI = 0.06
def verdict(d, lo, hi, manip_ok, excluded):
if excluded > 0.15:
return f"D = {d:+.3f}: NOT INTERPRETABLE - {excluded:.0%} excluded (limit 15 %)"
if not manip_ok:
return f"D = {d:+.3f}: NOT INTERPRETABLE - manipulation check failed"
if hi <= -SESOI:
v = "improves calibration by at least 0.06"
elif hi < 0:
v = "improves calibration; not shown to reach 0.06"
elif lo > 0:
v = "training WORSENS calibration"
elif lo >= -SESOI and hi <= SESOI:
v = "no difference worth acting on"
else:
v = "unclear: the interval is too wide to decide"
return f"D = {d:+.3f} ({lo:+.3f} to {hi:+.3f}): {v}"
for case in ((-0.095, -0.130, -0.060, True, 0.08), (-0.040, -0.075, -0.005, True, 0.07),
(-0.010, -0.045, 0.025, True, 0.09), (0.045, 0.010, 0.080, True, 0.06),
(-0.020, -0.080, 0.040, True, 0.10), (-0.110, -0.145, -0.075, False, 0.05),
(-0.090, -0.125, -0.055, True, 0.21)):
print(verdict(*case))
print("secondary (signed gap 0.80 - H, base-rate error, interval width, 2-week CE):")
print(" reported with intervals; none decides the verdict (the base-rate error is used")
print(" only as the manipulation check, which decides interpretability)")
# D = -0.095 (-0.130 to -0.060): improves calibration by at least 0.06
# D = -0.040 (-0.075 to -0.005): improves calibration; not shown to reach 0.06
# D = -0.010 (-0.045 to +0.025): no difference worth acting on
# D = +0.045 (+0.010 to +0.080): training WORSENS calibration
# D = -0.020 (-0.080 to +0.040): unclear: the interval is too wide to decide
# D = -0.110: NOT INTERPRETABLE - manipulation check failed
# D = -0.090: NOT INTERPRETABLE - 21% excluded (limit 15 %)
# secondary (signed gap 0.80 - H, base-rate error, interval width, 2-week CE):
# reported with intervals; none decides the verdict (the base-rate error is used
# only as the manipulation check, which decides interpretability)示例结果是编造的。如果 A 组在 10 道基础率题上的平均绝对误差没有低于 B 组(单侧 ),说明训练的内容本身没有被掌握,报告写「不可解释」:试验无法区分「训练失败」与「技能学会了但不改善校准」。如果因未完成或未通过注意力检查被排除的人超过随机化人数的 15%,同样写「不可解释」。只有当差异的 95% 置信区间上限不高于 −0.06 时,报告才写「训练使校准误差至少降低了最小关注的幅度」。
这项试验不能说明什么
第一,这是一个短期的实验室任务,不能说明训练会改善人们在工作或生活中对真实事件的预测,更不能说明会改善任何决策的结果。第二,A 组同时包含三个成分,即使结果为阳性,也不能说明是哪一个起了作用;如需分解,应另做析因设计。第三, 可以因为把所有区间都放宽而下降;所以次要结局中的区间宽度与基础率题误差是解读主要结局的必要旁证:如果 下降只来自区间普遍变宽、而点估计没有改善,应如实报告。第四,A 组的反馈可能让参与者猜到「区间要宽一些」,产生期望效应;B 组的反馈结构相同,只能部分缓解这一问题。第五,在线招募的志愿者与一般人群可能不同。第六,两周随访只能说明短期保持,不能说明长期效果。
伦理与利益冲突
这是一项最低风险的行为研究。试验须经独立伦理委员会批准,参与者签署知情同意,按平台的公平标准获得报酬,可以随时退出;数据去标识化保存。方案、测验题、分析代码与判定规则在第一位参与者入组之前公开登记 (Nosek et al. 2018);无论结果为正为负,结果、数据和代码都将公开。测验中的任何金融或利率题目只用于计分练习,研究材料中注明不构成投资建议。
利益披露
作者以 5D Library 的著作(包括本书)作为其社群课程、升维牌卡(5D Ascension Cards,Dimensional Shift Card Decks)与 Yafu AI 知识库的方法论基础,因此与本书主题有商业利益关系。本书不推销任何课程、牌卡或服务;第八章的试验设计为由独立于作者企业的研究人员实施和分析,不从作者的社群、学员或客户中招募;书中所有代码与研究方案均公开,供任何人检验。作者不持有任何金融服务牌照,不提供任何投资顾问、代客理财或交易信号服务。为限制利益的影响:训练材料与分析代码在入组前公开;无论结果如何,结果、数据和代码都将公开。
本章小结
四步校准核查表把前几章的结论落到四个可记录的环节:用数字替代模糊词,先找外部参考类别的基础率,事前写下更新触发条件,季度结束后用布莱尔分数复盘并与基础率比较;虚构示例显示,直觉数字可能比只报基础率还差,而按事先约定更新后有所改善,但一个季度的 12 个问题不足以下结论。试验方案比较 15 分钟校准微训练与时长匹配的逻辑思维阅读,唯一的主要结局是 20 道题上 80% 区间的校准误差 ,每组 133 人可分析、招募 148 人,判定规则包括操作检查与排除比例的门槛,写成代码并事先公开;无论结果正负都发表。
参考文献
Fan, Y. (2026). Probabilistic Judgement, Brier-Score Calibration and the Statistics of Collective Intelligence: From Base-Rate Neglect and Signal Detection Theory to Superforecasting, Read Beside Classical Chinese Strategic Calculation [概率判断、布莱尔分数校准与群体智慧的统计理论] (Version 1.0.0). 5D Library. https://doi.org/10.5281/zenodo.23764998 · Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0).