不平衡处理:xgboost 中scale_pos_weight、给样本设置权重weight、 自定义损失函数 和 简单复制正样本的区别

在对不平衡数据进行训练时,通常会考虑一下怎么处理不平衡数据能使训练出来的结果较好。能想到的比较基础的方法是过采样和下采样来缓解数据中的正负样本比。

在用xgboost训练二分类模型时,除了直接使用过采样和下采样,xgboost接口还提供一些处理不平衡数据的方法,有scale_pos_weight参数的设置,还有给样本赋予一定的权重。接下来让我们仔细看一下吧~

参数scale_pos_weight:

官方的解释是这样的,scale_pos_weight可以设置为数据中负样本数量/正样本数量

设置样本的权重,在DMatrix里边可以设置

这两者有什么区别吗?然后我做了一个实验。

实验使用的数据量80000,其中负样本60000,正样本20000,负样本是正样本数量的3倍。

1、使用scale_pos_weight

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
params = {
'booster': 'gbtree',
'objective': 'binary:logistic',
'metric':'auc',
'eval_metric':'auc',
'max_depth':5,
'min_child_weight':350,
'gamma':0,
'subsample':1,
'colsample_bytree':1,
'scale_pos_weight':3,
}
X_train=train_data[featureLsTest]
y_train=train_data[target]
dtrain=xgb.DMatrix(X_train,y_train)
xgbM = xgb.train(params=params, dtrain=dtrain, num_boost_round=100,evals=[(dtrain, 'train')],verbose_eval=True)

2、使用weight给样本赋予权重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
params = {
'booster': 'gbtree',
'objective': 'binary:logistic',
'metric':'auc',
'eval_metric':'auc',
'max_depth':5,
'min_child_weight':350,
'gamma':0,
'subsample':1,
'colsample_bytree':1,
'scale_pos_weight':1 ,  
}
X_train=train_data[featureLsTest]
y_train=train_data[target]
dtrain=xgb.DMatrix(data=X_train,label=y_train,weight=y_train.map(lambda x:1 if x==0 else 3))
xgbM = xgb.train(params=params, dtrain=dtrain, num_boost_round=100,evals=[(dtrain, 'train')],verbose_eval=True)

3、另外自己还写了一个自定义损失函数,将负样本的logloss设置为正样本的3倍,然后求一阶导和二阶导

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
params = {
'booster': 'gbtree',
'objective': 'binary:logistic',
'metric':'auc',
'eval_metric':'auc',
'max_depth':5,
'min_child_weight':350,
'gamma':0,
'subsample':1,
'colsample_bytree':1,
'scale_pos_weight':1 ,
}
beta_num=3
def logistic_obj(y_hat, dtrain):
    y = dtrain.get_label()
    p=y_hat
    beta = beta_num
    grad = p * (beta*y+1-y) - beta*y
    hess = p * (1 - p) * (beta*y+1-y)
    return grad, hess

X_train=train_data[featureLsTest]
y_train=train_data[target]
dtrain=xgb.DMatrix(X_train,y_train)
xgbM = xgb.train(params=params, dtrain=dtrain, num_boost_round=100,evals=[(dtrain, 'train')],verbose_eval=True,obj=logistic_obj)

4、为了对比,也做了这样一个试验,将正样本复制成原来的3倍,也就是6000条,和负样本拼起来组成训练集。然后训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
params = {
'booster': 'gbtree',
'objective': 'binary:logistic',
'metric':'auc',
'eval_metric':'auc',
'max_depth':5,
'min_child_weight':350,
'gamma':0,
'subsample':1,
'colsample_bytree':1,
'scale_pos_weight':1 ,
}
X_train=train_data_duplicate[featureLsTest]
y_train=train_data_duplicate[target]
dtrain=xgb.DMatrix(X_train,y_train)
xgbM = xgb.train(params=params, dtrain=dtrain, num_boost_round=100,evals=[(dtrain, 'train')],verbose_eval=True)

将1、2、3、4的训练过程中的auc打印出来:

后边的32-99的图就不往上放了。

实验证明,这四个方法是一模一样的。

关于scale_pos_weight ,在xgboost里边的源码是

1
2
3
if (label == 1.0f) {
            w *= scale_pos_weight;
          }

从源代码也可以看出,scale_pos_weight其实就是改变样本weight,也就是和在DMatrix里边设置每个样本的weight是一样的。

在DMatrix里边设置每个样本的weight 是 怎样改变训练过程的呢,其实是改变训练的损失函数,源代码里的代码如下,可以看到对不同的样本赋予不同的权重实际上是影响了该样本在训练过程中贡献的损失。

1
2
_out_gpair[_idx] = GradientPair(Loss::FirstOrderGradient(p, label) * w,
                   Loss::SecondOrderGradient(p, label) * w);

结论:

xgboost中scale_pos_weight、对样本进行weight设置 和 简单复制正样本得到的结果是一样的,本质上都是改变了训练的损失函数。通过自定义设置损失函数可得到验证。实际上基本思想都是通过过采样的方法处理不平衡数据。