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。

数据集

评估指标

赛题难点

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

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

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

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

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

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

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

1.模型框架

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

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

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

2.模型细节

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

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

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

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

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

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

23号和24号入站流量分布

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

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

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

核心思路Part2-特征工程

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

1. 强相关性信息

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

2. 趋势性

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

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

关键代码:

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'].valuesdf_feature_y['tmp_hours'] = df_feature_y['stationID'].values * 1000 + df_feature_y['hour'].valuesfor i in range(n): # 前n天每一天d = day - i - 1df_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']# sumdic_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).valuesdf_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).valuesdf_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'] - 1df_d['tmp_hours_bf'] = df['stationID'] * 1000 + df['hour'] - 1# sumdic_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)).valuesdf_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).valuesdf_feature_y['_bf1_' + str(day -d) + '_outnum_hour'] = df_feature_y['tmp_hours'].map(dic_outnums).valuesfor 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.周期性

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

关键代码:

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] = 0for 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 = colsdf_tmp.reset_index(inplace = True)df_station = df_station.merge(df_tmp, on ='stationID', how='left')return df_station

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

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

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

1.传统方案

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

2.平滑趋势

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

具体步骤:

  • 删除周六周日

  • 平滑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)-1for 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)-1for 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)-1for 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' ] = 0data.loc[data.outNums_new < 0 , 'outNums_new'] = 0data['inNums'] = data['inNums_new']data['outNums'] = data['outNums_new']

3.时序Stacking

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

4.模型融合

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

实验结果Part4

A榜结果第一

BC榜综合考虑第二名

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

比赛经验总结

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

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

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

请关注和分享↓↓↓ 

本站的知识星球(黄博的机器学习圈子)ID:92416895

目前在机器学习方向的知识星球排名第一

往期精彩回顾

  • 良心推荐:机器学习入门资料汇总及学习建议(2018版)

  • 黄海广博士的github镜像下载(机器学习及深度学习资源)

  • 吴恩达老师的机器学习和深度学习课程笔记打印版

  • 机器学习小抄-(像背托福单词一样理解机器学习)

  • 首发:深度学习入门宝典-《python深度学习》原文代码中文注释版及电子书

  • 机器学习的数学基础

  • 机器学习必备宝典-《统计学习方法》的python代码实现、电子书及课件

  • 吐血推荐收藏的学位论文排版教程(完整版)

  • Python环境的安装(Anaconda+Jupyter notebook+Pycharm)

  • Python代码写得丑怎么办?推荐几个神器拯救你

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

  1. TIANCHI天池-OGeek算法挑战赛-完整方案及代码(亚军)

    首先很幸运拿到TIANCHI天池-OGeek算法挑战赛大赛的亚军,同时非常感谢大佬队友的带飞,同时希望我的分享与总结能给大家带来些许帮助,并且一起交流学习.(作者:王贺,知乎:鱼遇雨欲语与余) 赛题链 ...

  2. 天池发起全球城市计算AI挑战赛-总奖池30万人民币

    点击我爱计算机视觉标星,更快获取CVML新技术 大赛概况 2019年,杭州市公安局联合阿里云智能启动首届全球城市计算AI挑战赛,本次挑战赛的题目选定为"地铁乘客流量预测".地铁目前 ...

  3. 在福州,看见智慧城市的务实完整方案

    [深几度·新基建及智慧城市系列] 撰稿|吴俊宇 「摘要:对国内当前缺乏明确样板的情况来说,一个在生态上具备聚合效应,在应用上具备纵深挖掘的智慧城市可能才是真正能让产业看到务实可落地的好的智慧城市.」 ...

  4. 2020中国高校计算机大赛·华为云大数据挑战赛--热身赛 Rank7 思路及代码分享

    队名:无能万金油 2020中国高校计算机大赛·华为云大数据挑战赛–热身赛 热身赛:Rank 7 CSDN博客:我的博客 数据相关知识和分析参考:[上分指南] 2020华为云大数据挑战赛热身赛如何&qu ...

  5. 2019天池大数据-全球城市计算AI挑战赛经验分享

    预选赛(2018年3月20日-3月30日),决赛(4月9日-11日),虽然这个题目真的很简单,但是还是没做好,初赛只拿到231名,拿过来练手还是比较不错的,本方案的思路是使用xgboost模型,构建数 ...

  6. 天池全球城市计算AI挑战赛-地铁客流量预测 22/2319

    感觉这个比赛是一个特别有意义的比赛,很遗憾,没有进入决赛,如果感兴趣的可以参考我的github,还有完整数据集下载. https://github.com/Justdcy/subway_traffic ...

  7. 2023美国大学生数学建模竞赛E题光污染完整数据集和求解代码分享

    目录 数据集收集 GeoNames 地理数据集 全球各国的经纬度数据集 协调一致的全球夜间灯光(1992 - 2018)数据集 NASA 的 EaN Blue Marble 2016 数据集 全球夜间 ...

  8. 大气层Shader(完整代码分享)

    老规矩先上图: 实现解析: 一.模型用的是Unity自带的Sphere 二.因为有透明的过度,所以需要设置为透明的 三.因为大气是包裹地球,并在地球底层的因而我们使用到的是模型的背面 四.最后是得用球 ...

  9. 对“科大讯飞2021丨广告点击率预估挑战赛 Top1方案(附完整代码)_Jack_Yang的博客-CSDN博客”的补充。

    这篇文章的初衷是针对科大讯飞2021丨广告点击率预估挑战赛 Top1方案(附完整代码)_Jack_Yang的博客-CSDN博客进行补充. 博客的信息量很少,对任务背景的介绍也不太对,说实话令人费解.我 ...

最新文章

  1. seata 如何开启tcc事物_微服务分布式事务4种解决方案实战
  2. 30天提升技术人的写作力-第二十三天
  3. android 条码扫描控件,Android Zxing条码扫描自定义控件(附代码)
  4. 数学归纳法在数据结构与算法分析设计中的应用
  5. STS热部署,springboot项目中修改代码不用重新启动服务
  6. JavaScript函数重载(js函数重载)
  7. 数据库系统概论第五版(王珊)-基础篇(三)
  8. 【总结】防病毒网关---防毒墙
  9. vnc远程访问linux服务器,如何通过 VNC 远程访问 Linux 虚拟机
  10. 郝斌C语言开篇 C语言简介
  11. 如何从硬盘安装Linux
  12. EAUML日拱一卒-活动图::活动分区
  13. xcode-instrument
  14. 202109青少年软件编程(Python)等级考试(五级编程题)
  15. 咦?智能颈部按摩仪还能语音播报,快搞起来!
  16. 这100 个网络基础知识,看完成半个网络高手
  17. 微机保护装置智能操控及无线测温等产品在某助剂厂新建项目的应用
  18. mmclassification
  19. Spring+Hibernate双数据源测试Mysql集群读写分离(转自http://blog.csdn.net/hzw2312/article/details/9083519)
  20. 抓娃娃机按钮按几下_抓娃娃机技巧

热门文章

  1. python面向编程:类继承、继承案例、单继承下属性查找、super方法
  2. python中获取中位数
  3. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些
  4. java中static代码块与非静态代码块
  5. python之旅(六) - 自省
  6. 基于matlab 求多边费马点,POJ2420(求多边形费马点) | 学步园
  7. 使用python画图表_利用Python绘制数据的瀑布图的教程
  8. 写毕业论文的最容易踩的几个坑
  9. GraphPad Prism 9.2.0 更新新功能说明
  10. pmp思维导图 第六版_《每天一小时,两月过PMP》备考指南(附计划表)