[机器学习] XGB/LGB---自定义损失函数与评价函数
一 自定義評(píng)價(jià)函數(shù)
Q: 評(píng)價(jià)函數(shù)為什么會(huì)影響模型訓(xùn)練?
A: 評(píng)價(jià)函數(shù)會(huì)決定best_iteration、best_score在哪里取得最優(yōu)解。
XGBoost模型支持自定義評(píng)價(jià)函數(shù)和損失函數(shù)。只要保證損失函數(shù)二階可導(dǎo),通過(guò)評(píng)價(jià)函數(shù)的最大化既可以對(duì)模型參數(shù)進(jìn)行求解。實(shí)際使用中,可以考慮根據(jù)業(yè)務(wù)目標(biāo)對(duì)這兩者進(jìn)行調(diào)整。
舉個(gè)例子,假設(shè)現(xiàn)在有一個(gè)提額模型,用處是將分?jǐn)?shù)最高的20%客戶給與更高的額度。也就是期望分?jǐn)?shù)最高的20%的客群正樣本捕獲率最大化。可能在保證上述前提,同時(shí)保證模型對(duì)正負(fù)樣本有一定的區(qū)分能力。所以可以改寫一個(gè)保證模型區(qū)分度,同時(shí)能優(yōu)化局部正樣本捕獲率的評(píng)價(jià)函數(shù) 。
自定義XGBoost模型損失函數(shù)與評(píng)價(jià)函數(shù)
# 自定義對(duì)數(shù)損失函數(shù) def loglikelood(preds, dtrain): labels = dtrain.get_label() preds = 1.0 / (1.0 + np.exp(-preds)) grad = preds - labels hess = preds * (1.0-preds) return grad, hess # 評(píng)價(jià)函數(shù):前20%正樣本占比最大化 def binary_error(preds, train_data): labels = train_data.get_label() dct = pd.DataFrame({'pred':preds,'percent':preds,'labels':labels}) #取百分位點(diǎn)對(duì)應(yīng)的閾值 key = dct['percent'].quantile(0.2) #按照閾值處理成二分類任務(wù) dct['percent']= dct['percent'].map(lambda x :1 if x <= key else 0) #計(jì)算評(píng)價(jià)函數(shù),權(quán)重默認(rèn)0.5,可以根據(jù)情況調(diào)整 result = np.mean(dct[dct.percent== 1]['labels'] == 1)*0.5 + np.mean((dct.labels - dct.pred)**2)*0.5 return 'error',result watchlist = [(dtest,'eval'), (dtrain,'train')] param = {'max_depth':3, 'eta':0.1, 'silent':1} num_round = 100 # 自定義損失函數(shù)訓(xùn)練 bst = xgb.train(param, dtrain, num_round, watchlist, loglikelood, binary_error)可以看到評(píng)價(jià)函數(shù)由兩部分組成,??第一部分權(quán)重默認(rèn)為0.5,目的是使得前20%樣本中的正樣本占比最大。因?yàn)檎龢颖镜臉?biāo)簽為0,因此pandas.quantile()函數(shù)分位點(diǎn)參數(shù)0.2,表示預(yù)估為正樣本概率最大的前20%分位點(diǎn)。??第二部分權(quán)重同樣默認(rèn)設(shè)置為0.5,目的是讓模型對(duì)正負(fù)樣本的識(shí)別能力得到保障。
實(shí)際使用中,可以根據(jù),對(duì)模型表現(xiàn)的側(cè)重點(diǎn),進(jìn)行權(quán)重選擇 。比如當(dāng)更希望模型關(guān)注于捕獲率時(shí),可以調(diào)整第一部分權(quán)重為0.8,將第二部分權(quán)重調(diào)整為0.2。本文給出的是一種啟發(fā)性的思路,讀者還可以根據(jù)實(shí)際情況改寫更貼合業(yè)務(wù)的損失函數(shù)。
??LightGBM中也同樣支持自定義損失函數(shù)和評(píng)價(jià)函數(shù)。代碼上有一些細(xì)微差別。評(píng)價(jià)函數(shù)需要返回三部分,用False代替。
# 自定義二分類對(duì)數(shù)損失函數(shù) def loglikelood(preds, train_data):labels = train_data.get_label()preds = 1. / (1. + np.exp(-preds))grad = preds - labelshess = preds * (1. - preds)return grad, hess# 自定義前20%正樣本占比最大化的評(píng)價(jià)函數(shù) def binary_error(preds, train_data):labels = train_data.get_label()dct = pd.DataFrame({'pred':preds,'percent':preds,'labels':labels})#取百分位點(diǎn)對(duì)應(yīng)的閾值key = dct['percent'].quantile(0.2)#按照閾值處理成二分類任務(wù)dct['percent']= dct['percent'].map(lambda x :1 if x <= key else 0) #計(jì)算評(píng)價(jià)函數(shù),權(quán)重默認(rèn)0.5,可以根據(jù)情況調(diào)整result = np.mean(dct[dct.percent== 1]['labels'] == 1)*0.9 + np.mean((dct.labels - dct.pred)**2)*0.5return 'error',result, Falsegbm = lgb.train(params,lgb_train,num_boost_round=100,init_model=gbm,fobj=loglikelood,feval=binary_error,valid_sets=lgb_eval)反欺詐自定義評(píng)估函數(shù)
評(píng)估指標(biāo)有2:1是 ks, 2是頭部 lift。 KS 反映整體排序能力,頭部 lift 反映分段排序性。之前反欺詐模型迭代時(shí)主要更新訓(xùn)練時(shí)間窗口,模型方法上改變很小。由于反欺詐模型實(shí)際應(yīng)用于砍刀,即按照某一分?jǐn)?shù) cutoff。反欺詐模型目標(biāo)其實(shí)是提升頭部抓壞率,不關(guān)心整體排序。所以在想是否可以改變模型訓(xùn)練目標(biāo),讓模型訓(xùn)練時(shí)不用追求全局排序最優(yōu),只需要頭部最優(yōu)。嘗試了改變 xgb 訓(xùn)練的評(píng)估函數(shù)和損失函數(shù),有一點(diǎn)效果。
# 自定義對(duì)數(shù)損失函數(shù) def loglikelood(preds, dtrain): labels = dtrain.get_label() preds = 1.0 / (1.0 + np.exp(-preds)) grad = preds - labels hess = preds * (1.0-preds) return grad, hess # 評(píng)價(jià)函數(shù)watchlist = [(dtest,'eval'), (dtrain,'train')] param = {'max_depth':3, 'eta':0.1, 'silent':1} num_round = 100 # 自定義損失函數(shù)訓(xùn)練 bst = xgb.train(params=params, dtrain=dtrain, num_boost_round=900, evals=watchlist, early_stoppingrounds=30,obj=loglikelood, feval=mse_top)自定義損失函數(shù)是交叉熵,評(píng)估函數(shù) mse_top 包含兩個(gè)部分mse和 gap, mse 只計(jì)算頭部10%的 均方誤差,gap 是頭部的平均分與剩下部分平均分的比值,mse * gap 目的是讓頭部誤差小,同時(shí)盡量讓壞人排到頭部。?
來(lái)源:反欺詐自定義損失函數(shù)和評(píng)估函數(shù) - 知乎 (zhihu.com)
二 自定義損失函數(shù)
XGBoost損失函數(shù)的預(yù)備知識(shí)
- 損失函數(shù):損失函數(shù)描述了預(yù)測(cè)值和真實(shí)標(biāo)簽的差異,通過(guò)對(duì)損失函數(shù)的優(yōu)化來(lái)獲得對(duì)學(xué)習(xí)任務(wù)的一個(gè)近似求解方法
- boosting類算法的損失函數(shù)的作用: Boosting的框架, 無(wú)論是GBDT還是Adaboost, 其在每一輪迭代中, 根本沒(méi)有理會(huì)損失函數(shù)具體是什么, 僅僅用到了損失函數(shù)的一階導(dǎo)數(shù)通過(guò)隨機(jī)梯度下降來(lái)參數(shù)更新
- 二階導(dǎo)數(shù):GBDT在模型訓(xùn)練時(shí)只使用了代價(jià)函數(shù)的一階導(dǎo)數(shù)信息,XGBoost對(duì)代價(jià)函數(shù)進(jìn)行二階泰勒展開(kāi),可以同時(shí)使用一階和二階導(dǎo)數(shù)
- 牛頓法梯度更新:XGBoost是用了牛頓法進(jìn)行的梯度更新。通過(guò)對(duì)損失進(jìn)行分解得到一階導(dǎo)數(shù)和二階導(dǎo)數(shù)并通過(guò)牛頓法來(lái)迭代更新梯度。
基于XGBoost的損失函數(shù)的分解求導(dǎo),可以知道XGBoost的除正則項(xiàng)以外的核心影響因子是損失函數(shù)的1階導(dǎo)和2階導(dǎo),所以對(duì)于任意的學(xué)習(xí)任務(wù)的損失函數(shù),可以對(duì)其求一階導(dǎo)數(shù)和二階導(dǎo)數(shù)帶入到XGBoost的自定義損失函數(shù)范式里面進(jìn)行處理。
def custom_obj(pred, dtrain):#pred 和dtrain 的順序不能弄反# STEP1 獲得labellabel = dtrain.get_label()# STEP2 如果是二分類任務(wù),需要讓預(yù)測(cè)值通過(guò)sigmoid函數(shù)獲得0~1之間的預(yù)測(cè)值# 如果是回歸任務(wù)則下述任務(wù)不需要通過(guò)sigmoid#分類任務(wù)sigmoid化def sigmoid(x):return 1/(1+np.exp(-x))sigmoid_pred = sigmoid(-原始預(yù)測(cè)值)#回歸任務(wù)pred = 原始預(yù)測(cè)值# STEP3 一階導(dǎo)和二階導(dǎo)grad = 一階導(dǎo)hess = 二階導(dǎo)return grad, hess常見(jiàn)的損失函數(shù)
分類問(wèn)題
非平衡分類學(xué)習(xí)任務(wù),例如首筆首期30+的風(fēng)險(xiǎn)建模任務(wù),首期30+的逾期率比例相對(duì)ever30+的逾期率為1/3左右,通過(guò)修正占比少的正樣本權(quán)重來(lái)對(duì)影響正樣本對(duì)損失函數(shù)的貢獻(xiàn)度,可以進(jìn)一步提升模型的
- 加權(quán)損失函數(shù)
- Focal loss
回歸問(wèn)題
應(yīng)用場(chǎng)景
美團(tuán)的ETA預(yù)估
https://tech.meituan.com/2019/02/21/meituan-delivery-eta-estimation-in-the-practice-of-deep-learning.html
通過(guò)對(duì)損失函數(shù)進(jìn)行修正大幅度提高了業(yè)務(wù)預(yù)測(cè)效果(使得結(jié)果傾向于提前到達(dá))
1.帶懲罰的最小二乘損失函數(shù)
def custom_normal_train( y_pred,dtrain):label = dtrain.get_label()residual = (label - y_pred).astype("float")grad = np.where(residual<0, -2*(residual)/(label+1), -10*2*(residual)/(label+1))#對(duì)預(yù)估里程低于實(shí)際里程的情況加大懲罰hess = np.where(residual<0, 2/(label+1), 10*2/(label+1))#對(duì)預(yù)估里程低于實(shí)際里程的情況加大懲罰return grad, hesshttps://www.kaggle.com/c/allstate-claims-severity/discussion/24520#140255
kaggle 的保險(xiǎn)定價(jià)競(jìng)賽中評(píng)價(jià)函數(shù)為MAE(存在不可導(dǎo)區(qū)域),通過(guò)使用下述的損失函數(shù)來(lái)獲得MAE的近似損失函數(shù)來(lái)求解該問(wèn)題
1.fair loss
def fair_obj( preds,dtrain):labels = dtrain.get_label() con = 2residual = preds-labelsgrad = con*residual / (abs(residual)+con)hess = con**2 / (abs(residual)+con)**2return grad,hesshttps://www.kaggle.com/c/allstate-claims-severity/discussion/24520#140255?提供了非常高質(zhì)量的近似MAE損失函數(shù)優(yōu)化的討論
2.mae的近似損失函數(shù)- Pseudo Huber loss
def huber_approx_obj( preds,dtrain): d = preds -dtrain.get_label() h = 1 #h is delta in the formula scale = 1 + (d / h) ** 2 scale_sqrt = np.sqrt(scale) grad = d / scale_sqrt hess = 1 / scale / scale_sqrt return grad, hess3.mae的近似損失函數(shù) ln(cosh) loss
def logcoshobj( preds,dtrain): label = dtrain.get_label()d = preds - labelgrad = np.tanh(d)/label hess = (1.0 - grad*grad)/label return grad, hessXGBoost損失函數(shù)優(yōu)化 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/278060736
風(fēng)控模型有2個(gè)基本問(wèn)題:
1)模型用歷史數(shù)據(jù)訓(xùn)練預(yù)測(cè)未來(lái)樣本表現(xiàn)。這里有個(gè)假設(shè)是未來(lái)樣本分布和歷史基本相似。但這個(gè)假設(shè)可能是不成立的,特別是對(duì)非銀機(jī)構(gòu),模型策略迭代比較頻繁時(shí),從申請(qǐng)到授信到用信漏斗下來(lái)的人群各月已經(jīng)發(fā)生了改變,另外宏觀環(huán)境也可能改變申請(qǐng)人群。所以如何選擇與未來(lái)樣本最相似的樣本訓(xùn)練是一個(gè)基本問(wèn)題。
2)風(fēng)控模型特別是 A 卡一般在固定時(shí)間點(diǎn)打分(比如授信時(shí)),但預(yù)測(cè)評(píng)估則是全生命周期的。既模型上線后,既要看短周期表現(xiàn)也要看長(zhǎng)周期表現(xiàn)。而模型訓(xùn)練的 label則是固定一個(gè)時(shí)間點(diǎn),比如 dpd30+ @mob12。所以會(huì)看到當(dāng) label對(duì)應(yīng)觀察時(shí)間窗口較短訓(xùn)練模型時(shí)(比如 dpd30+@mob2),短期的模型排序能力略強(qiáng)長(zhǎng)期較弱,若 label 對(duì)應(yīng)時(shí)間窗口較長(zhǎng)(dpd30+@mob12)時(shí)長(zhǎng)周期表現(xiàn)略好,短期可能比較弱。如何訓(xùn)練一個(gè)模型能對(duì)未來(lái)全生命周期都有較好排序能力是另一個(gè)基本問(wèn)題。
自定義損失函數(shù)--提升模型全周期排序能力
無(wú)論是用邏輯回歸還是樹模型訓(xùn)練,都是將目標(biāo)看成一個(gè)分類問(wèn)題(是否逾期)。模型損失函數(shù)為交叉熵,如下式:
從交叉熵的公式可以看出模型訓(xùn)練迭代時(shí),是將正樣本預(yù)測(cè)概率逼近1,負(fù)樣本逼近0。但同樣是1的正樣本之間是沒(méi)有區(qū)分的。早期壞的人理論上分?jǐn)?shù)理想情況下應(yīng)該比晚期壞的人更高。如果目標(biāo)函數(shù)能體現(xiàn)不同周期(mob)壞的人的分?jǐn)?shù)高低,那豈不是完美解決問(wèn)題了。所以嘗試在目標(biāo)函數(shù)加上另外一項(xiàng)損失,即排序損失。損失函數(shù)定義如下
第一項(xiàng)就是交叉熵,第二項(xiàng)是ranking 損失。ranking 損失定義是: 把好人,不同周期的壞人(mob2, mob3....)分組,然后理想情況下各組分?jǐn)?shù)從高到低排序應(yīng)該是:...mob12壞人,mob11壞人...mob3壞人,mob2壞人,好人,ranking 損失就是以上各組亂序的損失,同一組不比較。好的,到這里已經(jīng)比較好的把業(yè)務(wù)問(wèn)題轉(zhuǎn)化為數(shù)學(xué)問(wèn)題了,現(xiàn)在要做的是定義 Ranking 損失。Learning to ranking 是一個(gè)比較成熟的領(lǐng)域,常用于搜索排序。比較常用的方法有 pointwise, pairwise, listwise 等。貼上知乎上Ranking 的基本介紹:
一小撮人:Learning to Rank: pointwise 、 pairwise 、 listwise217 贊同 · 18 評(píng)論文章正在上傳…重新上傳取消?https://zhuanlan.zhihu.com/p/111636490
我們嘗試了用poitwise 和 pairwise 兩種方式來(lái)計(jì)算 ranking 損失,pointwise 用的損失函數(shù)NDCG定義如下,j 對(duì)應(yīng)各個(gè)壞的周期。
?
疊加 ndcg 的 自定義函數(shù)代碼如下。
def logloss_ndcg(preds, train_data):alpha = 0.5beta = 10mobs = train_data.get_label()# preds = 1. / (1. + np.exp(-preds))labels = [1 if int(item) != 13 else 0 for item in list(mobs)]grad_rank = np.exp(preds) * preds * (1. - preds) / np.log(1 + mobs * beta)hess_rank = (np.exp(preds) * preds ** 2 * (1. - preds) ** 2 + np.exp(preds) * preds * (1. - preds) ** 2 - np.exp(preds) * preds ** 2 * (1. - preds)) / np.log(1 + mobs * beta)grad = (1 - alpha) * (preds - labels) + alpha * grad_rankhess = (1 - alpha) * (preds * (1 - preds)) + alpha * hess_rankreturn grad, hessmobs = train_data.get_label()拿到的是各樣本壞的周期[13,13,2,3..12],13表示好人。y2將 labels 轉(zhuǎn)為0,1。grad_rank, hess_rank是 ndcg 推導(dǎo)的一階導(dǎo)數(shù)和二階導(dǎo)數(shù)。最后返回的為交叉熵疊加 ndcg 損失。
此處提醒一下:xgb1.1之后 obj 接口傳過(guò)來(lái)的preds是 sigmoid 之前的,而0.9是sigmoid 之后的。使用低版本 xgb 時(shí)preds = 1. / (1. + np.exp(-preds))要注掉。
pairwise 稍微復(fù)雜點(diǎn),對(duì)應(yīng)的損失函數(shù)為 max(0, y1-y2),即如果y1和y2不逆序則損失為0,逆序?yàn)?y1-y2。函數(shù)的一階導(dǎo)數(shù)g和二階導(dǎo)數(shù) h 如下。
理論上需要將各個(gè)樣本兩兩配對(duì)計(jì)算逆序損失,暴力配對(duì)方法時(shí)間復(fù)雜度為 O(n2),速度太慢。我們做了優(yōu)化:首先把每個(gè)分組的一階導(dǎo)和二階導(dǎo)先求和并存好。然后單樣本y1和其他組的樣本y2比較時(shí)只有兩種情況:樣本y1 mob 比y2 mob 低,則損失為 y1-y2。如果高則 y2-y1;所以單樣本 g如下:?
上式中g(shù)i分解為三個(gè)部分:(n-m)×自身 g, +比自己 mob 高的樣本 g求和,-比自己 mob 低的樣本 g求和。第二項(xiàng)和第三項(xiàng)以及 n,m都可以先算好并保存,后面遍歷所有樣本拿過(guò)來(lái)直接用。時(shí)間復(fù)雜度降低為 O(n)。代碼如下
def obj3(predt, dtrain):l1 = alphal2 = betay_true = list(dtrain.get_label())y_01 = [0 if item ==0 else 1 for item in y_true]infodic_c= {}infodic_g= {}infodic_h= {}predt_label = [item for item in zip(predt, y_true)]for p,y in predt_label:if y > 0:y2 = 1else:y2 = 0y = 999g = p * (1 - p)h = p - 3 * p * p + 2 * p * p * pif not y in infodic_c:infodic_c[y] = 1infodic_g[y] = ginfodic_h[y] = helse:infodic_c[y] += 1infodic_g[y] += ginfodic_h[y] += hgra = []hra = []for p,y in predt_label:sumg = 0.0sumh = 0.0count = 0.0for mob in infodic_c: if y == 0:y == 999y2 = 0else:y2 = 1if y < mob:count += infodic_c[mob]sumg += infodic_c[mob] * p * (1 - p) - infodic_g[mob]sumh += infodic_c[mob] * (p - 3 * p * p + 2 * p * p * p) - infodic_h[mob]elif y > mob:count += infodic_c[mob]sumg += -infodic_c[mob] * p * (1 - p) + infodic_g[mob]sumh += -infodic_c[mob] * (p - 3 * p * p + 2 * p * p * p) + infodic_h[mob]sumg = sumg / (count + 0.0001)sumh = sumh / (count + 0.0001)gra.append(-sumg)hra.append(-sumh)g = alpha * np.asarray(gra) + beta * (predt - y_01)h = alpha * np.asarray(hra) + beta * predt * (1 - predt) return g, h因?yàn)閭魅氲?y_true 是周期 mob 作為 label,所以評(píng)估函數(shù)的 auc 稍作修改,代碼如下
def custom_feval(y_prob, train):mobs = list(train.get_label())y_true = [1 if int(item) != 13 else 0 for item in mobs]y_prob = list(y_prob)return 'feval', roc_auc_score(y_true, y_prob)使用自定義損失函數(shù)后,我們?cè)诓煌瑘?chǎng)景的模型上收益各有不同,有的有明顯增益,有的基本沒(méi)啥變化,取決于場(chǎng)景和數(shù)據(jù)。這里不做延伸解釋,只是一種方法學(xué)的介紹。
提升訓(xùn)練樣本與預(yù)測(cè)樣本分布相似度
1.對(duì)抗樣本,基本思想步驟如下:
1)把歷史樣本和預(yù)測(cè)樣本作為負(fù)樣本和正樣本,構(gòu)造一個(gè)分類模型預(yù)測(cè)樣本是訓(xùn)練集或預(yù)測(cè)集的概率。概率越高,則說(shuō)明和預(yù)測(cè)集越相似。
2)有了樣本概率,接下來(lái)兩種做法:1)把概率作為樣本權(quán)重代入模型訓(xùn)練; 2)選擇 top n%的樣本訓(xùn)練模型。
某些場(chǎng)景下此方法比較有效。知乎上關(guān)于對(duì)抗樣本的鏈接如下
某些場(chǎng)景下此方法比較有效。知乎上關(guān)于對(duì)抗樣本的鏈接如下
劉秋言:還在用交叉驗(yàn)證?試試Kaggle大牛們常用的方法——對(duì)抗驗(yàn)證88 贊同 · 7 評(píng)論文章正在上傳…重新上傳取消?https://zhuanlan.zhihu.com/p/93842847
?原文:?風(fēng)控模型進(jìn)階--自定義損失函數(shù)與訓(xùn)練樣本選擇 - 知乎 (zhihu.com)
總結(jié)
以上是生活随笔為你收集整理的[机器学习] XGB/LGB---自定义损失函数与评价函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [深度学习] AutoDis --- K
- 下一篇: QEMU固件模拟技术-stm32仿真分析