前言
这算是博主完整参加的第一个数据竞赛,真的收获了好多东西,对数据竞赛有了全方位的认识,也全程目睹了前排大佬神仙打架。这篇文章也算是对这次比赛历程的一个回顾,因为复赛没有怎么做,这里主要还是总结自己初赛时收获的tricks,欢迎小伙伴们一起交流学习!Tips:当然还要非常感谢鱼佬和阿泽两位大神的baseline和思路分享!
这里介绍一下团队的主要成员:
- 壹磊,北京交通大学运筹学与控制论专业研二在读
- yiyang,东华大学应用统计专业研二在读
- 潜心,上海师范大学计算机技术专业研二在读
文章目录
-
-
-
- 前言
- 一、赛题介绍:时间序列问题
- 二、初赛:允许使用当前值和特征穿越
- 三、复赛:不允许使用当前值和特征穿越
- 四、总结
-
-
一、赛题介绍:时间序列问题
比赛网址:http://challenge.xfyun.cn/topic/info?type=temperature
因为初赛和复赛规则几乎完全不同,可以说是两个不同的比赛。初赛可以定位为多变量回归问题,复赛则变成了一个基于时间序列预测的结果补全问题。说来惭愧,复赛的一个月的时间我们团队都在忙各种事情,基本上只有一个队友偶尔在做,所以这篇主要是我对初赛特征工程方面的总结。
二、初赛:允许使用当前值和特征穿越
初赛里我们团队成员还没有相识,因此各自单打独斗进行建模,毫无例外都使用了xgboost单模,最终的成绩分别为0.104、0.106和0.117,这样的做法的优势是使我们各自的模型存在一定差异性的,也为后续的模型融合创造了条件,最终经过模型融合和调整,我们队伍的初赛A帮成绩定格在0.10034,最终A榜排在36名,切换到B榜排名上升到28名(28/771),顺利入围复赛。
A榜
B榜
下面,我主要总结一下我在数据处理和特征工程中做的工作,有很多借鉴了鱼佬的baseline,并且没有做分桶特征,同时较少涉及特征穿越。(单模成绩:0.106 ,A榜排名:60名左右,我觉得B榜应该会有不小的提升)
- 缺失值补全(使用fillna操作)
# 缺失值补全
f = ['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']
train_df[f] = train_df[f].fillna(method='ffill')
test_df[f] = test_df[f].fillna(method='ffill')
- 异常值截断(假设数据服从正态分布,使用3σ法则进行截断,可用前后均值替换)
# 气压异常值前后均值替换
for f in tqdm(['indoorAtmo', 'outdoorAtmo']):
upper = data_df[f].mean()+ 3 * data_df[f].std()
lower = data_df[f].mean()- 3 * data_df[f].std()
for i in data_df[data_df[f] > upper].index:
data_df.loc[i,f] = (data_df.loc[i-1,f] + data_df.loc[i+1,f])/2
for i in data_df[data_df[f] < lower].index:
data_df.loc[i,f] = (data_df.loc[i-1,f] + data_df.loc[i+1,f])/2
- 一小时前对应分钟同期值和差分(使用shift操作)
# 一小时前同期值
for f in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']):
train_df['ago_1hour_{}'.format(f)] = train_df[f].shift(1*60)
test_df['ago_1hour_{}'.format(f)] = test_df[f].shift(1*2)
- 半小时前数据滑窗均值(使用rolling操作)
# 开窗
for f in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']):
train_rolling = train_df[f].rolling(window=30)
train_df['rolling_{}_mean'.format(f)] = train_rolling.mean()
test_rolling = test_df[f].rolling(window=2)
test_df['rolling_{}_mean'.format(f)] = test_rolling.mean()
- 按月日小时基本聚合特征,包含均值、中位数等(groupby,存在特征穿越)
# 按月日小时基本聚合特征
group_feats = []
for f in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']):
data_df['MDH_{}_medi'.format(f)] = data_df.groupby(['month','day','hour'])[f].transform('median')
data_df['MDH_{}_mean'.format(f)] = data_df.groupby(['month','day','hour'])[f].transform('mean')
......(各种统计量)
group_feats.append('MDH_{}_mean'.format(f))
- 基本交叉特征(加减乘除,主要是比值和差值)
# 基本交叉特征比值
for f1 in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo'] + group_feats):
for f2 in ['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']+group_feats:
if f1 != f2:
colname = '{}_{}_ratio'.format(f1, f2)
data_df[colname] = data_df[f1].values / data_df[f2].values
# 基本交叉特征差值(大-小)
for f1 in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']):
for f2 in ['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']:
if (f1 != f2) & (data_df[f1].mean() > data_df[f2].mean()):
colname = '{}_{}_differ'.format(f1, f2)
data_df[colname] = data_df[f1].values - data_df[f2].values
- 一小时前同期值基本交叉特征
# 一小时前同期值基本交叉特征
for f1 in tqdm(['ago_1hour_outdoorTemp','ago_1hour_outdoorHum','ago_1hour_outdoorAtmo','ago_1hour_indoorHum','ago_1hour_indoorAtmo']):
for f2 in ['ago_1hour_outdoorTemp','ago_1hour_outdoorHum','ago_1hour_outdoorAtmo','ago_1hour_indoorHum','ago_1hour_indoorAtmo']:
if f1 != f2:
colname = 'ago_1hour_{}_{}_ratio'.format(f1, f2)
data_df[colname] = data_df[f1].values / data_df[f2].values
- 历史信息提取——前n天同小时基本特征(包括初始特征和交叉特征)的均值(dt)
# 2days历史信息提取
data_df['dt'] = data_df['day'].values + (data_df['month'].values - 3) * 31
for f in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']+ratio_feats):
tmp_df = pd.DataFrame()
for t in range(15, 45):
tmp = data_df[data_df['dt'].isin([t-1,t-2])].groupby(['hour'])[f].agg({ 'mean'}).reset_index()
tmp.columns = ['hour','hit2days_{}_mean'.format(f)]
tmp['dt'] = t
tmp_df = tmp_df.append(tmp)
data_df = data_df.merge(tmp_df, on=['dt','hour'], how='left')
- 历史信息提取——前n小时基本特征(包括初始特征和交叉特征)均值和差分(dh,使用diff操作)
# 前1和2小时历史信息差分
data_df['dh'] = data_df['hour'].values + (data_df['dt'].values -14) * 24
for f in tqdm(['outdoorTemp','outdoorHum','outdoorAtmo','indoorHum','indoorAtmo']+ratio_feats):
tmp_df = data_df.groupby(['dh'])[f].agg(['mean']).reset_index()
tmp_df.columns = ['dh','bef_{}_mean'.format(f)]
tmp_df['diff1_{}_mean'.format(f)] = tmp_df['bef_{}_mean'.format(f)].diff(1)
tmp_df['diff2_{}_mean'.format(f)] = tmp_df['bef_{}_mean'.format(f)].diff(2)
data_df = data_df.merge(tmp_df, on=['dh'], how='left')
三、复赛:不允许使用当前值和特征穿越
因为复赛的题目相比初赛变动较大,加上我们每个人的时间安排都比较紧张就没有怎么做,更好的解决方案大家可以去参考top大神们开源代码!(我也在等!)
四、总结
- 数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。特征工程无敌重要!
- 特征工程差异性较大的两个模型做简单的加权融合就能得到很好的效果!
- 时间序列问题的特征构造真是一门大学问!
- 做数据科学比赛每天打榜和AB榜切换真是一个美妙的过程!