逆变换采样 (inverse transform sampling) 的原理

本文深入探讨了逆变换采样方法,通过将随机变量的累积分布函数作为变换函数,实现对任意分布的有效采样。同时,揭示了逆变换采样与轮盘赌采样的等价性,并提供了Python代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文介绍了,对随机变量做函数变换 Y = f ( X ) Y = f(X) Y=f(X) 后的概率密度函数 (PDF) 之间的变化:
P Y ( y ) = P X ( f − 1 ( y ) ) ∣ d f − 1 ( y ) d y ∣ = P X ( x ) ∣ d x d y ∣ P_Y(y) = P_X(f^{-1}(y))\left|\frac{df^{-1}(y)}{dy}\right| = P_X(x) \left|\frac{dx}{dy}\right| PY(y)=PX(f1(y))dydf1(y)=PX(x)dydx

在这先声明一下记法:

  • P r ( ⋅ ) Pr(\cdot) Pr() 表示概率;
  • P X ( x ) P_X(x) PX(x) 表示随机变量 X X X 的概率密度函数;
  • F X ( x ) F_X(x) FX(x) 表示随机变量 X X X 的累计分布函数。

借助该引理,可以得到对任意分布采样的逆变换采样方法。

假设变量变换函数 Y = f ( X ) Y=f(X) Y=f(X) 正好是变量 X X X 的累积分布函数
f ( x ) = F X ( x ) = P r ( X ≤ x ) = ∫ − ∞ x P X ( s ) d s f(x) = F_X(x) = Pr(X \leq x) = \int_{-\infty}^x P_X(s) ds f(x)=FX(x)=Pr(Xx)=xPX(s)ds

那么变换后 Y = f ( X ) Y=f(X) Y=f(X) 的概率密度函数为:
P Y ( y ) = P X ( x ) ∣ d x d y ∣ = P X ( x ) ∣ f ′ ( x ) ∣ − 1 = P X ( x ) ∣ P X ( x ) ∣ = 1 P_Y(y) = P_X(x) \left|\frac{dx}{dy}\right| = P_X(x) |f'(x)|^{-1} = \frac{P_X(x)} {|P_X(x)|} = 1 PY(y)=PX(x)dydx=PX(x)f(x)1=PX(x)PX(x)=1
Y Y Y 的值域为 [ 0 , 1 ] [0,1] [0,1].

所以以累积分布函数作变换后得到的随机变量 Y Y Y 服从 [ 0 , 1 ] [0,1] [0,1] 上的均匀分布!!!

反之,如果先从 [ 0 , 1 ] [0,1] [0,1] 上的均匀分布上随机采样 y i y_i yi,再做某个累计分布函数的逆变换 x i = F X − 1 ( y i ) x_i = F_X^{-1}(y_i) xi=FX1(yi),得到的 x i x_i xi 的累积分布函数正好是 F X ( x ) F_X(x) FX(x) !!!

这就是实现了对任意分布 F X ( x ) F_X(x) FX(x) 采样。

其实逆变换采样和轮盘赌采样是一回事~

code

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline


class TreeSampling(object):
    """
    Sampling from a large population
    Construct in O(N) time
    Sample and update in O(log(N)) time
    """

    def __init__(self, dimension, weights=None):
        self.dimension = dimension
        self.layers = int(np.ceil(np.log2(dimension)))
        self.F = [np.array([])] * self.layers

        self.initialize(weights)

    def initialize(self, weights=None):
        """
        initialize F+ tree with uniform weights
        """
        # initialzie last layer with weights
        if weights is None:
            weight = 1.0 / self.dimension
            self.F[-1] = np.ones((self.dimension,)) * weight
        else:
            self.F[-1] = weights

        for l in range(self.layers - 2, -1, -1):
            length = int(np.ceil(self.F[l + 1].shape[0] / 2.0))
            self.F[l] = np.ones((length,))
            if len(self.F[l + 1]) % 2 != 0:
                self.F[l][:-1] = self.F[l + 1][:-1].reshape((-1, 2)).sum(axis=1)
                self.F[l][-1] = self.F[l + 1][-1]
            else:
                self.F[l] = self.F[l + 1].reshape((-1, 2)).sum(axis=1)

    def print_graph(self):
        if self.dimension > 1000:
            print("Are you crazy?")
            return
        for fl in self.F:
            for prob in fl:
                print(prob, end=" ")
            print("||")

    def total_weight(self):
        """
        return the total weight sum
        """
        return self.F[0][0] + self.F[0][1]

    def get_weight(self, indices):
        """
        return the weight of given indices
        """
        return self.F[-1][indices]

    def sample_batch(self, batch_size):
        """
        sample a batch without replacement
        """
        indices = np.zeros((batch_size,), dtype=np.int)
        weights = np.zeros((batch_size,), dtype=np.float)
        for i in range(batch_size):
            indices[i] = self.__sample()
            weights[i] = self.F[-1][indices[i]]
            self.__update(indices[i], 0)  # wighout replacement
        self.update_batch(indices, weights)  # resume their original weights
        return indices

    def update_batch(self, indices, probs):
        """
        update weights of a given batch
        """
        for i, p in zip(indices, probs):
            self.__update(i, p)

    def __sample(self):
        """
        sample a single node, in log(N) time
        """
        u = np.random.sample() * self.total_weight()
        i = 0
        for fl in self.F:
            # i_left = 2*i
            # i_right = 2*i +1
            if u > fl[2 * i] and fl.shape[0] >= 2 * (i + 1):  # then chose i_right
                u -= fl[2 * i]
                i = 2 * i + 1
            else:
                i = 2 * i
        return i

    def __update(self, idx, prob):
        """
        update weight of a single node, in log(N) time
        """
        delta = prob - self.F[-1][idx]

        for l in range(self.layers - 1, -1, -1):
            self.F[l][idx] += delta
            idx = idx // 2
N = 10000
idx = np.array([i for i in range(N)])
weights = np.square(np.sin(0.001*idx))
plt.plot(weights)
f = TreeSampling(N, weights)  

在这里插入图片描述

samples = []
for i in range(100):
    samples += list(f.sample_batch(batch_size=100))

_ = plt.hist(samples, bins=100)

在这里插入图片描述

相关知识点还可以参考概率积分变换(Probability Integral transform)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颹蕭蕭

白嫖?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值