XGBOOST_航班延误预测

最近,想尝试一下利用机器学习进行航班的延误预测,一开始的倾向是使用GBDT算法,使用了在scikit-learn上的肿瘤数据作为初步模型测试,使用网格搜索后发现,其预测结果仅仅只有50%不到,还不如KNN——《机器学习之Knn算法》。

后来在网上看到了XGBOOST算法,于是常识了一波,准确率可以达到90%,比knn要强出5%以上,所以在模型的选择上,博主决定使用XGBOOST。

对于GBDT和XGBOOST的原理,以及各超参的控制机理,博主这边还只是初步尝试,后面会较深入的去琢磨一番,然后完善博客,但是工具的使用还是简单的,问题在于数据集。


一、航班数据集

1.1 搜集数据

国内的航班数据几乎没有,也没有免费的公开网站,而公司的数据几乎不可用,因为只有航线、航司、出发、到达、计划时间等,没有博主需要的实际时间、天气状况、航班号等。

为此博主花了一整天时间搜罗各大网站,终于发现了这个国外网站——stat-computing.org,貌似需要。可以查询到美国的航空公司航班准点率数据,博主选取的是2016年的。

此外,还有这位博主,提供的数据也被博主参考了进来——《通过使用 Python 创建机器学习模型来预测航班晚点情况》。

但是唯一遗憾就是缺少天气数据,经过不断的搜索,博主发现了一个美国气象局,可以查到2016年的历史天气数据——ATL亚特兰大国际机场天气状况

亚特兰大国际机场2016年1月部分数据
XGBOOST_航班延误预测_第1张图片

本想通过爬虫进行爬取数据,但实际情况不允许。不过博主也是讲table标签数据copy下来,然后利用python进行数据的整理与合并。

经过分析,博主个人认为风速和降水量,由于变化幅度大,可以作为影响航班的主要因素,隐藏只提取这两个因素作为天气指数。

1.2 天气数据处理和合并

由于是调研,选取的是美国大型航空公司——DL达美,并选取了五个机场:ATL(佐治亚州亚特兰大:哈茨菲尔德-杰克逊亚特兰大国际机场)、DTW(底特律韦恩县国际机场)、JFK(纽约:约翰·F·肯尼迪国际机场)、MSP(明尼苏达州明尼阿波利斯:明尼阿波利斯圣保罗国际机场)、SEA(华盛顿州:西雅图/塔科马国际机场)

由两个文件,一个是USA_fightDataSet.xlsx,这个Excel文件记录的是起飞时间、机场、计划飞行时间、实际飞行实际、航班号等数据;另一个是由机场和月份组成的60个txt文件,里面是html的table标签数据。

博主要做的就是将table标签里的当天的天气情况,即风速和降水量,插入到表格相应的位置,代码如下:

## 获取天气数据的类方法
import pandas as pd
import numpy as np
np.set_printoptions(threshold=np.inf)
# 获取数据
def getData(url, airport, mon):
    # 读取配置文件
    table = pd.read_html(url);

    # 数据行数
    dayOfMonth = np.array(table[1])[1:, 0]
    size = dayOfMonth.size

    # 选取最后一个元素进行判断,因为爬取的数据有时候最后一个又从1号开始,这里进行判断排除处理
    ele = dayOfMonth[size-1]
    if ele == '1':
        dayOfMonth = np.array(table[1])[1:size, 0]
        wsp_data = np.array(table[5])[1:size, 1]
        precipitation_data = table[7].values[1:size, 0]
    else :
        # 读取数据,这里将header删掉,取数据
        # 日
        dayOfMonth = np.array(table[1])[1:, 0]

        # windSpeed的平均值
        wsp_data = np.array(table[5])[1:, 1]

        # 降水量
        precipitation_data = table[7].values[1:, 0]

    # 填充月份
    size = dayOfMonth.size
    month = np.array([mon for i in range(size)])

    # 填充机场
    airport = np.array([airport for i in range(size)])

    # 输出结果
    return np.vstack([month, dayOfMonth, airport, wsp_data, precipitation_data]).T

# 迭代合并数组,从2开始
def fibonacci(n, airport):
    if n==2 :
        url1 = "D:\\File\\航班预测\\天气数据\\"+airport+"\\table" + str(1) + ".txt"
        url2 = "D:\\File\\航班预测\\天气数据\\"+airport+"\\table" + str(2) + ".txt"
        return np.concatenate([getData(url1, airport, 1), getData(url2, airport, 2)])
    else :
        url = "D:\\File\\航班预测\\天气数据\\"+airport+"\\table" + str(n) + ".txt"
        return np.concatenate([fibonacci(n-1, airport), getData(url, airport, n)])

# 将各机场数据拼接
def getWeatherData():
    result1 = fibonacci(12, 'ATL')
    result2 = fibonacci(12, 'DTW')
    result3 = fibonacci(12, 'JFK')
    result4 = fibonacci(12, 'MSP')
    result5 = fibonacci(12, 'SEA')
    return np.concatenate([result1, result2, result3, result4, result5])

##
## 调用上面的天气数据包,然后与USA_fightDataSet.xlsx表格数据进行合并
import weatherData.getWeatherData as gd
import numpy as np
import openpyxl
np.set_printoptions(threshold=np.inf)

# 获取天气数据结果
result = gd.getWeatherData()

# 获取FlightData表格数据
wb = openpyxl.load_workbook('D:\\File\\航班预测\\天气数据\\USA_fightDataSet.xlsx')
sheet = wb['FlightData_DL']

# 获取fightData的行数,必须+1,因为后面的for循环判断是<判断
rows = sheet.max_row+1

# 获取天气数据的行数
rows_weather = result.shape[0]

# 外层fightData数据
for i in range(3, rows):

    # 获取月份/日期/出发和到达机场名字
    # 注意,openpyxl读取的数据是从1开始计数的
    month = sheet.cell(row=i, column=3).value

    # 表格读取的数据莫名其妙不是字符串,导致后面判断总是false,需要转一下
    dayOfMonth = str(sheet.cell(row=i, column=4).value)
    ori_airport = sheet.cell(row=i, column=9).value  # 出发机场
    dest_airport = sheet.cell(row=i, column=13).value  # 到达机场

    # 内层循环天气数据
    for j in range(0, rows_weather):

        # 获取天气行数据
        weather_row = result[j]

        # 获取天气的各数据
        w_month = weather_row[0]
        w_dayOfMonth = weather_row[1]
        w_airport = weather_row[2]
        w_wsp = weather_row[3] # 风速
        w_precipitation = weather_row[4] # 降水量

        # 循环对比,如果月份/日期/机场名字都对应,则将风速和降水量插入表格
        if month == w_month and dayOfMonth == w_dayOfMonth and ori_airport == w_airport:
            sheet.cell(row=i, column=10).value = w_wsp
            sheet.cell(row=i, column=11).value = w_precipitation
        if month == w_month and dayOfMonth == w_dayOfMonth and dest_airport == w_airport:
            sheet.cell(row=i, column=14).value = w_wsp
            sheet.cell(row=i, column=15).value = w_precipitation

# 保存操作
wb.save('D:\\jdFile\\航班预测\\天气数据\\USA_fightDataSet.xlsx')
表格部分数据
XGBOOST_航班延误预测_第2张图片

1.3 数据集处理

我们可以看看是否有缺省值,即空值null,进行数据一次处理,补充空值,抽出label,删除无效feature

# 读取路径的xlsx文件
    dataFrame = pd.read_excel(url, sheet_name = sheetname)

    # 判断是否有缺省值,即空值,true表示有空值
    result = dataFrame.isnull().values.any()

    # 有空值,找到空值所在位置
    if result :
        position = dataFrame.isnull().sum()
        print(position)
    print(result)

根据网上说法,xgboost在进行预测的时候,只对数字敏感,而不需要考虑量纲问题,所以对数据清洗的时候,我们可以最大限度保留可能的影响因素。另外,既然是分类树的形式,那么我们为了准确率和速度,必须对数据进行合理的分类,保证离散值足够小,数据处理原则:

1、去掉年份维度,保留季度、月、日、周日期
2、航班号,如果涉及到了历史准点率,则考虑航班号,否则取消航班号维度,因为我们有起飞的时刻,不需要航班号
3、出发到达机场ID或者说起飞城市ID,重要,因为这个维度从宏观上控制了地理位置、地形、海拔等不变因素
4、机场规模要考虑,一般小机场延误情况严重一些(1-小机场,2-中机场,3-大机场)
5、机场的风速,根据表格中的风速(单位是迈,我们转为km/h),对应风力等级,分为12个级别,减少数据散列程度

风力等级 风速(km/h)
0 <1
1 1-5
2 6-11
3 12-19
4 20-28
5 29-38
6 39-49
7 50-61
8 62-74
9 75-88
10 89-102
11 103-117
12 >117

6、降水量分为6个等级,减少散列程度

降雨等级 雨量(mm)
1 <10
2 10~24.9
3 25~49.9
4 50~99.9
5 100~250
6 >250

7、起飞时刻,这个维度散列值太多,我们可以÷100,将结果向下取整,其实就是小时,24个散列程度
8、飞行时长,对于国内航班,按照小时统计,÷60取整,缩小散列值
9、保留飞行距离

根据以上分析,将相应的数据补充进去

最终处理结果USA_fightDataSet

USA_fightDataSet
XGBOOST_航班延误预测_第3张图片

二、使用xgboost进行处理

我们根据《机器学习之Knn算法》文章,对数据集进行处理,分出feature、label、向量数据和特征矩阵值,并分解为训练集和测试集

2.1 读取文件数据得到train And test

import pandas as pd
import numpy as np
# np.set_printoptions(threshold=np.inf)

# 创建航班延误数据集对象
class FlightDelay:
    def __init__(self, feature_names, data, target_names, target):
        self.feature_names = feature_names
        self.data = data
        self.target_names = target_names
        self.target = target

# 读取excel文件(**强烈建议转为csv去处理,否则数据太容易被误改)
def getDataSet(url, sheetname):

    # 读取路径的xlsx文件
    dataFrame = pd.read_excel(url, sheet_name = sheetname)

    # 可以打印查看数据的类型是否有str
    # dataInfo = dataFrame.info()

    # 转为数据矩阵
    data_matrix = dataFrame.values

    # 获取feature_names,取第一行为feature,使用columns获取列名,然后使用values获取结果,去除掉最后的label列名
    feature_names = dataFrame.head(n=0).columns.values[:-1]

    # 获取label标签
    target_names = dataFrame.head(n=0).columns.values[-1]

    # feature特征矩阵data,全部取整,保证离散值足够小
    data = data_matrix[:, :-1].astype(int)

    # label标签向量,将小数转为整型
    target = data_matrix[:, -1].astype(int)

    return FlightDelay(feature_names, data, target_names, target)

2.2 xgboost训练数据

import xgboost as xgb
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV
import numpy as np
np.set_printoptions(threshold=np.inf)

# XGBOOST进行预测

# 通用参数:#
# booster:我们有两种参数选择,gbtree和gblinear。gbtree是采用树的结构来运行数据,而gblinear是基于线性模型
# silent:静默模式,为1时模型运行不输出
# nthread: 使用线程数,一般我们设置成-1,使用所有线程。如果有需要,我们设置成多少就是用多少线程

# Booster参数:#
# n_estimator:num_boosting_rounds 最大的迭代次数
# learning_rate: 有时也叫作eta,系统默认值为0.3,每一步迭代的步长,很重要。太大了运行准确率不高,太小了运行速度慢。我们一般使用比默认值小一点,0.1左右就很好
# gamma:系统默认为0,在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。gamma指定了节点分裂所需的最小损失函数下降值。 这个参数的值越大,算法越保守。因为gamma值越大的时候,损失函数下降更多才可以分裂节点。所以树生成的时候更不容易分裂节点
# subsample:系统默认为1,这个参数控制对于每棵树,随机采样的比例。减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。 典型值:0.5-1,0.5代表平均采样,防止过拟合
# colsample_bytree:系统默认值为1。我们一般设置成0.8左右,用来控制每棵随机采样的列数的占比(每一列是一个特征)。 典型值:0.5-1
# colsample_bylevel:默认为1,我们也设置为1;这个就相比于前一个更加细致了,它指的是每棵树每次节点分裂的时候列采样的比例
# max_depth: 系统默认值为6,我们常用3-10之间的数字。这个值为树的最大深度。这个值是用来控制过拟合的。max_depth越大,模型学习的更加具体。设置为0代表没有限制
# max_delta_step:默认0,我们常用0.,这个参数限制了每棵树权重改变的最大步长,如果这个参数的值为0,则意味着没有约束。如果他被赋予了某一个正值,则是这个算法更加保守。通常,这个参数我们不需要设置,但是当个类别的样本极不平衡的时候,这个参数对逻辑回归优化器是很有帮助的。
# lambda:也称reg_lambda,默认值为0就,权重的L2正则化项。(和Ridge regression类似)。这个参数是用来控制XGBoost的正则化部分的。这个参数在减少过拟合上很有帮助。
# alpha:也称reg_alpha默认为0,权重的L1正则化项。(和Lasso regression类似)。 可以应用在很高维度的情况下,使得算法的速度更快
# scale_pos_weight:默认为1,在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。通常可以将其设置为负样本的数目与正样本数目的比值

# 学习目标参数 #
# objective [缺省值=reg:linear]
# reg:linear– 线性回归
# reg:logistic – 逻辑回归
# binary:logistic – 二分类逻辑回归,输出为概率
# binary:logitraw – 二分类逻辑回归,输出的结果为wTx
# count:poisson – 计数问题的poisson回归,输出结果为poisson分布。在poisson回归中,max_delta_step的缺省值为0.7
# multi:softmax – 设置 XGBoost 使用softmax目标函数做多分类,需要设置参数num_class(类别个数),输出为概率最大的分类
# multi:softprob – 如同softmax,但是输出结果为ndata*nclass的向量,其中的值是每个数据分为每个类的概率

# eval_metric [缺省值=通过目标函数选择]
# rmse: 均方根误差(回归问题默认)
# mae: 平均绝对值误差
# logloss: negative log-likelihood数似然损失,对数损失函数,一般用于分类问题
# error: 二分类错误率。其值通过错误分类数目与全部分类数目比值得到。对于预测,预测值大于0.5被认为是正类,其它归为负类(分类问题默认)
# merror: 多分类错误率,计算公式为(wrong cases)/(all cases)
# mlogloss: 多分类log损失
# auc: 曲线下的面积
# ndcg: Normalized Discounted Cumulative Gain
# map: 平均正确率

def inputDataSet(X_train, X_test, y_train, y_test):

    # 设置xgboost分类器
    xlf = xgb.XGBClassifier(max_depth=6, learning_rate=0.1, n_estimators=1000, objective='binary:logistic',
                            nthread=-1, subsample=0.5, colsample_bytree=0.8)

    # 训练,verbose标识每迭代一次进行输出,可以指定迭代几次输出
    xlf.fit(X_train, y_train, eval_metric='error', verbose=True)

    # 预测准确率得分
    score = xlf.score(X_test, y_test)
    print("预测准确率得分 = {:.2f}".format(score),)

    # AUC模型评分
    y_pred = xlf.predict_proba(X_test)
    auc_score = roc_auc_score(y_test, y_pred[:, 1])
    print("AUC模型评价 = {:.2f}".format(auc_score))

    # roc曲线可视化输出
    roc_function_image(y_test, y_pred)

# 可视化的roc评价函数图像
def roc_function_image(y_test, y_pred):

    # fpr是假阳性率,tpr是真阳性率
    fpr, tpr, _ = roc_curve(y_test, y_pred[:, 1])

    plt.plot(fpr, tpr, color='darkred')
    plt.plot([0, 1], [0, 1], color='grey', lw=1, linestyle='--')
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.show()

方法的调用

import getdata.FlightDelay as fd
import getdata.XgboostModel as xm
from sklearn.model_selection import train_test_split

# 导入xlsx文件路径和工作簿名称
url = "D:\\python\\Projects\\USA_flightDelayInfo_dataSet\\USA_fightDataSet.xlsx"
sheet_name = 'DL'

# 调用方法获取数据集
flightDataSet = fd.getDataSet(url, sheet_name)
target_names = flightDataSet.target_names
target = flightDataSet.target
feature_names = flightDataSet.feature_names
data = flightDataSet.data

# 使用train_test_split方法获取训练集和测试集,这里二八分,随机种子为1
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=1)

# 使用xgboost进行模型预测
xm.inputDataSet(X_train, X_test, y_train, y_test)

输出结果:

预测准确率得分 = 0.85
AUC模型评价 = 0.70

roc模型评价曲线
XGBOOST_航班延误预测_第4张图片

2.3 结果说明

我们的模型预测准确率达到了85%,但事实并非如此,会有一种情况,博主称之为“机器欺骗”

为1(true)的情况太少,而0(false)的情况太多,导致,模型无论给定结果如何全部输出0,从而凭运气导致预测结果准确率很高,即模型的欺骗行为

为了避免这种情况,我们需要引入ROC(Receiver Operating Characteristic Curve) 受试者工作特征曲线。用来评价二分类问题的模型好坏。

ROC曲线主要是对二分类预测模型的预测概率情况进行描述,真实的结果分为了【0-false,1-true】,那么预测结果是否与真实结果一致,这边产生了四种情况:

预测概率情况矩阵
XGBOOST_航班延误预测_第5张图片

通俗来讲就是,我实际值是1,你预测为1的概率是多少,预测为0的概率是多少,即TPR+FNR=100%,我们的考察是TPR(True Positive Rate)FPR(False Positive Rate),前面输出的函数图像就是这个函数的结果。

AUC(Area Under Curve)这个得分指标其实就是ROC曲线与x轴的面积。

如果图像在y=x这个直线上,说明,auc得分是0.5,则这个模型就跟抛硬币一样,随机预测,根本没有“自己动脑”,这是不理想的,也是auc的底线。如果小于0.5,说明模型的预测与实际总是相反的,这时候我们要考虑对结果进行取反操作,保证预测准确。

所以最理想的就是在0.5以上,切越靠近y轴越精确,即每次的预测都是“有效思考”后的结果。


你可能感兴趣的:(Machine,Learning)