大数跨境
0
0

一个神奇的算法,贝叶斯检验 !!

一个神奇的算法,贝叶斯检验 !! 机器学习和人工智能AI
2025-12-08
2

哈喽,大家好~

在机器学习实践中,我们经常需要回答如下问题:

  • 某个特征在模型中是否“真正有效”?换言之,它的系数是否显著非零?
  • 两个模型(如不同特征集、不同架构)孰优孰劣?
  • 数据量不大、信噪比一般时,如何稳定评估证据,而不仅仅是依赖p值与显著性水平?

贝叶斯检验提供了一种以证据为核心的方式:直接比较数据在两种假设下的概率,用比值(贝叶斯因子)刻画数据对两种假设的相对支持程度。

本篇文章,聚焦于一个典型场景:线性回归中对某个特征系数是否为零的检验。

它对特征选择、可解释性分析以及小样本/中等样本场景下的稳健推断都很有价值。

贝叶斯检验核心理论

贝叶斯学派的假设检验从以下对象出发:

  • 先验:
  • 似然:
  • 后验:
  • 边际似然(模型证据):

当我们比较两个假设/模型      时,核心量是贝叶斯因子:

其对数形式(更数值稳定)为:

  •   (或  ),数据支持   的程度大于支持 
  • 证据强度的经验尺度(Jeffreys尺度)通常将   分段解释,例如   为“弱证据”,  为“中等证据”,  为“强证据”等(仅作参考)。

在贝叶斯框架下,检验还可以结合先验下的模型概率,通过后验赔率进行理性决策:

若我们没有偏好,令  ,则后验赔率即为贝叶斯因子。

一个非常有用的技巧是 Savage–Dickey 密度比(SDDR),在嵌套模型(如   约束   的某个参数取固定值)且先验在该参数上可分解且相容(compatible)时:

其中   是在   下被固定的参数值。例如检验   时,

于是  。这个公式可以大幅简化计算,因为只需计算先验和后验在一点的密度值。

在线性回归中的贝叶斯检验

考虑带高斯噪声的线性回归:

我们对权重向量   采用零均值各向同性高斯先验:

其中   控制先验强度(先验“尺度”或正则强度)。

在已知噪声方差   的情况下,后验仍为高斯分布(共轭):

后验协方差:

后验均值:

这是经典的线性高斯模型推导。MAP解等价于岭回归,其正则项 

边际似然(证据)可通过线性高斯积分得到:

因此对任意模型  ,其对数证据为:

现在我们进行变量重要性检验:

  • : 第1个特征的系数 
  • :  ,即完整模型。

注意,     的嵌套模型。我们可以有两种方式计算 

方式A:证据比(模型证据的比值)

  •    下,
  •    下,将第1列从   中去掉或设为0,得到  ,则 

计算时应基于 Cholesky 分解来保证数值稳定:若  ,则  ,且   可用回代求解。

方式B:Savage–Dickey 密度比(SDDR)

   下,先验对   因子化,且      的点假设,因此

因为先验  ,其在0处的密度是:

后验   的边际分布   仍是正态:

于是后验在0处的密度为:

从而

这个公式简单直观:当后验均值  偏离0越明显(相对于后验方差 越小),即 越大,贝叶斯因子越倾向支持 ;当后验方差 相较先验方差 显著收缩时(信息增益),也倾向于支持

两种方式(证据比与SDDR)在本设定下是等价的,我们会在代码中验证两者的数值一致性(允许小数值误差)。

后验预测

   上的后验预测分布为:

这为我们提供了预测不确定性的刻画,用于校准与模型诊断。

完整案例

我们构造一个带相关特征的虚拟数据集,  个样本、  个特征。

真实权重向量   中仅有第1个特征有效(非零),其余为零或近零。

使用贝叶斯线性回归(高斯先验),检验   vs 

实现细节与训练流程

数据生成:

  • 设置  ,生成带相关性的特征,协方差为 AR(1): ,取  ,再对列做标准化。
  • 设真实权重  (也可加一些较小扰动)。
  •   ,生成高斯噪声。

先验与正则:

  •   (可调),即先验方差 
  • 岭回归正则强度 

贝叶斯推断与数值稳定:

  • 后验参数:用 Cholesky 分解求解     (不直接求逆)。
  • 证据与贝叶斯因子:用   的 Cholesky 分解求 log evidence。
  • SDDR:只需要     ,从      中读取。

模型训练与对照:用 sklearn 的 Ridge 拟合(alpha =  ),比对 Ridge 的系数与后验均值   的一致性。

import numpy as np
import scipy.stats as st
from scipy.linalg import cho_factor, cho_solve, cholesky
from sklearn.linear_model import Ridge
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(42)

# 1) 生成数据:相关特征 + 标准化
n = 200
p = 6
rho = 0.6

# 生成协方差矩阵(AR(1))
Sigma_x = rho ** np.abs(np.subtract.outer(np.arange(p), np.arange(p)))
# 生成特征 X ~ N(0, Sigma_x)
X = np.random.multivariate_normal(mean=np.zeros(p), cov=Sigma_x, size=n)
# 对列做标准化(零均值、单位方差)
X = (X - X.mean(axis=0)) / X.std(axis=0, ddof=1)

# 真实权重(第1个特征有效)
w_true = np.array([2.00.00.00.00.00.0])

sigma = 1.0# 噪声标准差已知(这里我们“设定”已知,实践中可估计)
y = X @ w_true + np.random.normal(0, sigma, size=n)

# 2) 先验设置
tau = 1.5
tau2 = tau**2
sigma2 = sigma**2
lambda_ridge = sigma2 / tau2  # 岭回归中的alpha

# 3) 后验计算:S = (X^T X / sigma^2 + I / tau^2)^{-1}, m = S X^T y / sigma^2
XT_X = X.T @ X
XT_y = X.T @ y
A = XT_X / sigma2 + np.eye(p) / tau2  # p x p
# Cholesky 分解求解 A^{-1} @ b,比直接逆更稳定
c_factor = cho_factor(A, lower=True, check_finite=False)
S = cho_solve(c_factor, np.eye(p), check_finite=False)          # 协方差
m = cho_solve(c_factor, XT_y / sigma2, check_finite=False)      # 均值(也是MAP解)

# 4) 模型证据(H1 & H0)与贝叶斯因子(方式A:证据比)
def log_marginal_evidence(X, y, sigma2, tau2):
    # 计算 log p(y|X, sigma2, tau2) = -0.5[n log(2π) + log|Σ| + y^T Σ^{-1} y]
    # Σ = sigma2 I + tau2 X X^T
    n = X.shape[0]
    Sigma = sigma2 * np.eye(n) + tau2 * (X @ X.T)  # n x n
    # Cholesky for Σ
    L = cholesky(Sigma, lower=True, check_finite=False)
    # log|Σ| = 2 sum log diag(L)
    logdet = 2.0 * np.sum(np.log(np.diag(L)))
    # solve Σ^{-1} y
    # 先解 L z = y, 再解 L^T α = z
    z = np.linalg.solve(L, y)
    alpha = np.linalg.solve(L.T, z)
    quad = y @ alpha
    return-0.5 * (n * np.log(2 * np.pi) + logdet + quad)

# H1: 完整模型
log_ev_H1 = log_marginal_evidence(X, y, sigma2, tau2)

# H0: 去掉第1列(索引0)的嵌套模型
X0 = X[:, 1:]  # p-1个特征
log_ev_H0 = log_marginal_evidence(X0, y, sigma2, tau2)

log_BF_10_evidence = log_ev_H1 - log_ev_H0

# 5) 贝叶斯因子(方式B:SDDR)
m1 = m[0]
S11 = S[00]
log_prior_at_0 = -0.5 * np.log(2 * np.pi * tau2)  # N(0,tau2)在0处的 log 密度
log_post_at_0 = -0.5 * np.log(2 * np.pi * S11) - 0.5 * (m1**2) / S11
# BF10 = prior(0) / post(0)
log_BF_10_SDDR = log_prior_at_0 - log_post_at_0

# 6) 对照:Ridge回归的系数是否与后验均值一致?
ridge = Ridge(alpha=lambda_ridge, fit_intercept=False)  # 我们的 X 与 y 已均值中心化,无需截距
ridge.fit(X, y)
w_ridge = ridge.coef_

diff_norm = np.linalg.norm(w_ridge - m)

print("后验均值(m):", m)
print("Ridge 系数:", w_ridge)
print("二者差的范数:", diff_norm)
print("log BF10(证据比):", log_BF_10_evidence)
print("log BF10(SDDR):", log_BF_10_SDDR)

# 7) 贝叶斯因子随样本量增长
ns = np.linspace(20, n, 10, dtype=int)  # 10个点
logBFs = []
for n_sub in ns:
    X_sub = X[:n_sub]
    y_sub = y[:n_sub]
    log_ev_H1_sub = log_marginal_evidence(X_sub, y_sub, sigma2, tau2)
    log_ev_H0_sub = log_marginal_evidence(X_sub[:, 1:], y_sub, sigma2, tau2)
    logBFs.append(log_ev_H1_sub - log_ev_H0_sub)
logBFs = np.array(logBFs)

# 8) 不同tau的敏感性分析(对数刻度)
taus = np.logspace(-1120)  # 0.1 到 10
logBFs_tau = []
for t in taus:
    t2 = t**2
    log_ev_H1_t = log_marginal_evidence(X, y, sigma2, t2)
    log_ev_H0_t = log_marginal_evidence(X0, y, sigma2, t2)
    logBFs_tau.append(log_ev_H1_t - log_ev_H0_t)
logBFs_tau = np.array(logBFs_tau)

# 9) 后验预测评估(用留出集/或随机抽样简化)
# 为了展示,我们用8:2划分
n_train = int(0.8 * n)
X_train, y_train = X[:n_train], y[:n_train]
X_test, y_test = X[n_train:], y[n_train:]

# 重新计算训练集的后验
XT_X_tr = X_train.T @ X_train
XT_y_tr = X_train.T @ y_train
A_tr = XT_X_tr / sigma2 + np.eye(p) / tau2
c_factor_tr = cho_factor(A_tr, lower=True, check_finite=False)
S_tr = cho_solve(c_factor_tr, np.eye(p), check_finite=False)
m_tr = cho_solve(c_factor_tr, XT_y_tr / sigma2, check_finite=False)

# 后验预测:mean = X_test m_tr, var = sigma^2 + diag(X_test S_tr X_test^T)
pred_mean = X_test @ m_tr
pred_var = sigma2 + np.sum(X_test @ S_tr * X_test, axis=1)
pred_std = np.sqrt(pred_var)

# 10) 可视化分析
plt.figure(figsize=(1410))

# 图1:w1 的先验与后验密度对比
plt.figure(figsize=(76))
grid_w1 = np.linspace(-44400)
prior_w1_pdf = st.norm.pdf(grid_w1, loc=0, scale=tau)
post_w1_pdf = st.norm.pdf(grid_w1, loc=m1, scale=np.sqrt(S11))

ci_prior = np.array([st.norm.ppf(0.0250, tau), st.norm.ppf(0.9750, tau)])
ci_post = np.array([st.norm.ppf(0.025, m1, np.sqrt(S11)), st.norm.ppf(0.975, m1, np.sqrt(S11))])

plt.plot(grid_w1, prior_w1_pdf, color='magenta', lw=2,
         label=r'先验: $\mathcal{N}(0, \tau^2)$', alpha=0.9)
plt.plot(grid_w1, post_w1_pdf, color='deepskyblue', lw=3,
         label=r'后验: $\mathcal{N}(m_1, S_{11})$', alpha=0.9)

plt.fill_between(grid_w1, 0, prior_w1_pdf,
                 where=(grid_w1>=ci_prior[0]) & (grid_w1<=ci_prior[1]),
                 color='pink', alpha=0.3)
plt.fill_between(grid_w1, 0, post_w1_pdf,
                 where=(grid_w1>=ci_post[0]) & (grid_w1<=ci_post[1]),
                 color='cyan', alpha=0.25)

plt.axvline(0, color='orange', ls='--', lw=2,
            label=r'零值 ($H_0$)')

plt.title(r'图1:$w_1$ 的先验与后验密度对比', fontsize=13)
plt.xlabel(r'$w_1$')
plt.ylabel(r'密度')
plt.legend()
plt.show()


# 图2:log BF10 随样本数 n
plt.figure(figsize=(76))
plt.plot(ns, logBFs, marker='o', color='limegreen', lw=3,
         label=r'$\log \mathrm{BF}_{10}$ (证据比)', alpha=0.9)

plt.axhline(0.0, color='gray', ls='--', lw=1)
plt.axhline(np.log(3), color='gold', ls='--', lw=2,
            label=r'Jeffreys 阈值 $\approx \ln(3)$')
plt.axhline(np.log(10), color='red', ls='--', lw=2,
            label=r'Jeffreys 阈值 $\approx \ln(10)$')

plt.title(r'图2:$\log \mathrm{BF}_{10}$ 随样本数 $n$ 的增长', fontsize=13)
plt.xlabel(r'样本数 $n$')
plt.ylabel(r'$\log \mathrm{BF}_{10}$')
plt.legend()
plt.show()


# 图3:后验预测校准(预测均值 vs 真实)
plt.figure(figsize=(76))
plt.scatter(y_test, pred_mean, s=45, c='magenta', alpha=0.8,
            label=r'预测均值 $\mathbb{E}[y \mid x]$',
            edgecolors='white', linewidth=0.7)

# 画 95% 预测区间
for i in range(len(y_test)):
    y_true = y_test[i]
    y_hat = pred_mean[i]
    y_lo = y_hat - 1.96 * pred_std[i]
    y_hi = y_hat + 1.96 * pred_std[i]
    plt.plot([y_true, y_true], [y_lo, y_hi],
             color='deepskyblue', alpha=0.6, lw=2)

miny = min(y_test.min(), pred_mean.min()) - 1.0
maxy = max(y_test.max(), pred_mean.max()) + 1.0

plt.plot([miny, maxy], [miny, maxy], color='limegreen',
         ls='--', lw=2, label=r'理想线: $y = \hat{y}$')

plt.xlim(miny, maxy)
plt.ylim(miny, maxy)
plt.gca().set_aspect('equal', adjustable='box')

plt.title(r'图3:后验预测校准与不确定性', fontsize=13)
plt.xlabel(r'真实值 $y$')
plt.ylabel(r'预测均值 $\mathbb{E}[y\mid x]$')
plt.legend()
plt.show()


# 图4:log BF10 对先验尺度 τ 的敏感性
plt.figure(figsize=(76))
plt.semilogx(taus, logBFs_tau, marker='s', color='orange', lw=3,
             label=r'$\log \mathrm{BF}_{10}$ vs. $\tau$', alpha=0.9)

plt.axhline(0.0, color='gray', ls='--', lw=1)
plt.axvline(tau, color='red', ls=':', lw=2,
            label=r'当前 $\tau$')

plt.title(r'图4:先验尺度 $\tau$ 的敏感性分析', fontsize=13)
plt.xlabel(r'先验尺度 $\tau$(对数刻度)')
plt.ylabel(r'$\log \mathrm{BF}_{10}$')
plt.legend()
plt.show()

w₁ 的先验与后验密度对比

数据更新后,我们对   的信念如何变化?零点( )还合理吗?

log BF₁₀ 随样本数增长

随着样本数增加,数据对   相对于   的证据如何积累?

后验预测校准图(预测均值 vs 真实)与不确定性区间

在留出测试集上,模型是否“校准良好”?预测不确定性是否合理?

log BF₁₀ 对先验尺度 τ 的敏感性

不同先验强度下,贝叶斯因子的稳定性如何?结论是否“先验鲁棒”?

与频率学派检验的对比

与p值的差异:

  • p值是“在零假设为真”的条件下观测到当前或更极端统计量的概率。它不直接比较两个模型的“证据”。
  • 贝叶斯因子直接比较     ,因此体现“奥卡姆剃刀”:复杂模型的参数体积(通过行列式项)会被自动惩罚。
  • 在小样本场景或需要模型选择时,BF更契合实际需求。

与岭回归的关系:

  • 本文的先验与岭回归的 L2 正则具有等价关系,MAP 解等于岭回归解。
  • 贝叶斯推断提供了不确定性(后验协方差)与模型证据,而不仅仅是点估计。

g-prior 与闭式BF:

  • Zellner’s g-prior: ,在嵌套模型下有基于   的闭式贝叶斯因子表达式,非常适合变量选择。但需要注意   的选择或对其置超先验(如hyper-g prior)。

未知 

  •    未知,可对其设置共轭逆伽马先验(与   的正态先验构成正态-逆伽马共轭族)。此时后验边际分布中的许多量将以Student-t形式出现,SDDR有相应调整。实现略复杂,但同样有闭式表达。

广义线性模型(GLM)与分类任务:

  • 对于逻辑回归等非线性模型,后验不再有闭式;可以使用拉普拉斯近似、变分推断或MCMC进行贝叶斯检验与BF估计。
  • 在A/B测试(Bernoulli–Beta)中,SDDR与闭式BF也常见且实现简单(Beta–Binomial模型)。

总结

全文,我们从理论出发推导了后验、边际似然与贝叶斯因子,展示了两种计算方式(证据比与SDDR)及其等价性;也讨论了与频率学派检验的差异与互补性,并指出了进一步优化与注意事项。

这一套流程不仅适用于线性模型,也能启发你在更复杂的机器学习模型(广义线性模型、核方法、深度网络)中应用贝叶斯思想进行模型选择与不确定性评估。

最后

最近准备了16大块的内容,124个算法问题的总结,完整的机器学习小册,免费领取~
领取:备注「算法小册」即可~
扫码如有问题,记得添加微信号:xiaobai_ml012

【声明】内容源于网络
0
0
机器学习和人工智能AI
让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
内容 333
粉丝 0
机器学习和人工智能AI 让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
总阅读215
粉丝0
内容333