news 2026/4/18 5:32:33

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

作者头像

张小明

前端开发工程师

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进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 3:58:08

3、C 入门:“Hello World” 程序详解

C# 入门:“Hello World” 程序详解 1. 类、对象和类型基础 在 C# 中,类型通常由类来定义,类的单个实例被称为对象。虽然 C# 中除了类还有其他类型,如枚举、结构体和委托,但这里我们主要关注类。 “Hello World” 程…

作者头像 李华
网站建设 2026/4/17 19:29:44

Go 性能分析的“新范式”:用关键路径分析破解高并发延迟谜题

大家好,我是Tony Bai。“如果你喜欢快速的软件,那么你来对地方了。”在 GopherCon 2025 上,来自 Datadog 的工程师、Go Performance and diagnostics小组成员 Felix Geisendrfer 以这样一句开场白,将我们带入了一个 Go 性能分析的…

作者头像 李华
网站建设 2026/4/13 18:25:30

C#文件读取

File介绍using System.IO;IO输入和输出File:文件的一些读写操作的类,主要包括功能,文件读写、对文件的复制、剪切、删除、创建等操作方法Create()创建一个文件流,指定文件位置,//文件位置可以是…

作者头像 李华
网站建设 2026/4/15 7:30:49

28、Drupal开发参考:模板、测试、钩子与架构详解

Drupal开发参考:模板、测试、钩子与架构详解 1. 模板可用变量 在开发过程中,有一些辅助变量可供使用: - $classes_array :HTML类属性值的数组,在 $classes 变量中被展平为字符串。 - $is_admin :当前用户为管理员时标记为 true 。 - $is_front :在首页显…

作者头像 李华
网站建设 2026/4/17 17:56:55

无需专业录音设备:GPT-SoVITS对普通麦克风录音友好支持

无需专业录音设备:GPT-SoVITS对普通麦克风录音友好支持 在短视频博主用自己声音批量生成解说、听障用户定制专属语音助手、独立游戏开发者为角色赋予真实声线的今天,个性化语音合成早已不再是实验室里的高岭之花。一个令人惊讶的事实是——你不需要动辄上…

作者头像 李华