一:题目简介
在数据集中,每个样本都对应一个葡萄牙大学的学生。原始数据集中共有4424名学生,。对于每个学生,我们获得了人口统计数据、宏观经济数据以及课程前两个学期的表现。目标是预测学生在三年或四年学习后的状态:是否毕业、仍在就读,或退学。题目提供了训练集,测试集和提交格式。
下载或观看数据集入口:利用GBDT进行对表格类数据的机器学习的实战项目所需数据集 - 飞书云文档 (feishu.cn)
二:利用GBDT来进行机器学习对问题求解
本文要采取的方法是表格类比赛经典建模方法
数据EDA、特征工程、GBDT模型家族、交叉验证,模型融合
我们跳过数据EDA,直接先进入特征工程。
GBDT(Gradient Boosting Decision Tree,梯度提升决策树)是一种流行的机器学习算法,它主要用于回归和分类问题。GBDT 是一种集成学习算法,它通过构建和组合多个决策树来形成一个强大的预测模型。
以下是 GBDT 的一些关键特性:
基本原理
1. 迭代增强:GBDT 通过迭代地训练决策树来最小化损失函数。每一棵树都是为了纠正前一棵树的错误而构建的。
2. 梯度提升:算法的名字来源于它使用梯度下降法的近似来训练模型。在每次迭代中,算法计算损失函数关于当前模型的梯度,然后用一棵新的决策树来拟合这个梯度。
构建过程
1. 初始化:通常,GBDT 以一个常数预测(例如,回归问题的均值或分类问题的众数)开始。
2. 负梯度:在每次迭代中,算法计算当前损失函数的负梯度,这些梯度代表了当前模型的误差。
3. 决策树拟合:算法训练一个新的决策树来拟合这些负梯度(也称为残差)。
4. 更新模型:新训练的决策树用来更新模型,通常是通过加权求和的方式(树的预测乘以一个学习率)。
核心优势
1. 准确性:GBDT 在许多问题上都表现出很高的预测准确性。
2. 灵活性:它可以处理各种类型的数据,包括连续值和类别值。
3. 鲁棒性:GBDT 对于异常值不敏感,并且通常不需要太多的数据预处理。
关键参数
1. 树的数量:构建的决策树的数量,更多的树可能会提高模型的准确性,但也可能导致过拟合。
2. 树的大小:单个决策树的大小(深度或叶子节点数),控制模型的复杂度。
3. 学习率:也称为收缩率,它控制每棵树对最终模型的影响程度。
应用场景
GBDT 在各种应用中都非常流行,包括但不限于:
- 广告点击率预测
- 信用评分
- 异常检测
- 排序问题
常用实现
GBDT 有几个流行的开源实现,包括:
- XGBoost
- LightGBM
- CatBoost
这些库提供了高效的算法实现,并且支持并行计算和分布式计算,使得 GBDT 能够在大数据集上快速训练。
总的来说,GBDT 是一个强大的机器学习算法,它通过结合多个简单的决策树来构建一个复杂的、高准确度的预测模型。由于它的准确性和灵活性,GBDT 在工业界和学术界都得到了广泛的应用。
在开始之前,环境配置需要先用Python导入我们所需的库,sklearn集成了大多数机器学习库。
import pickle
import json
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import ExtraTreeClassifier
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold, StratifiedKFold
import lightgbm as lgb
import xgboost as xgb
import catboost as cb
1:特征工程
特征工程(FE)是将原始特征转化为更好的表达问题本质的特征。比如预测房价时,将房子的长宽组合成面积,就是更好的特征表达
GBDT类算法不需要做特征归一化或离散化
LightGBM/CatBoost可以处理类别特征,只需转成自然数即可; XGBoost则需要做OneHot编码,当 然也可尝试直接以自然数输入
可以通过观察数据的特征得到类别特征和数值特征。
数据简介入口:UCI Machine Learning Repository
先读入数据:
train_df = pd.read_csv('../input/train.csv', index_col=0)
test_df = pd.read_csv('../input/test.csv', index_col=0)
com_df = pd.concat([train_df, test_df], axis=0, ignore_index=False)
类别特征为:
ori_cat_feats = [
'Marital status',
'Application mode',
'Application order',
'Course',
'Daytime/evening attendance',
'Previous qualification',
'Nacionality',
'Mother\'s qualification',
'Father\'s qualification',
'Mother\'s occupation',
'Father\'s occupation',
'Displaced',
'Educational special needs',
'Debtor',
'Tuition fees up to date',
'Gender',
'Scholarship holder',
'International',
]
数值特征为:
ori_num_feats = [
'Previous qualification (grade)',
'Admission grade',
'Age at enrollment',
'Curricular units 1st sem (credited)',
'Curricular units 1st sem (enrolled)',
'Curricular units 1st sem (evaluations)',
'Curricular units 1st sem (approved)',
'Curricular units 1st sem (grade)',
'Curricular units 1st sem (without evaluations)',
'Curricular units 2nd sem (credited)',
'Curricular units 2nd sem (enrolled)',
'Curricular units 2nd sem (evaluations)',
'Curricular units 2nd sem (approved)',
'Curricular units 2nd sem (grade)',
'Curricular units 2nd sem (without evaluations)',
'Unemployment rate',
'Inflation rate',
'GDP',
]
然后清除列名中的特殊字符:
special_char = ["'", '/', ' ']
def normalize_feature_name(name):
for c in special_char:
name = name.replace(c, '_')
name = name.replace('(', '').replace(')', '')
return name
ori_all_feats = [normalize_feature_name(col) for col in ori_all_feats]
ori_cat_feats = [normalize_feature_name(col) for col in ori_cat_feats]
ori_num_feats = [normalize_feature_name(col) for col in ori_num_feats]
train_df.columns = train_df.columns.map(normalize_feature_name)
test_df.columns = test_df.columns.map(normalize_feature_name)
对分类特征以及预测的目标值进行数字编码,‘Graduate’ 被映射为 0,‘Enrolled’ 被映射为 1,而 ‘Dropout’ 被映射为 2。
for col in ori_cat_feats:
com_df[col] = LabelEncoder().fit_transform(com_df[col])
label2code = {
'Graduate': 0,
'Enrolled': 1,
'Dropout': 2,
}
code2label = {v: v for k, v in label2code.items()}
com_df['Target'] = com_df['Target'].map(label2code)
train_df = com_df.loc[train_df.index]
test_df = com_df.loc[test_df.index]
支持我们就完成了简易化的特征工程。
2:LightGBM模型建立与交叉验证(CV)
LightGBM是GBDT 有几个流行的开源实现。交叉验证是一种统计学方法,交叉验证涉及到将数据集分成几个较小的子集,并使用这些子集来训练和验证模型,用于评估机器学习模型在独立数据集上的预测性能。它是用来避免过拟合和提高模型泛化能力的一种技术。
如图为交叉验证的图解:
在LightGBM中,需要自定义参数,可以使用默认参数,也可以在网上找优秀的参数,也可以寻找软件开源包自动求参。
如下为一组适合本题的LightGBM参数:
params = {
'num_threads': 32,
'learning_rate': 0.01,
'objective': 'multiclass',
'num_class': 3,
'num_leaves': 31,
'min_data_in_leaf': 20,
'bagging_freq': 1,
'bagging_fraction': 0.8,
'feature_fraction': 0.8,
'metric': 'multi_error',
'early_stopping_rounds': 400,
}
然后我们进行LightGBM实现与交叉验证(CV)实现:
def lgb_cv(params, train_df, test_df, feat_cols, cat_feat_cols, target_col, stratified=False, nfold=5, num_boost_round=10000):
if stratified:
folds = StratifiedKFold(n_splits=nfold, shuffle=True, random_state=42)
else:
folds = KFold(n_splits=nfold, shuffle=True, random_state=42)
target = train_df[target_col]
oof = np.zeros((train_df.shape[0], 3), dtype=np.float64)
pred = np.zeros((test_df.shape[0], 3), dtype=np.float64)
for i, (trn_idx, val_idx) in enumerate(folds.split(train_df.index, train_df[target_col].astype(int))):
print(f'fold={i}', '- ' * 20)
trn_data = lgb.Dataset(train_df.loc[trn_idx, feat_cols], label=target.loc[trn_idx], categorical_feature=cat_feat_cols)
val_data = lgb.Dataset(train_df.loc[val_idx, feat_cols], label=target.loc[val_idx], categorical_feature=cat_feat_cols)
model = lgb.train(params, trn_data, num_boost_round, valid_sets=val_data, callbacks=[lgb.log_evaluation(200)])
oof[val_idx] = model.predict(train_df.loc[val_idx, feat_cols], num_iteration=model.best_iteration)
pred += model.predict(test_df[feat_cols], num_iteration=model.best_iteration) / nfold
cv = accuracy_score(target, oof.argmax(axis=-1))
return cv, oof, pred
这个定义了一个可以实现LightGBM并得到,cv
是交叉验证的准确度得分,oof
是训练集样本的交叉验证预测结果,而 pred
是测试集样本的交叉验证预测结果。这些变量都是评估模型性能的重要指标。
cv, oof, pred = lgb_cv(params, train_df, test_df, ori_all_feats, ori_cat_feats, 'Target')
print(cv)
simple_cv, simple_oof, simple_pred = cv, oof, pred
然后我们可以得到交叉验证的准确度得分(cv值)为0.8337123291251731,cv是衡量评估模型性能的最重要的指标。
3:XGBoost模型建立与交叉验证(CV)
我们使用和LightGBM实现相同的步骤:
params = {
# general parameters
'nthread': 32,
'objective': 'multi:softprob',
'num_class': 3,
'eval_metric': 'merror',
# tuning parameters
'learning_rate': 0.1259,
'max_depth': 3,
'min_child_weight': 85,
'subsample': 0.987,
'colsample_bytree': 0.5448,
'reg_lambda': 7.138,
}
def xgb_cv(params, train_df, test_df, feat_cols, cat_feat_cols, target_col, stratified=False, nfold=5, num_boost_round=10000):
if stratified:
folds = StratifiedKFold(n_splits=nfold, shuffle=True, random_state=42)
else:
folds = KFold(n_splits=nfold, shuffle=True, random_state=42)
target = train_df[target_col]
oof = np.zeros((train_df.shape[0], 3), dtype=np.float64)
pred = np.zeros((test_df.shape[0], 3), dtype=np.float64)
for i, (trn_idx, val_idx) in enumerate(folds.split(train_df.index, train_df[target_col].astype(int))):
print(f'fold={i}', '- ' * 20)
trn_data = xgb.DMatrix(train_df.loc[trn_idx, feat_cols], label=target.loc[trn_idx], enable_categorical=True)
val_data = xgb.DMatrix(train_df.loc[val_idx, feat_cols], label=target.loc[val_idx], enable_categorical=True)
model = xgb.train(params, trn_data, num_boost_round, evals=[(trn_data, 'train'), (val_data, 'valid')], verbose_eval=200, early_stopping_rounds=400)
oof[val_idx] = model.predict(val_data, iteration_range=(0, model.best_iteration + 1))
tst_data = xgb.DMatrix(test_df[feat_cols], enable_categorical=True)
pred += model.predict(tst_data, iteration_range=(0, model.best_iteration + 1)) / nfold
cv = accuracy_score(target, oof.argmax(axis=-1))
return cv, oof, pred
xgb_bo_cv, xgb_bo_oof, xgb_bo_pred = xgb_cv(params, fe_train_df, fe_test_df, null_imp_selected_feats, [], 'Target')
print(xgb_bo_cv)
最后得到XGBoost模型的cv值为0.8349407982435505
4:模型集成
这里的模型集成就是把我们之前得到的LightGBM和XGBoostt模型结合起来。模型集成有许多种方法,这里我们采用其中的一种:stacking,即每个模型预测的结果oof作为输 入,label作为目标,训练一个次级学习器,一般次级学习器会选一些简单的线性模型。
from sklearn.svm import SVC
svc = SVC()
trn_x = np.hstack([simple_oof, xgb_bo_oof])
tst_x = np.hstack([simple_pred, xgb_bo_pred])
svc.fit(trn_x, train_df['Target'])
stacking_pred = svc.predict(tst_x)
然后我们可以按提交格式上传我们的预测结果:
sub_df = pd.read_csv('../input/sample_submission.csv')
pred = stacking_pred
sub_df['Target'] = pred
sub_df['Target'] = sub_df['Target'].map(code2label)
sub_df.to_csv('prob_stacking.csv', index=False, header=True)
这些步骤就是表格类数据的机器学习的经典做法。
5:模型提升
通过上述答案得到的结果已经是较为不错的答案。如果还想进一步探索和提高,可以在特征工程和调参方面采用一些高阶方法。
点下关注,分享更多有关AI,数据分析和量化金融实用教程和实战项目。