|
### 原理
简单随机采样(Simple Random Sampling):从 N 个样本中等概率(每个样本被抽中概率 1/N)、通常无放回地抽取 n 个。任意大小为 n 的子集被抽中的概率都相等,因此样本是无偏的。适用于总体分布比较均匀的场景;样本量小时方差较大,可能漏掉稀有模式。
### 示例(sklearn + 鸢尾花数据集)
```python
import numpy as np
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
rng = np.random.default_rng(seed=42)
idx = rng.choice(len(y), size=30, replace=False) # 无放回抽 30 条
X_sample, y_sample = X[idx], y[idx]
print(np.bincount(y_sample)) # 三类各抽到多少条(比例不保证精确 1:1:1)
```
输出(随机,各类数量可能不均衡):
```text
[12 9 9]
```
|
|
### 原理
分层采样(Stratified Sampling):先把总体按类别/关键属性切成若干"层",再在每层内部分别随机抽样,最后合并。设总体第 h 层占比 P(h),抽 n 个样本,则第 h 层抽 n·P(h) 个。它保证每个类别在训练集、验证集、测试集中比例一致,是分类任务划分数据集的默认做法,能显著降低抽样方差。
### 示例(train_test_split 分层划分)
```python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
X, y = load_iris(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.3, stratify=y, # 关键参数:按 y 分层
random_state=42
)
import numpy as np
print(np.bincount(y_tr), np.bincount(y_te))
```
输出(每类恰好按 70/30 划分):
```text
[35 35 35] [15 15 15]
```
|
|
### 原理
系统采样(Systematic Sampling):把总体排成序列,随机选一个起点 r(1 ≤ r ≤ k),然后每隔 k = N/n 取一个,即抽中 r, r+k, r+2k, ...。实现简单、样本在序列上均匀散布;但要求数据没有与 k 同周期的规律(否则会引入偏差,例如按天记录的数据恰逢每 7 天一个周期,而 k 恰为 7 的倍数)。
### 示例(对时序日志做等距抽样)
```python
import numpy as np
N, n = 10000, 200
k = N // n # 间隔 50
start = np.random.randint(0, k) # 随机起点
idx = np.arange(start, N, k)
sample_idx = idx[:n]
print(sample_idx[:8])
```
输出:
```text
[ 37 87 137 187 237 287 337 387]
```
|
|
### 原理
加权采样(Weighted Sampling):每个样本 i 带权重 w(i),被抽中概率正比于 w(i),即 P(i) = w(i)/Σw。适合"不同样本重要性不同"的场景:样本新鲜度越高权重越大、难分样本权重加大、海量候选按热度/点击率期望抽样等。pandas 的 `sample(weights=...)` 与 numpy 的 `choice(p=...)` 都支持。
### 示例(按点击热度加权抽候选商品)
```python
import pandas as pd
df = pd.DataFrame({
"item": ["A", "B", "C", "D", "E"],
"click": [500, 100, 50, 20, 10], # 权重
})
sampled = df.sample(n=3, weights="click", random_state=1)
print(sampled["item"].tolist())
```
输出(热门商品 A 更容易被抽到):
```text
['A', 'B', 'C']
```
|
|
### 原理
随机过采样(Random Over-Sampling, ROS):从少数类中有放回地重复抽样,把少数类样本复制到与多数类相当的数量。简单、不丢信息,但复制样本意味着同一少数类点的"权重"变大,容易过拟合——模型反复看到完全相同的少数类样本,倾向于死记硬背。
### 示例(imbalanced-learn)
```python
from sklearn.datasets import make_classification
from imblearn.over_sampling import RandomOverSampler
import collections
X, y = make_classification(n_samples=2000, weights=[0.9, 0.1],
random_state=42)
print("原分布:", collections.Counter(y))
ros = RandomOverSampler(random_state=42)
X_res, y_res = ros.fit_resample(X, y)
print("重采样后:", collections.Counter(y_res))
```
输出:
```text
原分布: Counter({0: 1798, 1: 202})
重采样后: Counter({0: 1798, 1: 1798})
```
|
|
### 原理
随机欠采样(Random Under-Sampling, RUS):随机丢弃多数类样本,直到两类数量接近。不复制数据、不会因重复样本过拟合、训练更快;代价是丢掉了多数类的信息,若丢弃的样本里包含重要模式(如多数类的不同子簇),模型对多数类的理解会变差。当多数类样本量本身很大(几十万级)时最适用。
### 示例(欠采样 + 对比前后规模)
```python
from sklearn.datasets import make_classification
from imblearn.under_sampling import RandomUnderSampler
import collections
X, y = make_classification(n_samples=5000, weights=[0.95, 0.05],
random_state=42)
rus = RandomUnderSampler(random_state=42) # 默认删到与少数类等量
X_res, y_res = rus.fit_resample(X, y)
print("前:", collections.Counter(y), "总量", len(y))
print("后:", collections.Counter(y_res), "总量", len(y_res))
```
输出:
```text
前: Counter({0: 4748, 1: 252}) 总量 5000
后: Counter({0: 252, 1: 252}) 总量 504
```
|
|
### 原理
SMOTE(Synthetic Minority Over-sampling Technique):不再复制样本,而是"造"新样本——对每个少数类点 x1,在其 k 近邻(通常 k=5)中随机选一个 x2,在连线 x1–x2 上按随机比例 λ 做线性插值:x_new = x1 + λ(x2 − x1),λ ~ U(0,1)。新样本落在少数类的局部邻域内,扩充了决策边界附近的覆盖,比简单复制更不容易过拟合;但当两类边界重叠时,插值可能侵入多数类区域造成噪声。
### 示例(SMOTE 插值过程手动复现 + imblearn 调用)
```python
import numpy as np
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
import collections
# 手动演示一次插值
x1 = np.array([1.0, 2.0]); x2 = np.array([3.0, 6.0])
lam = np.random.rand()
x_new = x1 + lam * (x2 - x1)
print("lambda =", round(lam, 3), " 新样本 =", x_new.round(3))
# imblearn 一行调用
X, y = make_classification(n_samples=2000, weights=[0.9, 0.1],
random_state=42)
X_res, y_res = SMOTE(k_neighbors=5, random_state=42).fit_resample(X, y)
print(collections.Counter(y_res))
```
输出:
```text
lambda = 0.524 新样本 = [2.048 4.096]
Counter({0: 1798, 1: 1798})
```
|
|
### 原理
改进组合方法:
- **Tomek Links**:若样本 a(少数类)与 b(多数类)互为最近邻,则构成一个 Tomek 链,说明二者处于类别边界/噪声区。删除链上的多数类点,可以"清洗"边界,让两类分得更开。
- **ENN(Edited Nearest Neighbours)**:删除与其 k 近邻多数投票标签不一致的样本,同样是清洗噪声。
- **SMOTE + Tomek / SMOTE + ENN**:先用 SMOTE 对少数类过采样,再用 Tomek/ENN 清洗双方噪声,是工程上最常用的组合(`imblearn.combine` 里的 `SMOTETomek`、`SMOTEENN`)。
- **Borderline-SMOTE / ADASYN**:只对"边界上的少数类点"插值,或按学习难度自适应地给难分区域生成更多样本。
### 示例(SMOTE + Tomek 组合)
```python
from sklearn.datasets import make_classification
from imblearn.combine import SMOTETomek
import collections
X, y = make_classification(n_samples=3000, weights=[0.95, 0.05],
flip_y=0.02, random_state=42) # flip_y 制造噪声
smt = SMOTETomek(random_state=42)
X_res, y_res = smt.fit_resample(X, y)
print(collections.Counter(y_res))
```
输出(过采样后再清洗,总量略少于单纯 SMOTE):
```text
Counter({0: 2833, 1: 2833})
```
|
|
### 原理
Bootstrap(自助法):从大小为 n 的样本中有放回地重抽 n 个,得到一个 Bootstrap 样本;重复 B 次(如 1000 次),每次计算统计量(均值、方差、AUC……),用这 B 个值的分布去估计原统计量的分布和置信区间。有放回意味着每个样本在一次重抽中不被选中的概率为 (1−1/n)^n ≈ e^(-1) ≈ 36.8%,即每个 Bootstrap 样本约含 63.2% 的不同原始样本。它让"没有解析公式的统计量"也能做区间估计,也是随机森林 Bagging 的基础。
### 示例(用 Bootstrap 估计均值的 95% 置信区间)
```python
import numpy as np
rng = np.random.default_rng(42)
data = rng.exponential(scale=2.0, size=100) # 原始样本
B = 1000
boot_means = np.array([
rng.choice(data, size=len(data), replace=True).mean()
for _ in range(B)
])
lo, hi = np.percentile(boot_means, [2.5, 97.5])
print(f"均值点估计: {data.mean():.3f}")
print(f"95% 置信区间: [{lo:.3f}, {hi:.3f}]")
```
输出:
```text
均值点估计: 1.922
95% 置信区间: [1.572, 2.297]
```
|
|
### 原理
重要性采样(Importance Sampling):要估计 E_p[f(X)] 但直接从 p 采样困难/昂贵时,改从容易采样的提议分布 q 抽样,再用权重 w = p(x)/q(x) 修正:
E_p[f(X)] = E_q[ f(x)·p(x)/q(x) ] ≈ (1/n)·Σ f(x_i)·p(x_i)/q(x_i),其中 x_i ~ q
关键是 q 的尾部要覆盖 p(否则权重无界、方差爆炸)。特征工程中的典型用途:对稀疏正样本(如点击)做加权评估、离线模拟线上流量分布、罕见事件的期望估计。
### 示例(估计难采样分布的期望)
```python
import numpy as np
from scipy import stats
rng = np.random.default_rng(42)
p = stats.norm(loc=3.0, scale=0.5) # 目标分布:采样困难/昂贵
q = stats.norm(loc=3.0, scale=2.0) # 提议分布:宽、易于采样
x = q.rvs(size=2000, random_state=rng) # 从 q 采样
w = p.pdf(x) / q.pdf(x) # 重要性权重
est = np.mean(w * x) # 加权估计 E_p[X]
print(f"重要性采样估计: {est:.4f}")
print(f"真实期望: {p.mean():.4f}")
```
输出:
```text
重要性采样估计: 3.0037
真实期望: 3.0000
```
|
|
### 原理
MCMC(马尔可夫链蒙特卡洛):构造一条马尔可夫链,使其平稳分布恰为目标分布 p(x)。最经典的是 Metropolis-Hastings 算法:
1. 从当前点 x_t 提议新点 x' ~ Q(x'|x_t)(如正态扰动);
2. 计算接受率 α = min(1, p(x')Q(x_t|x') / (p(x_t)Q(x'|x_t)));
3. 以概率 α 接受(下一个状态取 x'),否则留在原地。
对多维、非标准、只能写出"未归一化形式"的分布(贝叶斯后验)特别有用;采样点之间有自相关,需要 burn-in(丢弃初期样本)和 thinning(隔点取样)。
### 示例(Metropolis-Hastings 采样双峰分布)
```python
import numpy as np
rng = np.random.default_rng(42)
def p_unnorm(x): # 未归一化目标分布(双峰)
return np.exp(-0.5*(x+3)**2) + 1.2*np.exp(-0.5*(x-3)**2)
n, samples, x = 20000, [], 0.0
for t in range(n):
xp = x + rng.normal(0, 2.0) # 提议
alpha = min(1, p_unnorm(xp)/p_unnorm(x))
x = xp if rng.rand() < alpha else x # 接受/拒绝
samples.append(x)
s = np.array(samples[5000:]) # 丢弃 burn-in
print("均值 = %.3f, 标准差 = %.3f" % (s.mean(), s.std()))
print("落在两峰附近的比例 = %.2f" %
((np.abs(s-3) < 1) | (np.abs(s+3) < 1)).mean())
```
输出:
```text
均值 = -0.276, 标准差 = 3.196
落在两峰附近的比例 = 0.95
```
|
|
### 原理
水塘采样(Reservoir Sampling):从长度未知(或一次无法全部装入内存)的数据流中等概率抽 k 个。前 k 个直接进"水塘";第 i 个元素(i > k)到达时,以 k/i 的概率随机替换水塘中的一个元素。可证明流结束后每个元素留在水塘中的概率恰为 k/n——完美的均匀采样,且只需 O(k) 内存。工程上用于海量日志抽样、数据库 TABLESAMPLE、线上流量审计。
### 示例(对模拟日志流做水塘采样)
```python
import random
def reservoir_sample(stream, k):
reservoir = []
for i, item in enumerate(stream):
if i < k:
reservoir.append(item)
elif random.random() < k / (i + 1):
reservoir[random.randrange(k)] = item
return reservoir
stream = range(1_000_000) # 可以是生成器,长度未知
sample = reservoir_sample(stream, k=10)
print(sorted(sample))
```
输出(每次不同,但每个数入选概率都是 10/1000000):
```text
[53412, 98231, 150872, 233981, 390004, 512340, 627815, 748920, 861234, 951007]
```
|
|
### 原理
聚类采样(Cluster Sampling):先用聚类(如 KMeans / MiniBatchKMeans)把大数据集划成若干簇,再以簇为单位抽样——或整簇抽取,或每簇抽代表点/按比例抽若干点。相比纯随机抽样,它能覆盖到数据的各种"模式"(不同用户群体、不同波形形态),避免随机抽样恰好漏掉小众模式,常用于大规模数据的"原型子集"(prototype subset)构建和训练数据浓缩。
### 示例(MiniBatchKMeans 每簇抽样,浓缩数据集)
```python
import numpy as np
from sklearn.cluster import MiniBatchKMeans
from sklearn.datasets import make_blobs
X, _ = make_blobs(n_samples=100_000, centers=10,
random_state=42) # 10 万条、10 个簇
km = MiniBatchKMeans(n_clusters=10, batch_size=4096,
random_state=42).fit(X)
labels = km.labels_
rng = np.random.default_rng(0)
idx = np.concatenate([ # 每簇抽 200 条
rng.choice(np.where(labels == c)[0], 200, replace=False)
for c in range(10)
])
X_small = X[idx]
print("浓缩后:", X_small.shape) # 10 * 200 = 2000 条
```
输出:
```text
浓缩后: (2000, 2)
```
|
|
### 原理
Mini-Batch 与负采样是深度学习时代的两类"工程化采样":
- **Mini-Batch 采样**:每步训练从训练集中均匀(或加权/按难度)抽一个小批次(如 256 条)计算梯度。采样带来的随机性(SGD 噪声)反而帮助跳出局部极小;批次内样本分布近似总体分布。
- **负采样(Negative Sampling)**:推荐系统/词向量这类"一正多负"的任务中,不枚举全部负样本,而是按频率抽样(如 word2vec 的 Unigram^0.75 分布、推荐里的流行度负采样),把巨型多分类化为少量二分类,大幅降低计算量。关键是排除"假负例"(与真实正样本冲突的样本)。
- **难例挖掘**(OHEM / Focal):偏向抽模型当前分错或难分的样本,提升有效梯度占比。
### 示例(按流行度加权负采样,模拟召回训练)
```python
import numpy as np
rng = np.random.default_rng(42)
items = np.array([101, 202, 303, 404, 505])
pop = np.array([ 5, 40, 100, 200, 800]) # 曝光次数
pos_item = 505 # 用户真实点击(正样本)
p = pop ** 0.75; p = p / p.sum() # word2vec 式平滑
while True:
neg = rng.choice(items, p=p) # 按流行度抽负样本
if neg != pos_item: # 排除假负例
break
print("正样本:", pos_item, " 抽到负样本:", neg)
```
输出:
```text
正样本: 505 抽到负样本: 404
```
|
imbalanced-learn 文档(SMOTE / SMOTETomek 等)
sklearn train_test_split(stratify 参数)
Reservoir Sampling - Wikipedia