印象里刘老师 @北冥乘海生 曾说过,算法工程师的第三层境界,是“擅定义问题”
。可以为业务写出优雅简洁的损失函数与目标函数。自认没有这个本事。新书里面有一小段代码,简单构造了一个局部捕获率最优的评价函数,在实际业务中也取得了不错的业务表现,分享给大家。
XGBoost模型支持自定义评价函数和损失函数。只要保证损失函数二阶可导,通过评价函数的最大化既可以对模型参数进行求解。实际使用中,可以考虑根据业务目标对这两者进行调整。
举个例子,假设现在有一个提额模型,用处是将分数最高的20%客户给与更高的额度。也就是期望分数最高的20%的客群正样本捕获率最大化。可能在保证上述前提,同时保证模型对正负样本有一定的区分能力。所以可以改写一个保证模型区分度,同时又能优化局部正样本捕获率的评价函数 。
⭐️自定义XGBoost模型损失函数与评价函数。
1. # 自定义对数损失函数
2. def loglikelood(preds, dtrain):
3. labels = dtrain.get_label()
4. preds = 1.0 / (1.0 + np.exp(-preds))
5. grad = preds - labels
6. hess = preds * (1.0-preds)
7. return grad, hess
8.
9. # 评价函数:前20%正样本占比最大化
10. def binary_error(preds, train_data):
11. labels = train_data.get_label()
12. dct = pd.DataFrame({'pred':preds,'percent':preds,'labels':labels})
13. #取百分位点对应的阈值
14. key = dct['percent'].quantile(0.2)
15. #按照阈值处理成二分类任务
16. dct['percent']= dct['percent'].map(lambda x :1 if x <= key else 0)
17. #计算评价函数,权重默认0.5,可以根据情况调整
18. result = np.mean(dct[dct.percent== 1]['labels'] == 1)*0.5
19. + np.mean((dct.labels - dct.pred)**2)*0.5
20. return 'error',result
21.
22. watchlist = [(dtest,'eval'), (dtrain,'train')]
23. param = {'max_depth':3, 'eta':0.1, 'silent':1}
24. num_round = 100
25. # 自定义损失函数训练
26. bst = xgb.train(param, dtrain, num_round, watchlist, loglikelood, binary_error)
可以看到评价函数由两部分组成,⭐️第一部分权重默认为0.5,目的是使得前20%样本中的正样本占比最大。因为正样本的标签为0,因此pandas.quantile()函数分位点参数0.2,表示预估为正样本概率最大的前20%分位点。⭐️第二部分权重同样默认设置为0.5,目的是让模型对正负样本的识别能力得到保障。
实际使用中,可以根据,对模型表现的侧重点,进行权重选择 。比如当更希望模型关注于捕获率时,可以调整第一部分权重为0.8,将第二部分权重调整为0.2。本文给出的是一种启发性的思路,读者还可以根据实际情况改写更贴合业务的损失函数。
⭐️LightGBM中也同样支持自定义损失函数和评价函数。代码上有一些细微差别。评价函数需要返回三部分,用False代替。
# 自定义二分类对数损失函数
def loglikelood(preds, train_data):
labels = train_data.get_label()
preds = 1. / (1. + np.exp(-preds))
grad = preds - labels
hess = preds * (1. - preds)
return grad, hess
# 自定义前20%正样本占比最大化的评价函数
def binary_error(preds, train_data):
labels = train_data.get_label()
dct = pd.DataFrame({'pred':preds,'percent':preds,'labels':labels})
#取百分位点对应的阈值
key = dct['percent'].quantile(0.2)
#按照阈值处理成二分类任务
dct['percent']= dct['percent'].map(lambda x :1 if x <= key else 0)
#计算评价函数,权重默认0.5,可以根据情况调整
result = np.mean(dct[dct.percent== 1]['labels'] == 1)*0.9 + np.mean((dct.labels - dct.pred)**2)*0.5
return 'error',result, False
gbm = lgb.train(params,
lgb_train,
num_boost_round=100,
init_model=gbm,
fobj=loglikelood,
feval=binary_error,
valid_sets=lgb_eval)
感谢阅读。