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 就会触发基数不匹配。
variable_card、evidence_card 与矩阵实际形状三者是否自洽。修正与验证
把 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 存在同类问题,按堆栈消息点名的变量逐个核对即可。