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。
數據集
評估指標
賽題難點
本次比賽分為三個榜,每個榜選取的日期不同,有周內,也有周末。我們將周內看作正常日期,周末看作特殊日期。面對這兩類日期如何進行建模,如何建模盡可能達到最大的預測準確性。我們將本次比賽的難點歸納為如下幾點。
(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.?趨勢性
挖掘趨勢性也是我們提取特征的關鍵,我們主要構造特征定義如下:
即表示前后時段的差值,這里可以是入站流量也可以是出戰流量。同樣,我們考慮了每天對應當前時段,每天對應上個時段等。當然我們也可以考慮差比:
關鍵代碼:
補充:上面是比賽所用代碼,但賽后才發現有部分邏輯錯誤,這個錯誤從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] = 04.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-全球城市计算挑战赛-完整方案及关键代码分享(季军)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 首发:一份国内机器学习爱好者的性别比例的
- 下一篇: 推荐ApacheCN开源的一个机器学习路