采样方法与技术综合教程
目录
01 采样的作用
在统计学和计算机模拟中,采样(Sampling)是指从某个目标总体或概率分布中抽取样本的过程。采样在许多领域发挥着关键作用:
-
模拟与蒙特卡罗方法
在蒙特卡罗方法中,我们通过对随机变量反复采样来近似计算期望值或积分。例如,若要求高维积分或复杂分布的期望,直接解析计算往往不可行,但通过对分布进行随机采样并取样本平均可以得到近似解。随着样本数量增加,蒙特卡罗估计通常收敛于真值。 -
算法与模型训练
机器学习中广泛使用采样技术来处理大规模数据和不均衡数据。例如,随机梯度下降法(SGD)在每次迭代中随机采样一小批训练样本(mini-batch)以近似计算梯度;随机森林模型通过自助采样(bootstrap)为每棵决策树生成不同的子集,增加模型多样性。 -
统计调查与推断
在统计学中,采样用于从总体中抽取部分个体进行调查,以此推断总体属性。这包括简单随机抽样、分层抽样、聚类抽样等方法,旨在以较小成本获取有代表性的数据。例如在人口调查中,通过适当的采样设计,可以用样本的统计量去估计整体人口参数,使研究更高效且减少偏差。
总而言之,采样的作用在于以随机化方式选取数据或生成样本,从而在可接受的计算成本下实现对复杂问题的近似。本教程将从基础的均匀随机数生成开始,深入讲解常见采样技术及其在机器学习和统计中的应用场景。
02 均匀分布随机数
均匀分布随机数是大多数采样方法的基础。均匀分布通常指在某区间内每个值出现概率相等的分布。例如,在区间 [ 0 , 1 ] [0,1] [0,1] 上的连续均匀分布记作 U ( 0 , 1 ) U(0,1) U(0,1)。
实现均匀分布采样通常使用伪随机数发生器(PRNG)。现代编程库(如 NumPy 或 PyTorch)内部实现了高质量的 PRNG(例如梅森旋转算法),能够生成近似独立同分布且均匀分布的伪随机序列,统计特性上非常接近真正的均匀分布。
在 PyTorch 中,可以使用 torch.rand()
生成 [ 0 , 1 ) [0,1) [0,1) 区间上的均匀随机数。以下代码示例演示如何生成均匀分布样本并进行基本验证:
import torch
# 生成10个 [0, 1) 区间的均匀分布随机数
samples = torch.rand(10)
print("Samples:", samples)
# 计算样本的平均值,理论上均值应接近0.5
mean_val = samples.mean().item()
print("Mean:", mean_val)
对于离散均匀分布(如在 { 1 , … , N } \{1,\dots,N\} {
1,…,N} 之间等可能),可使用 torch.randint(low, high, size)
来生成指定范围的整数均匀随机数。无论连续还是离散,均匀随机数是构建其它分布采样的基石:许多高级方法通过将均匀样本经过某种变换得到目标分布的样本。
03 常见的采样方法
3.1 反向变换采样(Inverse Transform Sampling)
原理基于概率积分变换:若 U ∼ U ( 0 , 1 ) U \sim U(0,1) U∼U(0,1) 为均匀分布随机数, F − 1 F^{-1} F−1 为目标分布的反累积分布函数 (Inverse CDF),则
X = F − 1 ( U ) X = F^{-1}(U) X=F−1(U)
服从目标分布。例如,要从参数 λ \lambda λ 的指数分布采样,指数分布的 CDF 为
F ( x ) = 1 − e − λ x , x ≥ 0. F(x) = 1 - e^{-\lambda x}, \quad x\ge0. F(x)=1−e−λx,x≥0.
给定 U ∼ U ( 0 , 1 ) U \sim U(0,1) U∼U(0,1),则
X = F − 1 ( U ) = − 1 λ ln ( 1 − U ) . X = F^{-1}(U) = -\frac{1}{\lambda}\ln\bigl(1-U\bigr). X=F−1(U)=−λ1ln(1−U).
由于 1 − U 1-U 1−U 与 U U U 同分布(也服从 U ( 0 , 1 ) U(0,1) U(0,1)),可简化为
X = − 1 λ ln ( U ) . X = -\frac{1}{\lambda}\ln(U). X=−λ1ln(U).
以下为 PyTorch 实现示例:
import torch
import math
# 参数 lambda
lam = 2.0
# 采样 N 个指数分布样本
N = 10000
U = torch.rand(N) # 均匀随机数 U
X = - (1.0/lam) * torch.log(U) # 反向变换公式
# 验证:计算采样值的均值和方差(理论值为1/lam和1/lam^2)
print("Sample mean:", X.mean().item())
print("Sample var:", X.var().item())
优点:概念清晰、易实现。
缺点:很多分布的 CDF 无解析反函数,或计算代价高。
适用场景:单变量分布且能求出反函数,如指数分布、卡方分布等。
3.2 接受-拒绝采样(Rejection Sampling)
当直接从目标分布采样困难时,可使用接受-拒绝法。其核心思想:
- 选择易采样的提议分布 g ( x ) g(x) g(x) 及常数 M M M,满足 ∀ x : f ( x ) ≤ M g ( x ) \forall x: f(x) \le M g(x) ∀x:f(x)≤Mg(x).
- 重复:
- 从 g ( x ) g(x) g(x) 采样候选 x ∗ x^* x∗.
- 从 U ( 0 , 1 ) U(0,1) U(0,1) 采样阈值 u u u.
- 若 u < f ( x ∗ ) M g ( x ∗ ) u < \frac{f(x^*)}{M g(x^*)} u<Mg(x∗)f(x∗),则接受 x ∗ x^* x∗;否则拒绝。
下面以 f ( x ) = 1.2 − x 4 f(x)=1.2 - x^4 f(x)=1.2−x4 (定义在 [ 0 , 1 ] [0,1] [0,1])为示例,我们选择提议分布为 Uniform ( 0 , 1 ) \text{Uniform}(0,1) Uniform(0,1),并取 M = 1.2 M=1.2 M=1.2:
import torch
def f(x):
return 1.2 - x**4 # 在 [0,1] 上最大值为1.2
def sample_via_rejection(n):
samples = []
count = 0
while len(samples) < n:
count += 1
x_candidate = torch.rand(1).item() # U(0,1)
y = torch.rand(1).item() * 1.2 # [0,1.2)
if y < f(x_candidate):
samples.append(x_candidate)
acceptance_rate = len(samples) / count
return torch.tensor(samples), acceptance_rate
samples, acc_rate = sample_via_rejection(10000)
print("Acceptance rate:", acc_rate)
print("First 10 samples:", samples[:10])
优点:适用于任意分布(只需能比较相对大小),算法简单直观。
缺点:若 M M M 很大或维度高,接受率会降低,效率较低。
适用场景:低维、分布形状复杂且无法直接采样时,或作为其他算法辅助方法。
3.3 重要性采样(Importance Sampling)
重要性采样主要用于估计期望值,而不一定要生成真正符合目标分布的独立样本。它通过从另一个容易采样的分布 g ( x ) g(x) g(x) 中生成样本,并给样本乘以权重 ω i = f ( x i ) g ( x i ) \omega_i = \frac{f(x_i)}{g(x_i)} ωi=g(xi)f(xi) 来校正偏差,从而在目标分布下估计积分或期望。这里不做具体代码演示,但在统计推断和机器学习中应用广泛(如估计不易计算的归一化常数、近似积分等)。
3.4 分层采样(Stratified Sampling)
将总体或区间划分为若干子部分(层),然后在每层内进行随机采样,可减少方差并保证覆盖度。在深度学习中,如果对类别进行分层采样,能确保每个类别有足够的样本进入训练小批量。
优点:采样更均匀、更具代表性。
缺点:需要先验信息来划分层;若层数过多或数据量大,管理复杂度上升。
04 高斯分布的采样
Box-Muller变换
Box-Muller方法可以从两组 [ 0 , 1 ) [0,1) [0,1) 均匀随机数生成两组标准正态 (均值0、方差1) 随机数。核心公式为:
Z 0 = − 2 ln U 1 cos ( 2 π U 2 ) , Z 1 = − 2 ln U 1 sin ( 2 π U 2 ) . \begin{aligned} Z_0 &= \sqrt{-2 \ln U_1} \,\cos\bigl(2\pi U_2\bigr),\\ Z_1 &= \sqrt{-2 \ln U_1} \,\sin\bigl(2\pi U_2\bigr). \end{aligned} Z0Z1=−2lnU1cos(2πU2),=−2lnU1sin(2πU2).
在 PyTorch 中实现如下:
import torch
def sample_normal_boxmuller(n):
U1 = torch.rand(n)
U2 = torch.rand(n)
R = torch.sqrt(-2 * torch.log(U1))
Theta = 2 * torch.pi * U2
Z0 = R * torch.cos(Theta)
Z1 = R * torch.sin(Theta)
return Z0, Z1
Z0, Z1 = sample_normal_boxmuller(10000)
print("Z0 mean = %.3f, var = %.3f" % (Z0.mean().item(), Z0.var().item()))
print("Z1 mean = %.3f, var = %.3f" % (Z1.mean().item(), Z1.var().item()))
理论上,标准正态分布均值应为0、方差为1,运行结果应接近该值。对于一般正态分布 N ( μ , σ 2 ) N(\mu,\sigma^2) N(μ,σ2),可将上述结果做线性变换 σ Z + μ \sigma Z + \mu σZ+μ 即可。
05 马尔科夫蒙特卡洛采样法(MCMC)
当目标分布复杂或维度高且无法直接采样时,马尔科夫链蒙特卡罗 (MCMC) 提供了可行途径。这里着重介绍Metropolis-Hastings算法。
Metropolis-Hastings算法
- 初始化:给定初始状态 x 0 x_0 x0.
- 迭代:对当前状态 x t x_t xt:
- 从提议分布 q ( x ′ ∣ x t ) q(x' \mid x_t) q(x′∣xt) 抽取候选 x ′ x' x′.
- 计算接受概率
α = min ( 1 , π ( x ′ ) q ( x t ∣ x ′ ) π ( x t ) q ( x ′ ∣ x t ) ) . \alpha = \min\!\Bigl(1,\; \frac{\pi(x')\,q(x_t \mid x')}{\pi(x_t)\,q(x' \mid x_t)}\Bigr). α=min(1,π(xt)q(x′∣xt)π(x′)q(xt∣x′)).
若提议分布对称(如高斯),则
α = min ( 1 , π ( x ′ ) π ( x t ) ) . \alpha = \min\bigl(1,\; \frac{\pi(x')}{\pi(x_t)}\bigr). α=min(1,π(xt)π(x′)). - 从 U ( 0 , 1 ) U(0,1) U(0,1) 产生 u u u. 若 u < α u < \alpha u<α,则接受 x ′ x' x′, 否则拒绝(保持 x t + 1 = x t x_{t+1} = x_t xt+1=xt)。
- 烧入 (burn-in):前若干步样本偏离目标分布,需丢弃;后续样本近似服从目标分布。
下面用 PyTorch 举例:目标分布设为
π ( x ) ∝ exp ( − ( x − 2 ) 2 2 ) + exp ( − ( x + 2 ) 2 2 ) , \pi(x)\propto \exp\Bigl(-\frac{(x-2)^2}{2}\Bigr) + \exp\Bigl(-\frac{(x+2)^2}{2}\Bigr), π(x)∝exp(−2(x−2)2)+exp(−2(x+2)2),
这是一个双峰分布。
import torch
def pi_unnormalized(x):
return torch.exp(-0.5*(x-2)**2) + torch.exp(-0.5*(x+2)**2)
def metropolis_hastings(pi_fn, steps=10000, proposal_std=1.0):
samples = []
x = torch.tensor(0.0) # 初始状态
for t in range(steps):
x_candidate = x + torch.randn(1)*proposal_std
alpha = min(1.0, (pi_fn(x_candidate)/pi_fn(x)).item())
u = torch.rand(1).item()
if u < alpha:
x = x_candidate
samples.append(x.item())
return torch.tensor(samples)
samples = metropolis_hastings(pi_unnormalized, 10000, 1.0)
print("First 10 samples:", samples[:10].tolist())
print("Mean of samples:", samples.mean().item())
得到的样本均值应接近0,并在 x ≈ − 2 x \approx -2 x≈−2 和 x ≈ 2 x \approx 2 x≈2 附近出现主要峰值。
优点:适用于高维或复杂分布(仅需相对密度),在贝叶斯后验推断中非常常见。
缺点:样本间相关性高,需要长链确保混合,提议分布设计与收敛诊断也较复杂。
06 贝叶斯网络的采样
贝叶斯网络(Bayesian Network) 是一种有向无环图 (DAG) 模型,用于表示变量间的条件依赖关系。最常用的采样方式是前向采样 (ancestral sampling):
- 按拓扑排序依次遍历网络节点;
- 若节点无父节点,则按其先验分布采样;若节点有父节点,则按条件概率表 (CPT) 采样;
- 重复以上过程生成任意多的独立样本。
以下是一个“多云 (Cloudy)、喷水 (Sprinkler)、下雨 (Rain)、草坪湿 (WetGrass)”的经典示例:
import torch
# 定义条件概率
P_Cloudy = 0.5
P_S_given_C = {
True: 0.1, False: 0.5}
P_R_given_C = {
True: 0.8, False: 0.2}
P_W_given_SR = {
(True, True): 0.99,
(True, False): 0.90,
(False, True): 0.90,
(False, False): 0.00
}
def sample_from_bn():
# 采样 Cloudy
cloudy = torch.rand(1).item() < P_Cloudy
# 采样 Sprinkler
sprinkler = torch.rand(1).item() < P_S_given_C[cloudy]
# 采样 Rain
rain = torch.rand(1).item() < P_R_given_C[cloudy]
# 采样 WetGrass
wet_grass = torch.rand(1).item() < P_W_given_SR[(sprinkler, rain)]
return cloudy, sprinkler, rain, wet_grass
# 生成一个示例样本
sample = sample_from_bn()
print("Sample (Cloudy, Sprinkler, Rain, WetGrass):", sample)
多次调用 sample_from_bn()
会产生大量符合该贝叶斯网络联合分布的独立样本。如果有观测证据(观测到部分节点),可使用拒绝采样、似然加权采样或吉布斯采样等方式提高效率。
07 不均衡样本集的重采样
在真实世界的数据集中,常见类别不均衡问题:多数类远多于少数类,模型可能偏向多数类。为改善少数类的学习,常用以下重采样策略:
-
过采样 (Oversampling)
- 简单重复少数类样本
- 使用 SMOTE 等方法插值合成新的少数类样本
优点:不会丢失已有信息;能平衡类别。
缺点:简单重复易导致过拟合;要谨慎合成数据避免噪声。
-
欠采样 (Undersampling)
- 随机移除部分多数类样本
优点:易于实现,减少数据量加快训练
缺点:丢失潜在有用信息,模型对多数类的学习可能不足
- 随机移除部分多数类样本
在 PyTorch 中,可使用 WeightedRandomSampler
实现过采样。以下为简单示例:
import torch
from torch.utils.data import WeightedRandomSampler
# 模拟不均衡标签张量
labels = torch.tensor([0,0,0,0,0,0,0,0, 1,1], dtype=torch.long)
print("Class counts:", torch.bincount(labels).tolist()) # [8, 2]
# 为每个类分配权重:类别越少,权重越大
class_counts = torch.bincount(labels)
weight_per_class = 1.0 / class_counts.float()
weights = weight_per_class[labels]
sampler = WeightedRandomSampler(weights, num_samples=len(labels), replacement=True)
indices = list(sampler)
print("Sampled indices:", indices)
print("Sampled labels:", labels[indices].tolist())
可见,少数类会被采样到的概率增大,从而在训练中被更多关注。对于欠采样,可直接随机丢弃多数类部分样本,例如:
idx_class0 = (labels == 0).nonzero(as_tuple=True)[0]
idx_class1 = (labels == 1).nonzero(as_tuple=True)[0]
perm = torch.randperm(len(idx_class0))
undersampled_idx0 = idx_class0[perm[:len(idx_class1)]]
undersampled_idx = torch.cat([undersampled_idx0, idx_class1])
print("Undersampled labels:", labels[undersampled_idx].tolist())
08 优化方法与未来研究方向
8.1 各类采样方法优缺点总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
均匀采样 | 简单快速,基础生成器 | 只能产生等可能分布 | 构造其他分布的中间步骤 |
反向变换采样 | 理论直观,易于实现 | 要求能求 CDF 反函数,难扩展到复杂分布 | 低维、CDF 可求反函数 |
接受-拒绝采样 | 通用性强,简单易懂 | 高维时接受率低;需精心选择提议分布 | 低维或中等维度下的通用方法 |
Box-Muller 高斯采样 | 算法直观,能精确生成正态分布 | 使用对数和三角函数,某些环境下略慢 | 生成正态噪声,模型初始化 |
MCMC (Metropolis 等) | 能处理高维复杂分布,可只需未归一化密度 | 样本相关性高,需要混合与收敛诊断 | 贝叶斯后验推断,高维复杂模型 |
贝叶斯网络采样 | 利用网络条件独立结构快速生成联合样本 | 有观测证据时效率下降,需要改进如吉布斯采样 | 模拟数据生成,或用来验证网络结构和参数 |
不均衡重采样 (过/欠) | 平衡类别分布,改进少数类学习 | 过采样易过拟合,欠采样会丢信息 | 不均衡分类任务的数据预处理 |
8.2 挑战与研究方向
-
高维高效采样
- 经典 MCMC 在高维时面临维数灾难。
- Hamiltonian Monte Carlo (HMC)、Langevin 动力学等利用梯度信息可更高效地探索高维目标分布。
-
自适应与智能采样
- 自适应 MCMC 动态调整提议分布协方差矩阵,提高接受率和混合速度。
- 序贯蒙特卡罗 (SMC) 通过一系列过渡分布逐渐逼近目标分布。
-
结合生成模型
- GAN、VAE、扩散模型等可学习复杂真实数据分布,采样迅速且质量高。
- 未来或将生成模型与 MCMC 结合,用生成器做提议分布以提升采样效率。
-
非随机采样(准蒙特卡罗)
- 使用低差异序列 (Halton、Sobol 等) 降低积分估计方差。
- 在高精度数值积分、渲染领域有广泛应用。
-
不均衡数据对策
- 采样之外,成本敏感学习、数据增强、元学习等可进一步改善少数类性能。
- 也可能出现更智能的动态采样算法,结合主动学习理念根据模型表现来调整过采样策略。
-
硬件与并行优化
- 新硬件 (GPU/TPU/FPGA) 能极大加速随机数生成与并行计算。
- 量子随机数与量子采样算法或将带来新的机遇,但仍属前沿研究。
总结
本教程从均匀分布随机数出发,系统介绍了反向变换采样、接受-拒绝采样、重要性采样、分层采样等常见方法,以及在实际应用中常用的Box-Muller 高斯采样与MCMC (Metropolis-Hastings) 等高级算法。我们还介绍了贝叶斯网络前向采样和在不均衡数据分类任务中广泛使用的重采样技术。最后,对各采样方法的优缺点、适用场景和前沿挑战进行了总结。
采样在统计推断、机器学习与数据分析中扮演着关键角色:从大规模数据中的随机选取到复杂高维分布的蒙特卡罗近似,都离不开合适的采样策略。随着数据和模型规模的不断扩大,采样技术仍将持续演进,以在效率、准确度与可扩展性之间取得平衡。希望本教程能帮助你更好地理解和应用这些采样技巧。