首页 > 其他分享 >TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)

时间:2022-12-12 13:34:21浏览次数:97  
标签:hour df feature 季军 TIANCHI stationID 挑战赛 data day


TIANCHI-全球城市计算挑战赛--参赛者将根据主办方提供的地铁人流相关数据,挖掘隐藏在背后的出行规律,准确预测各个地铁站点未来流量的变化。主办方希望通过这次挑战赛,用大数据和人工智能等技术助力未来城市安全出行。

本文的开源方案作者:王贺

参赛成员介绍:


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据

接下来将会呈现完整方案!!!满满干货!!!

竞赛官网:

​https://tianchi.aliyun.com/competition/entrance/231708/introduction​

数据集下载链接:

​https://pan.baidu.com/share/init?surl=exjtkUqYPzdWKsCUYGix_g​

提取码:0qsm

赛题分析

大赛以“地铁乘客流量预测”为赛题,参赛者可通过分析地铁站的历史刷卡数据,预测站点未来的客流量变化,帮助实现更合理的出行路线选择,规避交通堵塞,提前部署站点安保措施等,最终实现用大数据和人工智能等技术助力未来城市安全出行。

问题简介,通过分析地铁站的历史刷卡数据,预测站点未来的每十分钟出入客流量。

比赛训练集包含1月1日到1月25日共25天地铁刷卡数据记录。分为A、B、C三个榜,分别增加一天的数据记录,预测接下来一天每个站点每十分钟出入客流量。评估指标为MAE。

数据集

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_02

评估指标


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_03

赛题难点

本次比赛分为三个榜,每个榜选取的日期不同,有周内,也有周末。我们将周内看作正常日期,周末看作特殊日期。面对这两类日期如何进行建模,如何建模尽可能达到最大的预测准确性。我们将本次比赛的难点归纳为如下几点。

(1)本次比赛的label需要自己构建, 如何建模使我们能在给定的数据集上达到尽可能大的预测准确性?

(2)对于训练集不同时间段的选取对最终结果都很造成一定的影响,如何选用时间段,让训练集分布和测试集分布类似,也是本次比赛的关键之一。

(3)如何刻画每个时间段的时序特点,使其能够捕捉数据集的趋势性,周期性,循环性。

(4)地铁站点的流量存在太多影响因素,比如同时到站,突发情况,或者是盛大活动等,所以该如何处理异常值&保证模型稳定的情况。

针对上面的几个难点,下面我们分块阐述我们团队算法设计的思路&细节,我们的核心思路主要分为基于EDA的建模模块、特征工程模块以及模型训练&融合三个部分。

核心思路Part1- EDA-based建模框架

1.模型框架

模型我们采用滑窗滚动(天)的方式进行构建,这样可以防止因为某一天存在奇异值而导致模型训练走偏。最后将所有滚动滑窗的标签以及特征进行拼接形成我们最终的训练集。

滑窗的方式可以参考下图:


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_04

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_05

对于常见得时序问题时,都可以采样这种方式来提取特征,构建训练集。

2.模型细节

上面滑窗滚动需要选择分布于测试集类似的进行label的构建才能取得较好的结果,所以在此之前我们需要对分布差异大的数据进行删除。

这里我们进行了简单得EDA来分析label得分布情况。(好的EDA能够帮助你理解数据,挖掘更多细节,在比赛中必不可少)


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_06

5号-10号各时刻入站流量分布

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_07

12号-18号各时刻入站流量分布

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_08

19号-25号各时刻入站流量分布

从三幅图中可以看出周末与周内分布有很大差异,所以我们将测试集为周末和测试集为周内经行区别对待,保证训练集分布的稳定。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_09

23号和24号入站流量分布

从图中可以看出相同时间段流量突然相差巨大。可以考虑是因为突发性活动,特别事件等因素影响。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_10

元旦节及之后几天的入站流量分布

由节假日流量分布,我们发现,节假日的信息和非节假日的分布差异非常大,所以我们也选择将其删除。

核心思路Part2-特征工程

有了模型的框架,下面就是如何对每个站点不同时刻的流量信息进行刻画,此处需要切身地去思考影响地铁站点流量的因素,并从能使用的数据中思考如何构造相关特征来表示该因素。最终通过大量的EDA以及分析,我们通过以下几个模块来对地铁流量的特征进行构建。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_11

1. 强相关性信息

强相关性信息主要发生在每天对应时刻,所以我们分别构造了小时粒度和10分钟粒度的出入站流量特征。考虑到前后时间段流量的波动因素,所以又添加上个时段和下个时段,或者上两个和下两个时段的流量特征。同时还构造了前N天对应时段的流量。更进一步,考虑到相邻站点的强相关性,添加相邻两站对应时段的流量。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_12

2. 趋势性

挖掘趋势性也是我们提取特征的关键,我们主要构造特征定义如下:


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_13

即表示前后时段的差值,这里可以是入站流量也可以是出战流量。同样,我们考虑了每天对应当前时段,每天对应上个时段等。当然我们也可以考虑差比:


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_14

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_15

关键代码:

def time_before_trans(x,dic_):
if x in dic_.keys():
return dic_[x]
else:
return np.nan
def generate_fea_y(df, day, n):
df_feature_y = df.loc[df.days_relative == day].copy()

df_feature_y['tmp_10_minutes'] = df_feature_y['stationID'].values * 1000 + df_feature_y['ten_minutes_in_day'].values
df_feature_y['tmp_hours'] = df_feature_y['stationID'].values * 1000 + df_feature_y['hour'].values

for i in range(n): # 前n天每一天
d = day - i - 1
df_d = df.loc[df.days_relative == d].copy() # 当天的数据

# 特征1:过去在该时间段(一样的时间段,10minutes)有多少出入量
df_d['tmp_10_minutes'] = df['stationID'] * 1000 + df['ten_minutes_in_day']
df_d['tmp_hours'] = df['stationID'] * 1000 + df['hour']
# sum
dic_innums = df_d.groupby(['tmp_10_minutes'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_10_minutes'])['outNums'].sum().to_dict()
df_feature_y['_bf_' + str(day -d) + '_innum_10minutes'] = df_feature_y['tmp_10_minutes'].map(dic_innums).values
df_feature_y['_bf_' + str(day -d) + '_outnum_10minutes'] = df_feature_y['tmp_10_minutes'].map(dic_outnums).values

# 特征2:过去在该时间段(小时)有多少出入量
dic_innums = df_d.groupby(['tmp_hours'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours'])['outNums'].sum().to_dict()
df_feature_y['_bf_' + str(day -d) + '_innum_hour'] = df_feature_y['tmp_hours'].map(dic_innums).values
df_feature_y['_bf_' + str(day -d) + '_outnum_hour'] = df_feature_y['tmp_hours'].map(dic_outnums).values

# 特征3: 上10分钟
df_d['tmp_10_minutes_bf'] = df['stationID'] * 1000 + df['ten_minutes_in_day'] - 1
df_d['tmp_hours_bf'] = df['stationID'] * 1000 + df['hour'] - 1
# sum
dic_innums = df_d.groupby(['tmp_10_minutes_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours_bf'])['outNums'].sum().to_dict()
df_feature_y['_bf21_' + str(day -d) + '_innum_10minutes'] = df_feature_y['tmp_10_minutes'].agg(lambda x: time_before_trans(x,dic_innums)).values
df_feature_y['_bf1_' + str(day -d) + '_outnum_10minutes'] = df_feature_y['tmp_10_minutes'].agg(lambda x: time_before_trans(x,dic_outnums)).values

# 特征4:上个小时情况
dic_innums = df_d.groupby(['tmp_hours_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours_bf'])['outNums'].sum().to_dict()
df_feature_y['_bf1_' + str(day -d) + '_innum_hour'] = df_feature_y['tmp_hours'].map(dic_innums).values
df_feature_y['_bf1_' + str(day -d) + '_outnum_hour'] = df_feature_y['tmp_hours'].map(dic_outnums).values


for col in ['tmp_10_minutes','tmp_hours']:
del df_feature_y[col]

return df_feature_y

补充:上面是比赛所用代码,但赛后才发现有部分逻辑错误,这个错误从A榜到C榜都没发现

# 错误代码
dic_innums = df_d.groupby(['tmp_10_minutes_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours_bf'])['outNums'].sum().to_dict()
# 修改后代码
dic_innums = df_d.groupby(['tmp_10_minutes_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_10_minutes_bf'])['outNums'].sum().to_dict()

3.周期性

由于周末分布类似,工作日分布类似。所以我们选择对应日期对应时间段的信息进行特征的构建,具体地:


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_16

关键代码:

columns = ['_innum_10minutes','_outnum_10minutes','_innum_hour','_outnum_hour']
# # 过去n天的sum,mean
for i in range(2,left):
for f in columns:
colname1 = '_bf_'+str(i)+'_'+'days'+f+'_sum'
df_feature_y[colname1] = 0
for d in range(1,i+1):
df_feature_y[colname1] = df_feature_y[colname1] + df_feature_y['_bf_'+str(d) +f]
colname2 = '_bf_'+str(d)+'_'+'days'+f+'_mean'
df_feature_y[colname2] = df_feature_y[colname1] / i

# 过去n天的mean的差分
for i in range(2,left):
for f in columns:
colname1 = '_bf_'+str(d)+'_'+'days'+f+'_mean'
colname2 = '_bf_'+str(d)+'_'+'days'+f+'_mean_diff'
df_feature_y[colname2] = df_feature_y[colname1].diff(1)
df_feature_y.loc[(df_feature_y.hour==0)&(df_feature_y.minute==0), colname2] = 0

4.stationID相关特征

主要来挖掘不同站点及站点与其它特征组合得热度,关键代码:

def get_stationID_fea(df):
df_station = pd.DataFrame()
df_station['stationID'] = df['stationID'].unique()
df_station = df_station.sort_values('stationID')

tmp1 = df.groupby(['stationID'])['deviceID'].nunique().to_frame('stationID_deviceID_nunique').reset_index()
tmp2 = df.groupby(['stationID'])['userID'].nunique().to_frame('stationID_userID_nunique').reset_index()

df_station = df_station.merge(tmp1,on ='stationID', how='left')
df_station = df_station.merge(tmp2,on ='stationID', how='left')

for pivot_cols in tqdm_notebook(['payType','hour','days_relative','ten_minutes_in_day']):

tmp = df.groupby(['stationID',pivot_cols])['deviceID'].count().to_frame('stationID_'+pivot_cols+'_cnt').reset_index()
df_tmp = tmp.pivot(index = 'stationID', columns=pivot_cols, values='stationID_'+pivot_cols+'_cnt')
cols = ['stationID_'+pivot_cols+'_cnt' + str(col) for col in df_tmp.columns]
df_tmp.columns = cols
df_tmp.reset_index(inplace = True)
df_station = df_station.merge(df_tmp, on ='stationID', how='left')
return df_station

核心思路Part3-模型训练&融合

模型训练方面我们主要有三个方案,分别是传统方案、平滑趋势和时序stacking。最后将这三个方案预测的结果根据线下验证集的分数进行加权融合。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_数据_17

由于C榜分数得优越性,所以此处我们主要阐述C榜的方案。

1.传统方案


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_18

由于C榜测试集为周内数据,所以我们移除了周末数据,保证分布基本一致,为了保持训练集的周期性,我们移除了周一和周二。这也作为我们最基本的方案进行建模。

2.平滑趋势

我们设计了一种处理奇异值的方法,也就是第二个方案平滑趋势。方案思想是,对于周内分布大体相同的日期,如果相同时刻流量出现异常波动,那么我们将其定义为奇异值。然后选取与测试集有强相关性的日期作为基准,比如C榜测试集为31号,那么选择24号作为基准,对比24号与其它日期的相对应时刻的站点流量情况。这里我们构造其它日期对应24号时刻流量的趋势比,根据这个趋势比去修改对应时刻中每个10分钟的流量。因为小时的流量更具稳定,所以根据小时确定趋势比,再修改小时内10分钟的流量。对流量进行修改后再进行传统方案的建模,这里我们回保留周一和周二的数据。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_19

具体步骤:

  • 删除周六周日
  • 平滑24号之前日期对应24号的时刻流量趋势
  • 常规训练

下面将给出平滑趋势关键代码:

if (test_week!=6)&(test_week!=5):
inNums_hour = data[data.day!=31].groupby(['stationID','week','day','hour'])['inNums' ].sum().reset_index(name='inNums_hour_sum')
outNums_hour = data[data.day!=31].groupby(['stationID','week','day','hour'])['outNums'].sum().reset_index(name='outNums_hour_sum')
# 合并新构造特征
data = data.merge(inNums_hour , on=['stationID','week','day','hour'], how='left')
data = data.merge(outNums_hour, on=['stationID','week','day','hour'], how='left')
data.fillna(0, inplace=True)
# 提取24号流量
test_nums = data.loc[data.day==24, ['stationID','ten_minutes_in_day','inNums_hour_sum','outNums_hour_sum']]
test_nums.columns = ['stationID','ten_minutes_in_day','test_inNums_hour_sum' ,'test_outNums_hour_sum']
# 合并24号流量
data = data.merge(test_nums , on=['stationID','ten_minutes_in_day'], how='left')
# 构造每天与的趋势
data['test_inNums_hour_trend'] = (data['test_inNums_hour_sum'] + 1) / (data['inNums_hour_sum'] + 1 )
data['test_outNums_hour_trend'] = (data['test_outNums_hour_sum'] + 1) / (data['outNums_hour_sum'] + 1)

if (test_week!=6)&(test_week!=5):
# 初始化新的流量
data['inNums_new'] = data['inNums']
data['outNums_num'] = data['outNums']
for sid in range(0,81):
print('inNums stationID:', sid)
for d in range(2,24):
inNums = data.loc[(data.stationID==sid)&(data.day==d),'inNums']
trend = data.loc[(data.stationID==sid)&(data.day==d),'test_inNums_hour_trend']
data.loc[(data.stationID==sid)&(data.day==d),'inNums_new'] = trend.values*(inNums.values+1)-1
for d in range(25,26):
inNums = data.loc[(data.stationID==sid)&(data.day==d),'inNums']
trend = data.loc[(data.stationID==sid)&(data.day==d),'test_inNums_hour_trend']
data.loc[(data.stationID==sid)&(data.day==d),'inNums_new'] = trend.values*(inNums.values+1)-1

for sid in range(0,81):
print('outNums stationID:', sid)
for d in range(2,24):
outNums = data.loc[(data.stationID==sid)&(data.day==d),'outNums']
trend = data.loc[(data.stationID==sid)&(data.day==d),'test_outNums_hour_trend']
data.loc[(data.stationID==sid)&(data.day==d),'outNums_online'] = trend.values*(outNums.values+1)-1
for d in range(25,26):
outNums = data.loc[(data.stationID==sid)&(data.day==d),'outNums']
trend = data.loc[(data.stationID==sid)&(data.day==d),'test_outNums_hour_trend']
data.loc[(data.stationID==sid)&(data.day==d),'outNums_new'] = trend.values*(outNums.values+1)-1

# 后处理
data.loc[data.inNums_new < 0 , 'inNums_new' ] = 0
data.loc[data.outNums_new < 0 , 'outNums_new'] = 0

data['inNums'] = data['inNums_new']
data['outNums'] = data['outNums_new']

3.时序Stacking

因为历史数据中存在一些未知的奇异值,例如某些大型活动会导致某些站点在某些时刻流量增加,这些数据的影响很大,为了减小此类数据的影响,我们用了时序stacking的方式进行解决,如果模型预测结果和我们的真实结果相差较大,那么此类数据就是异常的,方案的可视化如下,通过下面的操作,我们线下和线上都能得到稳定的提升。


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_20

4.模型融合


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_时间段_21

三个方案各具优势,线下的表现的相关性也较低,经过过融合后线下的结果更加稳定,最终我们依线下CV的表现对其进行加权融合。

实验结果Part4


TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_22

A榜结果第一

TIANCHI-全球城市计算挑战赛-完整方案及关键代码分享(季军)_机器学习_23

BC榜综合考虑第二名

上面的代码作者在线上A榜取得了第一名的成绩。在BC榜去掉复现失败的队伍,也能取得第二的成绩。

比赛经验总结

1. 模型拥有较强的鲁棒性,在A榜取得了第一,BC榜综合成绩上第二

2. 设计了一种处理奇异值的方法, 线下线上都取得了一致的提升

3. 较为完备的时序特征工程 + 不同时段的数据选择

请关注和分享↓↓↓ 


标签:hour,df,feature,季军,TIANCHI,stationID,挑战赛,data,day
From: https://blog.51cto.com/u_15671528/5929487

相关文章

  • 2022第三届云原生编程挑战赛--Serverless VSCode WebIDE使用体验
    2022第三届云原生编程挑战赛--ServerlessVSCodeWebIDE使用体验​​活动背景​​​​赛题说明​​​​ServerlessVSCodeWebIDE体验​​​​体验入口​​​​什么是Server......
  • MathorCup 高校数学建模挑战赛——大数据竞赛
    MathorCup 高校数学建模挑战赛——大数据竞赛练习题:观影大数据分析王 S 聪想要在海外开拓万 D电影的市场,这次他在考虑:怎么拍商业电影才能赚钱?毕竟一些制作成本超过......
  • 微信大数据挑战赛:周星星方案汇总
    本文在比赛结束前将持续更新...5.23周星星方案已更新.多模态短视频分类是视频理解领域的基础技术之一,在安全审核、推荐运营、内容搜索等领域有着十分非常广泛的应用。一条......
  • 尝试了一下编程挑战赛
    不太习惯比赛用的编辑器,没法做过程的调试,这个就很考基本功了。一是是对算法已经完全心理有数,要不然调试起来很困难。二是要对语法非常熟,像我这种经常要查语法帮助的人,哈哈......
  • 2022 云原生编程挑战赛圆满收官,见证冠军战队的诞生
    11月3日,天池大赛·第三届云原生编程挑战赛在杭州云栖大会圆满收官。三大赛道18大战队手历经3个月激烈的角逐,终于交上了满意的答卷,同时也捧回了属于他们的荣耀奖杯。......
  • [复现]DASCTF Sept X 浙江工业大学秋季挑战赛-PWN
    hehepwn一开始泄露stack地址,然后写入shellcode返回到shellcode执行frompwnimport*context.os='linux'context.log_level="debug"context.arch='amd64'p=......
  • 登顶之路|数字海南架构师谈云原生编程挑战赛参赛心路历程
    2022年11月3日,第三届云原生编程挑战赛即将迎来终极答辩,18支战队、32位云原生开发者入围决赛,精彩即将开启。云原生编程挑战赛项目组特别策划了《登顶之路》系列选手......
  • 焦点科技编程挑战赛2022题解
    比赛说明:比赛在四个学校开展,南理南航南农和矿大。题目查找文本差异要求origin和dest中分别包含1000w+条数据dest对数据进行了打乱操作,即origin和dest中相同数据行的......
  • DASCTF X GFCTF 2022十月挑战赛 pwn R()P
    R()P⾼版本上GCC编译的程序,没有csu这种好⽤的gadget可以⽤由于是优化过的编译,没有rbp链,⻓度参数通过rsp取得,地址通过rax取得这就给了我们直接控制read的可能,可以直接......
  • DASCTF X CBCTF 2022九月挑战赛
    DASCTFXCBCTF2022九月挑战赛期中考结束了有点时间,做点题熟一下手,欢迎讨论dino3D审计源码找到了发送请求的部分importrequestsfromhashlibimportmd5import......