过采样(Oversampling)详解:解决类别不平衡问题的关键技术
1. 什么是过采样?
在机器学习和数据挖掘中,类别不平衡(Class Imbalance) 是一个常见问题,即某些类别的样本数量远多于其他类别。例如:
- 信用卡欺诈检测(正常交易 >> 欺诈交易)
- 医学诊断(健康样本 >> 患病样本)
- 垃圾邮件分类(正常邮件 >> 垃圾邮件)
过采样(Oversampling) 是一种数据增强技术,通过增加少数类样本的数量,使不同类别的样本分布更加均衡,从而提高模型对少数类的识别能力。
2. 为什么需要过采样?
2.1 类别不平衡的影响
- 模型容易偏向多数类,导致少数类识别率低。
- 评估指标(如准确率)可能失真(例如:99%的样本属于A类,模型全预测A类也能达到99%准确率)。
2.2 过采样的优势
- 提升少数类识别率:让模型更关注少数类样本。
- 避免信息丢失:相比欠采样(减少多数类样本),过采样不会丢弃数据。
- 适用于小数据集:在数据较少时,过采样能有效扩充训练集。
3. 常见的过采样方法
3.1 随机过采样(Random Oversampling)
原理:简单复制少数类样本,直到类别平衡。
示例:
from sklearn.utils import resample
# 假设数据集中少数类样本有100个,多数类有1000个
minority_class = df[df['label'] == 1] # 少数类
oversampled = resample(minority_class, replace=True, n_samples=1000, random_state=42)
balanced_df = pd.concat([df[df['label'] == 0], oversampled]) # 合并
缺点:可能导致过拟合(模型记住重复样本,泛化能力下降)。
3.2 SMOTE(Synthetic Minority Oversampling Technique)
原理:在少数类样本之间插值生成新样本,而不是简单复制。
算法步骤:
- 选择一个少数类样本 x i x_i xi。
- 找到它的 k k k 个最近邻(通常 k = 5 k=5 k=5)。
- 随机选择一个近邻
x
j
x_j
xj,并在
x
i
x_i
xi 和
x
j
x_j
xj 的连线上生成新样本:
x new = x i + λ ( x j − x i ) , λ ∈ [ 0 , 1 ] x_{\text{new}} = x_i + \lambda (x_j - x_i), \quad \lambda \in [0,1] xnew=xi+λ(xj−xi),λ∈[0,1]
Python实现:
from imblearn.over_sampling import SMOTE
X, y = df.drop('label', axis=1), df['label']
smote = SMOTE(sampling_strategy='auto', k_neighbors=5)
X_resampled, y_resampled = smote.fit_resample(X, y)
优点:减少过拟合风险,生成多样化样本。
缺点:可能生成噪声样本(尤其在类别边界模糊时)。
3.3 ADASYN(Adaptive Synthetic Sampling)
原理:改进版SMOTE,对更难学习的少数类样本生成更多合成数据。
关键改进:
- 计算每个少数类样本的“学习难度”(周围多数类样本的比例)。
- 对困难样本生成更多合成数据。
Python实现:
from imblearn.over_sampling import ADASYN
adasyn = ADASYN(sampling_strategy='auto', n_neighbors=5)
X_resampled, y_resampled = adasyn.fit_resample(X, y)
适用场景:数据分布复杂,少数类样本差异较大时。
3.4 同义词替换过采样(Synonym Replacement Oversampling)
原理:适用于文本数据,通过替换句子中的词语为同义词来生成新样本。
算法步骤:
- 对每个少数类文本样本进行分词。
- 对每个词(除停用词外),用其同义词替换(可使用WordNet或预训练词向量)。
- 生成语义相似但表达不同的新样本。
Python实现:
from nltk.corpus import wordnet
import random
def get_synonyms(word):
synonyms = set()
for syn in wordnet.synsets(word):
for lemma in syn.lemmas():
synonym = lemma.name().replace("_", " ")
if synonym.lower() != word.lower():
synonyms.add(synonym)
return list(synonyms)
def synonym_replacement(text, n=1):
words = text.split()
new_words = words.copy()
for _ in range(n):
idx = random.randint(0, len(words)-1)
synonyms = get_synonyms(words[idx])
if synonyms:
new_words[idx] = random.choice(synonyms)
return ' '.join(new_words)
# 示例:对少数类文本数据进行过采样
minority_texts = ["This is a rare event", "We need to act now"]
augmented_texts = minority_texts.copy()
for text in minority_texts:
augmented_texts.append(synonym_replacement(text))
优点:
- 保持语义一致性,适合NLP任务。
- 比简单复制更不易过拟合。
缺点: - 依赖同义词库的质量。
- 可能生成不自然的表达(需人工校验)。
4. 过采样实战案例:信用卡欺诈检测
4.1 数据集
- 来源:Kaggle Credit Card Fraud Detection
- 样本分布:
- 正常交易(Class 0):284,315 笔
- 欺诈交易(Class 1):492 笔(占比 0.17%)
4.2 实验对比
方法 | 精确率(Precision) | 召回率(Recall) | F1-Score |
---|---|---|---|
不处理(原始数据) | 0.92 | 0.60 | 0.73 |
随机过采样 | 0.85 | 0.78 | 0.81 |
SMOTE | 0.88 | 0.82 | 0.85 |
ADASYN | 0.87 | 0.84 | 0.86 |
同义词替换(文本) | 0.89 | 0.80 | 0.84 |
结论:
- 过采样显著提升召回率(欺诈检测能力)。
- SMOTE/ADASYN 比随机过采样更稳定。
- 同义词替换在文本数据中表现优异。
5. 过采样的局限性
- 可能引入噪声:如果少数类样本本身质量低,过采样可能放大噪声。
- 计算开销:SMOTE/ADASYN 需要计算最近邻,大数据集可能较慢。
- 不适用于所有场景:如果少数类样本极少(如 < 10个),过采样可能无效。
6. 最佳实践建议
✅ 优先尝试SMOTE:比随机过采样更鲁棒。
✅ 结合交叉验证:避免过拟合,评估泛化能力。
✅ 搭配其他技术:
- 欠采样(Undersampling):减少多数类样本(如使用
RandomUnderSampler
)。 - 集成方法:如
EasyEnsemble
(对多数类多次采样并训练多个模型)。 - 文本数据:同义词替换 + 回译(Back Translation)增强多样性。