当前位置: 首页 > news >正文

day 13 不平衡数据集的处理

一、不平衡数据集

判断标准:最直接的方式是查看不同类别样本数量的比例。如果某一类别的样本数量远远多于其他类别,例如,在一个二分类问题中,正类样本有 10000 个,而负类样本只有 100 个,正负样本比例达到 100:1,这就是典型的数据集不平衡情况。一般来说,当类别间样本数量比例超过 10:1 ,就可以初步认为存在不平衡问题,但具体比例阈值需结合实际问题分析。

可能造成的影响:

1、模型性能偏差:多数类样本在训练过程中会占据主导地位,模型倾向于学习多数类的特征模式,对少数类样本的特征学习不足。且传统的评估指标如准确率,在不平衡数据集上可能会产生误导。例如,一个模型在 99% 为正类的数据集上,即使将所有样本都预测为正类,也能获得 99% 的准确率,但这显然不能说明模型性能良好。

2、模型泛化能力弱:不平衡数据会导致模型过度拟合多数类样本的特征。由于多数类样本在训练集中占比大,模型会花费更多精力学习多数类的模式,对少数类样本特征挖掘不充分。当面对新数据时,模型对少数类样本的预测表现会很差,无法准确识别少数类的新样本,降低了模型在实际应用中的泛化能力。

3、特征选择偏差:在不平衡数据集中,与多数类样本紧密相关的特征在模型训练中更容易被选中,而少数类样本特有的重要特征可能被忽视。这会导致模型学到的特征不全面,进一步影响对少数类样本的分类性能,使模型在处理包含少数类特征的数据时表现不佳。

二、处理方法

数据层面

欠采样

与过采样相反,欠采样通过删除多数类中的部分样本,使其数量与少数类接近,从而实现数据集的平衡。欠采样后的数据集尺寸虽然变小,但各类别的样本数量将更为接近。而且一般缺数据,所以很少采用欠采样。

随机欠采样:

随机从多数类中删除部分样本。

优点:操作简单。

缺点:可能丢失重要信息,影响模型对多数类特征的学习。

Tomek Links:

识别并删除多数类中与少数类距离很近的样本(形成 Tomek Links 的样本)。

优点:既能减少多数类样本数量,又能去除干扰边界样本,提高模型性能。

缺点:若类别本身界限分明,则这一方法的效果有限。

ENN(编辑最近邻法):

对于每个样本,检查其 K 近邻中的类别分布。如果某样本的 K 近邻中多数类样本占比过高,且该样本属于少数类,则删除该样本;如果某样本属于多数类,但 K 近邻中少数类样本占比较高,则也考虑删除。

优点:通过调整样本分布,使数据分布更合理,有助于进一步清晰化类别边界以提高模型性能。 

缺点:并非所有噪声样本都需要删除,有时反而会丢失一些有价值的信息。

过采样

当数据集中某个类别的样本数量远少于其他类别时,过采样可以使数据集趋于平衡。其基本思路是通过复制少数类样本来增加其数量,使数据集能够更加均衡地代表各个类别。

ROS(随机过采样):

随机复制少数类样本,增加其数量以匹配多数类。

优点:简单易实现。

缺点:可能导致过拟合,因为复制的样本完全相同,没有提供新的信息。

SMOTE(合成少数类过采样技术):

通过在少数类样本特征空间插值生成新样本。它在少数类样本间的特征空间连线上随机选点作为新样本。

优点:生成多样化的样本,减少过拟合风险。提高模型对少数类的识别能力。

缺点:可能生成噪声样本,影响模型性能。在高维数据上计算成本大幅增加。

具体流程: 

1. 对于少数类中的每个样本,计算它与少数类中其他样本的距离,得到其 k 近邻(一般 k 取5或其他合适的值)。

2. 从 k 近邻中随机选择一个样本。

3. 计算选定近邻与原始样本的差值,并通过乘以 0 到 1 之间的随机数,再与原始样本相加得到新的合成样本。

4. 不断重复上述步骤合成新样本,直至少数类和多数类样本数量达到平衡。

5. 使用过采样后的数据集训练模型并评估模型性能。

如果数据集较小,优先使用 SMOTE 如果数据集较大,可以尝试 ROS 或结合方法

代码实例:

需要先安装imbalanced-learn库:pip install -U imbalanced-learn。这个库是专门用于处理不平衡数据集的,提供了多种重采样方法。

# 数据预处理
# 划分数据集
# 基准模型训练评估
# 随机过采样
from imblearn.over_sampling import RandomOverSampler# 创建一个 RandomOverSampler 类的实例 ros
ros = RandomOverSampler(random_state=42)# 对训练集进行随机过采样(先拟合数据学习类别分布等再随机复制少数类)
X_train_ros, y_train_ros = ros.fit_resample(X_train, y_train) # 训练随机森林模型(使用随机过采样后的训练集)
rf_model_ros = RandomForestClassifier(random_state=42)
start_time_ros = time.time()
rf_model_ros.fit(X_train_ros, y_train_ros)
end_time_ros = time.time()print(f"随机过采样后训练与预测耗时: {end_time_ros - start_time_ros:.4f} 秒")# 在测试集上预测
rf_pred_ros = rf_model_ros.predict(X_test)print("\n随机过采样后随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_ros))
print("随机过采样后随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_ros))
# smote 过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)print("smote过采样后训练集的形状:", X_train_resampled.shape, y_train_resampled.shape)# 训练随机森林模型
rf_model = RandomForestClassifier(random_state=42)
start_time = time.time()
rf_model.fit(X_train_resampled, y_train_resampled)
end_time = time.time()print(f"smote过采样后训练与预测耗时: {end_time - start_time:.4f} 秒")# 在测试集上预测
rf_pred = rf_model.predict(X_test)print("\nsmote过采样后随机森林在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("smote过采样后随机森林在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))

基准模型、随机过采样、smote过采样对比: 

 

算法层面

标准算法以最小化整体误差等为优化目标,使得它们优先拟合多数类,因为这样能更迅速降低总误差。然而,这会造成模型对少数类样本的识别能力不足,即便整体准确率看似较高,但其召回率往往较低。因此,我们的目标是提升模型对少数类的预测性能,通常重点关注召回率(Recall)、F1 分数(F1 - Score)、AUC - PR 等指标。

修改类别权重:

这种方法在模型训练阶段介入,通过调整不同类别样本对损失函数的贡献来影响模型的学习过程。

核心思想:为不同类别的错误分类分配不同的“代价”或“权重”。通常,将少数类样本错分为多数类的代价设置得远高于反过来的情况。

作用机制:修改模型的损失函数。当模型错误分类一个具有高权重的少数类样本时,会受到更大的惩罚(更高的损失值)。

目的:迫使学习算法在优化参数时更加关注少数类,努力学习到一个能够更好地区分少数类的决策边界。它试图从根本上让模型“学会”识别少数类。

影响:直接改变模型的参数学习过程和最终学到的模型本身。

部分模型提供参数来调整对不同类别样本的重视程度。如在 RandomForestClassifier 中,可通过设置 class_weight 参数,让模型对少数类样本赋予更高权重,在训练时更关注少数类,从而改善不平衡数据下的性能。

class_weight=None (默认值):
  • 模型在训练过程中会平等对待所有类别,为每个类别赋予相同的权重值 1。
  • 这意味着在构建决策树以及计算诸如基尼不纯度等分裂标准时,算法不会考虑类别样本数量的差异,对所有类别一视同仁。
  • 在不平衡数据集中,由于多数类样本数量占据主导地位,这种平等对待的方式会导致模型在学习过程中更多地关注多数类样本的特征,因为拟合多数类样本能够更有效地降低整体的误差度量。
class_weight='balanced':
  • 算法自动根据训练数据 y 中各类别的频率来调整权重。
  • 权重计算方式:weight = n_samples / (n_classes * np.bincount(y))。其中,n_samples是样本总数,n_classes是类别总数,np.bincount(y)用于统计每个类别出现的次数。这种计算方式使得类别频率越低(即少数类),其权重越高;类别频率越高(即多数类),其权重越低。
  • 通过这种权重调整,模型在训练时会更加关注少数类样本。它是一种相对自动化的处理不平衡数据的策略,无需手动去计算和调整权重。
class_weight={dict} (手动设置):
  • 允许用户根据自己的需求,以字典形式手动为每个类别标签指定权重。例如,class_weight={0: 1, 1: 10}表示类别 1 的权重是类别 0 权重的 10 倍。
  • 直接影响了模型的学习过程,使模型在构建决策边界时更加平衡地考虑各类别样本,从根本上优化了模型对不平衡数据的学习方式。在训练时给了少数类样本更多的关注,能更好的学习各类样本特征,提高了泛化能力,减少过拟合风险。并且许多常用算法内置该参数支持代价敏感学习,用户只需简单设置参数值。
注意:

在使用 class_weight 时,推荐结合交叉验证(特别是StratifiedKFold)来评估模型效果和稳定性。

交叉验证是一种将数据集多次划分成训练集和验证集进行模型训练评估的方法。由于不平衡数据集本身的特殊性,模型的性能可能会因为数据划分的不同而产生较大波动。而StratifiedKFold分层交叉验证能够确保在每次划分数据时,各类别样本在训练集和验证集中的分布相对稳定,从而更可靠地评估class_weight参数调整对模型性能的影响,以及模型本身的稳定性。

修改分类阈值:

这种方法在模型训练完成之后介入,通过调整最终分类的决策规则来平衡不同类型的错误。

核心思想: 改变将模型输出的概率(或得分)映射到最终类别标签的门槛。

作用机制:多数模型会输出样本属于某一类(这里设为正类,一般是少数类)的概率 p。默认当 p > 0.5 时,样本被预测为正类;否则为负类。通过修改这个 0.5 的阈值,比如降低到 0.3,意味着只要模型预测一个样本属于正类的概率大于 0.3,就将其判定为正类。这样做导致更多原本可能被判定为负类的样本现在被判定为正类,从而影响召回率和精确率。

目的: 在不触动已训练好的模型参数和结构的前提下,根据具体业务场景对精确率和召回率的侧重需求,通过调整分类阈值来达到二者之间的平衡。在不平衡数据集中,往往更关注少数类的召回率,即尽可能多地识别出少数类样本,尽管这可能会牺牲一定的精确率。

影响: 不改变模型学到的参数或决策边界本身,只在模型输出概率后对其进行解读和转换为类别标签的方式进行了改变。

优点:

  • 实现简单,无需重新训练模型。
  • 精确率 - 召回率(PR)曲线和接受者操作特征(ROC)曲线能够直观地展示不同阈值下模型的精确率和召回率等性能指标的变化情况,可以直接在 PR 曲线或 ROC 曲线上选择满足需求的阈值作为操作点。
  • 适用于任何输出概率或分数的模型。

缺点:治标不治本。如果模型本身就没学好如何区分少数类(概率输出普遍很低),单纯降低阈值可能效果有限或导致大量误报(低精确率)。

以欺诈交易检测为例,如果模型在训练时,由于欺诈交易样本过少,没有准确学习到欺诈交易的特征模式,那么其输出的欺诈交易概率普遍偏低。此时降低阈值,虽然会使更多交易被判定为欺诈交易,但其中可能大部分是正常交易被误判,真正的欺诈交易可能并没有多识别出几个,导致精确率急剧下降,而召回率提升也不明显。这表明调整阈值是基于模型已有的学习结果进行的后处理,若模型本身学习效果不佳,这种方法的效果就会受到限制。

核心差异: 

代码示例:

# 引入 numpy 用于计算平均值等
import numpy as np 
from sklearn.ensemble import RandomForestClassifier
# 引入分层 K 折和交叉验证工具
from sklearn.model_selection import StratifiedKFold, cross_validate 
# make_scorer用于自定义评估指标
from sklearn.metrics import make_scorer, accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report
import time
import warnings
warnings.filterwarnings("ignore")# 已经完成数据加载、预处理与划分# 基准模型
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
start_time = time.time()
rf_model_default = RandomForestClassifier(random_state=42)
rf_model_default.fit(X_train, y_train)
rf_pred_default = rf_model_default.predict(X_test)
end_time = time.time()
print(f"默认模型训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_default))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_default))
print("-" * 50)# 带权重的随机森林 + 交叉验证 (训练集上进行) 
print("--- 2. 带权重随机森林 + 交叉验证 (在训练集上进行) ---")
# 统计训练集各类别样本数量,找到少数类和多数类标签
counts = np.bincount(y_train)
minority_label = np.argmin(counts) 
majority_label = np.argmax(counts)
print(f"训练集中各类别数量: {counts}")
print(f"少数类标签: {minority_label}, 多数类标签: {majority_label}")# 定义带权重的模型
rf_model_weighted = RandomForestClassifier(random_state=42,class_weight='balanced'  
# 或者可以手动设置权重字典class_weight={minority_label: 10, majority_label: 1} 
)# 设置分层5折交叉验证策略,shuffle=True表示每次拆分前打乱数据
cv_strategy = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 定义用于交叉验证的评估指标,特别关注少数类指标
# 使用 make_scorer 自定义交叉验证评分器 pos_label要设置为少数类标签值
# zero_division=0:当分母为 0(即无预测为正类的样本)时将精确率设为 0。
scoring = {'accuracy': 'accuracy','precision_minority': make_scorer(precision_score, pos_label=minority_label, zero_division=0),'recall_minority': make_scorer(recall_score, pos_label=minority_label),'f1_minority': make_scorer(f1_score, pos_label=minority_label)
}print(f"开始进行 {cv_strategy.get_n_splits()} 折交叉验证...")
start_time_cv = time.time()# 执行交叉验证 (在 X_train, y_train 上进行)
# cross_validate 会自动完成训练和评估过程
cv_results = cross_validate(estimator=rf_model_weighted,X=X_train,y=y_train,cv=cv_strategy,scoring=scoring,n_jobs=-1, # 使用所有可用的 CPU 核心return_train_score=False # 只返回测试折的得分
)end_time_cv = time.time()
print(f"交叉验证耗时: {end_time_cv - start_time_cv:.4f} 秒")# 打印交叉验证结果的平均值
print("\n带权重随机森林 交叉验证平均性能 (基于训练集划分):")
for metric_name, scores in cv_results.items():if metric_name.startswith('test_'): # 我们关心的是在验证折上的表现# 以tset_为分隔符分割取索引为1的元素,即去掉test_前缀clean_metric_name = metric_name.split('test_')[1]# 计算平均得分和该得分的波动范围print(f"  平均 {clean_metric_name}: {np.mean(scores):.4f} (+/- {np.std(scores):.4f})")print("-" * 50)# 使用权重训练最终模型,并在测试集上评估
print("--- 3. 训练最终的带权重模型 (整个训练集) 并在测试集上评估 ---")
start_time_final = time.time()
# 使用与交叉验证中相同的设置来训练最终模型
rf_model_weighted_final = RandomForestClassifier(random_state=42,class_weight='balanced'
)
rf_model_weighted_final.fit(X_train, y_train) # 在整个训练集上训练
rf_pred_weighted = rf_model_weighted_final.predict(X_test) # 在测试集上预测
end_time_final = time.time()print(f"最终带权重模型训练与预测耗时: {end_time_final - start_time_final:.4f} 秒")
print("\n带权重随机森林 在测试集上的分类报告:")
# classification_report通过target_names参数指定标签名称更易找到少数类评估指标
# 如t..=[f'Class {majority_label}', f'Class {minority_label}']即['Class 1', 'Class 0']
# 或者直接查看(classification_report 生成的报告会按类别标签分行展示指标)
print(classification_report(y_test, rf_pred_weighted)) 
print("带权重随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_weighted))
print("-" * 50)# 对比总结 (简单示例)
print("性能对比 (测试集上的少数类召回率 Recall):")
recall_default = recall_score(y_test, rf_pred_default, pos_label=minority_label)
recall_weighted = recall_score(y_test, rf_pred_weighted, pos_label=minority_label)
print(f"  默认模型: {recall_default:.4f}")
print(f"  带权重模型: {recall_weighted:.4f}")

 

评估层面

在评估不平衡数据集时,以下指标可能不太适用:

准确率:分类正确的样本数占总样本数的比例,即 准确率 = (正确分类样本数) / (总样本数)

在不平衡数据集中,多数类样本数量占优。例如,一个数据集 95% 是正类样本,5% 是负类样本,若模型将所有样本都预测为正类,准确率会高达 95% ,但这显然不能说明模型对两类样本都有良好的分类能力,它掩盖了模型对少数类样本的误判情况,无法准确反映模型在不平衡数据上的真实性能。

错误率:分类错误的样本数占总样本数的比例,即 错误率 = (错误分类样本数) / (总样本数) = 1 - 准确率

与准确率类似,由于不平衡数据集中多数类主导,即使模型对少数类样本分类效果很差,但只要正确分类多数类样本,错误率可能依然很低,不能有效体现模型对少数类的分类性能,不能全面评估模型在不平衡数据下的表现。

应该使用更适合不平衡数据集的评估指标,如精确率、召回率、F1 值、AUC(曲线下面积)等。

实操建议:

评估指标先行: 明确目标,使用适合不平衡数据的指标(Recall, F1-Score, AUC-PR, Balanced Accuracy, MCC)来评估模型。

优先尝试根本方法: 通常建议首先尝试修改权重 (class_weight='balanced') 或数据采样方法 (如 SMOTE),因为它们试图从源头改善模型学习。

交叉验证评估: 在使用 class_weight 或采样方法时,务必使用分层交叉验证 (Stratified K-Fold) 来获得对模型性能的可靠估计。

阈值调整作为补充: 修改阈值可以作为一种补充手段或最后的微调。即使使用了权重调整,有时仍需根据具体的业务需求(如必须达到某个召回率水平)来调整阈值,找到最佳的操作点。

组合策略: 有时结合多种方法,如使用SMOTE 增加少数类样本数量后,再使用class_weight进一步调整模型对不同类别样本的关注程度,能从数据和模型训练两个层面同时优化,有可能使模型性能得到更大提升。

集成学习进一步优化:在对单个模型进行上述优化后,使用集成学习方法可以进一步提升模型对不平衡数据集的处理性能。

  • Bagging 策略:如随机森林通过对样本和特征进行随机采样构建多个决策树,每个决策树关注不同的样本子集和特征子集,综合决策树的结果提高对少数类的识别能力。这在一定程度上减少了模型的方差,提高了模型的稳定性和泛化能力,有助于应对不平衡数据带来的挑战。
  • Boosting 策略:像 AdaBoost 和 XGBoost,通过迭代调整样本权重,使模型更加关注被误分类的样本,特别是在不平衡数据中易被误分类的少数类样本。这种方法可以逐步降低模型的偏差,提升模型对少数类的分类性能。

总之,修改权重旨在训练一个“更好”的模型,而修改阈值是在一个“已有”模型上调整其表现。

阅读材料:

过采样与欠采样技术原理图解:基于二维数据的常见方法效果对比 - 知乎

@浙大疏锦行

相关文章:

  • ctfshow web入门 web52
  • 【coze】记忆体(变量、数据库、长期记忆、消息盒子)
  • B站视频下载到电脑的方法总结
  • 商业实战将归巢网内容构建为本地RAG模型的完整指南01-优雅草卓伊凡
  • 开发搭载OneNet平台的物联网数据收发APP的设计与实现
  • 【大模型面试】大模型(LLMs)高频面题全面整理(★2025年5月最新版★)
  • Python+Scrapy跨境电商爬虫实战:从亚马逊/沃尔玛数据采集到反爬攻克(附Pangolin API高效方案)
  • HarmonyOS开发:粒子动画使用详解
  • shell-流程控制-循环-函数
  • 新一代Python专业编译器Nuitka简介
  • 20. LangChain电商场景:构建智能客服与个性化推荐系统
  • MySQL 强制使用特定索引
  • Unity学习笔记二
  • (undone) xv6-labs-2020 补充 LAB lazy page allocation (Day11 xv6-2020 LAB5 懒分配)
  • py实现win自动化自动登陆qq
  • Android View#post()源码分析
  • tinyrenderer笔记(Shader)
  • C语言数组和函数的实践———扫雷游戏
  • APP自动化测试(一)
  • 9-4 USART串口数据包
  • 国铁集团:铁路五一假期运输收官,多项运输指标创历史新高
  • 民生访谈|摆摊设点、公园搭帐篷、行道树飘絮,管理难题怎么解?
  • 第四轮伊美核问题谈判预计5月11日举行
  • 博裕基金拟收购“全球店王”北京SKP最多45%股权
  • 新华每日电讯:上海“绿色大民生”撑起“春日大经济”
  • 什么让翻拍“语文”成为短视频新风潮