根据菜菜的课程进行整理,方便记忆理解
代码位置如下:
分类案例:XGB中的样本不均衡问题
在之前的学习中,我们一直以回归作为演示的例子,这是由于回归是XGB的常用领域的缘故。然而作为机器学习中的大头,分类算法也是不可忽视的,XGB作为分类的例子自然也是非常多。存在分类,就会存在样本不平衡问题带来的影响,XGB中存在着调节样本不平衡的参数scale_pos_weight,这个参数非常类似于之前随机森林和支持向量机中我们都使用到过的class_weight参数,通常我们在参数中输入的是负样本量与正样本量之比。
参数含义 | xgb.train() | xgb.XGBClassifier() |
---|---|---|
控制正负样本比例,表示为负/正样本比例 在样本不平衡问题中使用 | scale_pos_weight,默认1 | scale_pos_weight,默认1 |
- 导库,创建样本不均衡的数据集
import numpy as np
import xgboost as xgb
import matplotlib.pyplot as plt
from xgboost import XGBClassifier as XGBC
from sklearn.datasets import make_blobs #自创数据集
from sklearn.model_selection import train_test_split as TTS
from sklearn.metrics import confusion_matrix as cm, recall_score as recall, roc_auc_score as auc
class_1 = 500 #类别1有500个样本
class_2 = 50 #类别2只有50个
centers = [[0.0, 0.0], [2.0, 2.0]] #设定两个类别的中心
clusters_std = [1.5, 0.5] #设定两个类别的方差,通常来说,样本量比较大的类别会更加松散
X, y = make_blobs(n_samples=[class_1, class_2],
centers=centers,
cluster_std=clusters_std,
random_state=0, shuffle=False)
- 数据探索
X.shape
# (550, 2)
y
"""
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
"""
(y == 1).sum() / y.shape[0] #9%
# 0.09090909090909091
Xtrain, Xtest, Ytrain, Ytest = TTS(X,y,test_size=0.3,random_state=420)
- 在数据集上建模:sklearn模式
0.9701417004048585#在sklearn下建模#
clf = XGBC().fit(Xtrain,Ytrain)
ypred = clf.predict(Xtest)
ypred
"""
array([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1])
"""
clf.score(Xtest,Ytest) #默认模型评估指标 - 准确率
# 0.9272727272727272
cm(Ytest,ypred,labels=[1,0]) #少数类写在前面
"""
array([[ 9, 4],
[ 8, 144]], dtype=int64)
"""
recall(Ytest,ypred)
# 0.6923076923076923
auc(Ytest,clf.predict_proba(Xtest)[:,1])
# 0.9701417004048585
- 在数据集上建模:xgboost模式
#负/正样本比例
clf_ = XGBC(scale_pos_weight=10).fit(Xtrain,Ytrain)
ypred_ = clf_.predict(Xtest)
clf_.score(Xtest,Ytest)
cm(Ytest,ypred_,labels=[1,0])
recall(Ytest,ypred_)
auc(Ytest,clf_.predict_proba(Xtest)[:,1])
# 0.9696356275303644
#随着样本权重逐渐增加,模型的recall,auc和准确率如何变化?
for i in [1,5,10,20,30]:
clf_ = XGBC(scale_pos_weight=i).fit(Xtrain,Ytrain)
ypred_ = clf_.predict(Xtest)
print(i)
print("\tAccuracy:{}".format(clf_.score(Xtest,Ytest)))
print("\tRecall:{}".format(recall(Ytest,ypred_)))
print("\tAUC:{}".format(auc(Ytest,clf_.predict_proba(Xtest)[:,1])))
"""
1
Accuracy:0.9272727272727272
Recall:0.6923076923076923
AUC:0.9701417004048585
5
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9660931174089069
10
Accuracy:0.9333333333333333
Recall:0.7692307692307693
AUC:0.9696356275303644
20
Accuracy:0.9333333333333333
Recall:0.7692307692307693
AUC:0.9686234817813765
30
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9701417004048583
"""
#负/正样本比例
clf_ = XGBC(scale_pos_weight=20).fit(Xtrain,Ytrain)
ypred_ = clf_.predict(Xtest)
clf_.score(Xtest,Ytest)
# 0.9333333333333333
cm(Ytest,ypred_,labels=[1,0])
"""
array([[ 10, 3],
[ 8, 144]], dtype=int64)
"""
recall(Ytest,ypred_)
# 0.7692307692307693
auc(Ytest,clf_.predict_proba(Xtest)[:,1])
# 0.9686234817813765
dtrain = xgb.DMatrix(Xtrain,Ytrain)
dtest = xgb.DMatrix(Xtest,Ytest)
#看看xgboost库自带的predict接口
param = {'silent':True,'objective':'binary:logistic',"eta":0.1,"scale_pos_weight":1}
num_round = 100
bst = xgb.train(param, dtrain, num_round)
preds = bst.predict(dtest)
preds
"""
array([0.00110357, 0.00761518, 0.00110357, 0.00110357, 0.93531454,
0.00466839, 0.00110357, 0.00110357, 0.00110357, 0.00110357,
0.00110357, 0.00410493, 0.00454478, 0.00571528, 0.00751026,
0.00110357, 0.00110357, 0.00110357, 0.00110357, 0.00110357,
0.00110357, 0.00110357, 0.00110357, 0.00110357, 0.00110357,
0.00712637, 0.00110357, 0.00110357, 0.00110357, 0.00110357,
0.00110357, 0.00110357, 0.00110357, 0.00793251, 0.00466839,
0.00110357, 0.00339395, 0.00657186, 0.00110357, 0.00457053,
0.00571528, 0.0026763 , 0.00110357, 0.00110357, 0.00110357])
"""
#自己设定阈值
ypred = preds.copy()
ypred[preds > 0.5] = 1
ypred[ypred != 1] = 0
#写明参数
scale_pos_weight = [1,5,10]
names = ["negative vs positive: 1"
,"negative vs positive: 5"
,"negative vs positive: 10"]
#导入模型评估指标
from sklearn.metrics import accuracy_score as accuracy, recall_score as recall, roc_auc_score as auc
for name,i in zip(names,scale_pos_weight):
param = {'silent':True,'objective':'binary:logistic'
,"eta":0.1,"scale_pos_weight":i}
num_round = 100
clf = xgb.train(param, dtrain, num_round)
preds = clf.predict(dtest)
ypred = preds.copy()
ypred[preds > 0.5] = 1
ypred[ypred != 1] = 0
print(name)
print("\tAccuracy:{}".format(accuracy(Ytest,ypred)))
print("\tRecall:{}".format(recall(Ytest,ypred)))
print("\tAUC:{}".format(auc(Ytest,preds)))
"""
negative vs positive: 1
Accuracy:0.9272727272727272
Recall:0.6923076923076923
AUC:0.9741902834008097
negative vs positive: 5
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9635627530364372
negative vs positive: 10
Accuracy:0.9515151515151515
Recall:1.0
AUC:0.9665991902834008
"""
#当然我们也可以尝试不同的阈值
for name,i in zip(names,scale_pos_weight):
for thres in [0.3,0.5,0.7,0.9]:
param= {'silent':True,'objective':'binary:logistic'
,"eta":0.1,"scale_pos_weight":i}
clf = xgb.train(param, dtrain, num_round)
preds = clf.predict(dtest)
ypred = preds.copy()
ypred[preds > thres] = 1
ypred[ypred != 1] = 0
print("{},thresholds:{}".format(name,thres))
print("\tAccuracy:{}".format(accuracy(Ytest,ypred)))
print("\tRecall:{}".format(recall(Ytest,ypred)))
print("\tAUC:{}".format(auc(Ytest,preds)))
"""
negative vs positive: 1,thresholds:0.3
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9741902834008097
negative vs positive: 1,thresholds:0.5
Accuracy:0.9272727272727272
Recall:0.6923076923076923
AUC:0.9741902834008097
negative vs positive: 1,thresholds:0.7
Accuracy:0.9212121212121213
Recall:0.6153846153846154
AUC:0.9741902834008097
negative vs positive: 1,thresholds:0.9
Accuracy:0.9515151515151515
Recall:0.5384615384615384
AUC:0.9741902834008097
negative vs positive: 5,thresholds:0.3
Accuracy:0.9515151515151515
Recall:1.0
AUC:0.9635627530364372
negative vs positive: 5,thresholds:0.5
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9635627530364372
negative vs positive: 5,thresholds:0.7
Accuracy:0.9272727272727272
Recall:0.6923076923076923
AUC:0.9635627530364372
negative vs positive: 5,thresholds:0.9
Accuracy:0.9212121212121213
Recall:0.6153846153846154
AUC:0.9635627530364372
negative vs positive: 10,thresholds:0.3
Accuracy:0.9515151515151515
Recall:1.0
AUC:0.9665991902834008
negative vs positive: 10,thresholds:0.5
Accuracy:0.9515151515151515
Recall:1.0
AUC:0.9665991902834008
negative vs positive: 10,thresholds:0.7
Accuracy:0.9393939393939394
Recall:0.8461538461538461
AUC:0.9665991902834008
negative vs positive: 10,thresholds:0.9
Accuracy:0.9212121212121213
Recall:0.6153846153846154
AUC:0.9665991902834008
"""
可以看出,在xgboost库和sklearnAPI中,参数scale_pos_weight都非常有效。本质上来说,scale_pos_weight参数是通过调节预测的概率值来调节,大家可以通过查看bst.predict(Xtest)返回的结果来观察概率受到了怎样的影响。因此,当我们只关心预测出的结果是否准确,AUC面积或者召回率是否足够好,我们就可以使用scale_pos_weight参数来帮助我们。然而xgboost除了可以做分类和回归,还有其他的多种功能,在一些需要使用精确概率的领域(比如排序ranking),我们希望能够保持概率原有的模样,而提升模型的效果。这种时候,我们就无法使用scale_pos_weight来帮助我们。来看看xgboost官网是怎么说明这个问题的:
官网上说,如果我们只在意模型的整表现,则使用AUC作为模型评估指标,使用scale_pos_weight来处理样本不平衡问题,如果我们在意预测出正确的概率,那我们就无法通过调节scale_pos_weight来减轻样本不平衡问题带来的影响。
这种时候,我们需要考虑另一个参数:max_delta_step。这个参数非常难以理解,它被称之为是“树的权重估计中允许的单次最大增量”,既可以考虑成是影响的估计的参数。xgboost官网上认为,如果我们在处理样本不均衡问题,并且十分在意得到正确的预测概率,则可以设置max_delta_step参数为一个有限的数(比如1)来帮助收敛。max_delta_step参数通常不进行使用,二分类下的样本不均衡问题时这个参数唯一的用途。
XGBoost类中的其他参数和功能
更多计算资源:n_jobs
nthread和n_jobs都是算法运行所使用的线程,与sklearn中规则一样,输入整数表示使用的线程,输入-1表示使用计算机全部的计算资源。如果我们的数据量很大,则我们可能需要这个参数来为我们调用更多线程。
降低学习难度:base_score
base_score是一个比较容易被混淆的参数,它被叫做全局偏差,在分类问题中,它是我们希望关注的分类的先验概率。比如说,如果我们有1000个样本,其中300个正样本,700个负样本,则base_score就是0.3。对于回归来说,这个分数默认0.5,但其实这个分数在这种情况下并不有效。许多使用XGBoost的人已经提出,当使用回归的时候base_score的默认应该是标签的均值,不过现在xgboost库尚未对此做出改进。使用这个参数,我们便是在告诉模型一些我们了解但模型不一定能够从数据中学习到的信息。通常我们不会使用这个参数,但对于严重的样本不均衡问题,设置一个正确的base_score取值是很有必要的。
生成树的随机模式:random_state
在xgb库和sklearn中,都存在空值生成树的随机模式的参数random_state。在之前的剪枝中,我们提到可以通过随机抽样样本,随机抽样特征来减轻过拟合的影响,我们可以通过其他参数来影响随机抽样的比例,却无法对随机抽样干涉更多,因此,真正的随机性还是由模型自己生成的。如果希望控制这种随机性,可以在random_state参数中输入固定整数。需要注意的是,xgb库和sklearn库中,在random_state参数中输入同一个整数未必表示同一个随机模式,不一定会得到相同的结果,因此导致模型的feature_importances也会不一致。
自动处理缺失值:missing
XGBoost被设计成是能够自动处理缺失值的模型,这个设计的初衷其实是为了让XGBoost能够处理稀疏矩阵。我们可以在参数missing中输入一个对象,比如np.nan,或数据的任意取值,表示将所有含有这个对象的数据作为空值处理。XGBoost会将所有的空值当作稀疏矩阵中的0来进行处理,因此在使用XGBoost的时候,我们也可以不处理缺失值。当然,通常来说,如果我们了解业务并且了解缺失值的来源,我们还是希望手动填补缺失值。