基于xgboost对公共自行车投放预测总结
目录
1.XGboost模型概述
2.数据处理
3.调参
4.提交预测结果
1.XGboost模型概述
1.1 xgboost的预测
xgboost是基于决策树上一个优化集成的模型,xgboost由若干棵决策树的一个集成,每棵决策树的叶子节点上有对应的值,将每样本分类到各个叶子节点,该叶子节点对应的值,即为一棵决策树对一个样本的预测值,若干棵决策树对一个样本有若干个预测值,将这些值进行累加,最后即得到xgboost对一个样本的一个预测值。
xgboost预测公式如下:
其中 i 表示样本的序号, 表示第 i 个样本,k 表示决策树的棵数 下面用图1展示xgboost的预测过程。 图1
tree1和tree2分别表示两个决策树,而xgboost即将这两个决策树做一个集成,图中的人即为我们的数据样本,我们拿其中一个样本(小男孩)来分析,在tree1中小男孩被分类到第一个叶子节点上,其中tree1第一个叶子节点对应的预测值为+2,而在tree2中小男孩也被分类到第一个叶子节点上,tree2对应的预测值为+0.9,那么模型对该样本的预测值为2.9。
1.2 xgboost的目标函数
目标函数有两部分构成,第一就是损失函数,第二是正则项。
其中损失函数: 损失函数
正则项: 正则项
T表示第K棵决策树中叶子节点的个数,为该棵决策树叶子节点的预测值。
1.3 xgboost的训练过程推导
根据1.2的目标函数,使用损失函数和正则项结合,目的就是使预测值与真实值的差值最小,即目标函数最小。在1.1xgboost的预测中提到xgboost是若干棵决策树的集成,那么面临的问题就是若干棵决策树使目标函数最小,这个若干棵具体到几棵。接下来就是围绕这这个问题进行推导。
首先,假设xgboost中决策树个数为t时,使得目标函数最小。那么模型中加入第t棵树时,第 t 棵树中叶子节点的个数和对应的权重值都是一个未知数,则目标函数可表示为:
由于第 t 棵树中的叶子节点T和权重都是未知,所以第t棵树的预测值和正则项的值都是未知数,而前t-1项的预测值和正则项都为已知的常数,所以公式中在推导过程产生的常数都归为constant(常数)项。
然后,我们根据泰勒公式对目标函数进行二次展开
其中和分别为目标函数对前t-1项预测值的一阶偏导和二阶偏导,常数项在分析过程中进行了省略。
由于()是模型遍历样本而得出的预测值,由于每个样本的预测值即为该样本所在的叶子节点的,为了方便计算改用遍历叶子节点,则目标函数表示为:
其中( )表示在同一个叶子节点的每个样本对应的一阶导的累加和与所在叶子节的权重值相乘,( )同理。
设= ,= ,化简得:
求出使目标函数最小时的,则目标函数对求导结果为0,得
得出:
最后,目标函数的结果与无关,目标函数结果的大小表示模型结构的好坏,目标函数值越小,表示模型越优。
2.数据处理
2.1 数据检查
首先查看数据集中是否存在缺失值和重复值。如图1所示:
图1从图1中我们可以看到训练数据集中有10000个数据样本,其中无缺失值,有14个重复值,我们将多余的重复值进行删除。
然后对训练数据一些重要指标的查看。如图2 图2 其中的指标有样本数据的个数、各个特征的平均值、方差、最大最小值等等。
2.2 数据特征分析
通过一些指标去分析数据集特征的数值,根据各个指标之间的对比分析,会将一些不重要的特征和数据进行剔除,或者将一些特殊数据进行取平均值等操作。
2.2.1特征重要性与相关性分析
首先,查看数据集中各个特征的重要程度和各个特征之间相关性大小,目的就是确定特征分析的顺序。如图3
图3我们可以发现时间和温度这两个特征的重要程度高达2000左右,而节假日这个特征的重要程度仅仅只有8.7。在特征相关性表中我们可以看出除了本身与本身特征的相关性为1,其次是温度和体感温度的相关性为0.987,其他特征之间的相关性都不高,而与借车数量y之间特征比较高的有温度和体感温度0.417和0.414,其次就是时间与借车数量,相关性达到0.406,然后是城市、天气状况还有风速与借车数量相关性在l0.1l左右,最后是是否为节假日与借车数量的相关性为0.029。综合以上两个指标可知时间与温度对借车数量的影响是比较大的,而是否为节假日这个特征对借车数量的影响极微。
2.2.2温度特征分析
根据图3中特征的重要程度与相关性大小我们先从温度这个特征开始分析。如图4
图4 各个温度对应的借车数量从图中我们看出在35摄氏度以下自行车的借取数量与温度呈正相关,而35摄氏度以上程负相关,而在借车数量高峰值处于25-35摄氏度,温度低于0摄氏度和高于35摄氏度借车数量明显骤减,所以我们看出温度对于自行车借取的数量影响明显,与特征重要性和相关性分析结果一致,可见温度是考虑自行车投放数量的第一考虑指标。
2.2.3时间特征分析
图5为不同时间段对应的借车数量。 图5 不同时间段的借车数量从图中我们可以看出,借车的高峰值分布在早上7-9点和下午17-19点,这反映出在上下班时间段的自行车的使用量是一天之中的高峰,在之后使用量呈下降的趋势,而在0-4点的自行车使用量更是寥寥无几。从中可以得到的信息是在一天之中不同时间的投放数量的一个参考指标。
2.2.4天气状况特征分析
主要查看不同天气状况下不同城市的借车数量的情况。如图6
图6 不同天气状况下不同城市的借车数量 图中我们可以看到随着天气状况的恶劣,两个城市的借车数量都有下降的趋势,但在天气状况为4时,1城市的借车数量为0,初步的一个判断这是一个特殊的情况,那么我们将进一步展开具体的分析。如图7 图7 对不同天气状况和不同城市进行分组汇总从图中我们可以看到在天气状况为4时,借车数量一共仅为46量,所以可以认为这是一个特殊的一个情况,不符合常理的一个情况,我们常理的一个分析是在天气状况为4时,应该是没有人借车的,但为了防止一个意外情况,将天气状况为4时的数据集进行修改,改为在天气状况为4时,借车数量为一个固定值5,也就是说不管什么情况,不管哪个城市,都要放置5辆自行车以防意外情况发生。
2.2.5风速特征分析
查看不同城市不同风速下的借车数量。如图8
图8 不同城市不同风速的借车数量从两个城市的借车分布来看风速为1-5之间的借车数量较多,但是对整体的影响不大,从图中可以反应出0城市比1城市对公共自行车的使用比较多,在不同风速下借车数量的差异不明显。
2.2.6异常数据和预测结果处理
异常数据处理:此处说的异常数据是指,借车数量大于3倍标准差的样本,进行处理。其中处理的方法有两种,第一就是将数据样本进行剔除,第二将数据样本进行取平均值。
预测结果处理:通过对预测结果的观察,我们发现预测结果中出现了一些负数,但是自行车的投放数量肯定不能为负,所以将所有预测结果为负的数据都改为0。
其次预测结果出现了小数,由于自行车的投放数量为整数,所以我们将采用四舍五入的方法对数据进行取整。
以上为本次数据处理的全部操作,下面将对以上数据处理方法对数据集进行处理,查看对应误差值的变化。
2.2.7 各个数据处理对应的误差结果展示
我们将比赛提供的训练数据集进行划分为训练集和测试集,从而验证每步数据处理对应的误差,下面代码为我们数据处理和以下调参所用到的xgboost模型代码
#导入模块
import pandas as pd
import matplotlib.pyplot as plt
import xgboost as xgb
import numpy as np
from xgboost import plot_importance
from sklearn.impute import SimpleImputer
from sklearn.feature_selection import f_regression
from sklearn.model_selection import train_test_split
from math import sqrt
#数据清洗
def data_clean(train,test,sample):
imputer=SimpleImputer(missing_values='NaN',strategy='mean')#空值用平均数填充
imputer.fit(train.loc[1:,:])
train=imputer.transform(train.loc[1:,:])
return train,test,sample
#训练和测试
def train_d(X_train, y_train, X_test):
#训练
model=xgb.XGBRegressor()
model.fit(X_train,y_train)
#测试
y_pred=model.predict(X_test)
return y_pred
#定义误差函数
def rmse(y_pre,y_test):
e=[]
for i in range(len(y_test)):
e.append(y_pre[i]-y_test[i])
squaredError=[]
for h in e:
squaredError.append(h * h) # target-prediction之差平方
RMSE =sqrt(sum(squaredError) / len(squaredError)) #均方根误差RMSE
return RMSE
#定义主函数
if __name__=='__main__':
# 读取数据
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
sample = pd.read_csv("sample_submit.csv")
# 删除id
train.drop('id', axis=1, inplace=True)
test.drop('id', axis=1, inplace=True)
Y = train.pop('y')
# 划分数据集
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(train, Y, test_size=test_size, random_state=seed)
y_pre=train_d(X_train, y_train, X_test)
y_test = [i for i in y_test]
#打印损失值
print(rmse(y_pre,y_test))
首先是未经处理的模型对应的损失值为:
经过数据处理和调参后的最小损失值为:
相比处理后损失值下降了1.97,下面为处理流程。(1)去除重复值后的损失值为:
损失值降低,保留次操作。
(3)将天气状况为4时的借车数量设置为5后的损失值: 损失升高,说明在训练集中这个设置是不适用,因为我们训练集中天气状况为4时的借车数量是一个已知值那就是46,所以在用训练集划分为训练集和测试集中测试,误差就会偏大,但是对于一个未知借车的测试预测,我个人认为这是有必要,所以先保留此次操作。
(4)对预测结果进行取整和将负值设为0后损失值为: 损失值再次下降,经过数据处理后,下面进行调参。
3. 调参
调参的大体思路就是针对四个参数进行调参,分别为学习率、迭代次数、树的深度、权重值。采用数据处理后的xgboost模型。代码如下
# 导入模块
import pandas as pd
import matplotlib.pyplot as plt
import xgboost as xgb
import numpy as np
from xgboost import plot_importance
from sklearn.impute import SimpleImputer
from sklearn.feature_selection import f_regression
from sklearn.model_selection import train_test_split
from math import sqrt
# 数据清洗
def data_clean(train, test, sample):
imputer = SimpleImputer(missing_values='NaN', strategy='mean') # 空值用平均数填充
imputer.fit(train.loc[1:, :])
train = imputer.transform(train.loc[1:, :])
return train, test, sample
# 训练和测试
def train_d(X_train, y_train, X_test,weight,le,estimators):
# 训练
model = xgb.XGBRegressor(max_depth=5, learning_rate=le, n_estimators=estimators,min_child_weight=weight,
gamma=0.2,colsample_bytree= 1, subsample=0.7,reg_alpha=0.02)
model.fit(X_train, y_train)
# 测试
y_pred = model.predict(X_test)
y_pred = list(map(lambda x: x if x >= 0 else 1, y_pred))
y_pred=list(map(lambda x: round(x) ,y_pred))
return y_pred
# 定义误差函数
def rmse(y_pre, y_test):
e = []
for i in range(len(y_test)):
e.append(y_pre[i] - y_test[i])
squaredError = []
for h in e:
squaredError.append(h * h) # target-prediction之差平方
RMSE = sqrt(sum(squaredError) / len(squaredError)) # 均方根误差RMSE
return RMSE
# 定义主函数
if __name__ == '__main__':
# 读取数据
train = pd.read_csv("train.csv")
# 删除id
train.drop('id', axis=1, inplace=True)
train = train.drop_duplicates(keep='first') # 删除重复值
train.loc[train['weather'] == 4, 'y'] = 5
train = train[np.abs(train['y'] - train['y'].mean()) <= (3 * train['y'].std())]
Y = train.pop('y')
# 划分数据集
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(train, Y, test_size=test_size, random_state=seed)
c=0
i = [0.03,0.05, 0.06, 0.07, 0.08, 0.09, 0.1]#学习率的取值范围
dd=[3,4,5,6,7,8,9,10,11]#树的深度取值范围
A = []
a=0
for le in i :
w=[]
A.append(le)
aa=A[a]
a+=1
df1=pd.DataFrame(aa,index=['学习率'],columns=['le'])
for weight in range(5,20,1):#权重取值范围
e=[]
for estimators in range(200, 650, 50):#迭代次数取值范围
y_pre = train_d(X_train, y_train, X_test,weight,le,estimators)
y_test = [i for i in y_test]
#print("学习率为:",le,"权重为:",weight,"下损失为:",rmse(y_pre, y_test))
e.append(rmse(y_pre, y_test))
w.append(e)
df=pd.DataFrame(w,index=range(5,20,1),columns=range(200, 650, 50))
df1.append(df)
print(df1.append(df).min())
思路:通过逐层遍历四个维度的参数,然后去计算出一个四个维度不同参数组合下的所有损失值,我们在将这些损失值进行筛选,筛选出损失值最小的参数,然后返回出这个最小损失值对应的四个参数组合,即为我们的最优参数组合。
操作:我们通过以上的代码进行运行,由于参数组合过多,数据运算量过大,我们先固定一个参数(树的深度),运行模型,然后计算在相同树的深度——不同学习率——不同权重——不同迭代次数下不同的损失值,返回这些损失值中最小值,最后产生最小损失值的参数组合即为最优参数组合,这个参数组合为max_depth=6,learning_rate=0.05,n_estimators=250,min_child_weight=7
4.提交预测结果
下面就将数据处理后的最优参数组合,对测试集进行预测,并向平台提交预测结果,结果如下:最后平台给出的损失值为14.871相比未处理xgboost模型损失值18.847下降4.076,排名在57名。
代码和数据链接:https://gitee.com/liu_ji_duan/DuanGe/tree/master/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/xgbooost%E5%85%AC%E5%85%B1%E8%87%AA%E8%A1%8C%E8%BD%A6%E9%A2%84%E6%B5%8B
参考:https://zhuanlan.zhihu.com/p/48693895