前言
本次练习还是sofa的下一个竞赛,主要内容是特征工程,调参和模型选择可参考上两篇竞赛。(足球运动员身价估计)
MAE越小,说明模型预测得越准确。本文最终处理结果为MAC:19.0208,排名43。
1 数据分析
训练集中共有10441条样本,预测集中有7000条样本。每条样本代表一位球员,数据中每个球员有63项属性。数据中含有缺失值。
1.1 单特征处理
1.1.1 补充缺失值
print(train.isnull().any())# 缺失值检查
发现位置(rw-gk)的值都有缺失,原因是守门员仅有自己gk的值,没有其他位置的值,非守门员反之。
train = train.loc[:].fillna(0)
test = test.loc[:].fillna(0)
我们可以补充值为0,方便后面计算。
1.1.2 数据量化
①存在像work_rate_att和work_rate_def有序多分类变量
'''字符串特征转换为数字特征'''
work_rate_columns = ['work_rate_att', 'work_rate_def']
for i in work_rate_columns:
train[i] = pd.factorize(train[i])[0].astype(np.uint16)
for i in work_rate_columns:
test[i] = pd.factorize(test[i])[0].astype(np.uint16)
认为低中高又一定的顺序,按0、1、2赋值
②还有birth_date这种日期格式
'''生日转换为年龄'''
today = date.today().year
train['birth_date'] = pd.to_datetime(train['birth_date'])
train['age'] = today - train.birth_date.dt.year
test['birth_date'] = pd.to_datetime(test['birth_date'])
test['age'] = today - test.birth_date.dt.year
train = train.drop('birth_date', axis=1)
test = test.drop('birth_date', axis=1)
转换为年龄方便计算。
1.2 降维处理
观察训练集csv文件和使用数据透视表,比较守门员和非守门员区别。
图1可看出守门员和非守门员的y分布形状相似,但守门员数量比非守门员少。
图2和图3,average_ngk来自crossing到sliding_tackle的平均数,average_gk来自gk_diving到gk_reflexes的平均数。这是因为从数据直接观察可以发现守门员有些数据(gk_diving到gk_reflexes的)普遍较高,而有些数据(crossing到sliding_tackle)普遍偏低,非守门员则相反,于是尝试计算出平均值并比较,发现确实如此。
'''守门员专属能力的平均值'''
gk_columns = ['gk_diving', 'gk_diving', 'gk_handling', 'gk_kicking', 'gk_positioning', 'gk_reflexes']
train['average_gk'] = np.mean(list(train[i] for i in gk_columns), 0)
train['average_gk'] = np.around(train['average_gk'])
test['average_gk'] = np.mean(list(test[i] for i in gk_columns), 0)
test['average_gk'] = np.around(test['average_gk'])
train = train.drop(gk_columns, axis=1)
test = test.drop(gk_columns, axis=1)
'''非守门员专属能力的平均值'''
ngk_columns = ['crossing', 'finishing', 'heading_accuracy', 'short_passing', 'volleys', 'dribbling',
'curve', 'free_kick_accuracy', 'long_passing', 'ball_control', 'acceleration', 'sprint_speed',
'agility', 'reactions', 'balance', 'shot_power', 'jumping', 'stamina', 'strength',
'long_shots', 'aggression', 'interceptions', 'positioning', 'vision', 'penalties',
'marking', 'standing_tackle', 'sliding_tackle']
train['average_ngk'] = np.mean(list(train[i] for i in ngk_columns), 0)
train['average_ngk'] = np.around(train['average_ngk'])
test['average_ngk'] = np.mean(list(test[i] for i in ngk_columns), 0)
test['average_ngk'] = np.around(test['average_ngk'])
train = train.drop(ngk_columns, axis=1)
test = test.drop(ngk_columns, axis=1)
以上特征合成我们还进行一项处理:将处理前的特征删去。
特征合成视为降维处理,包含处理前的特征信息,而且还可以减少计算开销。
1.3 特征选择
模型选择xgboost回归模型,原因参考《竞赛练习—公共自行车使用量预测》这篇。同时还有个原因,xgboost训练过程中自动进行了特征选择,我们可以通过重要性排序观察,在调参前先筛出一些低重要性特征。(用xgboost模型对特征进行重要性排序)
'''划分守门员和非守门员训练集'''
gk_train = data[data['is_gk'] ==1]
y_gk_train = gk_train.pop('y')
ngk_train = data[data['is_gk'] ==0]
y_ngk_train = ngk_train.pop('y')
y_train = data.pop('y')
'''使用xgboost查看特征重要性'''
for x, y in [(data, y_train), (gk_train, y_gk_train), (ngk_train, y_ngk_train)]:
model = XGBRegressor().fit(x, y)
plot_importance(model, max_num_features=25, height=0.5)
pyplot.show()
认为守门员和非守门员之间有一定差别,选择将两者分开特征选择,分开训练。最终都选取重要性超过10的特征。
gk_columns=['age','potential','best_pos','club','sho','average_gk','dri','pac','phy','def','BMI','pas']
ngk_columns=['best_pos','potential','age','sho','cam','def','club','dri',
'international_reputation','pas','average_ngk','phy','nationality']
在考虑保留特征个数为多少时踩了一个坑,刚开始是采用以下代码来得到特征的得分以及筛选特征(代码来自:https://github.com/ymGdragon/Fragmentary_MachineLearning_Algorithm/blob/master/xgboost-23.py)
#筛选出重要的特征 (此为函数有关特征选择的部分,alg表示使用的算法)
feat_imp = pd.Series(alg.get_booster().get_fscore()).sort_values(ascending=False)
feat_sel = list(feat_imp.index)
feat_val = list(feat_imp.values)
featur = []
for i in range(len(feat_sel)):
featur.append([cols[int(feat_sel[i][1:])], feat_val[i]])
print('--------------------所有特征及其得分--------------------\n', featur)
feat_sel2 = list(feat_imp[feat_imp.values > target].index)
featur2 = []
for i in range(len(feat_sel2)):
featur2.append(cols[int(feat_sel2[i][1:])])
return featur2
通过运行得到特征的得分,同时考虑到前两次竞赛尝试删去特征后,信息不足,不利于模型较好地进行预测,故最终决定对守门员和非守门员数据的特征保留个数均在25个左右,但模型之后经过多次调参,模型的预测能力仍然较差,跟同学讨论后,采用以下代码来进行特征选择
#特征选择
def Select_feature(data):
#划分守门员和非守门员训练集
gk_train = data[data['is_gk'] == 1]
y_gk_train = gk_train.pop('y')
ngk_train = data[data['is_gk'] == 0]
y_ngk_train = ngk_train.pop('y')
y_train = data.pop('y')
#使用xgboost查看特征重要性
for x, y in [(data, y_train), (gk_train, y_gk_train), (ngk_train, y_ngk_train)]:
model = XGBRegressor().fit(x, y)
plot_importance(model, max_num_features=25, height=0.5)
plt.show()
gk_columns=['club','potential','age','league','gk','pac','nationality','dri','sho','BMI','reactions','pas']
ngk_columns=['potential','club', 'age','league','best_pos', 'nationality','BMI','vision','heading_accuracy','aggression','crossing','free_kick_accuracy','jumping','pac','long_shots']
return gk_train,y_gk_train,ngk_train,y_ngk_train,gk_columns,ngk_columns
运行结果如下:
最终决定保留的特征如下,保留的特征个数均在15个左右
#守门员保留的特征
gk_columns=['club','potential','age','league','gk','pac','nationality','dri','sho','BMI','reactions','pas']
#非守门员保留的特征
ngk_columns=['potential','club', 'age','league','best_pos', 'nationality','BMI','vision','heading_accuracy','aggression','crossing','free_kick_accuracy','jumping','pac','long_shots']
根据以上筛选出的特征进行多次调参,排名为:97, 模型预测结果的MAE为:21.6183
思考:由于分别根据以上两种方法进行特征筛选和调参后,得到的模型的预测能力相差较大,刚开始以为是这两种方法赋予特征重要性得分的原理不同,搜索后发现这两种方法大致是一样的,只是对特征重要性的输出形式不同。(参考:知乎)
要做属性重要性排名,可以用这个,输出的是一个图形:xgb.plot_importance(xgboost_model)
如果想输出数值,可以这样写:pd.Series(xgboost_model.get_fscore()).sort_values(ascending=False)
既然两段代码大致是一个意思的话,那么造成两次结果差距较大的原因主要是保留的特征个数不同,那在特征处理过程中应该如何确定保留的特征个数呢?如果只是一次一次去尝试的话,比较花费时间。在网上搜了一下这个问题,发现一个不错的方法:(参考:特征工程-特征选择、特征表达、特征预处理)
第一步是找到该领域懂业务的专家,让他们给一些建议。
比如:我们需要解决一个药品疗效的分类问题,那么先找到领域专家,向他们咨询哪些因素(特征)会对该药品的疗效产生影响,较大影响的和较小影响的都要。这些特征就是我们的特征的第一候选集。这个特征集合有时候也可能很大,在尝试降维之前,我们有必要用特征工程的方法去选择出较重要的特征结合,这些方法不会用到领域知识,而仅仅是统计学的方法。
之后可以采用合适的特征选择方法来选择特征
[特征选择方法了解:特征工程-特征选择、特征表达、特征预处理]
所以感觉下次在进行特征处理前,要先了解一下要预测的领域的一些知识,大体确认一下哪些特征比较关键,再进行特征的构造和特征的删除会好一点。(像这次在决定保留特征个数的时候,考虑了前两次sofasofa竞赛的经验是不太合理的,因为预测的领域不同。)
2 训练模型
观察训练集还可以发现y的预测值都在6以上,所以可以将预测结果设为6以上。
submit['y'] = np.array(test['pred'])
submit['y'] = submit['y'].map(lambda x:6 if x<6 else x)
submit.to_csv('prediction.csv', index=False)
使用网格搜索GridSearchCV进行调参
调参前:
26.538——最终成绩
21.294521517653838——gk_clf
27.97521351856118——ngk_clf
单独gk_clf调参:
25.9657——最终成绩
15.974057084529958——gk_clf
27.97521351856118——ngk_clf
单独ngk_clf调参:
19.5931——最终成绩
21.294521517653838——gk_clf
20.232612056551094——ngk_clf
同时调参:
19.0208——最终成绩
15.974057084529958——gk_clf
20.232612056551094——ngk_clf
两者的最佳参数:
gk_params={'n_estimators':800, 'learning_rate':0.01, 'max_depth':8, 'min_child_weight':1,
'gamma':0.3, 'colsample_bytree':0.9, 'subsample':0.8, 'reg_alpha':0.1, 'reg_lambda':3,
'criterion':'entropy', 'objective':'reg:squarederror'}
ngk_params={'n_estimators':400, 'learning_rate':0.1, 'max_depth':8, 'min_child_weight':4,
'gamma':0.2, 'colsample_bytree':0.9, 'subsample':0.9, 'reg_alpha':0.05, 'reg_lambda':3,
'criterion':'entropy', 'objective':'reg:squarederror'}
发现gk_clf调参后守门员的MAE降低很大,但最终成绩提升不多。ngk_clf调参后非守门员的MAE降低不大,但最终成绩提升较为同步。原因可能是测试集和训练集一样守门员的数据较少,所以守门员预测提升带来成绩提升不多。
小结
①本次是回归问题,数据最大问题就是特征较多。
②处理以上问题,我们进行特征工程,包含单特征处理、多特征处理中的降维和特征选择。(使用sklearn做单机特征工程)
③对表现出明显不同的数据集分开训练。
④使用xgboost的重要性特征排序选择特征,在回归到xgboost训练,可以大幅度降低耗时。