在机器学习的探索过程中,我们经常遇到不平衡数据集这一棘手挑战,即一个类别的样本数量明显超过另一个类别。这种不平衡往往会导致模型的预测产生偏差。为了解决这一问题,我们主要有两种核心技术:上采样和下采样。
- 上采样: 增加少数类中的样本数量。
- 下采样: 减少多数类的规模以匹配少数类。
在本文中,我们将深入探讨这些技术,了解如何使用 imbalanced-learn 等库在 Python 中实现它们,以及如何融合 2026 年最新的开发理念来优化它们,以提升机器学习模型的性能。
目录
为什么不平衡数据集是个问题?
当目标类别的分布不均匀,即一个类别的观测值明显多于另一个时,就会出现不平衡数据的情况。
例如:
- 欺诈检测数据集通常包含大量的合法交易(多数类)和极少的欺诈交易(少数类)。
- 不平衡数据会导致模型偏向多数类,从而导致在少数类上的表现不佳,这在欺诈检测或医疗诊断等关键应用中尤为致命。
不平衡数据集可能会产生具有误导性的指标,例如准确率。例如,在一个 95% 的案例属于多数类的数据集中,一个仅预测多数类的模型可以达到 95% 的准确率,但完全无法识别少数类。
处理不平衡数据时的关键考虑因素
- 避免数据泄漏:在应用重采样技术之前,先将数据集分割为训练集和测试集。这可以防止模型学到它在现实场景中不会遇到的模式。
- 使用合成方法:诸如 SMOTE (合成少数类过采样技术) 之类的技术可以为少数类生成合成样本,从而提高多样性和模型性能。
- 尝试两种技术:同时测试上采样和下采样,以找到适合您数据集的最佳方案。具体选择取决于数据集大小、模型需求和应用场景。
什么是上采样?
上采样通过增加少数类中的样本数量来解决数据集中的类别不平衡问题。这可以通过复制现有样本或通过 SMOTE(合成少数类过采样技术)等方法生成新的合成样本来实现。
> 示例: 在一个包含 1,000 笔合法交易(类别 0)和 50 笔欺诈交易(类别 1)的欺诈检测数据集中,上采样会复制或合成欺诈交易,以平衡数据集。
上采样改善了训练期间少数类的代表性,从而带来更好的模型性能。
什么是下采样?
下采样通过减少多数类中的样本数量,使其与少数类相匹配。这涉及从多数类中随机选择一个样本子集,直到其规模与少数类一致。
> 示例: 在一个医疗诊断场景中,如果您有 950 名健康患者(类别 0)和仅 50 名患有罕见疾病的患者(类别 1),下采样将涉及随机选择 50 名健康患者,使其数量与患病患者数量相匹配。这有助于确保在训练期间两个类别得到平等的代表。
下采样减少了向多数类的偏差,并有助于将注意力集中在少数类的预测上。
2026 开发者视角:现代 AI 辅助开发工作流
在我们深入代码实现之前,我想先谈谈我们在 2026 年是如何处理这类数据工程任务的。现在的开发范式已经发生了巨大的变化,我们不再仅仅是编写代码,更是在与 AI 结对编程。这就是所谓的 Vibe Coding(氛围编程)——利用 AI 的直觉来辅助我们的逻辑实现。
AI 辅助工作流
在我们最近的一个大型金融风控项目中,我们采用了 Agentic AI 工作流。我们不再手动编写每一行数据清洗代码,而是定义一个清晰的“目标”,让 AI 代理(Agent)来处理繁琐的重采样尝试。
例如,使用 Cursor 或 GitHub Copilot 等现代 IDE 时,我们可以这样提示 AI:
> “我们有一个严重倾斜的数据集(1:100)。请生成一个 Pipeline,它包含 RandomOverSampler 和 SMOTE 的对比实验,并使用 Stratified K-Fold 验证。”
这种 多模态开发 方式让我们能够同时关注代码、生成的可视化图表以及文档,大大加速了迭代过程。但在让 AI 辅助之前,我们自己必须深刻理解原理。
在 Python 中进行上采样和下采样的实战步骤
步骤 1 : 环境准备与库安装
要执行上采样和下采样,我们需要安装 imbalanced-learn 以及其他核心科学计算库。为了确保环境的一致性,我们建议使用虚拟环境。
# 在终端中运行以下命令
# pip install pandas numpy scikit-learn imbalanced-learn
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_openml
import matplotlib.pyplot as plt
import seaborn as sns
步骤 2 : 加载与探索数据集
让我们加载 Pima 印第安人糖尿病数据集。这是一个经典的二分类问题,数据往往存在轻微的不平衡。
# 定义 URL 和列名
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"
column_names = [‘Pregnancies‘, ‘Glucose‘, ‘BloodPressure‘, ‘SkinThickness‘, ‘Insulin‘,
‘BMI‘, ‘DiabetesPedigreeFunction‘, ‘Age‘, ‘Outcome‘]
# 读取数据
data = pd.read_csv(url, names=column_names)
print("--- 数据预览 ---")
print(data.head())
步骤 3:诊断类别分布
在动手解决不平衡问题之前,我们必须先量化它。仅仅看平均值是不够的,我们需要可视化的支持。
from collections import Counter
# 分离特征和目标
X = data.drop(‘Outcome‘, axis=1)
y = data[‘Outcome‘]
# 统计类别分布
print(f"原始类别分布: {Counter(y)}")
# 简单可视化
sns.countplot(x=y)
plt.title(‘原始数据类别分布‘)
plt.show()
输出:
原始类别分布: Counter({0: 500, 1: 268})
我们可以看到,类别 0(非糖尿病)大约是类别 1(糖尿病)的两倍。这种程度的不平衡虽然不是灾难性的,但足以影响模型的敏感度。
深入实战:企业级采样策略
步骤 4:执行上采样
上采样通过增加少数类样本来平衡数据。虽然简单的复制可行,但在生产环境中,我们更倾向于使用合成技术。不过,让我们先从基础的 RandomOverSampler 开始,看看它是如何工作的。
from imblearn.over_sampling import RandomOverSampler
# 初始化上采样器
# 我们可以设置 shrinkage 参数来平滑决策边界 (imblearn 0.12+ 新特性)
ros = RandomOverSampler(sampling_strategy=‘auto‘, random_state=42)
# 拟合和重采样
# 注意:我们通常只对训练数据进行重采样,测试集必须保持原始分布!
X_resampled, y_resampled = ros.fit_resample(X, y)
print(f"上采样后的类别分布: {Counter(y_resampled)}")
代码解析:
-
sampling_strategy=‘auto‘: 这意味着它会自动将少数类上采样至与多数类数量相同。 - INLINECODEa9f89271: 这是一个便捷方法,相当于先 INLINECODE937e2112 再
resample。
输出:
上采样后的类别分布: Counter({0: 500, 1: 500})
进阶方案:SMOTE (合成少数类过采样技术)
在实际项目中,简单地复制样本容易导致模型过拟合。这时我们会引入 SMOTE。它通过在特征空间中插值来生成新的合成样本。
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split
# 关键步骤:先分割数据,防止数据泄漏!
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# 初始化 SMOTE
# k_neighbors 参数决定了合成样本的邻近点数量
smote = SMOTE(k_neighbors=5, random_state=42)
# 仅对训练集应用 SMOTE
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
print(f"SMOTE 后训练集分布: {Counter(y_train_smote)}")
print(f"测试集分布 (保持不变): {Counter(y_test)}")
常见陷阱提示: 请务必注意,我们没有对测试集进行 SMOTE。测试集必须代表真实世界的分布。如果我们在测试集上生成了假数据,我们的评估指标就是虚高的。
步骤 5:执行下采样
下采样适用于数据量极大的场景。如果我们的数据集有数百万行,上采样可能会导致计算成本过高。下采样通过随机删除多数类样本来实现平衡。
from imblearn.under_sampling import RandomUnderSampler
# 初始化下采样器
rus = RandomUnderSampler(sampling_strategy=‘auto‘, random_state=42)
# 拟合和重采样
X_res, y_res = rus.fit_resample(X, y)
print(f"下采样后的类别分布: {Counter(y_res)}")
输出:
下采样后的类别分布: Counter({0: 268, 1: 268})
决策时刻: 你可能会问,“丢弃数据不是一种浪费吗?” 是的。当我们拥有海量数据时,为了训练速度和减少内存消耗,牺牲部分多数类数据通常是值得的。但如果数据集本身就很小(如本文的例子),下采样会导致信息严重丢失,此时应优先考虑上采样。
工程化最佳实践:构建鲁棒的 Pipeline
在现代机器学习工程中,我们不会像上面那样孤立地操作数据。我们会使用 Scikit-learn Pipeline 将采样步骤集成到训练流程中。这是防止数据泄漏和自动化模型部署的黄金标准。
端到端 Pipeline 示例
让我们构建一个完整的 Pipeline,结合数据预处理、SMOTE 采样和分类器。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from imblearn.pipeline import Pipeline as ImbPipeline # 注意:必须使用 imblearn 的 Pipeline
# 定义 Pipeline
# 步骤:1. 标准化 -> 2. SMOTE -> 3. 逻辑回归
pipeline = ImbPipeline(steps=[
(‘scaler‘, StandardScaler()),
(‘smote‘, SMOTE(random_state=42)),
(‘classifier‘, LogisticRegression(solver=‘lbfgs‘, max_iter=1000))
])
# 训练模型
# 这里的 Pipeline 会自动处理 fit_resample,仅对训练部分生效
pipeline.fit(X_train, y_train)
# 预测
y_pred = pipeline.predict(X_test)
# 评估
from sklearn.metrics import classification_report
print("--- 分类报告 ---")
print(classification_report(y_test, y_pred))
云原生与边缘计算视角的考量
在 2026 年,随着边缘计算的普及,我们必须考虑模型的大小和推理延迟。SMOTE 虽然好,但它生成的数据增加了训练集的大小,从而增加了模型训练的时间和资源消耗。
- Serverless 训练: 在使用 AWS SageMaker 或无服务器容器进行训练时,过大的训练集会导致冷启动时间变长。因此,对于边缘设备模型,我们可能会倾向于使用下采样或集成学习方法(如 BalancedRandomForest),而不是简单地生成海量样本。
- 数据监控: 在生产环境中,数据分布是动态漂移的。我们建议使用 Prometheus 或 Grafana 监控输入数据的类别比例。如果比例发生显著变化(例如欺诈手段改变),我们的 Pipeline 应该触发自动重训练机制。
总结与展望
在这篇文章中,我们不仅介绍了如何使用 Python 处理不平衡数据,还融合了 AI 辅助开发和工程化实践的视角。
核心要点回顾:
- 总是先分割数据:永远不要在测试集上执行重采样,这是导致模型“纸上谈兵”的元凶。
- 选择合适的工具:数据量大选下采样,数据量小选 SMOTE 等上采样。
- Pipeline 是关键:使用
imblearn.pipeline将采样和模型训练捆绑在一起,确保流程的可复现性。 - 拥抱 AI 辅助:利用现代 AI IDE 帮助你快速生成这些模板代码,但作为工程师,你必须理解每一行代码背后的统计原理。
随着我们进入 2026 年,数据处理将越来越多地被 AI Agents 自动化。掌握这些基础原理,将使你成为设计这些 Agent 的架构师,而不仅仅是使用者。让我们在编码中保持探索的好奇心!