数据, 术→技巧

处理类别不平衡的Python库imbalanced-learn

钱魏Way · · 272 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

imbalanced-learn(通常简称为 imblearn)是一个专门用于处理类别不平衡数据的 Python 库。它与 Scikit-learn 兼容,提供了多种方法来解决分类任务中类别样本数量差异过大的问题。

为什么需要 imbalanced-learn?

在处理分类任务时,数据中的类别不平衡(Class Imbalance)问题广泛存在且极具挑战性。例如:

  • 欺诈检测:99% 的交易是正常的,1% 是欺诈行为。
  • 医学诊断:罕见病患者的样本远少于健康人群。
  • 网络入侵检测:大多数流量是合法的,攻击仅占极少数。

传统机器学习模型(如逻辑回归、决策树、SVM 等)的优化目标通常是整体准确率最大化,导致模型倾向于忽略少数类,将所有样本预测为多数类。例如,若数据中 99% 是多数类,模型只需全部预测为多数类,就能达到 99% 的准确率,但这对实际问题毫无意义。

imbalanced-learn 的核心价值在于提供系统化的解决方案,直接针对类别不平衡问题调整数据或模型,而非依赖传统模型的默认行为。

传统方法的局限性

直接训练模型的缺陷

  • 问题:模型偏向多数类,少数类的召回率(Recall)或精确率(Precision)极低。
  • 示例:在癌症检测中,模型将所有样本预测为“健康”,虽然准确率高,但会漏诊所有真实患者。

简单策略的不足

  • 调整类别权重:Scikit-learn 的class_weight 参数可为少数类分配更高权重,但仅适用于部分模型(如逻辑回归、SVM),且无法解决极端不平衡(如 1:1000)。
  • 随机过采样/欠采样:手动复制少数类样本会导致过拟合,随机删除多数类样本会丢失关键信息。

imbalanced-learn 的独特优势

系统化的重采样技术

  • 过采样(Oversampling)
    • SMOTE:通过插值生成“逼真”的少数类样本,而非简单复制,缓解过拟合。
    • Borderline-SMOTE:专注于在类别边界附近生成样本,强化分类边界。
    • ADASYN:根据少数类样本的密度动态调整生成策略,优先覆盖难以学习的区域。
  • 欠采样(Undersampling)
    • Tomek Links:移除多数类中与少数类相邻的样本,简化分类边界。
    • NearMiss:基于距离线路最具代表性的多数类样本,保留更多信息。
  • 混合采样(Combined)
    • SMOTE + Tomek Links:先过采样生成新样本,再清理噪声,平衡数据质量与数量。

集成学习与自适应算法

  • Balanced Bagging:在集成过程中对每个基学习器的训练数据进行重采样,确保每轮训练的数据平衡。
  • RUSBoost:结合欠采样与 AdaBoost,动态调整样本权重,提升对少数类的关注。

与 Scikit-learn 无缝兼容

管道(Pipeline)支持

from imblearn.pipeline import Pipeline
pipeline = Pipeline([
    ('smote', SMOTE(sampling_strategy=0.5)),  # 过采样至多数类的50%
    ('scaler', StandardScaler()),             # 标准化
    ('clf', RandomForestClassifier())         # 分类器
])

确保重采样仅在训练集进行,避免验证集/测试集数据泄露。

交叉验证集成

from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import cross_val_score

sampler = RandomUnderSampler()
model = LogisticRegression()
pipeline = Pipeline([('sampler', sampler), ('model', model)])
scores = cross_val_score(pipeline, X, y, cv=5)  # 正确重采样流程

实际场景中的必要性

极端不平衡数据

  • 示例:信用卡欺诈检测(正样本占比1%)。
  • 解决方案
    • 使用SMOTE 生成欺诈样本,将其提升至 10%。
    • 结合BalancedRandomForest 训练模型,每棵树仅采样部分多数类样本。

多类别不平衡

  • 示例:手写数字识别,但数字“1”的样本是其他数字的 10 倍。
  • 解决方案
from imblearn.over_sampling import SMOTE
smote = SMOTE(sampling_strategy={2: 500, 4: 1000})  # 指定每个类别的目标样本数
X_res, y_res = smote.fit_resample(X, y)

高维数据与小样本

  • 示例:基因数据中,某疾病的阳性样本仅 50 个,特征维度超过 1000。
  • 解决方案:使用SVM-SMOTE 在高维空间生成样本,避免普通 SMOTE 因距离计算失效导致的噪声。

与其他工具的区别

方法 Scikit-learn imbalanced-learn
处理类别不平衡 仅支持 class_weight 提供 20+ 种重采样算法和集成模型
数据预处理流程 无重采样 Pipeline 支持 专为不平衡数据设计的 Pipeline 类
过采样技术 SMOTE、ADASYN、KMeans-SMOTE 等
欠采样技术 Tomek Links、NearMiss、ClusterCentroids
评估指标 需手动计算 F1、AUC-ROC 内置与不平衡数据兼容的评估工具

何时不需要 imbalanced-learn?

  • 数据本身平衡(如 MNIST 数据集)。
  • 少数类样本极度稀缺(如仅 5 个样本),此时重采样可能无效,需依赖数据增强或迁移学习。

imbalanced-learn的价值

imbalanced-learn 的必要性源于其对类别不平衡问题的针对性解决能力。它通过以下方式提升模型性能:

  • 数据层面:调整样本分布,使模型更关注少数类。
  • 算法层面:集成重采样与模型训练,避免信息丢失和过拟合。
  • 流程层面:与 Scikit-learn 生态无缝集成,简化实验流程。

对于任何涉及类别不平衡的任务(如金融风控、医疗诊断、异常检测),imbalanced-learn 都是提升模型鲁棒性和实用性的关键工具。

imbalanced-learn核心功能

imbalanced-learn 的方法主要分为四类:重采样(Resampling)集成学习(Ensemble)混合方法(Hybrid)和数据清洗(Cleaning)。以下分别展开:

重采样(Resampling)

通过调整训练数据的类别分布,直接解决不平衡问题。

过采样(Oversampling)

  • 目标:增加少数类样本数量,使其接近多数类。
  • 核心方法
    • SMOTE(Synthetic Minority Over-sampling Technique)
      • 原理:在少数类样本的特征空间中,随机选择两个近邻样本,并在其连线上通过线性插值生成新样本。
      • 公式:对两个样本$x_i$ 和 $x_j $,生成新样本 $x_{\text{new}} = x_i + \lambda (x_j – x_i)$ ,其中 $\lambda \in [0, 1] $是随机数。
      • 优势:避免简单复制样本导致的过拟合。
      • 变体
        • Borderline-SMOTE:仅对靠近类别边界的少数类样本生成新样本。
        • SVMSMOTE:使用 SVM 找到支持向量,沿支持向量方向生成样本。
      • ADASYN(Adaptive Synthetic Sampling)
        • 原理:根据少数类样本的密度动态调整生成策略,对难以学习的区域(即周围多数类样本较多的区域)生成更多样本。

适用于少数类样本极少的场景,通过生成新样本增加少数类多样性。

方法 原理 适用场景 优缺点
SMOTE 在特征空间插值生成新样本 中小规模数据,特征间线性关系较强 简单高效,可能生成噪声样本
SMOTE-NC 支持数值和分类特征的混合插值 包含分类特征的数据集 处理混合特征,计算复杂度较高
ADASYN 根据样本密度自适应生成更多困难样本 少数类分布不均匀(如存在多个子簇) 关注分类边界,可能过拟合噪声区域
Borderline-SMOTE 仅在边界区域生成新样本 类别边界模糊的场景 提升边界清晰度,对噪声敏感
KMeans-SMOTE 先聚类再在簇内生成样本 高维数据或少数类存在明显子簇结构 减少噪声生成,依赖聚类效果
SVMSMOTE 使用 SVM 支持向量定位边界区域生成样本 类别边界复杂的非线性数据 适合复杂边界,计算成本高

代码示例:

from imblearn.over_sampling import SMOTE
smote = SMOTE(sampling_strategy='auto', k_neighbors=5)
X_resampled, y_resampled = smote.fit_resample(X, y)

欠采样(Undersampling)

  • 目标:减少多数类样本数量,降低其主导地位。
  • 核心方法
    • RandomUnderSampler:随机删除多数类样本。
    • Tomek Links
      • 原理:找到两个不同类别的最近邻样本对(称为 Tomek Link),移除其中的多数类样本,从而清晰化类别边界。
    • ClusterCentroids
      • 原理:对多数类样本进行 K-Means 聚类,用聚类中心替代原始样本,保留多数类的整体分布信息。
    • NearMiss
      • 原理:选择多数类中与少数类样本距离最近的样本(三种策略):
        • NearMiss-1:保留与少数类样本平均距离最小的多数类样本。
        • NearMiss-2:保留与少数类样本最远距离最小的多数类样本。
        • NearMiss-3:为每个少数类样本保留固定数量的多数类近邻。

适用于 多数类样本冗余 的场景,通过减少多数类样本降低计算成本。

方法 原理 适用场景 优缺点
RandomUnderSampler 随机删除多数类样本 快速降低数据规模,基线方法 可能丢失重要信息
Tomek Links 删除边界附近的多数类样本 清理类别边界重叠样本 保留全局分布,删除样本有限
ENN 删除类别与邻居不一致的样本 去除噪声样本 有效清理噪声,可能误删少数类边缘样本
Repeated ENN 多次迭代应用 ENN 存在深层噪声或复杂分布 更彻底清理,但数据量可能大幅减少
Cluster Centroids 对多数类聚类后保留聚类中心 高维数据或多数类存在冗余子结构 保留代表性样本,依赖聚类效果
NearMiss 保留与少数类最近的多数类样本 需要保留多数类关键信息的场景 增强类别边界,可能引入偏差

代码示例:

from imblearn.under_sampling import TomekLinks
tl = TomekLinks()
X_resampled, y_resampled = tl.fit_resample(X, y)

混合采样(Combined Sampling)

  • 原理:同时使用过采样和欠采样,避免单一方法的缺陷。
  • 常用方法
    • SMOTEENN:先用 SMOTE 过采样,再用 ENN(Edited Nearest Neighbours)删除噪声样本。
    • SMOTETomek:SMOTE 过采样后,用 Tomek Links 清理边界样本。

SMOTEENN 示例

原理:先用 SMOTE 过采样生成少数类样本,再用 ENN(Edited Nearest Neighbours)删除噪声样本。

import numpy as np
from sklearn.datasets import make_classification
from imblearn.combine import SMOTEENN
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report

# 生成不平衡数据集(多数类:1000,少数类:50)
X, y = make_classification(
    n_samples=1050, 
    weights=[0.95], 
    n_classes=2, 
    random_state=42
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 初始化 SMOTEENN
# 参数说明:
# - smote=SMOTE(sampling_strategy='auto'):过采样至多数类的 50%
# - enn=EditedNearestNeighbours():删除与邻居类别不一致的样本
smote_enn = SMOTEENN(
    sampling_strategy='auto',  # 目标类别平衡策略(默认自动调整)
    random_state=42
)

# 应用混合采样
X_resampled, y_resampled = smote_enn.fit_resample(X_train, y_train)

# 查看采样后的类别分布
print("采样后的类别数量:", np.bincount(y_resampled))
# 输出示例:array([492, 441]) 表示多数类和少数类数量接近平衡

# 训练模型并评估
model = LogisticRegression()
model.fit(X_resampled, y_resampled)
y_pred = model.predict(X_test)

# 打印分类报告
print(classification_report(y_test, y_pred))

SMOTETomek 示例

原理:先用 SMOTE 过采样,再用 Tomek Links 清理边界模糊样本。

from imblearn.combine import SMOTETomek

# 初始化 SMOTETomek
smote_tomek = SMOTETomek(
    sampling_strategy='auto',  # 自动平衡至多数类的 50%
    random_state=42
)

# 应用混合采样
X_resampled, y_resampled = smote_tomek.fit_resample(X_train, y_train)

# 查看采样后的类别分布
print("采样后的类别数量:", np.bincount(y_resampled))
# 输出示例:array([517, 488]) 

# 训练模型并评估
model = LogisticRegression()
model.fit(X_resampled, y_resampled)
y_pred = model.predict(X_test)

print(classification_report(y_test, y_pred))

参数调优与解释

关键参数

  • sampling_strategy:控制采样后的类别比例。
    • ‘auto’:默认将少数类过采样至多数类的 50%。
    • 字典形式:例如{0: 100, 1: 200} 指定类别 0 保留 100 个样本,类别 1 生成 200 个样本。
  • smote=SMOTE()和 tomek=TomekLinks():可自定义底层方法的参数。
    • 例如调整 SMOTE 的k_neighbors 或 Tomek Links 的 n_jobs。

自定义 SMOTE 和 ENN/Tomek

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import TomekLinks

# 自定义 SMOTE 和 Tomek Links
smote = SMOTE(sampling_strategy=0.5, k_neighbors=3)
tomek = TomekLinks(n_jobs=-1)  # 使用所有 CPU 核心

# 组合为 SMOTETomek
smote_tomek = SMOTETomek(
    smote=smote,
    tomek=tomek,
    sampling_strategy='auto'
)

注意事项

  • 过采样风险:SMOTE 可能生成噪声样本,需通过 ENN 或 Tomek 清理。
  • 类别比例控制:合理设置sampling_strategy,避免极端过采样导致模型过拟合。
  • 与 Pipeline 结合:确保重采样仅在训练集进行。
from imblearn.pipeline import Pipeline
pipeline = Pipeline([
    ('smote_tomek', SMOTETomek()),
    ('classifier', LogisticRegression())
])
pipeline.fit(X_train, y_train)

效果对比

方法 适用场景 优点 缺点
SMOTEENN 数据中存在较多噪声或类别重叠 清理噪声后数据更干净 ENN 可能删除有用的少数类样本
SMOTETomek 需要保留更多多数类信息的场景 仅清理边界样本,保留多数类分布信息 对高维数据效果可能下降

通过混合采样,既能增加少数类样本的多样性,又能清理噪声或冗余样本,是处理类别不平衡问题的实用工具。

集成方法(Ensemble Learning)

将重采样技术与集成学习结合,提升模型鲁棒性。

方法 原理 适用场景 优缺点
BalancedBagging 每轮 Bagging 对多数类欠采样 中小规模数据,需兼容任意基模型 灵活,计算效率高
BalancedRandomForest 每棵树训练时欠采样多数类 大规模数据,需并行训练和特征重要性分析 高效,支持高维数据
EasyEnsemble 将多数类划分为多个子集并分别训练 多数类冗余度高,需增强多样性 降低过拟合风险,计算成本较高
RUSBoost 欠采样 + AdaBoost 动态调整样本权重 需要自适应关注困难样本的场景 提升少数类召回率,可能过拟合噪声

Balanced Bagging

原理:在 Bagging 的每轮迭代中,对多数类进行欠采样,使每个子训练集的类别分布平衡,从而降低模型对多数类的偏向。

代码示例:

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.tree import DecisionTreeClassifier
from imblearn.ensemble import BalancedBaggingClassifier
import matplotlib.pyplot as plt
from sklearn.metrics import RocCurveDisplay

# 生成不平衡数据集(多数类:1000,少数类:100)
X, y = make_classification(
    n_samples=1100, 
    n_classes=2, 
    weights=[0.9],  # 90% 多数类
    random_state=42
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 初始化 Balanced Bagging 模型
bbc = BalancedBaggingClassifier(
    base_estimator=DecisionTreeClassifier(max_depth=3),  # 基模型为决策树
    n_estimators=50,           # 50 个子模型
    sampling_strategy='auto',  # 欠采样至与少数类数量相同
    replacement=False,          # 无放回采样
    random_state=42,
    n_jobs=-1                  # 使用所有 CPU 核心
)

# 训练模型
bbc.fit(X_train, y_train)

# 预测并评估
y_pred = bbc.predict(X_test)
y_proba = bbc.predict_proba(X_test)[:, 1]  # 预测概率(用于 ROC-AUC)

print("=== Balanced Bagging 分类报告 ===")
print(classification_report(y_test, y_pred))
print("ROC-AUC 分数:", roc_auc_score(y_test, y_proba))

# 绘制 ROC 曲线
RocCurveDisplay.from_estimator(bbc, X_test, y_test)
plt.title("Balanced Bagging ROC Curve")
plt.show()

关键参数解释

  • n_estimators:子模型数量,值越大模型越稳定,但计算成本增加。
  • sampling_strategy:
    • ‘auto’:默认将多数类欠采样至少数类数量。
    • 5:将多数类欠采样至少数类的 50%。
  • replacement:是否允许有放回采样(设为True 可模拟自助采样法)。

Balanced Random Forest

原理:在随机森林的每棵树训练时,对多数类欠采样,确保每棵树的训练数据平衡。支持并行计算,适合大规模数据。

代码示例:

from imblearn.ensemble import BalancedRandomForestClassifier

# 初始化 Balanced Random Forest
brf = BalancedRandomForestClassifier(
    n_estimators=100,          # 100 棵树
    sampling_strategy='auto',  # 欠采样至与少数类平衡
    max_depth=5,               # 控制树复杂度
    random_state=42,
    n_jobs=-1                  # 并行计算
)

# 训练模型
brf.fit(X_train, y_train)

# 预测并评估
y_pred = brf.predict(X_test)
y_proba = brf.predict_proba(X_test)[:, 1]

print("=== Balanced Random Forest 分类报告 ===")
print(classification_report(y_test, y_pred))
print("ROC-AUC 分数:", roc_auc_score(y_test, y_proba))

# 绘制特征重要性
importances = brf.feature_importances_
plt.bar(range(X.shape[1]), importances)
plt.title("Feature Importance (Balanced Random Forest)")
plt.xlabel("Feature Index")
plt.ylabel("Importance")
plt.show()

优势

  • 天然支持并行训练(通过n_jobs=-1)。
  • 提供特征重要性分析,适合高维数据特征筛选。

EasyEnsemble

适用场景

  • 多数类样本高度冗余(如多数类样本数量是少数类的 100 倍以上)。
  • 需要提升模型多样性,避免欠采样导致的信息丢失。

代码实现

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score, RocCurveDisplay
from imblearn.ensemble import EasyEnsembleClassifier
from sklearn.ensemble import AdaBoostClassifier

# 生成极端不平衡数据集(多数类:5000,少数类:50)
X, y = make_classification(
    n_samples=5050,
    n_classes=2,
    weights=[0.99],  # 99% 多数类
    flip_y=0.02,     # 2% 标签噪声
    random_state=42
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 初始化 EasyEnsemble
ee = EasyEnsembleClassifier(
    n_estimators=100,           # 生成 100 个平衡子集
    base_estimator=AdaBoostClassifier(n_estimators=10),  # 基模型为 AdaBoost
    sampling_strategy='auto',   # 每个子集样本数与少数类相同
    random_state=42,
    n_jobs=-1                   # 并行加速
)

# 训练模型
ee.fit(X_train, y_train)

# 预测与评估
y_pred = ee.predict(X_test)
y_proba = ee.predict_proba(X_test)[:, 1]  # 预测概率

print("=== EasyEnsemble 分类报告 ===")
print(classification_report(y_test, y_pred))
print("ROC-AUC:", roc_auc_score(y_test, y_proba))

# 可视化基模型的 ROC 曲线(部分示例)
plt.figure(figsize=(8, 6))
for i in range(10):  # 仅绘制前10个基模型的曲线
    RocCurveDisplay.from_estimator(ee.estimators_[i], X_test, y_test, alpha=0.1)
plt.title("EasyEnsemble 基模型 ROC 曲线(部分)")
plt.show()

关键参数说明

  • n_estimators:生成的平衡子集数量,值越大模型稳定性越高,但计算成本增加。
  • base_estimator:基模型选择,推荐使用轻量级模型(如浅层决策树)以提高效率。
  • sampling_strategy:控制多数类欠采样的比例,设为5 可保留更多多数类信息。

RUSBoost

适用场景

  • 需要动态关注分类错误的困难样本(如医疗诊断中的高风险误诊样本)。
  • 数据中存在噪声,但希望通过权重调整抑制噪声影响。

代码实现

from imblearn.ensemble import RUSBoostClassifier

# 生成不平衡数据集(多数类:1000,少数类:100)
X, y = make_classification(
    n_samples=1100,
    n_classes=2,
    weights=[0.9],  # 90% 多数类
    random_state=42
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 初始化 RUSBoost
rusboost = RUSBoostClassifier(
    n_estimators=200,          # 迭代次数
    learning_rate=0.1,         # 学习率(控制权重更新幅度)
    sampling_strategy='auto',  # 欠采样至与少数类平衡
    algorithm='SAMME.R',       # 使用概率提升算法
    random_state=42
)

# 训练模型
rusboost.fit(X_train, y_train)

# 预测与评估
y_pred = rusboost.predict(X_test)
y_proba = rusboost.predict_proba(X_test)[:, 1]

print("=== RUSBoost 分类报告 ===")
print(classification_report(y_test, y_pred))
print("ROC-AUC:", roc_auc_score(y_test, y_proba))

# 绘制特征重要性(仅当基模型为决策树时有效)
importances = rusboost.feature_importances_
plt.bar(range(X.shape[1]), importances)
plt.title("RUSBoost 特征重要性")
plt.xlabel("特征索引")
plt.ylabel("重要性")
plt.show()

关键参数说明

  • learning_rate:学习率控制权重更新幅度,值越小模型越保守(需配合更多迭代次数)。
  • algorithm:
    • ‘SAMME’:适用于离散类别预测。
    • ‘SAMME.R’:利用概率估计提升效果,通常更优。

数据清洗(Data Cleaning)

去除噪声样本或重叠样本,提升数据质量。

方法

  • Edited Nearest Neighbours (ENN):删除类别与周围样本不一致的样本。
  • Repeated Edited Nearest Neighbours:多次应用 ENN 直到无法进一步清理。

Edited Nearest Neighbours (ENN)

原理:删除与周围邻居多数类别不一致的样本。

import numpy as np
from sklearn.datasets import make_classification
from imblearn.under_sampling import EditedNearestNeighbours
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report

# 生成包含噪声的不平衡数据集(多数类:1000,少数类:100,噪声比例约5%)
X, y = make_classification(
    n_samples=1100,
    n_features=2,
    n_redundant=0,
    weights=[0.9],  # 90% 多数类
    flip_y=0.05,    # 5% 标签噪声
    random_state=42
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 查看原始数据分布
print("原始训练集类别分布:", np.bincount(y_train))
# 输出示例:array([792,  88])

# 初始化 ENN
enn = EditedNearestNeighbours(
    n_neighbors=3,  # 检查每个样本的 3 个最近邻
    kind_sel="all"  # 删除与所有邻居类别不一致的样本(可选 "mode" 保留多数类别)
)

# 应用 ENN 清洗数据
X_cleaned, y_cleaned = enn.fit_resample(X_train, y_train)

# 查看清洗后的数据分布
print("清洗后训练集类别分布:", np.bincount(y_cleaned))
# 输出示例:array([755,  80])  # 删除了部分噪声样本

# 训练模型并评估
model = LogisticRegression()
model.fit(X_cleaned, y_cleaned)
y_pred = model.predict(X_test)

# 打印分类报告
print("=== ENN 清洗后模型性能 ===")
print(classification_report(y_test, y_pred))

Repeated Edited Nearest Neighbours

原理:多次应用 ENN 直到无法进一步清理。

from imblearn.under_sampling import RepeatedEditedNearestNeighbours

# 初始化 Repeated ENN
renn = RepeatedEditedNearestNeighbours(
    n_neighbors=3,
    max_iter=100,    # 最大迭代次数(即使未收敛也停止)
    kind_sel="all"
)

# 应用 Repeated ENN 清洗数据
X_cleaned, y_cleaned = renn.fit_resample(X_train, y_train)

# 查看清洗后的数据分布
print("清洗后训练集类别分布:", np.bincount(y_cleaned))
# 输出示例:array([742,  78])  # 比单次 ENN 删除更多样本

# 训练模型并评估
model = LogisticRegression()
model.fit(X_cleaned, y_cleaned)
y_pred = model.predict(X_test)

print("=== Repeated ENN 清洗后模型性能 ===")
print(classification_report(y_test, y_pred))

参数解释与调优

关键参数

  • n_neighbors:
    • 值越大,对噪声的容忍度越高,但可能无法有效清理局部不一致样本。
    • 典型取值范围:3-10。
  • kind_sel:
    • “all”:仅当样本与所有邻居类别一致时保留。
    • “mode”:保留与邻居多数类别一致的样本。
  • max_iter(仅用于 Repeated ENN):
    • 设置最大迭代次数,防止无限循环。

自定义参数示例

# 更严格的清洗策略
strict_enn = EditedNearestNeighbours(
    n_neighbors=5,      # 检查 5 个邻居
    kind_sel="mode"     # 允许少数服从多数
)

# 更激进的 Repeated ENN
aggressive_renn = RepeatedEditedNearestNeighbours(
    n_neighbors=3,
    max_iter=50,
    kind_sel="all"
)

效果对比

方法 适用场景 优点 缺点
ENN 数据中存在局部噪声或少量类别重叠 快速删除明显不一致的样本 可能误删少数类边缘样本
Repeated ENN 数据中存在复杂噪声或多次迭代可清理的场景 更彻底清理噪声 可能过度删除样本,导致数据量减少

与模型训练的集成

将数据清洗步骤嵌入 Pipeline,确保流程可复用:

from imblearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipeline = Pipeline([
    ('enn', EditedNearestNeighbours(n_neighbors=3)),
    ('scaler', StandardScaler()),
    ('clf', LogisticRegression())
])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

注意事项

  • 类别不平衡场景的特殊处理
    • 如果少数类样本本身很少,ENN 可能过度删除样本,需谨慎使用。
    • 可先对少数类过采样(如 SMOTE),再应用 ENN 清理噪声。
  • 高维数据问题
    • 在高维空间中,距离计算可能失效,导致 ENN 效果下降。建议先降维(如 PCA)再清洗。
  • 评估指标选择
    • 重点关注少数类的召回率(Recall)和精确率(Precision),而非整体准确率。

通过 ENN 和 Repeated ENN 的合理使用,可以显著提升数据质量,尤其适用于噪声较多或类别边界模糊的场景。

方法选择指南

场景 推荐方法
少数类样本极少(<100) SMOTE 或 Borderline-SMOTE(需确保特征空间可插值)
多数类样本冗余(如百万级) ClusterCentroids 或 NearMiss-3(保留代表性样本)
数据中存在噪声 先使用 SMOTEENN 或 Tomek Links 清理数据,再训练模型
计算资源充足 Balanced Random Forest 或 EasyEnsemble(集成方法效果更稳定)
高维数据(如文本、基因数据) SVM-SMOTE 或 KMeans-SMOTE(基于模型或聚类生成样本,避免高维空间插值失效)

注意事项

  • 过采样潜在风险
    • SMOTE 可能在类别重叠区域生成噪声样本,导致模型性能下降。
    • 对高维稀疏数据(如文本 TF-IDF 矩阵),插值生成样本可能无效。
  • 欠采样信息丢失
    • 随机欠采样可能丢失重要样本,需结合筛选策略(如 NearMiss)。
  • 评估指标选择
    • 避免使用准确率(Accuracy),推荐使用F1-ScoreROC-AUCPrecision-Recall Curve

参考链接:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注