模型在验证集上准确率高达95%,上线后却在少数类样本上频繁出错,这是许多算法工程师都遇到过的困惑。问题的根源往往不在模型结构或超参数,而在训练数据本身。当数据集中某些类别的样本数量远多于其他类别,或者数据采集方式导致分布偏离真实场景时,就产生了所谓的数据集偏差。本文将围绕类别不均衡和采样偏差两大问题,详细讲解平衡采样与数据增强的具体实践方法。

数据集偏差从哪里来
数据集偏差主要分为两类。第一类是类别不均衡,例如在欺诈检测中,正常交易可能占99%以上,欺诈样本不足1%;在医学影像诊断中,患病样本也远少于健康样本。这种情况下,模型只要把所有样本都预测为多数类,就能获得很高的准确率,但对实际业务毫无价值。
第二类是采样偏差,即训练数据的分布与真实应用场景不一致。比如用互联网上爬取的人脸图片训练模型,却要部署在监控摄像头场景中,光照、角度、分辨率的差异会让模型性能大幅下降。这类问题不是简单的数量问题,而是数据代表性的问题,需要通过更贴近真实场景的数据采集或增强手段来缓解。
在实际项目中,两类偏差常常同时存在。诊断时可以先统计各类别样本数量,计算不均衡比例,再分析训练集与测试集之间的分布差异。只有先定位偏差类型,才能选择合适的处理策略。
平衡采样的常用方法
平衡采样通过调整各类别被抽到的概率,让模型在每个批次中看到相对均衡的样本分布。常见做法包括过采样、欠采样、SMOTE以及加权随机采样。
过采样是对少数类样本进行重复采样,简单直接,但容易导致少数类样本过拟合。欠采样则丢弃部分多数类样本,适合数据量非常大的场景,但可能损失有用信息。SMOTE通过在少数类样本之间插值生成合成样本,缓解了简单重复带来的过拟合问题。以下是使用Python的imbalanced-learn库实现这几种方法的示例:
from imblearn.over_sampling import SMOTE, RandomOverSampler from imblearn.under_sampling import RandomUnderSampler # 过采样:随机复制少数类样本 ros = RandomOverSampler(random_state=42) X_ros, y_ros = ros.fit_resample(X_train, y_train) # 欠采样:随机丢弃多数类样本 rus = RandomUnderSampler(random_state=42) X_rus, y_rus = rus.fit_resample(X_train, y_train) # SMOTE:在特征空间插值生成合成样本 smote = SMOTE(random_state=42, k_neighbors=5) X_smo, y_smo = smote.fit_resample(X_train, y_train)
在深度学习训练中,更常见的做法是使用加权随机采样器,让少数类样本在一个epoch内被更频繁地抽到,同时保持数据集本身不变。以PyTorch为例:
import torch
from torch.utils.data import WeightedRandomSampler
# 根据类别频率计算每个样本的权重
class_counts = [1000, 50, 200] # 三个类别的样本数
weights = [1.0 / class_counts[label] for label in labels]
sampler = WeightedRandomSampler(
weights=weights,
num_samples=len(labels),
replacement=True
)
# 将sampler传入DataLoader即可
# dataloader = DataLoader(dataset, sampler=sampler, batch_size=32)
需要注意的是,SMOTE这类基于特征插值的方法只适用于结构化数据或低维特征,直接在高维图像像素上插值意义不大,图像任务应优先考虑加权采样或数据增强。
数据增强如何扩充少数类样本
数据增强通过已有的变换规则生成样本变体,本质上是在不增加采集成本的前提下扩充数据分布。对于图像任务,常用的基础增强包括随机裁剪、水平翻转、颜色抖动、旋转等。关键技巧是针对少数类施加更强的增强强度,让模型在少数类上看到更多样的样本变化。
import torchvision.transforms as transforms
# 少数类使用更强的增强策略
minority_transform = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.6, 1.0)),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3),
transforms.RandomRotation(20),
transforms.ToTensor(),
])
除了基础几何与色彩变换,Mixup和CutMix是近年来广泛使用的两种高级增强手段。Mixup将两张图片按比例线性混合,标签也按同样比例混合;CutMix则把一张图片的某个区域裁剪后粘贴到另一张图片上。这两种方法不仅能缓解类别不均衡,还能提升模型的泛化能力和校准程度。
import numpy as np
def cutmix(data, targets, alpha=1.0):
indices = torch.randperm(data.size(0))
shuffled_targets = targets[indices]
lam = np.random.beta(alpha, alpha)
# 随机生成裁剪区域
W, H = data.size(2), data.size(3)
cut_ratio = np.sqrt(1.0 - lam)
cut_w, cut_h = int(W * cut_ratio), int(H * cut_ratio)
cx, cy = np.random.randint(W), np.random.randint(H)
x1, y1 = np.clip(cx - cut_w // 2, 0, W), np.clip(cy - cut_h // 2, 0, H)
x2, y2 = np.clip(cx + cut_w // 2, 0, W), np.clip(cy + cut_h // 2, 0, H)
data[:, :, x1:x2, y1:y2] = data[indices, :, x1:x2, y1:y2]
# 按面积比例调整标签权重
lam = 1 - (x2 - x1) * (y2 - y1) / (W * H)
return data, targets, shuffled_targets, lam
对于文本任务,可以使用同义词替换、回译(翻译成另一种语言再翻回来)、随机遮盖等增强方式;音频任务则可以施加变速、加噪、音调变换。数据增强的核心原则是:增强后的样本必须保持语义不变且贴近真实场景中的变化,否则会引入噪声反而伤害模型。
方案对比与组合策略
各种方法并非互斥,实践中通常组合使用。下表对常见方案的特点做一对比:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 过采样 | 实现简单,不丢失信息 | 少数类易过拟合 | 少数类样本量尚可时 |
| 欠采样 | 训练快,分布均衡 | 可能丢弃有价值样本 | 数据量极大的场景 |
| 加权采样 | 无需修改数据,灵活可控 | 权重设置需要调参 | 深度学习批式训练 |
| 基础数据增强 | 提升泛化性,成本低 | 对极端不均衡帮助有限 | 几乎所有视觉任务 |
| Mixup/CutMix | 同时改善泛化与校准 | 实现与调试较复杂 | 分类任务进阶优化 |
推荐的实践路线是:先统计偏差情况,若不均衡比例在10倍以内,可优先使用加权采样加基础增强;若比例超过100倍,再考虑结合过采样或SMOTE;同时配合类别加权损失函数(如Focal Loss或带权重的交叉熵),从损失层面进一步抑制多数类主导梯度的问题。
最后要强调评估方式的重要性。类别不均衡场景下不应只看准确率,而应关注宏平均F1、各类别召回率、AUC等指标,并在与真实场景分布一致的测试集上验证。只有采样策略、增强手段、损失函数和评估指标四者配合到位,才能真正让模型摆脱数据偏差的束缚,在实际业务中稳定发挥作用。