PyMC 架构解析:从概率图模型定义到后验推断的模块化设计
2026/9/15 20:12:16 网站建设 项目流程

PyMC 架构解析:从概率图模型定义到后验推断的模块化设计

【免费下载链接】pymcBayesian Modeling and Probabilistic Programming in Python项目地址: https://gitcode.com/GitHub_Trending/py/pymc

导读:本文基于 PyMC 仓库中的 ARCHITECTURE.md,系统梳理 PyMC 的高层架构设计:概率图模型的定义、对数概率计算与随机采样如何被组织为相互独立的模块,PyTensor 与 ArviZ 如何在外围承担张量运算与诊断绘图职责,以及pymc.modeldistributions/sampling.pystep_methods/tests/等核心代码单元的职责边界。读完本文,你将掌握 PyMC 的整体分层模型,能够快速定位"模型定义—对数概率—采样器—结果诊断"这条主链路在源码中的具体落点,并理解为什么这类架构设计有利于推理算法与分布实现的独立演进。

PyMC 的定位是"贝叶斯建模与概率编程框架":它允许用户定义概率图(probabilistic graph)或模型,并基于该图便捷地完成两类核心计算——计算对数概率(用于后验推断)以及抽取随机样本(用于先验/后验预测)。围绕这两条主线,PyMC 提供了多种推断技术,其中最主要的是:

  • 马尔可夫链蒙特卡洛(MCMC):包括 Hamiltonian Monte Carlo 与 NUTS 等步进方法;
  • 变分推断(Variational Inference):如 ADVI、FullRankADVI 与 Stein 变分梯度下降;
  • 序列蒙特卡洛(Sequential Monte Carlo,SMC):适用于多模态与不可微模型的采样方法。

此外,PyMC 还内置了 Graphviz 模型可视化工具(见 Model.to_graphviz 与 model_graph.py)以及各类数学辅助函数。官方架构图清晰展示了这些模块与外部包的协作关系(注意:图中并未列出全部模块,一些小而自明的模块以及处于弃用流程中的模块被省略了)。

职责边界:什么"不在" PyMC 中

理解 PyMC 架构的第一步,恰恰是先划清它的边界。PyMC 刻意将大量底层与外围工作委托给专门库,避免重复造轮子:

PyTensor:张量计算引擎

PyTensor 承担了 PyMC 中几乎所有与张量操作相关的底层工作,包括:

  • 梯度计算:NUTS 采样所需的dlogp即由 Model.dlogp 构造对数概率图后交给 PyTensor 的gradient求导完成;
  • 随机数生成:所有分布变量在底层都是 PyTensor 的RandomVariable(见 pymc/distributions/distribution.py 中Distribution.rv_op的定义);
  • 底层张量运算定义与**底层操作图(operation graph)**的构建、重写与编译。

因此 PyMC 中出现的TensorVariableFunctionGraph等类型均来自 PyTensor,PyMC 更多扮演"概率语义"层。

ArviZ:推断结果的后处理

ArviZ 负责推断完成之后的全部"下游"工作,包括:

  • 绘图:迹线图(trace plots)、排名图(rank plots)、后验图(posterior plots)等;
  • MCMC 采样诊断:R-hat、有效样本量(Effective Sample Size)等收敛指标(对应 pymc/backends/arviz.py 中把采样结果转换为 ArviZ 数据结构的逻辑);
  • 模型比较,尤其是高效的留一法交叉验证近似(LOO);
  • 面向贝叶斯推断数据存储与操作的数据结构(InferenceData)。

PyMC 的pm.sample()返回的正是 ArviZ 的InferenceData对象,其内部由 backends/ 目录中的多个后端(arvizndarrayzarrmcbackend)负责落盘与传输。

核心模块逐层拆解

PyMC 的代码库由根目录下的单文件 Python 模块与按功能逻辑分组的子目录共同构成。作者在架构文档中坦诚指出:单文件与目录的划分并非由严格标准决定,通常只是当单个.py文件"过大"时才拆分为目录。下面以文档给出的"简单 MCMC 模型"为例,逐一深入各模块。

import pymc as pm with pm.Model() as model: theta = pm.Beta("theta", alpha=1, beta=2) p = pm.Beta("n", p=theta, n=2, observed=[1, 2]) inf_data = pm.sample()

注:原文示例中的pm.Beta("n", p=theta, n=2, ...)系笔误(Beta的第二参数是beta而非n);更常见的可运行写法是pm.Binomial("n", p=theta, n=2, observed=[1, 2])。此处保留原文形态以对照架构文档,实际使用时请按正确的分布参数编写。

pymc.model:模型定义与求值的核心

pymc/model/ 承载了模型定义与模型求值方法所需的一切原语,是理解 PyMC 架构的起点。架构文档点名的四个关键组件全部位于 pymc/model/core.py:

组件源码位置职责
ContextMetacore.py#L308-L317元类(metaclass),在Model.__init__期间自动激活模型上下文,从而支持with pm.Model() as model:语法
Model/BaseModelcore.py#L334模型容器,维护free_RVsobserved_RVsdeterministicspotentials等变量注册表
ValueGradFunctioncore.py#L142构建"同时计算数值与梯度"的 PyTensor 函数,是 PyMC 与 PyTensor 的主要连接点
Deterministic/Potentialcore.py#L2467 / core.py#L2554模型定义中的两类特殊节点

上下文管理机制ContextMetaModel的元类,其__call__方法在实例化时先__new__出实例、随即with instance:进入上下文、再执行__init__BaseModel.__enter__/__exit__维护线程安全的MODEL_MANAGER.active_contexts栈(core.py#L101-L127),这也是modelcontext()Model.get_context()能够"找到当前模型"的根本原因。

Factor的历史角色与现状。架构文档提到Factor"为模型定义各类对数概率方法"。需要说明的是,在本文所对应的仓库版本中,这一角色已由独立的 pymc/logprob/ 包(包含abstract.pybasic.pytransforms.py等)与Model上的logp/dlogp/d2logp方法承担,pymc.Factor类已不在当前代码库中——这正是架构文档强调"并非所有模块都被列出,有些处于弃用流程中"的典型例证。当前求值链路的实际入口是:

  • Model.logp:汇总free_RVs + observed_RVs + potentials的逐项对数概率,经transformed_conditional_logp得到含变换 Jacobian 的联合对数概率图;
  • Model.dlogp:对logp图调用 PyTensorgradient求梯度,供 HMC/NUTS 使用;
  • Model.compile_logp 等编译方法:把图编译为可被采样器反复调用的PointFunc

DeterministicPotential。这两个函数分别解决两类模型定义需求:

  • Deterministic(name, var)注册一个"确定性节点",用于在采样迹线中记录中间计算结果(如逻辑回归中的p = pm.Deterministic("p", pm.math.invlogit(alpha * x + intercept)))。文档与源码 core.py#L2523-L2532 特别强调:它不会带来性能损失,因为确定性节点在主计算图之外单独求值,主图可被优化得如同不存在该节点一样,而 NUTS 每步可能求值数千次的只是主图,确定性量在每步末尾用最终变量值计算一次即可;
  • Potential(name, var)向模型联合对数概率中追加任意项,典型用途是施加软/硬约束。源码 core.py#L2554-L2600 同时给出重要警告:Potential 项只影响概率类采样(如pm.sample),不影响前向采样(如sample_prior_predictive/sample_posterior_predictive,对含 Potential 的模型做前向采样时会抛出警告。

distributions/:分布家族与对数概率

pymc/distributions/ 包含多个子模块,既定义各类分布,也提供服务于分布使用的辅助逻辑。架构文档点名的三个重点文件如下(注意个别文件已随版本演进改名或迁移):

  • distribution.py:所有 PyMC 分布的父类Distribution所在(distribution.py#L465)。其__new__方法(distribution.py#L475-L524)是整个 PyMC 变量创建的枢纽:在上下文中取得当前Model,解析dimsobservedinitvaltotal_sizetransform等参数后调用Model.register_rv完成注册。文档特别强调的observed参数正是在此发挥作用——observed的有无区分了"随机变量分布"与"似然分布":未观测的变量进入free_RVs(作为采样目标),带观测数据的变量进入observed_RVs(其logp项会保留在联合概率中)。
  • 对数概率逻辑:架构文档将其描述为distributions/logprob.py,而在当前版本中,分布与图节点的对数概率推导已被系统化地重写为独立的 pymc/logprob/ 包(含basic.pyabstract.pytransforms.pymixture.pyscan.pycensoring.py等十余个模块),对数概率的计算最终委托给 PyTensor,PyMC 只负责把概率语义(如变换的 Jacobian 修正)翻译成可求导的符号图。
  • dist_math.py:分布专用的便捷算子集合(pymc/distributions/dist_math.py),包括logpow等数学算子、一系列对数正态方法与变换方法。与之并列的还有moments/(矩的符号计算,供initvalsupport_point使用)以及shape_utils.py等形状推导工具。

sampling.py:采样入口与步进方法初始化

架构文档中的sampling.py在当前仓库中对应 pymc/sampling/ 目录,其职责是:

  • 后验/先验/后验预测采样接口sample定义于 mcmc.py#L554-L620,先验/后验预测采样则位于 forward.py(sample_prior_predictivesample_posterior_predictive);
  • 步进方法的识别与初始化:根据模型变量类型选择合适的 stepper;
  • "全连续"检查与 NUTS 初始化all_continuous(vars)(mcmc.py#L364)判断模型是否全部为连续变量,init_nuts(mcmc.py#L1759)则负责质量矩阵(mass matrix)的初始化。

init_nuts的实现细节很能体现架构设计:NUTS 的收敛与采样速度高度依赖质量/缩放矩阵的选择,因此该函数提供了init参数,支持auto(默认解析为jitter+adapt_diag)、adapt_diagjitter+adapt_diag_gradadvi+adapt_diagadviadvi_mapmapadapt_fulljitter+adapt_full等多种初始化策略;同时通过model.logp_dlogp_function(ravel_inputs=True)(内部正是ValueGradFunction)构造值-梯度函数,并用_init_jitter对初始点加均匀抖动、以jitter_max_retries控制重试次数,最终返回(initial_points, NUTS)元组供采样循环使用。

step_methods/:步进方法家族

pymc/step_methods/ 汇集了各种采样算法对应的步进方法,支撑 MCMC 与 SMC。其中:

  • step_methods/hmc/ 包含 Hamiltonian Monte Carlo 采样方法族及其辅助函数——hmc.pynuts.py(NUTS 实现)、integration.py(蛙跳/辛积分器)、quadpotential.py(二次型势能,即质量矩阵的抽象)与base_hmc.py(HMC 基类);
  • step_methods/metropolis.py 与 step_methods/slicer.py 分别提供 Metropolis 类方法与切片采样;
  • step_methods/arraystep.py、compound.py(复合步进)与 state.py 提供了步进方法的基类、组合与状态持久化。

由于离散变量无法使用 HMC/NUTS,sample会在内部为离散变量装配 Metropolis 等步进器,并与连续变量的 NUTS 组成复合步进器——这正是sampling模块执行"识别并初始化 stepper"职责的具体体现。

tests/:测试的组织方式

tests/ 存放全部功能测试。其组织规则与代码库的模块划分保持镜像:所有以test_前缀命名的模块都是测试本体,其余模块则是支撑代码(fixtures、配置、共享模型等)。例如 tests/models.py 提供可在多个测试间复用的模型定义,tests/helpers.py 与 tests/sampler_fixtures.py 提供断言与采样夹具。分布测试(如 tests/distributions/test_continuous.py)、对数概率测试(tests/logprob/)、步进方法测试(tests/step_methods/)与采样测试(tests/sampling/)分别验证对应模块的行为,其中test_random_alternative_backends.py还验证了 JAX 等替代后端上的随机行为一致性。

模型可视化:Graphviz 工具链

除了推断与求值,PyMC 还提供模型结构可视化能力。架构文档提及的 Graphviz 工具由 Model.to_graphviz 与 pymc/model_graph.py 实现,可将模型变量及其依赖关系渲染为有向图,便于在建模阶段检查变量连接与观测/未观测状态。

从架构回望:一条完整的主调用链

把上述模块串起来,pm.sample()背后的主干链路可以概括为:

  1. 模型定义with pm.Model()经由ContextMeta激活上下文,pm.Beta(...)通过Distribution.__new__Model.register_rv把随机变量注册到模型;
  2. 图构建Model.logp汇总自由变量、观测变量与 potential 的logp项,经 logprob/ 与变换系统得到含 Jacobian 的联合对数概率符号图;
  3. 编译求值ValueGradFunctionlogpdlogp图编译为 PyTensor 函数,为 NUTS 的每一步提供数值与梯度;
  4. 步进与初始化init_nuts(含all_continuous检查)确定质量矩阵初始化策略,step_methods/hmc/中的积分器与 NUTS 算法驱动采样;
  5. 结果交付:采样迹线经 backends/ 转成 ArviZInferenceData,供用户做 R-hat 诊断、绘制后验图或进一步模型比较。

这条链路的每一环都能在本文对应的模块划分中找到精确的落点——这也是 PyMC 将"概率语义"(PyMC)、"张量计算"(PyTensor)与"诊断分析"(ArviZ)分层解耦的架构价值所在。对于希望深入源码的读者,建议从 pymc/model/core.py 出发,沿register_rv → logp → compile_logp的路径逐段阅读,再结合 tests/model/test_core.py 中的断言理解各 API 的实际行为约束。

【免费下载链接】pymcBayesian Modeling and Probabilistic Programming in Python项目地址: https://gitcode.com/GitHub_Trending/py/pymc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询