本博客分享新人第一次参加天池比赛的实况记录,比较完整地给出了数据预处理,缺失值补全,特征分析过程以及训练和交叉验证的注意事项,适合数据挖掘新人找到解题思路,全程没有调参,没有模型融合,只凭一手简单的特征和xgboost,最后止步41/1716,基本上可以作为时间序列预测类的比赛的baseline.完整代码在Github
(ps. 不是我不调参,不融合模型,是以现在的特征即使做了这些,提高也不会很大,所以还是特征的问题,可能太简单了)
1. 数据和题目说明
这个比赛的目标是提供一些路段流量的历史信息, 以此来预测未来一段时间的交通流量, 提供的数据一共有3个表: link_info, link_tops 和travel_time. 分别如下所示:
link_infos = pd.read_csv('../raw/gy_contest_link_info.txt', delimiter=';', dtype={
'link_ID': object})
print link_infos.head(5)
link_ID length width link_class
0 4377906289869500514 57 3 1
1 4377906284594800514 247 9 1
2 4377906289425800514 194 3 1
3 4377906284525800514 839 3 1
4 4377906284422600514 55 12 1
link_info表里共存着每条路的id, 长度, 宽度和类型,共有132条路
link_tops = pd.read_csv('../raw/gy_contest_link_top.txt', delimiter=';', dtype={
'link_ID': object})
print link_tops.head(5)
link_ID in_links out_links
0 4377906289869500514 4377906285525800514 4377906281969500514
1 4377906284594800514 4377906284514600514 4377906285594800514
2 4377906289425800514 NaN 4377906284653600514
3 4377906284525800514 4377906281234600514 4377906280334600514
4 4377906284422600514 3377906289434510514#4377906287959500514 4377906283422600514
link_top里储存每一条路的上下游关系, in_links里放着这条路的上游路id, 中间用#
分割, 而out_links里则给出了这条路的下游路id; 下游路可以理解为出路, 上游路为入路
df = pd.read_csv('../raw/quaterfinal_gy_cmp_training_traveltime.txt', delimiter=';', dtype={
'link_ID': object})
print df.head(5)
link_ID date time_interval travel_time
0 4377906283422600514 2017-05-06 [2017-05-06 11:04:00,2017-05-06 11:06:00) 3.00
1 3377906289434510514 2017-05-06 [2017-05-06 10:42:00,2017-05-06 10:44:00) 1.00
2 3377906285934510514 2017-05-06 [2017-05-06 11:56:00,2017-05-06 11:58:00) 35.20
3 3377906285934510514 2017-05-06 [2017-05-06 17:46:00,2017-05-06 17:48:00) 26.20
4 3377906287934510514 2017-05-06 [2017-05-06 10:52:00,2017-05-06 10:54:00) 10.40
travel_time表里存着这132条路从2017.4-2017.6以及2016.7每天车通过路的平均旅行时间, 统计的时间间隔为2分钟; 除了2016.4到6月每天的信息, 还有2017.7月每天6:00-8:00, 13:00-15:00, 16:00-18:00的记录, 然后我们需要预测的就是7月每天在早高峰, 午平峰, 晚高峰三个时间段(8:00-9:00, 15:00-16:00, 18:00-19:00)每条路上的车平均旅行时间
2. 题目分析和思路
这是一个关于时间序列预测的问题, 并不是普通的回归问题, 而是自回归, 一般的回归问题比如最简单的线性回归模型:Y=a*X1+b*X2
, 我们讨论的是因变量Y关于两个自变量X1和X2的关系, 目的是找出最优的a和b来使预测值y=a*X1+b*X2
逼近真实值Y. 而自回归模型不一样, 在自回归中, 自变量X1和X2都为Y本身, 也就是说Y(t)=a*Y(t-1)+ b*Y(t-2)
,其中Y(t-1)
为Y在t-1时刻的值, 而 Y(t-2)
为Y在t-2时刻的值, 换句话说, 现在的Y值由过去的Y值决定, 因此自变量和因变量都为自身, 这种回归叫自回归.
根据题目给出的信息, 除了路本身的信息外, 训练数据基本上只有旅行时间, 而我们要预测的也是未来的平均旅行时间, 而且根据我们的常识, 现在的路况跟过去一段时间的路况是很有关系的, 因此该问题应该是一个自回归问题, 用过去几个时刻的交通状况去预测未来时刻的交通状况
传统的自回归模型有自回归模型(AR)、移动平均模型(MA)、自回归移动平均模型(ARMA)以及差分自回归移动平均模型(ARIMA), 这些自回归模型都有着严格理论基础,讲究时间的平稳性, 需要对时间序列进行分析才能判断是否能使用此类模型. 这些模型对质量良好的时间序列有比较高的精度, 但此比赛中有大量的缺失值, 因此我们并没有采用此类模型. 我们的思路其实很简单: 就是构建Y(t)=a*Y(t-1)+ b*Y(t-2)+..
, 但并不是用的线性模型, 用的是基于树的非线性模型, 比如随机森林和梯度提升树.
3. 数据分析
3.1 特征变换
先对原始数据进行一些分析, 首先了解一下平均旅行时间的分布:
fig, axes = plt.subplots(nrows=2, ncols=1)
df['travel_time'].hist(bins=100, ax=axes[0])
df['travel_time'] = np.log1p(df['travel_time'])
df['travel_time'].hist(bins=100, ax=axes[1])
plt.show()
我们发现这个平均旅行时间变量travel_time是一个长尾分布, 也就是大数值的特别少, 而大部分数据都集中在很小的区域, 如上面的图, 因此做一个log的特征变换, 一般对数变换为ln(x+1), 避免x=0而出现负无穷大, 可以看出经过对数变换后, 数据的分布非常均匀, 类似正态分布, 比较适合模型来处理
3.2 数据平滑
即使做了log变换后, 还是有部分travel_time值过于大, 为了消除一些离群点的影响, 我们对travel_time做一个百分位的裁剪clip, 我们把上下阈值设为95百分位和5百分位, 即将所有大于上阈值的travel_time归为95百分位数, 而小于小阈值的travel_time设为05百分位数:
def quantile_clip(group):
group.plot()
group[group < group.quantile(.05)] = group.quantile(.05)
group[group > group.quantile(.95)] = group.quantile(.95)
group.plot()
plt.show()
return group
df['travel_time'] = df.groupby(['link_ID', 'date'])['travel_time'].transform(quantile_clip)
这个clip百分位过滤是对于每一条路每天的travel_time来说的,因此使用groupby方法并传入link_ID和date, 因此得到的group变量里储存着某条路某天的所有travel_time, 对此进行百分位的clip, 如下图: