Python拒绝采样算法优化与微调模拟

拒绝采样是一种从复杂分布中生成样本的蒙特卡洛方法,是目前LLM微调优化的关键技术。

这里尝试基于python模拟和对比多种拒绝采样的实现方法。

所用样例参考和优化自网络资料。

1 什么是拒绝采样

拒绝采样(Rejection Sampling)是一种从复杂分布中生成样本的蒙特卡洛方法。

下面将展示多种拒绝采样算法。

1.1 基础拒绝采样

  • 核心原理:从提议分布生成样本,以一定概率接受

  • 关键参数:常数M,需要满足 target_pdf(x) ≤ M * proposal_pdf(x)

  • 优化方向:选择合适的提议分布和尽可能小的M值

1.2 自适应拒绝采样优化

  • 动态调整提议分布:根据已接受的样本调整提议分布的参数

  • 自动优化M值:在采样过程中重新计算最优M值

  • 分批更新:每接受一定数量样本后更新提议分布

1.3 分层拒绝采样优化

  • 定义域分层:将目标分布的定义域分成多个子区域

  • 各层独立优化:为每个子区域选择最合适的提议分布

  • 平衡采样:在各层之间合理分配采样数量

1.4 混合提议分布优化

  • 多个提议分布混合:使用多个提议分布的加权和

  • 自适应权重调整:根据接受率动态调整各分布的权重

  • 多峰分布匹配:特别适合多峰目标分布

2 python模拟拒绝采样

2.1 拒绝采样优化模拟示例

下面将展示一个完整的Python实现,包括基础版本和多个优化版本。

复制代码
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import time
from functools import wraps
import warnings
warnings.filterwarnings('ignore')

# ==================== 基础拒绝采样算法 ====================
def basic_rejection_sampling(target_pdf, proposal_pdf, proposal_rv, M, n_samples=1000, max_iter=10000):
    """
    基础拒绝采样算法
    
    参数:
    target_pdf: 目标分布的概率密度函数
    proposal_pdf: 提议分布的概率密度函数
    proposal_rv: 提议分布的随机变量生成函数
    M: 常数M,使得 target_pdf(x) ≤ M * proposal_pdf(x) 对所有x成立
    n_samples: 需要生成的样本数量
    max_iter: 最大迭代次数(防止无限循环)
    
    返回:
    samples: 从目标分布中采样的样本
    acceptance_rate: 接受率
    """
    samples = []
    attempts = 0
    
    while len(samples) < n_samples and attempts < max_iter:
        attempts += 1
        
        # 1. 从提议分布生成样本
        x = proposal_rv()
        
        # 2. 从均匀分布U(0,1)生成样本
        u = np.random.uniform(0, 1)
        
        # 3. 接受/拒绝决策
        if u < target_pdf(x) / (M * proposal_pdf(x)):
            samples.append(x)
    
    acceptance_rate = len(samples) / attempts if attempts > 0 else 0
    
    return np.array(samples), acceptance_rate

# ==================== 自适应拒绝采样优化版本 ====================
class AdaptiveRejectionSampler:
    """
    自适应拒绝采样器 - 优化版本1: 动态调整提议分布
    """
    def __init__(self, target_pdf, initial_proposal, domain=(-10, 10)):
        """
        初始化自适应拒绝采样器
        
        参数:
        target_pdf: 目标分布的概率密度函数
        initial_proposal: 初始提议分布 (dict with 'pdf' and 'rv' functions)
        domain: 采样域
        """
        self.target_pdf = target_pdf
        self.proposal_pdf = initial_proposal['pdf']
        self.proposal_rv = initial_proposal['rv']
        self.domain = domain
        self.samples = []
        self.rejections = []
        self.M_history = []
        
    def find_optimal_M(self, n_test=1000):
        """
        通过测试点找到最优的M值
        
        参数:
        n_test: 测试点数量
        
        返回:
        optimal_M: 最优M值
        """
        test_points = np.linspace(self.domain[0], self.domain[1], n_test)
        ratios = []
        
        for x in test_points:
            target_val = self.target_pdf(x)
            proposal_val = self.proposal_pdf(x)
            if proposal_val > 0:
                ratio = target_val / proposal_val
                ratios.append(ratio)
        
        # 使用最大比值的1.1倍作为M,确保覆盖
        optimal_M = np.max(ratios) * 1.1 if ratios else 2.0
        return optimal_M
    
    def adaptive_sampling(self, n_samples=1000, adapt_every=100, verbose=False):
        """
        自适应采样过程
        
        参数:
        n_samples: 需要生成的样本数量
        adapt_every: 每多少个样本调整一次提议分布
        verbose: 是否显示详细信息
        
        返回:
        samples: 采样结果
        acceptance_rate: 平均接受率
        """
        samples = []
        self.M_history = []
        total_attempts = 0
        
        # 初始M值
        M = self.find_optimal_M()
        self.M_history.append(M)
        
        for batch in range(0, n_samples, adapt_every):
            batch_size = min(adapt_every, n_samples - len(samples))
            batch_samples, batch_attempts = self._sample_batch(batch_size, M)
            
            samples.extend(batch_samples)
            total_attempts += batch_attempts
            
            # 更新提议分布(基于已接受的样本)
            if len(samples) > 10:
                self._update_proposal(samples)
                M = self.find_optimal_M()
                self.M_history.append(M)
                
                if verbose:
                    acceptance = len(batch_samples) / batch_attempts if batch_attempts > 0 else 0
                    print(f"Batch {batch//adapt_every}: Acceptance rate = {acceptance:.4f}, M = {M:.4f}")
        
        acceptance_rate = len(samples) / total_attempts if total_attempts > 0 else 0
        return np.array(samples), acceptance_rate
    
    def _sample_batch(self, batch_size, M):
        """采样一个批次"""
        samples = []
        attempts = 0
        
        while len(samples) < batch_size and attempts < batch_size * 100:  # 防止无限循环
            attempts += 1
            x = self.proposal_rv()
            u = np.random.uniform(0, 1)
            
            if u < self.target_pdf(x) / (M * self.proposal_pdf(x)):
                samples.append(x)
            else:
                self.rejections.append(x)
        
        return samples, attempts
    
    def _update_proposal(self, accepted_samples):
        """基于接受的样本更新提议分布参数"""
        # 这里使用简单的高斯分布作为提议分布
        # 在实际应用中可以使用更复杂的分布
        mean = np.mean(accepted_samples)
        std = np.std(accepted_samples) + 0.1  # 加一个小值防止方差为0
        
        # 更新提议分布为新的正态分布
        self.proposal_pdf = lambda x: stats.norm.pdf(x, mean, std)
        self.proposal_rv = lambda: np.random.normal(mean, std)

# ==================== 分层拒绝采样优化版本 ====================
def stratified_rejection_sampling(target_pdf, domains, proposal_funcs, n_samples=1000):
    """
    分层拒绝采样 - 优化版本2: 将定义域分层
    
    参数:
    target_pdf: 目标分布的概率密度函数
    domains: 每个层的定义域列表 [(a1, b1), (a2, b2), ...]
    proposal_funcs: 每个层的提议分布函数列表 [{'pdf': pdf1, 'rv': rv1}, ...]
    n_samples: 需要生成的样本数量
    
    返回:
    samples: 采样结果
    acceptance_rates: 各层的接受率
    """
    samples = []
    acceptance_rates = []
    n_layers = len(domains)
    
    # 分配每层的采样数量(可以根据层的权重分配)
    samples_per_layer = n_samples // n_layers
    remainder = n_samples % n_layers
    
    for i in range(n_layers):
        # 当前层的采样数量
        layer_samples = samples_per_layer + (1 if i < remainder else 0)
        
        # 为当前层定义截断的提议分布
        a, b = domains[i]
        layer_proposal_pdf = proposal_funcs[i]['pdf']
        layer_proposal_rv = proposal_funcs[i]['rv']
        
        # 在当前层中找到M值
        test_points = np.linspace(a, b, 1000)
        max_ratio = 0
        for x in test_points:
            ratio = target_pdf(x) / layer_proposal_pdf(x)
            if ratio > max_ratio:
                max_ratio = ratio
        M = max_ratio * 1.1
        
        # 在当前层进行拒绝采样
        layer_samples_list = []
        attempts = 0
        while len(layer_samples_list) < layer_samples and attempts < layer_samples * 100:
            attempts += 1
            x = layer_proposal_rv()
            # 确保x在当前层内
            if a <= x <= b:
                u = np.random.uniform(0, 1)
                if u < target_pdf(x) / (M * layer_proposal_pdf(x)):
                    layer_samples_list.append(x)
        
        samples.extend(layer_samples_list)
        acceptance_rate = len(layer_samples_list) / attempts if attempts > 0 else 0
        acceptance_rates.append(acceptance_rate)
    
    return np.array(samples), acceptance_rates

# ==================== 混合提议分布优化版本 ====================
class MixtureProposalSampler:
    """
    混合提议分布拒绝采样 - 优化版本3: 使用多个提议分布的混合
    """
    def __init__(self, target_pdf, proposal_components):
        """
        初始化混合提议分布采样器
        
        参数:
        target_pdf: 目标分布的概率密度函数
        proposal_components: 提议分布组件列表
                            [{'pdf': pdf1, 'rv': rv1, 'weight': w1}, ...]
        """
        self.target_pdf = target_pdf
        self.components = proposal_components
        self.n_components = len(proposal_components)
        self.weights = np.array([comp['weight'] for comp in proposal_components])
        self.weights = self.weights / np.sum(self.weights)  # 归一化
    
    def sample(self, n_samples=1000):
        """执行采样"""
        samples = []
        attempts = 0
        
        while len(samples) < n_samples and attempts < n_samples * 100:
            attempts += 1
            
            # 1. 选择提议分布组件
            comp_idx = np.random.choice(self.n_components, p=self.weights)
            component = self.components[comp_idx]
            
            # 2. 从选定的组件生成样本
            x = component['rv']()
            
            # 3. 计算混合提议分布的概率密度
            mixture_pdf = 0
            for i, comp in enumerate(self.components):
                mixture_pdf += self.weights[i] * comp['pdf'](x)
            
            # 4. 计算M值(对于混合分布,需要找到全局M)
            # 这里简化处理,使用当前点的比值
            if mixture_pdf > 0:
                ratio = self.target_pdf(x) / mixture_pdf
                M = 2.0  # 简化:使用固定的M,实际应用中需要计算合适的M
                
                # 5. 接受/拒绝决策
                u = np.random.uniform(0, 1)
                if u < ratio / M:
                    samples.append(x)
        
        acceptance_rate = len(samples) / attempts if attempts > 0 else 0
        return np.array(samples), acceptance_rate

# ==================== 性能比较和可视化 ====================
def compare_sampling_methods(target_pdf, true_samples=None):
    """比较不同采样方法的性能"""
    # 定义目标分布(混合高斯分布)
    print("目标分布: 混合高斯分布")
    print("=" * 60)
    
    # 基础拒绝采样参数
    # 使用高斯分布作为提议分布
    proposal_mean = 0
    proposal_std = 2
    
    proposal_pdf = lambda x: stats.norm.pdf(x, proposal_mean, proposal_std)
    proposal_rv = lambda: np.random.normal(proposal_mean, proposal_std)
    
    # 找到合适的M值
    test_points = np.linspace(-10, 10, 1000)
    max_ratio = 0
    for x in test_points:
        ratio = target_pdf(x) / proposal_pdf(x)
        if ratio > max_ratio:
            max_ratio = ratio
    M = max_ratio * 1.1
    
    print(f"基础拒绝采样 - 使用的M值: {M:.4f}")
    
    # 方法1: 基础拒绝采样
    start_time = time.time()
    basic_samples, basic_acceptance = basic_rejection_sampling(
        target_pdf, proposal_pdf, proposal_rv, M, n_samples=2000
    )
    basic_time = time.time() - start_time
    
    print(f"基础拒绝采样 - 接受率: {basic_acceptance:.4f}, 时间: {basic_time:.4f}s")
    
    # 方法2: 自适应拒绝采样
    initial_proposal = {
        'pdf': proposal_pdf,
        'rv': proposal_rv
    }
    adaptive_sampler = AdaptiveRejectionSampler(target_pdf, initial_proposal, domain=(-10, 10))
    
    start_time = time.time()
    adaptive_samples, adaptive_acceptance = adaptive_sampler.adaptive_sampling(
        n_samples=2000, adapt_every=200, verbose=False
    )
    adaptive_time = time.time() - start_time
    
    print(f"自适应拒绝采样 - 接受率: {adaptive_acceptance:.4f}, 时间: {adaptive_time:.4f}s")
    
    # 方法3: 分层拒绝采样
    # 定义三个层和对应的提议分布
    domains = [(-10, -2), (-2, 2), (2, 10)]
    proposal_funcs = []
    for a, b in domains:
        mean = (a + b) / 2
        std = (b - a) / 4
        proposal_funcs.append({
            'pdf': lambda x, mean=mean, std=std: stats.norm.pdf(x, mean, std),
            'rv': lambda mean=mean, std=std: np.random.normal(mean, std)
        })
    
    start_time = time.time()
    stratified_samples, layer_acceptance = stratified_rejection_sampling(
        target_pdf, domains, proposal_funcs, n_samples=2000
    )
    stratified_time = time.time() - start_time
    
    print(f"分层拒绝采样 - 平均接受率: {np.mean(layer_acceptance):.4f}, 时间: {stratified_time:.4f}s")
    print(f"  各层接受率: {layer_acceptance}")
    
    # 方法4: 混合提议分布拒绝采样
    proposal_components = [
        {'pdf': lambda x: stats.norm.pdf(x, -3, 1), 'rv': lambda: np.random.normal(-3, 1), 'weight': 0.3},
        {'pdf': lambda x: stats.norm.pdf(x, 0, 1.5), 'rv': lambda: np.random.normal(0, 1.5), 'weight': 0.4},
        {'pdf': lambda x: stats.norm.pdf(x, 3, 1), 'rv': lambda: np.random.normal(3, 1), 'weight': 0.3},
    ]
    
    mixture_sampler = MixtureProposalSampler(target_pdf, proposal_components)
    
    start_time = time.time()
    mixture_samples, mixture_acceptance = mixture_sampler.sample(n_samples=2000)
    mixture_time = time.time() - start_time
    
    print(f"混合提议分布 - 接受率: {mixture_acceptance:.4f}, 时间: {mixture_time:.4f}s")
    
    # 可视化结果
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 绘制目标分布
    x_vals = np.linspace(-10, 10, 1000)
    target_vals = target_pdf(x_vals)
    
    # 子图1: 基础拒绝采样
    axes[0, 0].hist(basic_samples, bins=50, density=True, alpha=0.6, color='blue', label='采样分布')
    axes[0, 0].plot(x_vals, target_vals, 'r-', linewidth=2, label='目标分布')
    axes[0, 0].set_title(f'基础拒绝采样\n接受率: {basic_acceptance:.4f}')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    
    # 子图2: 自适应拒绝采样
    axes[0, 1].hist(adaptive_samples, bins=50, density=True, alpha=0.6, color='green', label='采样分布')
    axes[0, 1].plot(x_vals, target_vals, 'r-', linewidth=2, label='目标分布')
    axes[0, 1].set_title(f'自适应拒绝采样\n接受率: {adaptive_acceptance:.4f}')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)
    
    # 子图3: 分层拒绝采样
    axes[0, 2].hist(stratified_samples, bins=50, density=True, alpha=0.6, color='orange', label='采样分布')
    axes[0, 2].plot(x_vals, target_vals, 'r-', linewidth=2, label='目标分布')
    axes[0, 2].set_title(f'分层拒绝采样\n平均接受率: {np.mean(layer_acceptance):.4f}')
    axes[0, 2].legend()
    axes[0, 2].grid(True, alpha=0.3)
    
    # 子图4: 混合提议分布
    axes[1, 0].hist(mixture_samples, bins=50, density=True, alpha=0.6, color='purple', label='采样分布')
    axes[1, 0].plot(x_vals, target_vals, 'r-', linewidth=2, label='目标分布')
    axes[1, 0].set_title(f'混合提议分布\n接受率: {mixture_acceptance:.4f}')
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)
    
    # 子图5: 接受率比较
    methods = ['基础', '自适应', '分层', '混合']
    acceptances = [basic_acceptance, adaptive_acceptance, np.mean(layer_acceptance), mixture_acceptance]
    times = [basic_time, adaptive_time, stratified_time, mixture_time]
    
    x_pos = np.arange(len(methods))
    axes[1, 1].bar(x_pos, acceptances, color=['blue', 'green', 'orange', 'purple'])
    axes[1, 1].set_xticks(x_pos)
    axes[1, 1].set_xticklabels(methods)
    axes[1, 1].set_ylabel('接受率')
    axes[1, 1].set_title('各方法接受率比较')
    axes[1, 1].grid(True, alpha=0.3, axis='y')
    
    # 在柱状图上显示数值
    for i, v in enumerate(acceptances):
        axes[1, 1].text(i, v + 0.01, f'{v:.4f}', ha='center')
    
    # 子图6: 时间比较
    axes[1, 2].bar(x_pos, times, color=['blue', 'green', 'orange', 'purple'])
    axes[1, 2].set_xticks(x_pos)
    axes[1, 2].set_xticklabels(methods)
    axes[1, 2].set_ylabel('时间 (秒)')
    axes[1, 2].set_title('各方法时间消耗比较')
    axes[1, 2].grid(True, alpha=0.3, axis='y')
    
    # 在柱状图上显示数值
    for i, v in enumerate(times):
        axes[1, 2].text(i, v + 0.01, f'{v:.4f}', ha='center')
    
    plt.tight_layout()
    plt.show()
    
    # 返回结果供进一步分析
    results = {
        'basic': {'samples': basic_samples, 'acceptance': basic_acceptance, 'time': basic_time},
        'adaptive': {'samples': adaptive_samples, 'acceptance': adaptive_acceptance, 'time': adaptive_time},
        'stratified': {'samples': stratified_samples, 'acceptance': np.mean(layer_acceptance), 'time': stratified_time},
        'mixture': {'samples': mixture_samples, 'acceptance': mixture_acceptance, 'time': mixture_time}
    }
    
    return results

# ==================== 测试和演示 ====================
if __name__ == "__main__":
    print("拒绝采样算法优化与微调模拟")
    print("=" * 60)
    
    # 定义目标分布:混合高斯分布
    def target_pdf(x):
        # 两个高斯分布的混合
        return 0.7 * stats.norm.pdf(x, -2, 1) + 0.3 * stats.norm.pdf(x, 3, 1.5)
    
    # 比较不同采样方法
    results = compare_sampling_methods(target_pdf)

python模拟输出如下所示

虽然,由于中文字体原因,图例不是很清晰,但依然能看出代码较好的拟合了双峰高斯分布。

拒绝采样算法优化与微调模拟

============================================================

目标分布: 混合高斯分布

============================================================

基础拒绝采样 - 使用的M值: 5.7572

基础拒绝采样 - 接受率: 0.1761, 时间: 2.6821s

自适应拒绝采样 - 接受率: 0.3519, 时间: 4.3582s

分层拒绝采样 - 平均接受率: 0.0632, 时间: 10.5702s

各层接受率: [0.030725999631472267, 0.07261049423035053, 0.0863925282137761]

混合提议分布 - 接受率: 0.4773, 时间: 2.0706s

2.2 拒绝采样优化模拟总结

对于简单分布,基础拒绝采样通常足够;对于复杂多峰分布,自适应或分层方法更有效;混合提议分布可以更好地匹配目标分布的形状。

对于优化和模拟拒绝采样,需要注意以下几点:

  • 选择合适的提议分布以匹配目标分布

  • 找到尽可能小的M值以提高接受率

  • 对于不同区域使用不同的提议分布(分层/混合)

reference


相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab4 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab4 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼8 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS8 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区9 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈10 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang10 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx