1. 概率模型计算报错定位:从堆栈到模型结构

错误现场:维度不匹配的堆栈长什么样

假设我们有一个三变量贝叶斯网络 Cloudy → Rain → WetGrass,用 pgmpy 构建并做一次变量消元推理。脚本运行后抛出如下异常(示例堆栈,来自 pgmpy 的 CPT 校验路径):

Traceback (most recent call last):
  File "infer.py", line 21, in <module>
    model.add_cpds(cpd_c, cpd_r, cpd_w)
  File ".../models/BayesianNetwork.py", line 214, in add_cpds
    raise ValueError(f"CPD {cpd.variable} does not match the cardinality of the variable")
ValueError: CPD Rain does not match the cardinality of the variable

堆栈的关键信息只有三层:调用点(add_cpds)、校验函数(add_cpds 内部的基数比对)、异常消息(点名 Rain 这个 CPD 与变量基数不符)。读堆栈的顺序应当是:先看最底部的异常类型与消息确定“哪一类错”,再往上看最近一帧业务代码确定“哪个对象错”,最后回到模型定义处找“为什么错”。

从堆栈回溯到模型结构定义

异常消息点名 Rain,说明问题出在 Rain 的条件概率表。检查模型定义:

from pgmpy.models import BayesianNetwork
from pgmpy.factors.discrete import TabularCPD

model = BayesianNetwork([('Cloudy', 'Rain'), ('Rain', 'WetGrass')])

# Cloudy: 2 个取值 (T/F)
cpd_c = TabularCPD('Cloudy', 2, [[0.5], [0.5]])

# Rain: 声明基数为 2,但父变量 Cloudy 有 2 个取值,
# 条件表却只给了 1 列 —— 维度声明与变量基数不一致
cpd_r = TabularCPD(
    'Rain', 2,
    [[0.8, 0.2], [0.2, 0.8]],
    evidence=['Cloudy'], evidence_card=[2]
)

# WetGrass: 2 个取值,父变量 Rain 有 2 个取值
cpd_w = TabularCPD(
    'WetGrass', 2,
    [[0.9, 0.1], [0.1, 0.9]],
    evidence=['Rain'], evidence_card=[2]
)

model.add_cpds(cpd_c, cpd_r, cpd_w)

这里 cpd_r 的 evidence_card=[2] 表示父变量 Cloudy 有 2 个取值,因此 Rain 的 CPT 应当是 2×2 的矩阵(每列对应一个父变量取值组合,列和为 1)。但实际传入的 [[0.8, 0.2], [0.2, 0.8]] 是 2 行 2 列,看似正确——真正的问题在于 evidence_card 与 variable_card 的乘积关系:当 variable_card=2、evidence_card=[2] 时,期望的 CPT 形状是 2 × (2^1) = 2×2,而如果误写成 evidence_card=[3](比如把 Cloudy 的基数记成 3),期望形状变成 2×3,传入的 2×2 就会触发基数不匹配。

堆栈里的“cardinality”错误几乎总是同一类根因:CPT 的列数 ≠ 变量基数 × 各父变量基数之积。定位时不要急着改数据,先核对 variable_card、evidence_card 与矩阵实际形状三者是否自洽。
堆栈回溯链 ① 异常消息 CPD Rain 基数不匹配 ② 校验函数帧 add_cpds → 基数比对 ③ 业务调用点 infer.py:21 add_cpds(...) ④ 模型定义处 cpd_r 的 evidence_card CPT 维度自洽检查 期望形状 = variable_card × ∏ evidence_card Rain: 2 × 2 = 2×2 ✓ 与传入矩阵一致 若 evidence_card=[3] → 期望 2×3 ✗ 传入 2×2 列数必须等于父变量取值组合数,每列概率和为 1 修正:让声明与矩阵对齐 evidence_card=[2] ← 与 Cloudy 基数一致 矩阵保持 2×2,每列和 = 1 验证通过条件 model.check_model() → True query(...) 返回合法后验,无异常

修正与验证

把 evidence_card 改回与父变量基数一致的值,并确保矩阵列数匹配:

cpd_r = TabularCPD(
    'Rain', 2,
    [[0.8, 0.2], [0.2, 0.8]],
    evidence=['Cloudy'], evidence_card=[2]   # 与 Cloudy 的 2 个取值对齐
)
model.add_cpds(cpd_c, cpd_r, cpd_w)
assert model.check_model() is True

验证分两步:先用 check_model() 确认所有 CPD 的维度与网络结构自洽;再跑一次推理,确认输出是合法的后验分布(各状态概率非负且和为 1)。若 check_model() 仍报错,说明还有别的 CPD 存在同类问题,按堆栈消息点名的变量逐个核对即可。

定位口诀:消息点名变量 → 回到该变量的 CPD 定义 → 核对 variable_card、evidence_card、矩阵形状三者乘积关系。绝大多数“维度不匹配”都能在这一步收敛,无需改动推理算法本身。