如何让基于XGBoost的橄榄球胜率模型预测概率和为100%?
解决XGBoost同场比赛预测胜率和不为1的问题
一、单队建模后用1减胜率的合理性
这么做其实有一定道理——毕竟一场比赛两队胜负互斥(忽略平局的话),理论上胜率加起来该是100%。但得满足两个前提:
- 你的特征必须是完全对称的:比如同一场比赛里,PAT的特征是自己的得分、防守得分,加上对手的得分、防守得分;而DAL的特征是自己的得分(对应PAT的对手得分)、防守得分(对应PAT的对手防守得分),加上对手的得分(对应PAT的得分)、防守得分(对应PAT的防守得分)。说白了就是两队的特征是镜像的,这样模型学到的规律才能保证“1-本队胜率”就是对手的真实胜率。
- 如果有非对称特征(比如某队的伤病信息只在自己的行里,对手行没有对应镜像数据),那1减出来的结果肯定不准,因为模型对两队的判断逻辑不一样。
二、让XGBoost识别game_id分组的方法(强制胜率和为1)
要是不想用1减的方法,试试成对建模+自定义损失函数的思路,核心是让模型训练时就考虑同一场比赛的两队样本:
1. 先把数据改成成对格式
把同一场比赛的两队数据合并成一行,比如:
# 示例:按game_id合并两队数据 pair_data <- your_data %>% group_by(game_id) %>% mutate(row_num = row_number()) %>% pivot_wider( names_from = row_num, values_from = c(team, pts, def_pts, opp_pts, opp_def_pts, did_win), names_sep = "_" )
改完后每一行对应一场比赛,包含两队的所有特征和胜负标签(比如did_win_1为1就代表team_1赢了,did_win_2自然就是0)。
2. 给XGBoost写个自定义损失函数
用交叉熵损失的变体,强制模型输出的两队胜率加起来是1。具体来说,让模型预测一个对数几率值,然后转成概率p,对手的概率就是1-p,损失函数基于真实标签计算:
# 自定义成对损失函数 pairwise_loss <- function(preds, dtrain) { labels <- getinfo(dtrain, "label") # labels是两列矩阵:第一列是team1的胜负标签,第二列是team2的 p <- plogis(preds) # 把预测值转成概率p loss <- - (labels[,1] * log(p) + labels[,2] * log(1 - p)) grad <- p - labels[,1] # 梯度 hess <- p * (1 - p) # 海森矩阵 return(list(grad = grad, hess = hess)) }
然后在tidymodels里调用这个自定义损失:
library(tidymodels) library(xgboost) xgb_spec <- boost_tree( trees = 1000, mtry = tune(), min_n = tune(), learn_rate = tune() ) %>% set_engine("xgboost", objective = pairwise_loss) %>% set_mode("classification")
这种方法让模型从比赛整体角度学习,天生就能保证同场比赛的胜率和为1。
三、额外提醒
- 要是你的数据里有平局,得调整标签和损失函数(比如改成三分类),不过橄榄球平局概率低,大概率不用管。
- 做交叉验证时一定要按game_id分组,别让同一场比赛的两队样本分到不同折叠里,不然会高估模型性能。用
group_vfold_cv()就能实现:
cv_folds <- group_vfold_cv(your_data, group = game_id, v = 5)
内容的提问来源于stack exchange,提问作者codeweird
相关产品推荐
相关产品推荐

