PAC-Bayes边界理论
PAC-Bayes理论是连接贝叶斯推断与PAC学习理论的重要桥梁,提供了一种在随机假设空间上进行泛化分析的统一框架。1
理论基础
PAC学习回顾
PAC(Probably Approximately Correct)学习研究算法的样本复杂度:
PAC定义:算法 在假设空间 上是 PAC 学习的,如果对任意目标分布 和 ,以至少 的概率,输出假设 满足:
当样本数 。
PAC-Bayes的创新
PAC-Bayes 不再假设存在固定的最优假设,而是考虑假设上的分布:
- 先验分布 :学习前对假设的偏好
- 后验分布 :学习后对假设的置信度
PAC-Bayes 边界关注:给定训练数据,从先验 到后验 的学习过程有多好?
PAC-Bayes 边界
核心定理
设 为数据分布, 为 个 i.i.d. 样本。对于任意先验分布 、任意随机化算法(产生后验 )、任意 ,以至少 的概率:
其中:
- : 下假设的期望风险
- : 下假设的期望经验风险
- :KL散度,衡量 与 的差异
import numpy as np
from scipy.special import kl_div
def pac_bayes_bound(emp_risk_Q, kl_Q_P, m, delta=0.05):
"""
计算 PAC-Bayes 边界
R(Q) ≤ emp_risk_Q + sqrt((KL(Q||P) + ln(2m/δ)) / (2m))
Args:
emp_risk_Q: 后验Q下的期望经验风险
kl_Q_P: KL(Q || P)
m: 样本数量
delta: 置信度
Returns:
upper_bound: 风险上界
"""
complexity_term = np.sqrt((kl_Q_P + np.log(2 * m / delta)) / (2 * m))
return emp_risk_Q + complexity_term
def compute_kl_divergence(Q_params, P_params, dist_type='gaussian'):
"""
计算 KL(Q || P)
对于高斯分布:
KL(N(μ_Q, σ²_Q) || N(μ_P, σ²_P))
= log(σ_P/σ_Q) + (σ²_Q + (μ_Q-μ_P)²)/(2σ²_P) - 1/2
"""
if dist_type == 'gaussian':
mu_Q, sigma_Q = Q_params
mu_P, sigma_P = P_params
kl = (np.log(sigma_P/sigma_Q) +
(sigma_Q**2 + (mu_Q - mu_P)**2)/(2*sigma_P**2) - 0.5)
return kl
elif dist_type == 'bernoulli':
q, p = Q_params
# KL(q||p) for Bernoulli
return q * np.log(q/p) + (1-q) * np.log((1-q)/(1-p))
return None
# 示例:计算神经网络的 PAC-Bayes 边界
def demo_pac_bayes_nn():
"""
对神经网络权重应用 PAC-Bayes 边界
"""
# 假设条件
m = 10000 # 样本数
emp_risk_Q = 0.05 # 训练误差
delta = 0.05 # 置信度
# 先验:标准差为0.1的高斯分布
P = {'mu': 0.0, 'sigma': 0.1}
# 后验:训练后的权重分布
Q = {'mu': 0.05, 'sigma': 0.08}
# 计算 KL 散度
# 对于权重向量的近似:kl = sum_i KL(N(μ_i^Q, σ_i^Q²) || N(μ_i^P, σ_i^P²))
n_params = 1_000_000 # 假设100万参数
# 假设所有参数共享相同的分布
kl_per_param = compute_kl_divergence(
(Q['mu'], Q['sigma']),
(P['mu'], P['sigma']),
'gaussian'
)
kl_Q_P = n_params * kl_per_param
# 计算边界
bound = pac_bayes_bound(emp_risk_Q, kl_Q_P, m, delta)
print(f"PAC-Bayes 边界分析:")
print(f" 样本数 m = {m:,}")
print(f" 期望经验风险 = {emp_risk_Q:.4f}")
print(f" KL(Q||P) 每参数 = {kl_per_param:.6f}")
print(f" KL(Q||P) 总计 = {kl_Q_P:.2e}")
print(f" 复杂度项 = √({kl_Q_P:.2e} + ln({2*m/delta:.0f})) / {2*m:.0f})")
print(f" PAC-Bayes 边界 = {bound:.4f}")
return bound边界变体
Catoni 边界
Catoni 提出了基于尾部指数化的更紧边界:
def catoni_bound(emp_risk, kl, m, delta=0.05, alpha_range=None):
"""
Catoni 的 PAC-Bayes 边界
使用尾部指数化,比标准边界更紧
"""
if alpha_range is None:
alpha_range = np.logspace(-3, 3, 100)
bounds = []
for alpha in alpha_range:
# alpha-经验风险
emp_risk_alpha = (1 - (1-emp_risk)**(1/alpha)) * alpha
bound = emp_risk_alpha + (alpha * kl + np.log(1/delta)) / m
bounds.append(bound)
return np.min(bounds)Seeger 边界
Seeger 边界利用数据依赖的先验,提供更紧的边界:
其中 是某个凸函数。
压缩边界与随机子空间
McAllester 压缩边界
PAC-Bayes 框架可以推导出PAC学习的压缩边界:
class PACBayesCompression:
"""
PAC-Bayes 压缩边界分析
将随机子空间方法与 PAC-Bayes 边界结合
"""
def __init__(self, n_features, compression_ratio):
self.n_features = n_features
self.compression_ratio = compression_ratio
self.n_selected = int(n_features * compression_ratio)
def compression_bound(self, m, delta=0.05):
"""
计算压缩边界
基于随机子空间的复杂度
"""
# 先验:所有特征等权重
log_P_complexity = 0
# 后验:在选中的特征上均匀分布
log_Q_complexity = -np.log(self.n_selected)
# KL 散度(近似)
kl = log_Q_complexity - log_P_complexity
# PAC-Bayes 边界
bound = np.sqrt((kl + np.log(2*m/delta)) / (2*m))
return bound
def generalisation_bound(self, emp_error, m, delta=0.05):
"""
泛化边界
R ≤ emp_error + compression_bound
"""
comp_bound = self.compression_bound(m, delta)
return emp_error + comp_bound
# 示例:特征选择压缩
def demo_feature_compression():
pac = PACBayesCompression(n_features=10000, compression_ratio=0.1)
for m in [100, 1000, 10000, 100000]:
bound = pac.generalisation_bound(emp_error=0.1, m=m)
print(f"m={m:>6}: 泛化边界 = {bound:.4f}")神经网络的 PAC-Bayes 分析
权重空间视角
PAC-Bayes 为分析神经网络泛化提供了有力工具:
class NeuralNetworkPACBayes:
"""
神经网络的 PAC-Bayes 分析
"""
def __init__(self, prior_std=0.1, posterior_std=None):
self.prior_std = prior_std
self.posterior_std = posterior_std or prior_std
def compute_prior(self, model):
"""
计算先验:假设权重服从 N(0, σ²_p I)
"""
return {
'mean': 0,
'std': self.prior_std,
'entropy': 0.5 * np.log(2*np.pi*np.e*self.prior_std**2)
}
def compute_posterior(self, model, training_data):
"""
估计后验:使用训练后权重的统计量
实际中需要变分推断或采样
"""
weights = []
for param in model.parameters():
weights.extend(param.data.flatten().numpy())
weights = np.array(weights)
return {
'mean': weights.mean(),
'std': weights.std(),
'kl_per_param': self._kl_gaussian(
weights.mean(), weights.std(),
0, self.prior_std
)
}
def _kl_gaussian(self, mu_q, sigma_q, mu_p, sigma_p):
"""高斯分布的 KL 散度"""
return np.log(sigma_p/sigma_q) + (sigma_q**2 + (mu_q-mu_p)**2)/(2*sigma_p**2) - 0.5
def pac_bayes_bound(self, model, emp_error, m, delta=0.05):
"""
计算网络的 PAC-Bayes 边界
"""
prior = self.compute_prior(model)
posterior = self.compute_posterior(model, None)
# 总 KL 散度
n_params = sum(p.numel() for p in model.parameters())
kl_total = n_params * posterior['kl_per_param']
# 边界
complexity = np.sqrt((kl_total + np.log(2*m/delta)) / (2*m))
return {
'emp_error': emp_error,
'kl_total': kl_total,
'complexity': complexity,
'bound': emp_error + complexity,
'n_params': n_params
}
# 示例:比较不同初始化和训练的影响
def compare_nn_pac_bayes():
"""
比较不同网络的 PAC-Bayes 边界
"""
scenarios = [
{'name': '随机初始化', 'prior_std': 0.1, 'posterior_std': 0.1, 'emp_error': None},
{'name': '训练后', 'prior_std': 0.1, 'posterior_std': 0.08, 'emp_error': 0.05},
{'name': '过拟合', 'prior_std': 0.1, 'posterior_std': 0.05, 'emp_error': 0.001},
]
n_params = 1_000_000
m = 50000
delta = 0.05
for s in scenarios:
pac = NeuralNetworkPACBayes(
prior_std=s['prior_std'],
posterior_std=s['posterior_std']
)
# 近似计算
kl_per_param = pac._kl_gaussian(
0, s['posterior_std'], 0, s['prior_std']
)
kl_total = n_params * kl_per_param
complexity = np.sqrt((kl_total + np.log(2*m/delta)) / (2*m))
print(f"\n{s['name']}:")
print(f" KL 每参数: {kl_per_param:.6f}")
print(f" KL 总计: {kl_total:.2f}")
print(f" 复杂度项: {complexity:.4f}")
if s['emp_error'] is not None:
print(f" 经验误差: {s['emp_error']:.4f}")
print(f" PAC-Bayes 边界: {s['emp_error'] + complexity:.4f}")信息论视角
PAC-Bayes 与信息论
PAC-Bayes 边界可以解释为信息论中的”率-失真”边界:
def pac_bayes_information_theoretic():
"""
PAC-Bayes 边界的信息论解释
"""
# KL(Q||P) 可以解释为编码从 P 切换到 Q 所需的额外比特数
# 复杂度项 sqrt((KL + ln(1/δ))/2m) 控制了这个权衡
# Rate-Distortion 视角
def rate_distortion_bound(emp_risk, m, delta=0.05):
"""
将泛化视为率-失真问题
- 失真:经验风险与真实风险的差距
- 率:从先验到后验的信息量(KL散度)
"""
# 最优的率-失真权衡
complexity = np.log(1/delta) / m
return emp_risk + np.sqrt(complexity / 2)
# 边际似然视角
def marginal_likelihood_bound(emp_risk, m, kl, delta=0.05):
"""
PAC-Bayes 框架中,边际似然 bound 为:
-log p(S) ≥ -m·emp_risk - KL(Q||P)
这连接了贝叶斯证据和 PAC 学习
"""
evidence_bound = -m * emp_risk - kl
return evidence_bound
print("PAC-Bayes 的信息论解释:")
print(" KL(Q||P) = 从 P 到 Q 所需的编码额外信息")
print(" 复杂度项 = 权衡探索(复杂度)与利用(性能)")
print(" 边际似然 = 贝叶斯证据的 bound")PAC-Bayes 与其他泛化理论的关系
对比分析
| 理论框架 | 核心量 | 优势 | 局限 |
|---|---|---|---|
| VC维 | $\log | \mathcal{H} | $ |
| Rademacher | 数据依赖 | 计算复杂 | |
| PAC-Bayes | 贝叶斯解释、灵活 | 需要选择先验 | |
| 算法稳定性 | 稳定性系数 | 直观 | 过于保守 |
统一视图
class GeneralizationUnified:
"""
统一泛化理论框架
"""
@staticmethod
def vc_bound(n_samples, vc_dim, delta=0.05):
"""VC 维度边界"""
return np.sqrt(
(vc_dim * np.log(2 * n_samples / vc_dim) + np.log(2 / delta))
/ (2 * n_samples)
)
@staticmethod
def rademacher_bound(rademacher_complexity, delta=0.05):
"""Rademacher 复杂度边界"""
return rademacher_complexity + np.sqrt(np.log(1/delta) / (2 * n_samples))
@staticmethod
def pac_bayes_bound(kl_divergence, m, delta=0.05):
"""PAC-Bayes 边界"""
return np.sqrt(
(kl_divergence + np.log(2 * m / delta)) / (2 * m)
)
@staticmethod
def compare_bounds(n_samples=10000, vc_dim=1000, rademacher=0.1, kl=10):
"""
比较不同边界的紧度
"""
bounds = {
'VC': GeneralizationUnified.vc_bound(n_samples, vc_dim),
'Rademacher': GeneralizationUnified.rademacher_bound(rademacher),
'PAC-Bayes': GeneralizationUnified.pac_bayes_bound(kl, n_samples)
}
print("泛化边界比较:")
for name, bound in bounds.items():
print(f" {name}: {bound:.4f}")
return bounds实践应用
1. 超参数选择
class PACBayesHyperparameterTuner:
"""
基于 PAC-Bayes 边界的超参数调优
"""
def __init__(self):
self.results = []
def evaluate_config(self, config, val_error, n_params, m):
"""
评估超参数配置的 PAC-Bayes 边界
"""
# 假设先验 σ_p = 0.1
prior_std = 0.1
# 估计后验 σ_q(可以从训练过程推断)
posterior_std = config.get('posterior_std', 0.08)
# 计算 KL 散度
kl_per_param = 0.5 * np.log(prior_std/posterior_std) + \
(posterior_std**2 - prior_std**2)/(2*prior_std**2)
kl_total = n_params * kl_per_param
# PAC-Bayes 边界
complexity = np.sqrt((kl_total + np.log(2*m/0.05)) / (2*m))
pac_bound = val_error + complexity
self.results.append({
'config': config,
'val_error': val_error,
'kl': kl_total,
'complexity': complexity,
'pac_bound': pac_bound
})
return pac_bound
def best_config(self):
"""返回边界最低的配置"""
return min(self.results, key=lambda x: x['pac_bound'])2. 早停的 PAC-Bayes 分析
class EarlyStoppingPACBayes:
"""
早停作为 PAC-Bayes 正则化的分析
"""
def __init__(self, n_params, prior_std=0.1):
self.n_params = n_params
self.prior_std = prior_std
self.history = []
def step(self, epoch, val_error, posterior_std):
"""
记录每个 epoch 的边界
"""
kl_per_param = 0.5 * np.log(self.prior_std/posterior_std) + \
(posterior_std**2 - self.prior_std**2)/(2*self.prior_std**2)
kl_total = self.n_params * kl_per_param
m = 50000 # 假设样本数
complexity = np.sqrt((kl_total + np.log(2*m/0.05)) / (2*m))
self.history.append({
'epoch': epoch,
'val_error': val_error,
'posterior_std': posterior_std,
'kl': kl_total,
'complexity': complexity,
'total_bound': val_error + complexity
})
def optimal_epoch(self):
"""找到最优 epoch(边界最低)"""
return min(self.history, key=lambda x: x['total_bound'])PAC-Bayes 的最新进展
2024-2025 年研究
- Sharpness-Aware PAC-Bayes:将平坦性(sharpness)纳入 PAC-Bayes 边界
- Data-dependent priors:使用训练数据的统计量构造更紧的先验
- PAC-Bayes for transformers:分析注意力机制的泛化性质
class SharpnessAwarePACBayes:
"""
感知尖锐度的 PAC-Bayes 边界
考虑参数空间中的局部曲率
"""
def __init__(self, hessian_eigenvalues):
self.hessian_eigenvalues = hessian_eigenvalues
def sharpness_factor(self):
"""
计算尖锐度因子
曲率越高(hessian特征值越大),边界越松
"""
max_eigenvalue = max(self.hessian_eigenvalues)
sharpness = np.mean(np.abs(self.hessian_eigenvalues))
return sharpness / max_eigenvalue
def adjusted_bound(self, base_bound, alpha=0.1):
"""
调整后的 PAC-Bayes 边界
加入尖锐度惩罚
"""
sharpness_factor = self.sharpness_factor()
return base_bound * (1 + alpha * sharpness_factor)参考
Footnotes
-
McAllester, D. A. (1999). PAC-Bayesian model averaging. ↩