数据, 术→技巧

Scikit-Learn 处理类别不平衡问题

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

类别不平衡是分类任务中常见的问题,即某些类别的样本数量显著少于其他类别。除了前面介绍的imbalanced-learn库以外,还能使用class_weight参数进行处理。

class_weight与imbalanced-learn的对比

核心定义与原理

方法 原理 实现方式
class_weight 通过调整模型训练时不同类别的权重,改变损失函数或决策规则中的类别重要性。 直接作用于模型参数(如SVM、逻辑回归、树模型),无需修改原始数据。
imbalanced-learn 通过过采样(增加少数类样本)或欠采样(减少多数类样本)直接平衡数据集分布。 数据预处理阶段修改样本分布(如SMOTE、RandomUnderSampler),再输入模型训练。

优缺点对比

class_weight

  • 优点:
    • 计算高效:无需生成或删除数据,节省内存和时间。
    • 保持原始分布:不改变数据本身的统计特性(如方差、分布形态)。
    • 模型原生支持:适用于支持该参数的模型(如SVM、随机森林)。
  • 缺点:
    • 依赖模型支持:部分模型(如KNN、朴素贝叶斯)无法使用。
    • 可能过拟合:对极端不平衡数据(如1:1000),单纯调整权重可能无法充分学习少数类特征。

imbalanced-learn

  • 优点:
    • 模型无关性:适用于所有模型(包括不支持class_weight的模型)。
    • 灵活性高:支持多种采样策略(如SMOTE生成合成样本、NearMiss删除冗余多数类样本)。
    • 缓解极端不平衡:过采样能显著增加少数类样本的多样性。
  • 缺点:
    • 计算成本高:过采样可能大幅增加数据量(如SMOTE),导致训练时间增加。
    • 引入噪声:合成样本可能破坏原始数据分布(如SMOTE生成不合理的样本)。

适用场景

方法 推荐场景
class_weight – 数据量较大,希望快速训练模型。

– 模型原生支持权重调整(如逻辑回归、SVM)。

– 需保留原始数据分布(如数据分布本身具有业务意义)。

imbalanced-learn – 模型不支持class_weight(如KNN、AdaBoost)。

– 极端不平衡数据(如1:1000)且需增强少数类特征学习。

– 需要结合采样策略调优(如实验不同过采样/欠采样组合)。

关键选择因素

因素 class_weight imbalanced-learn
计算效率 ✅ 更高效 ❌ 过采样可能增加数据量
模型兼容性 ❌ 依赖模型支持 ✅ 所有模型可用
数据分布保留需求 ✅ 保持原始分布 ❌ 可能改变分布
极端不平衡处理 ❌ 效果有限 ✅ 更适合
  • 优先尝试class_weight:若模型支持且数据不平衡程度适中(如1:10),优先使用权重调整。
  • 极端不平衡时选择imbalanced-learn:当少数类样本极少(如<5%),结合过采样和模型权重调整。
  • 灵活组合:根据模型类型、数据规模、业务需求混合使用两种方法。

注意事项

  • 避免过采样过拟合:SMOTE可能生成与真实场景不符的样本,需通过交叉验证验证泛化性。
  • 权重调整的合理性:class_weight的权重值需基于业务需求或实验确定(如医疗场景中假阴性代价更高)。
  • 数据泄露风险:过采样/欠采样需仅在训练集进行,验证集和测试集保持原始分布。

class_weight支持的模型

在scikit-learn中,class_weight参数主要用于处理类别不平衡问题,但并非所有模型都支持该参数。以下是支持class_weight的常见模型及其使用场景的详细分类:

线性模型

  • LogisticRegression
    • 通过调整类别权重,改变损失函数中不同类别的惩罚力度。
    • 示例:LogisticRegression(class_weight=’balanced’)
  • SGDClassifier
    • 随机梯度下降分类器,支持class_weight参数影响梯度更新权重。
    • 示例:SGDClassifier(class_weight={0: 0.3, 1: 0.7})
  • Perceptron
    • 线性分类器,支持通过class_weight调整类别权重。

支持向量机(SVM)

  • SVCNuSVC
    • 通过class_weight调整软间隔分类中对不同类别的误分类惩罚(参数C的权重)。
    • 示例:SVC(class_weight=’balanced’, kernel=’rbf’)
  • LinearSVC
    • 线性核SVM,但需注意其class_weight的实现可能与其他SVM不同(需设置dual=False)。

树模型

  • DecisionTreeClassifier
    • 节点分裂时根据class_weight调整基尼系数或信息增益的计算。
    • 示例:DecisionTreeClassifier(class_weight={0: 1, 1: 10})
  • RandomForestClassifierExtraTreesClassifier
    • 继承自树模型,每个树节点分裂时加权计算类别重要性。
  • XGBoost(需使用scikit-learn API)
    • 通过参数scale_pos_weight调整正负样本权重(仅二分类场景)。

集成方法

  • GradientBoostingClassifier
    • 通过class_weight调整每棵树的样本权重(需注意早期版本可能不支持)。
  • HistGradientBoostingClassifier
    • 高性能梯度提升树,支持class_weight参数(需scikit-learn ≥23)。

其他模型

  • RidgeClassifier
    • 岭分类器,支持通过class_weight调整类别权重。
  • PassiveAggressiveClassifier
    • 在线学习模型,支持class_weight参数。

不支持class_weight的常见模型

  • KNeighborsClassifier
    • K近邻不支持类别权重,需通过过采样或调整样本权重(如sample_weight)。
  • GaussianNBMultinomialNB
    • 朴素贝叶斯模型无法直接使用类别权重。
  • AdaBoostClassifier
    • 通过调整基学习器的class_weight间接实现(如基学习器是决策树)。
  • MLPClassifier
    • 神经网络分类器不支持class_weight,但可通过损失函数中的样本权重实现。

使用注意事项

  • 优先级冲突:若同时设置class_weight和fit()中的sample_weight,后者会覆盖前者。
  • 多类别问题:对于多分类任务,class_weight需以字典形式指定所有类别的权重(如{0: 1, 1: 2, 2: 1})。
  • 版本兼容性:部分模型(如HistGradientBoostingClassifier)仅在较新的scikit-learn版本中支持class_weight。

class_weight的使用

以下是一些使用 class_weight 参数的代码示例,涵盖不同模型和常见场景:

示例 1:二分类问题(逻辑回归)

from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# 生成不平衡数据集(类别0:1000,类别1:100)
X, y = make_classification(n_samples=1100, n_classes=2, weights=[0.9, 0.1], random_state=42)

# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 默认不调整权重(对比效果)
model_default = LogisticRegression(max_iter=1000)
model_default.fit(X_train, y_train)
print("默认权重分类报告:\n", classification_report(y_test, model_default.predict(X_test)))

# 使用自动平衡权重
model_balanced = LogisticRegression(class_weight='balanced', max_iter=1000)
model_balanced.fit(X_train, y_train)
print("\n平衡权重分类报告:\n", classification_report(y_test, model_balanced.predict(X_test)))

# 手动指定权重(类别0权重1,类别1权重10)
model_custom = LogisticRegression(class_weight={0: 1, 1: 10}, max_iter=1000)
model_custom.fit(X_train, y_train)
print("\n自定义权重分类报告:\n", classification_report(y_test, model_custom.predict(X_test)))

输出说明

  • 默认权重下,类别1(少数类)的召回率(Recall)通常较低。
  • 使用class_weight=’balanced’ 或手动设置高权重后,类别1的召回率会显著提升。

示例 2:随机森林(多分类问题)

from sklearn.ensemble import RandomForestClassifier

# 生成三分类不平衡数据(类别0:500,类别1:100,类别2:50)
X, y = make_classification(n_samples=650, n_classes=3, weights=[0.77, 0.2, 0.03], random_state=42)

# 设置权重字典(需为所有类别指定权重)
class_weights = {0: 1, 1: 5, 2: 10}  # 少数类(类别2)权重最高

model = RandomForestClassifier(
    class_weight=class_weights,
    n_estimators=100,
    random_state=42
)
model.fit(X_train, y_train)

# 查看类别重要性(权重影响特征分裂)
print("特征重要性:", model.feature_importances_)

示例 3:SVM(使用样本权重计算)

如果模型不支持 class_weight,可手动计算样本权重:

from sklearn.svm import SVC
from sklearn.utils.class_weight import compute_class_weight

# 计算类别权重
classes = np.unique(y_train)
class_weights = compute_class_weight(
    class_weight='balanced',
    classes=classes,
    y=y_train
)
# 转换为样本权重
sample_weights = compute_sample_weight(class_weight={0: 0.5, 1: 2.0}, y=y_train)

# 应用样本权重到SVM
model = SVC(kernel='linear')
model.fit(X_train, y_train, sample_weight=sample_weights)

示例 4:梯度提升树(GradientBoostingClassifier

from sklearn.ensemble import GradientBoostingClassifier

# 注意:需scikit-learn≥0.22版本支持class_weight
model = GradientBoostingClassifier(
    class_weight='balanced',  # 或自定义字典
    n_estimators=100,
    learning_rate=0.1,
    random_state=42
)
model.fit(X_train, y_train)

示例 5:网格搜索优化权重参数

from sklearn.model_selection import GridSearchCV

# 定义参数网格
param_grid = [
    {'class_weight': [None, 'balanced']},
    {'class_weight': [{0: 1, 1: w} for w in [2, 5, 10]]}
]

model = LogisticRegression(max_iter=1000)
grid_search = GridSearchCV(model, param_grid, cv=5, scoring='f1')
grid_search.fit(X_train, y_train)

print("最佳参数:", grid_search.best_params_)
print("最佳F1分数:", grid_search.best_score_)

关键注意事项

  • 权重值的选择:
    • 权重值一般与类别样本量的倒数成比例(如类别0:1000,类别1:100 → 权重比约为1:10)。
    • 可通过交叉验证或业务需求(如医疗场景中假阴性代价更高)调整权重。
  • 与过采样的对比:
    • class_weight更适合大规模数据,而过采样(如SMOTE)可能在小数据集中更有效。
    • 两者可结合使用(见下方示例)。
  • 多分类问题:
    • 必须为所有类别指定权重字典,例如:{0: 1, 1: 2, 2: 3}。

结合imbalanced-learn的示例

from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier

# 生成合成样本(过采样少数类)
smote = SMOTE(sampling_strategy='minority')
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# 在过采样后的数据上调整权重
model = RandomForestClassifier(
    class_weight={0: 1, 1: 5},  # 进一步加权少数类
    n_estimators=100,
    random_state=42
)
model.fit(X_resampled, y_resampled)

通过上述示例,可根据具体需求灵活调整 class_weight 参数,优化模型在不平衡数据上的表现。

发表回复

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