news 2026/1/9 17:28:32

如何在机器学习项目中处理不平衡数据集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何在机器学习项目中处理不平衡数据集

原文:towardsdatascience.com/how-to-handle-imbalanced-datasets-in-machine-learning-projects-a95fa2cd491a

想象一下,你已经训练了一个准确率高达 0.9 的预测模型。像精确度、召回率和 f1 分数这样的评估指标也看起来很有希望。但你的经验和直觉告诉你,事情并不对劲,所以你进行了进一步的调查,并发现了以下情况:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1054c96b3e3bc64e1949fcfe7d69dd68.png

Image_1 – 作者截图

模型的看似强大的性能是由其目标变量中的多数类0驱动的。由于多数类和少数类之间明显的不平衡,模型在预测多数类0方面表现出色,而少数类1的性能则远未令人满意。然而,由于类1只代表了目标变量的一小部分,其性能对整体评估指标的总体得分影响很小,这给你造成了一种模型强大的错觉。

这并不是一个罕见的情况。相反,数据科学家在现实世界的项目中经常遇到不平衡的数据集。不平衡数据集指的是类别或类别在数据集中没有平等代表的情况。我们不应该忽略数据集中的不平衡,因为它可能导致模型性能偏差、泛化能力差和误导性的评估指标问题。

本文将讨论解决不平衡数据集带来的挑战的技术。为了演示目的,我将继续使用我在另一篇文章中使用过的 UCI 机器学习仓库中的银行营销数据集。你可以在这里检查有关数据集的所有信息并下载数据这里。这个数据集受 Creative Commons Attribution 4.0 International (CC BY 4.0)许可协议的许可,允许用于任何目的的共享和改编,前提是给予适当的信用。

银行营销数据集包含 16 个特征和一个二元目标变量,该变量表示客户是否订阅了定期存款。目标变量高度不平衡,其多数类0占总数据的 88.3%,而少数类1占 11.7%。

如果我们不处理数据集中的不平衡,我们可以简单地使用下面的脚本训练一个预测模型来预测客户是否会订阅定期存款,评估指标在本文开头 Image_1 中展示。

# Import librariesimportpandasaspdimportnumpyasnpimportioimportrequestsfromzipfileimportZipFilefromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportOneHotEncoder,StandardScalerfromsklearn.ensembleimportRandomForestClassifierfromsklearn.metricsimportaccuracy_score,classification_report,roc_auc_score,roc_curveimportmatplotlib.pyplotasplt# Download the dataurl="https://archive.ics.uci.edu/static/public/222/bank+marketing.zip"response=requests.get(url)# Open the datasetwithZipFile(io.BytesIO(response.content))asouter_zip:withouter_zip.open('bank.zip')asinner_zip_file:withZipFile(io.BytesIO(inner_zip_file.read()))asinner_zip:withinner_zip.open('bank-full.csv')ascsv_file:df=pd.read_csv(csv_file,sep=';')# Initial EDA:# Check for missing values and basic statsprint(df.isnull().sum())# No missing values in this dataset# Drop columns 'day' and 'month'df=df.drop(columns=['day','month'])# Loop One-Hot Encoding for categorical columnscategorical_columns=['job','marital','education','default','housing','loan','contact','poutcome']encoder=OneHotEncoder(drop='first',sparse=False)forcolumnincategorical_columns:encoded_cols=encoder.fit_transform(df[[column]])encoded_df=pd.DataFrame(encoded_cols,columns=[f"{column}_{cat}"forcatinencoder.categories_[0][1:]])df=pd.concat([df.drop(columns=[column]),encoded_df],axis=1)# Separate features (X) and the target variable (y)X=df.drop('y',axis=1)# 'y' is the target variabley=df['y'].apply(lambdax:1ifx=='yes'else0)# Convert target to binary# Split the data into training and testing sets without using SMOTEX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=42)# Standardizing numerical featuresscaler=StandardScaler()numerical_columns=X_train.select_dtypes(include=['int64','float64']).columnsforcolumninnumerical_columns:X_train[column]=scaler.fit_transform(X_train[[column]])X_test[column]=scaler.transform(X_test[[column]])# Train the RandomForestClassifier without any method to handle imbalancerf=RandomForestClassifier(n_estimators=100,random_state=42,class_weight=None)rf.fit(X_train,y_train)# Make predictionsy_pred=rf.predict(X_test)y_pred_proba=rf.predict_proba(X_test)[:,1]# Evaluation metricsaccuracy=accuracy_score(y_test,y_pred)classification_rep=classification_report(y_test,y_pred)roc_auc=roc_auc_score(y_test,y_pred_proba)# Print evaluation resultsprint(f"Accuracy:{accuracy}")print("Classification Report:")print(classification_rep)print(f"ROC-AUC:{roc_auc}")

在接下来的章节中,我将介绍处理不平衡数据集最常用的方法,并将几种合适的技巧应用于这个银行营销数据集。


处理不平衡数据集的常用方法

随机欠采样

随机欠采样是一种从多数类中移除样本以平衡类别分布的方法。它通常在多数类显著较大,且开发者可以承受因数据减少而丢失一些信息时使用。

  • 优点:简单且减少了训练时间。

  • 缺点:可能会移除重要信息并导致欠拟合。

Python 示例:

fromimblearn.under_samplingimportRandomUnderSamplerfromcollectionsimportCounterfromsklearn.datasetsimportmake_classification# Create a mock imbalanced datasetX,y=make_classification(n_classes=2,weights=[0.99,0.01],n_samples=1000,random_state=42)print('Original class distribution:',Counter(y))# Apply random undersamplingrus=RandomUnderSampler(random_state=42)X_res,y_res=rus.fit_resample(X,y)print('Resampled class distribution:',Counter(y_res))

随机过采样

与随机欠采样相反,随机过采样通过复制少数类的样本来平衡数据集。它通常在数据有限,开发者希望在解决不平衡的同时保留所有样本时使用。

  • 优点:保留了所有原始样本。

  • 缺点:可能会通过重复相同的信息导致过拟合。

Python 示例:

fromimblearn.over_samplingimportRandomOverSampler# Apply random oversamplingros=RandomOverSampler(random_state=42)X_res,y_res=ros.fit_resample(X,y)print('Resampled class distribution:',Counter(y_res))

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

SMOTE通过在现有样本之间插值来为少数类生成合成样本。SMOTE 与随机过采样(ROS)的关键区别在于,ROS 只是简单地复制数据点而不引入任何新信息,这可能导致过拟合,而 SMOTE 生成新的合成样本,与随机复制相比,降低了过拟合的风险。

  • 优点:比随机过采样更稳健,且不太容易过拟合。

  • 缺点:如果生成的样本不具有代表性,可能会引入噪声。

Python 示例:

fromimblearn.over_samplingimportSMOTE# Apply SMOTEsmote=SMOTE(random_state=42)X_smote,y_smote=smote.fit_resample(X,y)print('SMOTE class distribution:',Counter(y_smote))

成本敏感学习

成本敏感学习是一种通过为每个类别分配不同的成本来直接调整模型的方法,而不是引入重采样技术来改变原始数据集。这种方法通常在少数类更重要时使用(例如,欺诈检测、医疗诊断)。

  • 优点:无需修改数据。

  • 缺点:需要仔细调整成本参数。

Python 示例:

fromsklearn.treeimportDecisionTreeClassifierfromsklearn.metricsimportclassification_report# Train a cost-sensitive decision treemodel=DecisionTreeClassifier(class_weight={0:1,1:10},random_state=42)model.fit(X,y)# Evaluate the modely_pred=model.predict(X)print(classification_report(y,y_pred))

平衡随机森林

平衡随机森林是一种将随机森林与类别的平衡采样相结合的方法。这种方法使开发者能够构建一个稳健的模型并避免欠拟合。

  • 优点:在平衡数据集的同时保持模型复杂性。

  • 缺点:计算密集。

Python 示例:

fromimblearn.ensembleimportBalancedRandomForestClassifierfromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportaccuracy_score# Split the dataX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=42)# Train a Balanced Random Forest modelbrf=BalancedRandomForestClassifier(random_state=42)brf.fit(X_train,y_train)# Evaluatey_pred=brf.predict(X_test)print('Balanced Random Forest Accuracy:',accuracy_score(y_test,y_pred))

解决银行营销数据不平衡问题

为了解决本文开头提到的银行营销数据不平衡问题,我应用了包括 SMOTE、ADASYN、平衡随机森林(BRF)和成本敏感学习等技术。然后我选择了 BRF,因为它将 f1-score 从 0.45 提高到 0.52,并且这种改进在所有方法中是最显著的。此外,BRF 是一种集成方法,它内部平衡类别,这使得该方法适合这个银行营销数据集。通过使用 BRF,我们不必过于担心过拟合或欠拟合的问题,因为这是一个稳健的方法。

# Import librariesimportpandasaspdimportnumpyasnpimportioimportrequestsfromzipfileimportZipFilefromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportOneHotEncoder,StandardScalerfromimblearn.ensembleimportBalancedRandomForestClassifier# Import BRFfromsklearn.feature_selectionimportSelectFromModelimportmatplotlib.pyplotaspltfromsklearn.metricsimportaccuracy_score,roc_auc_score,precision_score,recall_score,f1_score,classification_report# Download the dataurl="https://archive.ics.uci.edu/static/public/222/bank+marketing.zip"response=requests.get(url)# Open the datasetwithZipFile(io.BytesIO(response.content))asouter_zip:withouter_zip.open('bank.zip')asinner_zip_file:withZipFile(io.BytesIO(inner_zip_file.read()))asinner_zip:withinner_zip.open('bank-full.csv')ascsv_file:df=pd.read_csv(csv_file,sep=';')# Initial EDA:# Check for missing values and basic statsprint(df.isnull().sum())# No missing values in this dataset# Drop columns 'day' and 'month'df=df.drop(columns=['day','month'])# Loop One-Hot Encoding for categorical columnscategorical_columns=['job','marital','education','default','housing','loan','contact','poutcome']encoder=OneHotEncoder(drop='first',sparse_output=False)forcolumnincategorical_columns:encoded_cols=encoder.fit_transform(df[[column]])encoded_df=pd.DataFrame(encoded_cols,columns=[f"{column}_{cat}"forcatinencoder.categories_[0][1:]])df=pd.concat([df.drop(columns=[column]),encoded_df],axis=1)# Separate features (X) and the target variable (y)X=df.drop('y',axis=1)# 'y' is the target variabley=df['y'].apply(lambdax:1ifx=='yes'else0)# Convert target to binary# Split the data into training and testing setsX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=42)# Standardizing numerical featuresscaler=StandardScaler()numerical_columns=X_train.select_dtypes(include=['int64','float64']).columns X_train[numerical_columns]=scaler.fit_transform(X_train[numerical_columns])X_test[numerical_columns]=scaler.transform(X_test[numerical_columns])# Feature Selection using BalancedRandomForestClassifierselector=BalancedRandomForestClassifier(n_estimators=100,random_state=42)selector.fit(X_train,y_train)model=SelectFromModel(selector,threshold='median',prefit=True)selected_mask=model.get_support()selected_columns=X_train.columns[selected_mask]X_train_selected=model.transform(X_train)X_test_selected=model.transform(X_test)# Visualize feature importance of the selected featuresimportances=selector.feature_importances_ selected_importances=importances[selected_mask]indices=np.argsort(selected_importances)[::-1]selected_names_sorted=[selected_columns[i]foriinindices]plt.figure(figsize=(12,8))plt.title("Feature Importance of Selected Features")plt.barh(range(len(selected_importances)),selected_importances[indices])plt.yticks(range(len(selected_importances)),selected_names_sorted)plt.xlabel('Relative Importance')plt.gca().invert_yaxis()plt.show()# Define parameter grid for BalancedRandomForestn_estimators_options=[50,100]max_depth_options=[10,20,30]best_f1_score=0best_accuracy=0best_params={}best_classification_report=""best_brf=None# Nested loop to iterate through hyperparametersforn_estimatorsinn_estimators_options:formax_depthinmax_depth_options:brf=BalancedRandomForestClassifier(n_estimators=n_estimators,max_depth=max_depth,random_state=42)brf.fit(X_train_selected,y_train)# Make predictions on the test sety_pred=brf.predict(X_test_selected)# Calculate performance metrics for the test setaccuracy=accuracy_score(y_test,y_pred)f1=f1_score(y_test,y_pred,average='weighted')# If current model has better F1-score, update best model detailsiff1>best_f1_scoreor(f1==best_f1_scoreandaccuracy>best_accuracy):best_f1_score=f1 best_accuracy=accuracy best_params={'n_estimators':n_estimators,'max_depth':max_depth}best_classification_report=classification_report(y_test,y_pred)best_brf=brf# Store the best model# Print the best model performance and hyperparametersprint(f"nBest F1 Score:{best_f1_score:.4f}")print(f"Best Accuracy:{best_accuracy:.4f}")print(f"Best Parameters:{best_params}")# Print the classification report of the best modelprint("nClassification Report for the Best Model:n")print(best_classification_report)# Check for overfittingy_train_pred=best_brf.predict(X_train_selected)# Calculate metrics on the training settrain_accuracy=accuracy_score(y_train,y_train_pred)train_f1=f1_score(y_train,y_train_pred,average='weighted')print("nTraining Set Performance:")print(f"Accuracy:{train_accuracy:.4f}")print(f"F1 Score:{train_f1:.4f}")print("nTest Set Performance:")print(f"Accuracy:{best_accuracy:.4f}")print(f"F1 Score:{best_f1_score:.4f}")# Simple overfitting checkiftrain_accuracy>best_accuracy+0.05:print("nOverfitting Detected: The model performs significantly better on the training set.")else:print("nNo significant overfitting detected.")

输出结果:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/82c825f5ee2a26f4d8f5e12f5bf6b3b7.png

作者截图


结论

这个选择是一个完美的解决方案吗?尽管 BRF 将少数类的召回率从 0.36 提高到 0.86,将 F1 分数从 0.45 提高到 0.52,但我们看到了精确度的下降。这意味着解决方案不成功吗?不一定。处理不平衡数据集的技术效果取决于以下因素:

  • 不平衡程度:不平衡程度越严重,这些方法做出显著改进就越困难。

  • 模型适应性:如果模型未能捕捉到所有潜在的规律,它们可能无法充分利用处理不平衡数据的技巧。

  • 评估指标:在特定应用中,某些指标的微小提升可以被认为是显著的。

对于这个银行营销数据集,将少数类的 F1 分数从 0.45 提高到 0.52 是一个显著的提升。由于平衡随机森林确保每棵树都能获得数据的平衡视图,它提高了模型从少数类学习的能力。尽管增加的误报可能会导致更高的营销成本,但显著提高的召回分数可以带来更好的转化机会、提高的营销效率和甚至更大的客户参与度。因此,认识到精确度和召回率之间的权衡非常重要,在这个特定情况下,提高的 F1 分数是有合理依据的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/25 2:19:13

39、Git 子树操作与钩子功能全解析

Git 子树操作与钩子功能全解析 1. Git 子树操作 在进行 Git 子树操作前,需要满足一定的前提条件。你需要有网络连接,并且至少完成了相关实验的前两个步骤,即把原始 calc2 项目的各个拆分项目 fork 到你在 GitHub 的区域,并将 super_calc 项目克隆到本地系统。 以下是具体…

作者头像 李华
网站建设 2026/1/3 10:30:31

STM32CubeMX汉化环境下外设初始化代码生成解析

深入STM32CubeMX中文环境:外设初始化代码是如何“一键生成”的?你有没有经历过这样的场景?刚打开STM32参考手册,上千页的英文文档扑面而来,RCC_APB2ENR、GPIOx_MODER这些寄存器看得人头晕眼花。明明只是想点亮一个LED&…

作者头像 李华
网站建设 2025/12/25 2:16:40

苹果手机文件管理在测试与问题排查中的实际作用

在 iOS 生态里,苹果手机文件管理一直显得有些“低调”。 对普通用户来说,系统已经把文件藏得足够深; 对开发者来说,沙盒机制又让一切看起来井然有序。 但只要你真正参与过线上问题排查、测试回归,或者需要复现用户环境…

作者头像 李华
网站建设 2025/12/25 2:15:21

2025运维四大主流ITSM产品核心能力对比与选型建议

在数字化转型向纵深推进的 2025 年,IT 服务管理(ITSM)已从传统工单工具升级为连接 IT 运维与业务价值的核心枢纽。企业对 ITSM 的需求不再局限于流程流转,而是延伸到合规保障、生态协同、敏捷响应等多元维度。本文聚焦当前主流 IT…

作者头像 李华
网站建设 2025/12/31 6:05:25

跨平台上位机串口通信模块开发实战记录

跨平台上位机串口通信模块开发实战:从原理到落地的完整路径你有没有遇到过这样的场景?——在实验室里,你的Windows电脑能完美连接下位机读取数据;可客户一拿到Linux系统上运行,串口直接“失联”;或者macOS用…

作者头像 李华