欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

天池盐城汽车上牌预测

程序员文章站 2022-07-14 14:42:34
...

上牌预测这题是一个时序预测,需要根据车管所历史上每天的上牌的记录,预测未来某天的汽车上牌量。需要选手利用历史某3年的汽车日上牌数据,预测某2年每天的汽车上牌数。初赛将挑选出5个汽车品牌,给出这些品牌每天的上牌数,当天是星期几,来预测5个汽车品牌未来每天的上牌总数。复赛将挑选出10个汽车品牌,仍旧给出品牌每天的上牌数,当天是星期几,来预测10个汽车品牌未来每天的上牌数。赛题与数据

数据说明

前三个字段是特征变量,“cnt”是目标变量。数据经过严格脱敏,所以选手看到的”cnt”并非真值;字段”date”, “brand”用数字代替;字段”day_of_week”是真实的数据。

字段 数据类型 说明
date int 日期,经过脱敏,用数字表示
day_of_week int 表示星期几
brand int 汽车品牌
cnt int 上牌数

思路

与之前的“智能制作”相反,本题是特征数量太少,需要自己添加一些特征维度。时序预测,时间是最重要的维度,但是题目给的准确的信息只有星期几,date信息是经过脱敏的,无法反应年份、月份、日期等重要信息,所以要想办法构造出准确的时间序列。

开始的想法是通过一些特征构造方法比如对date进行取平方、取对数等,构造一些新的特征,希望通过这些扩充的特征之间的运算,能够表示出真实日期的一些信息,从而能够被模型学得。但是经过尝试,效果并不好。

数据分析

通过简单的画图,可以直观感受周几对上牌量的的影响。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

train=pd.read_csv("fusai_train_20180227.txt",sep="\t")

_=train.groupby(["date","day_of_week"]).cnt.sum()

sns.barplot(data=_,x="day_of_week",y="cnt")

天池盐城汽车上牌预测

过程

这个比赛正好是在春节的时候,在过年期间,比赛暂停了几天,我也没管。当我想起来这个比赛的时候,初赛只剩最后一天了。所以没时间考虑,祭出了杀器tpot,结果意外地闯进复赛。(TPOT真是太强了!挖个坑,希望之后能写文章探究一下tpot的原理)

进入复赛后,有些队伍就在技术圈分享了他们的做题思路。有队伍讲的就是从如何从星期几和数据的规律反推真实日期(文章链接),得到真实日期后为日期做节假日标记!还有这种操作? 我竟然不知道日期反推,为进入复赛感到羞愧⊙﹏⊙|||,但是反过来想,TPOT真的太6了。

日期反推

既然日期反推大家都知道了,而且确实可以有方法,我觉得进复赛的队伍都会用上这个方法。所以我也必须找方法恢复出真实日期数据。根据分享,初赛训练数据的第一天是2013年1月2日,而且A榜和B榜的日期是紧接在训练数据之后的。虽然复赛训练数据的日期的第一天还不知道,其实可以猜到,就在2013年1月2日附近,就是2013年1月1日!

得到初始日期之后,需要补全训练数据和测试数据里每一天的真实日期。大致步骤是:
- 按照星期几补足缺省的日期(我是手动在excel里观察补充的,添加完的文件命名为holiday.csv)
- 根据第一天顺推之后的日期

# 往训练集添加时间特征
train_with_date=pd.read_csv("holiday.csv",encoding="gb2312")

total_num=train_with_date.shape[0]

ts=[datetime.date(2013,1,1)+datetime.timedelta(days=i) for i in range(total_num)]

tsarray= np.array([[t.year,t.month,t.day] for t in ts])

# 将日历分成了年、月、日三个维度
pd.DataFrame(data=tsarray,columns=["year","month","day"])

得到每天的真实日期之后,就需要标记节假日。原来想的是会不会有什么接口,能够查询历史上某一天是不是节假日,虽然网上有这种功能的接口,但看了之后我觉得不好用,而且得收费,就没有使用。没错,后来我就是对照着百度的日历做节假日标记的┐(゚~゚)┌。

天池盐城汽车上牌预测

通过观察发现,车管所大部分周六会上班,但是上牌量相对较少;周天一般不上班,所以训练集里缺失了很多周天日期;法定节假日的时候,和周末的情况差不多。添加新的特征holiday,将日期分成三类:0上班日期、1不上班的周末、2、法定节假日(不含周末)。当时做题心切,就没有对节假日进行细致的分类,后来看技术圈的分享,成绩较好的队伍对节假日也进行了细致的分类。同样根据之前文章分享的思路,除了节假日不上班外,节假日之前和之后通常会迎来一定的上牌高峰。这里我只对节假日后的一天进行了标注,添加了一个特征first_day_after_holiday,如果是法定节假日的后一天则为1,否则为0。至此,完成了复赛新训练集的构造。

天池盐城汽车上牌预测

Xgboost & LightGBM

复赛的模型我主要尝试了xgboost、lightgbm。xgboost和lightgbm的5折交叉验证的均方误差分别是33904和33596,效果只能说还能过得去。这里我没有进行调参,不知道调参是不是能大幅度提升性能。

from sklearn.model_selection import cross_val_score
import xgboost as xgb
import lightgbm as lgb

print cross_val_score(xgb.XGBRegressor(),X,y,cv=5,scoring="neg_mean_squared_error").mean()
print cross_val_score(lgb.LGBMRegressor(),X,y,cv=5,scoring="neg_mean_squared_error").mean()

TPOT
当然,我也用TPOT进行了模型搜索。经过100轮的搜索,最后的交叉验证的均方误差平均值为26269。(就是这么暴力)

from tpot import TPOTRegressor

tpot = TPOTRegressor(verbosity=2,n_jobs=-1)
tpot.fit(X,y)
tpot.export('tpot_plain.py')

导出的模型,可以看到tpot导出的模型(简称tpot模型)主体使用了xgboost回归,但先将数据进行了归一化,然后堆叠了一层ExtraTreesRegressor,最后才是xgboost。

import numpy as np
import pandas as pd
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline, make_union
from sklearn.preprocessing import MaxAbsScaler
from tpot.builtins import StackingEstimator
from xgboost import XGBRegressor

# NOTE: Make sure that the class is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1).values
training_features, testing_features, training_target, testing_target = \
            train_test_split(features, tpot_data['target'].values, random_state=42)

# Score on the training set was:-26269.08920913306
exported_pipeline = make_pipeline(
    MaxAbsScaler(),
    StackingEstimator(estimator=ExtraTreesRegressor(bootstrap=True, max_features=0.45, min_samples_leaf=15, min_samples_split=14, n_estimators=100)),
    XGBRegressor(learning_rate=0.1, max_depth=4, min_child_weight=4, n_estimators=100, nthread=1, subsample=0.9000000000000001)
)

exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)

Facebook prophet

同样是来自推荐,Facebook开源了一个专门应对时序问题的机器学习库,叫做prophet,翻译过来就是先知。prophet只接受两维数据,一维是时间,一维是我们感兴趣的值,这里就是每天的上牌量。因为之前的日期已经被我拆成三个特征了,现在需要合并回去。

但是不知道为什么,prophet的预测效果并不是很好,a榜的均方误差都到了49923,我觉得就是因为没有考虑节假日。当我把节假日的数量都置0之后,a榜的均方误差降到了45114,还是不理想,所以最后也没有采取这个模型。(如果后面有机会,也可以探索一下先知的原理)

from fbprophet import Prophet
import datetime

# 训练数据的日期
DS=X[["year","month","day"]].apply(lambda x: datetime.date(x[0],x[1],x[2]),axis=1,raw=True)
# A榜数据的日期
DS_A=pd.DataFrame(dict(ds=aug_A[["year","month","day"]].apply(lambda x: datetime.date(x[0],x[1],x[2]),axis=1,raw=True)))
prophet_train=pd.DataFrame(dict(ds=DS,y=y))

# 训练
m=Prophet()
m.fit(prophet_train)

# 预测
prophet_A_forecast=m.predict(DS_A)

总结

最后b榜的时候,我提交了两个版本,一个版本是纯粹tpot搜索出的结果;一个版本是对tpot搜索得到的模型和xgboost和lightgbm进行集成。在线下做交叉验证的时候,集成模型和非集成cross validation的结果相差不大,我以为集成的模型泛化性能应该会好一点,但是提交之后,还是非集成模型的测试误差要小。最后复赛的成绩是47名。

总结下来我觉得自己还存在以下问题:
- 对库的依赖比较严重,但是不了库底层运行的原理
- 没有好的特征工程的方法

相关标签: 天池 机器学习