一、赛题理解
本次比赛任务是利用历史数据并结合地图信息
预测五和张衡交叉路口 未来一周 周一(2019年2月11日)和周四(2019年2月14日)两天 的5:00-21:00通过wuhe_zhangheng路口4个方向的车流量总和。
要求模型输出格式如下:
{"data":{"resp_data":{"wuhe_zhangheng":[1,4,5,6,4...]}}}
从5:00开始每5min的预测数据,第一个数据为5:00-5:05的流量值,最后一个数据为20:55-21:00。
两天的数据按时间先后放在一起,总共有384个数据。
预测输入形式:输入需要预测的日期,按照赛题,输入 {"req_data":["2019-2-11,2019-2-14"]}
预测数据形式:一条道路,一天,四个方向总和,每五分钟预测一次,一天就有60 * 24 / 5 = 288 条记录,输出5:00 - 21:00这16个小时的数据,便是288/24 * 16 = 192 条数据
预测一天输出结果为 1 * 192 ,两天便是 1 * (192 * 2 )= 1 * 384
注意点:预测的是一周中两天的数据,并且这两天都是独立的,没有前后数据,这意味着很难用基于时间顺序流进行预测(需要输入预测值之前一段时间的数据)。
之前看很多交通预测论文,预测未来数据时,会将连续历史信息作为默认信息
比如预测T时段的信息,那么T-1、T-2、T-3 ……等前段的数据都是已知的,并且是作为输入送到模型的。
交通预测理论分析,和物理实验一样,做了很多假设,面对现实,未免会有误差。(同时也说明我的能力太菜了……这个问题应该是可以通过对模型构建来解决,但是我想了几天,脑阔疼)
二、原始数据分析
官方数据: 4周(2019.1.12 – 2019.2.8)深圳龙岗区坂田街道交通流量历史数据
数据形式:
其中,time为上述格式时间字符串,cross为路口名,direction为车流起始方向,leftFlow是左转车流,straightFlow是直行车流。
说明:
(1) 十字路口包含四个方向车流数据,此处未全部列出。
(2) 路口名称分别为:五和路、张衡路、稼先路、隆平路、冲之大道。可以通过但不限于百度地图等地图软件获取地图路网信息。
(3) 因为右转车流不受信号灯控制,因此未做统计。
数据理解:
官方说明给出了五个路口,但是实际上有一条冲之-贝尔路口没有说明。
这些路口分别为:冲之-贝尔路口、冲之-稼先路口、冲之-隆平路口、五和-稼先路口、五和-隆平路口、五和-张衡路口这六条。
其中四条路口为十字路口、两条路口为三叉路口。所以每一天都会有4*4 + 2*3 = 22个方向的数据,每个方向记录每隔五分钟的交通流,为288条数据。
官方给出4周,也就是 4 * 7 = 28 天,【28,22,288】
三、官方baseline 数据处理
数据处理方式一般是需要根据模型来变动。
官方baseline是根据梯度提升决策树进行预测,可以看作是回归模型,根据输入特征值{Xi : i = 1,2,3,……n},获得回归结果(Y)
baseline只使用了五和-张衡路口的数据,【28,4,288】 = 32256,但是真实提取数据只有29036条数据,说明28天的数据中存在缺失。(若使用时间顺序预测,那么需要补充这部分数据)
baseline从每一天的日期中,提取出了两个特征,X1 = 星期/6(X1为一个数值,取值范围是【0,1,2,3,4,5,6】/ 6 = 【0,0.167,0.333,0.5,0.667,0.833,1】)
X2 = 每5分钟的时刻 【0,5,10,15,……,23*60 + 55】/(24*60) (【1*288】)
预测目标:Y = number 【1*288】
baseline的模型输入:( X1 , X2(i))输出:Y(i)
所以官方baseline数据处理主要就是通过原始数据生成这两个特征。
首先,读取原始数据,将五和-张衡路口数据中时间和交通流全部提取,大小为【29036,2】
def read_file(path, filename): calfile = os.path.join(path, filename) original = pd.read_csv(calfile, header=None) data = pd.DataFrame(columns=["time", "number"]) data["time"] = original[0] data["number"] = original[3] + original[4] return data # read data of one day def read_data_day(path, date): day_data = pd.DataFrame(columns=["time","number"]) caldir = os.path.join(path, date) # read data of one day for f in os.listdir(caldir): if re.match(r'wuhe_zhangheng.*.csv', f): day_data = day_data.append(read_file(caldir, f), ignore_index=True) return day_data
随后,将提取出的数据针对时间进行编码,得到两个特征分别对应星期和时刻。并将所有相同特征的数值取平均值。
注意点:虽然是有四个星期的内容,但是baseline并没有针对第几个星期进行处理。所以不管是第一个星期一,还是第四个星期一,星期编码都是0。
时刻也是一样。第一个星期一00:05时刻 与 第四个星期一00:05时刻,他们的X1, X2(i)编码是完全相同的。
同时,五和-张衡路口时十字路口,每个时刻会记录四个不同方向的数据。
所以对于每个X1、X2(i)会有16 条数据 (四个方向、四个星期),然后将这16个数据平均,变为一个输入
缺失数据分析:
上文提到,如果数据完整那么存在28 * 4 * 288 = 32256 条数据。
按照时刻和星期进行分组,组成【288,7,16】的矩阵,每个元素代表某一个时刻,四个星期的所有方向数据集(总共4周,4个方向,于是有16个数据)
所以数据应为【288,7,16】。每一个时刻,包含的数据总数应该为【16,16,16,16,16,16,16】
但是,分析大赛给出数据中有三种形式,分别为
【12,12,16,16,16,16,16】 前57个时刻
【12,12,16,16,16,16,12】共223个时刻
【12,13,16,16,16,16,12】 共8个时刻
验证:57*(104)+ 223 * (100)+ 8*(101)= 29036 。
通过缺失数据可以分析,数据量为12个星期,可能说明有一天没有五和-张衡路的数据(一天四个方向数据),或是某几天的数据中缺少五和-张衡路其中一个方向的 数据。数据量为13则是某几天的数据缺失一个方向的记录。
def get_data(path): raw_data = pd.DataFrame(columns=["time", "number"]) for day in os.listdir(path): raw_data = raw_data.append(read_data_day(path, day)) # encode time in raw data to weekday and timeindex(the n minutes of the day) df_dt = to_datetime(raw_data.loc[:, "time"], format="%Y/%m/%d %H:%M:%S") all_data = pd.DataFrame({ "weekday": df_dt.dt.weekday/6.0, "timeindex": (df_dt.dt.hour * 60 + df_dt.dt.minute)/(24*60.0), "number": raw_data["number"].astype(int)}) all_data = all_data.groupby(["weekday", "timeindex"]).sum().reset_index(level = ["weekday", "timeindex"]) return all_data
模型训练:
baseline直接引用sklearn中的梯度提升决策树回归GradientBoostingRegressor进行预测。(没了解过,只知道决策树……基础要补课啊 )
def train_model(): X_train, X_test, y_train, y_test = train_test_split(local_data[['weekday','timeindex']], local_data['number'], test_size=0.1, random_state=42) print("X_train shape is: " + str(X_train.shape)) print("X_test shape is: " + str(X_test.shape)) params = {'n_estimators': 500, 'max_depth': 4, 'min_samples_split': 2, #最优 'learning_rate': 0.01, 'loss': 'ls'} clf = GradientBoostingRegressor(**params) clf.fit(X_train, y_train) joblib.dump(clf, LOCAL_MODEL_PATH) #模型保存 y_predict = clf.predict(X_test) mse = mean_squared_error(y_test, y_predict) print("MSE: %.4f" % mse)