1. 成人收入不平衡分类项目解析
在机器学习分类任务中,我们经常会遇到类别分布不平衡的情况。成人收入数据集(Adult Income Dataset)就是一个典型的例子,它需要根据个人特征预测年收入是否超过5万美元。这个数据集的特点是少数类(>50K)约占25%,多数类(<=50K)约占75%,属于中等程度的不平衡分类问题。
1.1 数据集背景与特点
该数据集源自1994年美国人口普查数据,包含45,222条完整记录(去除缺失值后),14个特征变量和1个目标变量。特征类型多样,包括:
- 数值型:年龄、教育年限、资本收益等
- 分类型:工作类型、婚姻状况、职业等
数据集中的缺失值用"?"表示,约占总数据的7.4%。在预处理阶段,我们选择直接删除含有缺失值的记录,因为占比不大且不影响整体数据分布。
注意:在实际项目中,当缺失值比例较高时,直接删除可能不是最佳选择。可以考虑使用均值/众数填充、模型预测填充等方法,具体取决于数据特性和业务场景。
1.2 不平衡分类的挑战
虽然75:25的类别比例看起来不算极端不平衡,但仍然会带来一些建模挑战:
- 准确率陷阱:如果简单预测所有样本为多数类,就能获得75%的准确率
- 模型偏向:许多算法会倾向于优化多数类的预测性能
- 评估失真:传统的准确率指标可能无法反映模型真实性能
针对这些问题,我们需要:
- 选择合适的评估指标(如F1-score、AUC-ROC等)
- 使用分层抽样保证训练/测试集的类别比例一致
- 考虑采用过采样、欠采样或代价敏感学习等方法
2. 数据探索与预处理
2.1 数据加载与清洗
首先,我们使用Pandas加载数据并处理缺失值:
from pandas import read_csv from collections import Counter # 加载数据 filename = 'adult-all.csv' dataframe = read_csv(filename, header=None, na_values='?') # 删除缺失值 dataframe = dataframe.dropna() # 查看数据形状和类别分布 print(dataframe.shape) target = dataframe.values[:,-1] counter = Counter(target) for k,v in counter.items(): per = v / len(target) * 100 print('Class=%s, Count=%d, Percentage=%.3f%%' % (k, v, per))输出结果:
(45222, 15) Class= <=50K, Count=34014, Percentage=75.216% Class= >50K, Count=11208, Percentage=24.784%2.2 特征工程策略
针对不同类型的特征,我们需要采取不同的处理方式:
数值型特征:
- 标准化/归一化(如MinMaxScaler)
- 检查是否需要非线性变换(如对数变换)
- 考虑分箱处理(特别是对于资本收益等长尾分布的特征)
分类型特征:
- 有序类别:使用标签编码(Label Encoding)
- 无序类别:使用独热编码(One-Hot Encoding)
- 高基数类别:考虑目标编码(Target Encoding)或频率编码
特殊处理:
- "教育年限"和"教育程度"可能包含重复信息,需要评估保留哪个
- "最终权重"(Final Weight)需要理解其统计含义再决定如何处理
2.3 特征可视化分析
通过绘制数值特征的分布直方图,我们可以发现:
from matplotlib import pyplot # 选择数值型列 num_ix = dataframe.select_dtypes(include=['int64', 'float64']).columns subset = dataframe[num_ix] # 绘制直方图 subset.hist(figsize=(12,10)) pyplot.show()从直方图中可以观察到:
- 年龄呈近似正态分布
- 资本收益和资本损失具有极端偏态分布
- 每周工作时间呈现双峰分布
这些观察结果将指导我们后续的特征工程决策。
3. 建模与评估框架
3.1 评估策略设计
为了可靠地评估模型性能,我们采用以下策略:
分层重复K折交叉验证:
- K=10,重复3次
- 保持每折中类别比例与整体一致
- 减少评估结果的方差
评估指标选择:
- 主要指标:分类准确率(与文献基准比较)
- 辅助指标:F1-score、AUC-ROC(更全面评估不平衡分类性能)
基线模型:
- 使用DummyClassifier预测多数类
- 预期准确率约75.2%
实现代码:
from sklearn.dummy import DummyClassifier from sklearn.model_selection import RepeatedStratifiedKFold from sklearn.metrics import accuracy_score # 基线模型 baseline = DummyClassifier(strategy='most_frequent') cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) scores = cross_val_score(baseline, X, y, scoring='accuracy', cv=cv, n_jobs=-1) print('Baseline Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))3.2 特征预处理管道
构建一个统一的预处理管道,确保在交叉验证中正确应用转换:
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, MinMaxScaler from sklearn.pipeline import Pipeline # 定义预处理步骤 preprocessor = ColumnTransformer( transformers=[ ('num', MinMaxScaler(), num_ix), ('cat', OneHotEncoder(handle_unknown='ignore'), cat_ix) ]) # 完整建模管道 def make_pipeline(model): return Pipeline(steps=[ ('preprocessor', preprocessor), ('classifier', model) ])这种设计确保了:
- 数值特征被归一化到[0,1]范围
- 分类特征被适当编码
- 处理过程不会泄露测试集信息到训练集中
4. 模型比较与选择
4.1 候选模型测试
我们评估了五种不同类型的模型:
- 决策树(CART)
- 支持向量机(SVM)
- 装袋决策树(Bagging)
- 随机森林(RF)
- 梯度提升树(GBM)
每种模型使用默认参数(除n_estimators=100外),通过统一的评估框架进行比较:
from sklearn.tree import DecisionTreeClassifier from sklearn.svm import SVC from sklearn.ensemble import (RandomForestClassifier, GradientBoostingClassifier, BaggingClassifier) models = { 'CART': DecisionTreeClassifier(), 'SVM': SVC(gamma='scale'), 'BAG': BaggingClassifier(n_estimators=100), 'RF': RandomForestClassifier(n_estimators=100), 'GBM': GradientBoostingClassifier(n_estimators=100) } results = {} for name, model in models.items(): pipeline = make_pipeline(model) scores = evaluate_model(X, y, pipeline) results[name] = scores print(f'{name}: {mean(scores):.3f} (±{std(scores):.3f})')4.2 结果分析与模型选择
测试结果可能类似于:
Baseline: 0.752 (±0.000) CART: 0.812 (±0.004) SVM: 0.843 (±0.003) BAG: 0.851 (±0.003) RF: 0.859 (±0.003) GBM: 0.867 (±0.003)从结果可以看出:
- 所有模型都显著优于基线(75.2%)
- 集成方法普遍优于单一模型
- GBM表现最佳,达到约86.7%的准确率
实际建议:虽然GBM表现最好,但随机森林(RF)的准确率与之接近(85.9%),而训练速度更快。在计算资源有限的情况下,RF可能是更好的选择。
5. 模型优化与调参
5.1 梯度提升树(GBM)调优
GBM有多个关键参数可以优化:
- 学习率(learning_rate):控制每棵树的贡献程度
- 树的数量(n_estimators):集成中树的总数
- 最大深度(max_depth):单棵树的最大深度
- 子采样比例(subsample):训练每棵树使用的数据比例
使用网格搜索进行参数优化:
from sklearn.model_selection import GridSearchCV param_grid = { 'classifier__learning_rate': [0.01, 0.1, 0.2], 'classifier__n_estimators': [100, 200, 300], 'classifier__max_depth': [3, 5, 7], 'classifier__subsample': [0.8, 1.0] } gbm = GradientBoostingClassifier() pipeline = make_pipeline(gbm) search = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1) search.fit(X, y) print(f'Best score: {search.best_score_:.3f}') print('Best params:', search.best_params_)5.2 处理类别不平衡的专门技术
虽然GBM本身对类别不平衡有一定鲁棒性,但我们还可以尝试以下方法:
类别权重调整:
class_weight = {0: 1, 1: 3} # 给少数类更高权重 model = GradientBoostingClassifier(class_weight=class_weight)过采样技术(如SMOTE):
from imblearn.over_sampling import SMOTE from imblearn.pipeline import make_pipeline as make_imb_pipeline smote = SMOTE(sampling_strategy=0.5, random_state=42) pipeline = make_imb_pipeline(preprocessor, smote, model)代价敏感学习:
- 使用代价敏感的GBM变体
- 或在损失函数中引入不对称代价
经验分享:在实际项目中,我发现简单的类别权重调整(如少数类权重设为2-3)通常就能取得不错的效果,且实现简单。SMOTE等过采样技术虽然理论上更先进,但计算成本较高,且可能引入噪声。
6. 模型部署与预测
6.1 最终模型训练
确定最佳参数后,我们在全部训练数据上重新训练模型:
best_params = {'learning_rate': 0.1, 'max_depth': 5, 'n_estimators': 200, 'subsample': 0.8} final_model = GradientBoostingClassifier(**best_params) final_pipeline = make_pipeline(final_model) final_pipeline.fit(X, y)6.2 新数据预测示例
假设有新样本需要进行预测:
import pandas as pd # 新样本数据(示例) new_data = pd.DataFrame([[ 45, 'Private', 200000, 'Masters', 14, 'Married-civ-spouse', 'Exec-managerial', 'Husband', 'White', 'Male', 5000, 0, 50, 'United-States' ]], columns=dataframe.columns[:-1]) # 预测 prob = final_pipeline.predict_proba(new_data)[0, 1] pred = final_pipeline.predict(new_data) print(f'Predicted probability: {prob:.3f}') print('Predicted class:', '>50K' if pred[0] == 1 else '<=50K')6.3 模型解释与特征重要性
理解模型决策过程对于实际应用至关重要:
# 获取特征重要性 feature_importances = final_pipeline.named_steps['classifier'].feature_importances_ # 获取特征名称(考虑OneHotEncoder生成的列) cat_encoder = final_pipeline.named_steps['preprocessor'].named_transformers_['cat'] cat_features = cat_encoder.get_feature_names_out(input_features=cat_ix) all_features = num_ix.tolist() + cat_features.tolist() # 创建重要性DataFrame importance_df = pd.DataFrame({ 'feature': all_features, 'importance': feature_importances }).sort_values('importance', ascending=False) # 可视化前20个重要特征 importance_df.head(20).plot.barh(x='feature', y='importance') pyplot.show()典型的重要特征可能包括:
- 年龄
- 教育年限
- 资本收益
- 每周工作时间
- 职业类型
7. 项目总结与经验分享
通过这个项目,我们系统地完成了从数据探索到模型部署的全流程。以下是一些关键经验:
数据质量至关重要:
- 原始数据中有约7.4%的缺失值
- 不同特征需要不同的处理策略
- 可视化分析帮助发现数据特性
模型选择考量:
- GBM表现最好(86.7%准确率)
- 随机森林是很好的备选(85.9%)
- 简单模型如决策树也有不错表现(81.2%)
不平衡处理心得:
- 75:25的不平衡程度不算极端
- 直接使用GBM等集成方法通常足够
- 更复杂的方法(如SMOTE)可能提升有限
实际应用建议:
- 关注业务需求决定优化方向(准确率 vs 召回率)
- 模型解释性有时比绝对性能更重要
- 考虑部署环境的计算限制
这个项目展示了如何处理一个真实世界的不平衡分类问题。虽然我们达到了86%以上的准确率,但仍有改进空间,比如:
- 更精细的特征工程
- 尝试深度学习模型
- 集成多种方法
最终模型的选择应该综合考虑性能、复杂度和业务需求。希望这个案例能为处理类似的不平衡分类问题提供有价值的参考。