近几年,机器学习的浪潮席卷了众多行业,在强(公)大(司)的求(强)知(需)欲(求)下,我开始了探索。各种比赛,无疑是练手和提高能力的好机会。于是,我参加了一个比赛,没想到运气不错,拿了第二名。下面就讲一下当时的一些思路。
比赛是携程机票航班延误预测算法大赛。拿到赛题怎么办,先不急,好好审题!(仿佛回到高中…)数据主要包括历史航班动态起降数据、历史城市天气表、机场城市对应表以及历史机场特情表。目标是航班计划起飞时间前2小时,预测航班是否会延误3小时以上的概率。
题面很直白,航班延误大家或多或少都了解,滑头的点在于题目明确说明了可以利用公开的数据。这个其实是需要特别注意的,因为现在市面上有很多现成的预测服务,而且给的很多数据并没有脱敏,如果有能力完全可以从现成的服务中获取很多相关的数据(比如飞行距离)和预测值,并入自己的模型中以提高成绩。我个人并没有太多空余时间来处理这部分,所以只取了一些非常官方(民航网站和维基百科)的外部数据。
分析和处理数据
OK,审题以后就是分析数据。
历史航班动态起降数据
这毫无疑问是最重要的部分,是否延误3小时,就要从这个数据表计算出。航班编号和飞机编号几乎是决定性的,排序编号方便算法处理,起飞降落时间可以获取非常多的时间属性,比如小时,早中晚,星期几,月份等,这些可能都有作用。比如,周末和非周末可能就有不同延误。再比如节假日(外部数据,自己收集)附近延误可能会更严重。
# 每个tuple第一个表示起始日期,第二个表示连续的天数,后来发现连续天数用处并不大。这个是公开数据,自己收集
HOLIDAYS = [('2014-10-07', 1), ('2015-01-01', 3), ('2015-01-02', 2), ('2015-01-03', 1), ('2015-02-18', 7), ('2015-02-19', 6) ...]
城市天气数据
最低最高温度数据有缺失, 需要自己想办法补全(比如靠外部数据)。我用的补全方式很简单粗暴,就是缺最低的用最高的补,缺最高的用最低的补即可。主要是因为算下来,这些因素影响不大。而天气本身,我用了一个天气向量表示:
WEATHER_NAMES = ['雷阵雨', '阵雨', '暴雨', '大雨', '雨', '多云', '晴', '雾']
# 下面是提取的全量,但是用处并不大。最终用的是上面的简化版
# WEATHER_NAMES = ['雷阵雨', '雷雨', '阵雨', '暴雨', '冻雨', '大雨', '中雨', '小雨', '雨', '大雪', '阵雪', '中雪', '小雪', '冰雪', '雪', '严寒', '多云', '少云', '低云', '晴', '飓风', '狂风', '大风', '风', '霾', '扬沙', '冰霰', '沙尘暴', '浓雾', '雾', '浮尘', '阴']
由于天气中会出现“转”字,代表天气的变换,可以用两个连续的天气向量表示, 因为最后试验出来用处不大,我是在一个向量中表示的:
# 例如‘雷阵雨转多云'
vector = [1, 0, 0, 0, 0, 0, 0, 0] + [0, 0, 0, 0, 0, 0, 1, 0]
# 而我实际用的类似下面的表示法
vector = [1, 0, 0, 0, 0, 0, 1, 0]
机场城市对应数据
用来连接各个表的。无需多说。
机场特情表
基本上都是要延误,影响起飞的意思。直接获取对应的开始和结束时间作为影响因素,当然完全可以对具体特请内容做一些简单的文本分析,提取一些关键词等。
一些非常规处理
数据初步处理后,如果回过头检查,会发现有很多缺失的。可以自己想些简单的办法补全。比如,有些机场代码对应出来的城市就是没有天气信息。回想小时候看中央台的天气预报,其实也没有自己家乡的名字,参考最近的大城市的天气即可。所以,这里可以采取同样的方式,进行一个简单的替换:
CITY_NAME_MAPPING = {''三沙': '三亚', '荔波': '遵义', '甘南': '临夏' ...}
特征
这是我个人最终用到的特征,肯定不是最优的。
- 航班号被拆解成了: 航空公司, 编号本身, 是否候补。 航空公司的排序, 是根据公司的平均航班延误从小到大排列的。具体拆解原理来自百科
- 预计起飞时间做了转化,变成 小时、月份、星期等
- 预计结束时间只保留了小时(因为跟起飞很类似)
- 根据预计起飞时间,计算了距离之前、之后节假日(五一、中秋、国庆之类)的天数,因为最开始认为延误与节假日关联比较大,后来实验发现影响不大
- 如果出发机场在预警(特请内容)中,记录距离预警开始和结束的时间。同样记录到达机场的预警。只有预计起飞两小时之前收到这些预警,才把预警作为考虑因为。
- 预计起飞前两小时,出发机场延误的飞机个数。因为数据缺损比较多,其实这个数不那么准确,但是我发现这是影响延误的一个重要因素。
- 前序航班的起飞延误时间。因为数据缺损比较多,其实这个数不那么准确,但是我发现这也是影响延误的一个重要因素。
- 各种平均值(也就是历史信息), 比如出发机场的平均延误时间,平均每天延误航班数,航班平均延误时长,飞机平均延误时长等数据。本来我计算过每天的延误时长等数据,打算预测时加上“机场前一天的平均延误”这样的信息,但是发现预测效果并不好。[大概因为数据缺损等因素,导致平均值不准确]
- 出发地的天气信息,最低最高温度,以及到达地的这些信息。
- 标签。 我用的不是0准时,1延误两个标签, 而是多个。 0表示准点或延误3小时以内,1表示3到4小时, 2表示4到5小时,3表示5小时以上,4表示取消。预测时,标签1、2、3、4的可能性相加,代表延误的可能性。
模型
模型没太多好BB的。网上特别多通用的优化方式。我在编写过程中尝试了多种不同的模型,最后根据实际情况(包括准确度,运行时间等)选择了stacking
。 基础层中用的是sklearn
的random forest
, extra trees
和xgboost
的XGBClassifier
。 第二层中用的是lightgbm
的LGBMClassifier
。
训练、调参时, fold可以设置5, 最终预测时fold设置10。
调参过程就是RandomizedSearchCV和GridSearchCV根据情况换着用即可。
细节补充
好了, 这应该是比较风骚的部分了。其实大概每个比赛都有一些领域相关的操作吧,需要在各种尝试中慢慢打开脑洞。
- 训练集对于缺失的数据直接丢弃, 测试集中测试数据采用默认值补全。
- 机场代码,是根据2016客流量排序的,在wikipedia上可以查到。
- 实际试验中,为了节省时间, 往往不会用全量的训练集作训练和调参,所以我经常会从test ABC中取数据,构成临时的训练集。后来发现,用这种跟测试临近的部分数据,可能比用全量数据效果要好。我的理解是,大概航班延误有一定的短期趋势(比如可能被额外的信息影响,但比赛并未提供相应数据)。 举个例子,为了预测8月的数据,可能要5,6,7或者4, 5,6,7月的训练数据就行了, 用前两年的反而效果会差。
结语
大概情况就是这样。从比赛中学到了很多平时看教程学不到的知识点,实战中提高了python
编码能力,初步了解了一些脑洞大开的操作,收获良多。而且第一次比赛能拿到名次,还是蛮开心的~