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 年研究

  1. Sharpness-Aware PAC-Bayes:将平坦性(sharpness)纳入 PAC-Bayes 边界
  2. Data-dependent priors:使用训练数据的统计量构造更紧的先验
  3. 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

  1. McAllester, D. A. (1999). PAC-Bayesian model averaging.