python案例如何利用半场数据调整预测?

wen python案例 5

利用半场数据调整预测的Python案例

核心思想

利用半场数据调整全场预测的核心逻辑是:半场数据能反映比赛的实时状态(节奏、攻防效率、体能、战术执行),从而修正赛前基于历史数据的静态预测。

python案例如何利用半场数据调整预测?

下面用一个足球比赛预测的完整案例来说明。


案例场景

预测一场足球比赛的全场总进球数,赛前模型给出预测,中场休息时根据半场数据动态调整。


完整代码实现

import numpy as np
import pandas as pd
from scipy.stats import poisson
# ============ 1. 赛前预测模型 ============
class MatchPredictor:
    def __init__(self, home_attack, home_defense, away_attack, away_defense):
        """
        基于历史数据的主客场攻防强度(相对联赛平均)
        """
        self.home_attack = home_attack    # 主队进攻强度
        self.home_defense = home_defense  # 主队防守强度(越小越好)
        self.away_attack = away_attack
        self.away_defense = away_defense
        self.league_avg_goals = 1.35      # 联赛场均单队进球
    def pre_match_lambda(self):
        """赛前计算主客队预期进球数 λ"""
        lambda_home = self.home_attack * self.away_defense * self.league_avg_goals
        lambda_away = self.away_attack * self.home_defense * self.league_avg_goals
        return lambda_home, lambda_away
    def pre_match_total_goals_prob(self, max_goals=8):
        """赛前总进球数分布"""
        lh, la = self.pre_match_lambda()
        total_lambda = lh + la  # 简化:总进球近似泊松
        return {k: poisson.pmf(k, total_lambda) for k in range(max_goals)}
# ============ 2. 半场数据驱动的动态调整 ============
class HalftimeAdjuster:
    def __init__(self, pre_lambda_home, pre_lambda_away):
        self.pre_lh = pre_lambda_home
        self.pre_la = pre_lambda_away
    def adjust(self, ht_stats):
        """
        ht_stats: 半场统计数据字典
        - goals_home, goals_away: 半场比分
        - shots_home, shots_away: 射门数
        - xg_home, xg_away: 半场预期进球
        - possession_home: 控球率
        - minutes: 已进行分钟数(默认45)
        """
        minutes = ht_stats.get('minutes', 45)
        remaining = (90 - minutes) / 90.0  # 剩余时间比例
        # ---- 策略A:基于半场xG重新估算全场强度 ----
        # 半场xG按时间放大到全场,再与赛前λ做加权融合
        xg_home_full = ht_stats['xg_home'] * (90 / minutes)
        xg_away_full = ht_stats['xg_away'] * (90 / minutes)
        # 权重:半场样本小,给赛前更多信任;但半场信息新鲜,也需体现
        # 用时间进度作为动态权重(越往后,半场数据权重越大)
        w_ht = minutes / 90.0        # 半场0.5,60分钟0.67,75分钟0.83
        w_pre = 1 - w_ht
        lambda_home_adj = w_pre * self.pre_lh + w_ht * xg_home_full
        lambda_away_adj = w_pre * self.pre_la + w_ht * xg_away_full
        # ---- 策略B:射门效率修正(可选)----
        # 若射门多但xG低 → 说明远射多/低质量;若射门少但xG高 → 高效
        shots_home = ht_stats.get('shots_home', 0)
        shots_away = ht_stats.get('shots_away', 0)
        if shots_home > 0:
            conversion_home = ht_stats['xg_home'] / shots_home
        else:
            conversion_home = 0.1  # 默认
        # ---- 策略C:控球率影响节奏 ----
        possession_home = ht_stats.get('possession_home', 0.5)
        # 控球率极端(>65% 或 <35%)通常预示持续压制或反击
        tempo_factor = 1 + 0.3 * abs(possession_home - 0.5)  # 节奏修正
        lambda_home_adj *= tempo_factor if possession_home > 0.5 else 1.0
        lambda_away_adj *= tempo_factor if possession_home < 0.5 else 1.0
        # 按剩余时间缩放(关键!)
        lambda_home_rem = lambda_home_adj * remaining
        lambda_away_rem = lambda_away_adj * remaining
        return {
            'lambda_home_adj': lambda_home_adj,
            'lambda_away_adj': lambda_away_adj,
            'lambda_home_remaining': lambda_home_rem,
            'lambda_away_remaining': lambda_away_rem,
            'current_score': (ht_stats['goals_home'], ht_stats['goals_away'])
        }
    def full_time_total_goals_dist(self, ht_stats, max_goals=10):
        """半场调整后的全场总进球数分布"""
        adj = self.adjust(ht_stats)
        current = sum(adj['current_score'])
        # 剩余进球近似泊松,叠加已进球
        lam_rem = adj['lambda_home_remaining'] + adj['lambda_away_remaining']
        dist = {}
        for k in range(max_goals):
            final_total = current + k
            if final_total < max_goals:
                dist[final_total] = dist.get(final_total, 0) + poisson.pmf(k, lam_rem)
        return dist
# ============ 3. 案例演示 ============
if __name__ == '__main__':
    # 数据准备:主队强攻,客队防守强
    predictor = MatchPredictor(
        home_attack=1.3, home_defense=0.9,
        away_attack=1.0, away_defense=0.85
    )
    lh, la = predictor.pre_match_lambda()
    print(f"【赛前】主队λ={lh:.2f}, 客队λ={la:.2f}, 总进球期望={lh+la:.2f}")
    pre_dist = predictor.pre_match_total_goals_prob()
    print("【赛前】总进球概率分布:")
    for k, p in pre_dist.items():
        print(f"  {k}球: {p:.3f}")
    # 半场数据(主队完全压制但0-0,xG很高)
    ht_stats = {
        'goals_home': 0, 'goals_away': 0,
        'shots_home': 12, 'shots_away': 2,
        'xg_home': 1.4, 'xg_away': 0.2,
        'possession_home': 0.68,
        'minutes': 45
    }
    adjuster = HalftimeAdjuster(lh, la)
    result = adjuster.adjust(ht_stats)
    print("\n【半场调整】")
    print(f"  调整后全场λ: 主={result['lambda_home_adj']:.2f}, 客={result['lambda_away_adj']:.2f}")
    print(f"  剩余时间λ: 主={result['lambda_home_remaining']:.2f}, 客={result['lambda_away_remaining']:.2f}")
    post_dist = adjuster.full_time_total_goals_dist(ht_stats)
    print("\n【半场调整后】全场总进球概率分布:")
    for k, p in sorted(post_dist.items()):
        print(f"  {k}球: {p:.3f}")
    # 对比关键盘口
    print("\n【盘口变化对比】")
    for threshold, label in [(2.5, '大2.5球'), (3.5, '大3.5球')]:
        pre_over = sum(p for k, p in pre_dist.items() if k > threshold)
        post_over = sum(p for k, p in post_dist.items() if k > threshold)
        print(f"  {label}: 赛前 {pre_over:.1%} → 半场后 {post_over:.1%}")

核心调整逻辑解析

模块 思路 权重/公式
xG重估 半场xG按时间比例外推到全场 xg_ht × (90/45)
贝叶斯融合 赛前先验 + 半场观测,用时间进度做权重 w = minutes/90
节奏修正 极端控球率反映攻防态势 tempo_factor = 1+0.3×|poss-0.5|
剩余时间缩放 预测应针对"剩余比赛" λ_rem = λ_adj × (90-min)/90
已进球叠加 已发生进球是确定项 total = current + 剩余分布

关键注意事项

权重设计是最核心的超参数

  • 半场样本只有45分钟,不能完全替代赛前,建议 w_ht ∈ [0.3, 0.5]
  • 时间越靠后(如75分钟),历史数据价值越低,w_ht 应递增
  • 可以用滚动回测优化权重(见下方调参示例)

区分"过程数据"和"结果数据"

  • 比分(0-0)是噪声大的结果,xG/射门是更稳定的过程信号
  • 强队半场0-0但xG=1.5 → 预测应上调进球预期(回归均值)

不同运动的调整方式不同

运动 半场关键指标 调整方向
足球 xG、射门、控球 剩余时间泊松
篮球 半场得分、节奏(回合数)、命中率 节奏驱动的线性外推
网球 半场发球局胜率、制胜分/失误 发球局成功率贝叶斯更新

避免"小数定律"陷阱

  • 单场半场数据波动极大,不要用简单线性外推(如"半场2球→全场4球")
  • 必须与赛前模型融合,且保留不确定性(用分布而非点估计)

篮球场景简版扩展

def adjust_basketball(ht_home, ht_away, pre_total, pace_ht, pace_pre, minutes=24):
    """篮球:半场得分+节奏双因素调整"""
    remaining_ratio = (48 - minutes) / 48
    # 用节奏变化修正
    pace_factor = pace_ht / pace_pre
    # 半场得分按剩余时间外推,并与赛前融合
    projected_remain = (ht_home + ht_away) * (48/minutes - 1) * pace_factor
    # 加权融合
    w = 0.6
    final_total = w * (ht_home + ht_away + projected_remain) + (1-w) * pre_total
    return final_total

建议的调参/验证流程

# 网格搜索最优融合权重
def backtest_weights(historical_matches, weights=np.arange(0.1, 0.7, 0.05)):
    best_w, best_score = None, np.inf
    for w in weights:
        errors = []
        for m in historical_matches:
            pred = w * m['ht_projection'] + (1-w) * m['pre_prediction']
            errors.append((pred - m['actual']) ** 2)
        mse = np.mean(errors)
        if mse < best_score:
            best_w, best_score = w, mse
    return best_w, best_score

半场数据调整的本质是 "先验(赛前模型)+ 似然(半场观测)→ 后验(调整预测)" 的贝叶斯更新过程,过程数据(xG、节奏、控球)比结果数据(比分)信息量更大,但都要与赛前模型合理加权融合,并对剩余比赛时间做正确缩放。

抱歉,评论功能暂时关闭!

友情链接攻略窝 · 礼包窝 · 配置窝 · 换算窝 · 算法窝 · 查文窝 · 手册窝 · 问答窝 · 全文搜 · 手册库
相关站点礼包窝 · 查文窝