You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 03:34:54