很多人一开始做足球预测模型,最容易犯的错误不是算法选错,而是数据边界没想清楚。
看到一堆比赛数据,就想全部喂给模型。
比如:
比赛比分;
球队排名;
积分榜;
近期进球;
近期失球;
主客场表现;
历史交锋;
射门数据;
控球率;
赔率数据;
赛后技术统计;
最终排名;
赛季总进球;
球队最终积分。
这些数据看起来都和比赛有关。
但问题是:
不是所有“和比赛有关的数据”,都可以作为赛前预测模型的输入。
足球预测模型训练时,最重要的边界是:
模型在预测某一场比赛时,只能使用这场比赛开赛前已经知道的数据。
这句话必须反复强调。
如果你把赛后才知道的数据、赛季结束后才知道的数据、比赛进行中才知道的数据放进特征里,模型回测会变得很好看,但那是假的。
这种错误叫:
未来数据泄漏。
它是足球模型训练里最危险、也最常见的问题之一。
所以,在正式讲特征工程、泊松模型、LightGBM 之前,必须先把数据层讲清楚。
本章要解决三个问题:
第一,足球模型到底需要哪些数据;
第二,哪些数据可以作为赛前特征;
第三,哪些数据只能作为标签或复盘数据,不能进入赛前训练特征。
一、足球模型的数据要先分三类
做足球模型时,所有数据先分成三类。
第一类:赛前特征数据
这是模型真正可以使用的输入。
它必须满足一个条件:
在比赛开始前就已经可获得。
比如:
比赛日期;
联赛;
主队;
客队;
主客场;
赛前积分;
赛前排名;
比赛前的近期战绩;
比赛前的近期进球和失球;
比赛前的 Elo 评分;
比赛前双方休息天数;
比赛前主队主场表现;
比赛前客队客场表现;
比赛前历史交锋记录;
比赛前已知伤停;
比赛前天气;
比赛前外部市场预期。
这些才是模型预测时可以用的数据。
第二类:赛后结果数据
这是比赛结束后才知道的数据。
它可以用来生成标签,也可以用来复盘,但不能作为预测这场比赛的赛前特征。
比如:
全场比分;
半场比分;
最终胜平负;
总进球;
红牌;
点球;
射门;
射正;
控球率;
角球;
赛后 xG;
传球成功率;
最终技术统计。
这些数据对模型训练很重要,但用途不同。
全场比分可以用来生成标签:
主队进球 = 2
客队进球 = 1
胜平负标签 = 主胜
总进球标签 = 3球
但你不能把这场比赛的赛后射门、赛后控球率、赛后角球放进预测这场比赛的输入特征。
因为赛前并不知道这些。
第三类:未来汇总数据
这是最危险的一类。
它往往看起来像普通数据,但其实包含未来信息。
比如:
赛季最终排名;
赛季最终积分;
赛季最终场均进球;
赛季最终主场胜率;
赛季结束后的球队总失球;
某队本赛季最终排名第几;
整个赛季结束后统计出来的联赛均值。
这些数据不能直接用于赛季中某一场比赛的赛前预测。
举个例子。
你要预测 2025 年 9 月 1 日的一场比赛。
如果你给模型使用“该队 2025 赛季最终排名”,那就是未来数据。
因为 9 月 1 日时,赛季还没有结束。
模型等于提前知道了未来结果。
这种回测一定会虚高。
所以,未来汇总数据必须重新构造为“截至比赛日前”的滚动数据。
二、一场比赛的最小数据表应该长什么样?
如果从零开始,不要一上来追求复杂数据。
先建立一张最基础的比赛表。
可以叫:
Matches
字段示例:
MatchId
LeagueId
Season
MatchDate
HomeTeamId
AwayTeamId
HomeGoals
AwayGoals
HalfTimeHomeGoals
HalfTimeAwayGoals
IsNeutralVenue
Status
字段说明:
MatchId:比赛唯一ID
LeagueId:联赛ID
Season:赛季
MatchDate:比赛时间
HomeTeamId:主队ID
AwayTeamId:客队ID
HomeGoals:主队全场进球
AwayGoals:客队全场进球
HalfTimeHomeGoals:主队半场进球
HalfTimeAwayGoals:客队半场进球
IsNeutralVenue:是否中立场
Status:比赛状态,例如 Finished、Cancelled、Postponed
这张表是所有模型的根。
后面所有标签都可以从这张表生成。
比如胜平负标签:
if HomeGoals > AwayGoals => H
if HomeGoals = AwayGoals => D
if HomeGoals < AwayGoals => A
总进球标签:
TotalGoals = HomeGoals + AwayGoals
比分标签:
Score = HomeGoals + "-" + AwayGoals
半全场标签:
HalfTimeResult = H / D / A
FullTimeResult = H / D / A
HalfFullLabel = HalfTimeResult + "/" + FullTimeResult
所以,最小比赛表不需要很复杂,但必须干净、稳定、时间准确。
如果这张基础表混乱,后面所有模型都会出问题。
三、球队表必须做统一映射
足球数据最常见的问题之一,是队名不统一。
同一支球队,不同数据源可能写成不同名字。
比如:
Man United
Manchester United
曼联
Manchester Utd
Man Utd
如果不统一,模型会把它们当成不同球队。
这会直接毁掉球队历史特征。
所以必须有一张球队映射表。
可以叫:
Teams
字段示例:
TeamId
StandardName
Country
再建一张别名表:
TeamAliases
字段示例:
AliasId
TeamId
SourceName
DataSource
例如:
TeamId = 1001
StandardName = Manchester United
SourceName = Man United
SourceName = Manchester Utd
SourceName = Man Utd
SourceName = 曼联
训练模型前,所有比赛都必须转成统一的 TeamId。
不要让模型直接依赖原始队名字符串。
这是最基础的数据工程。
很多模型效果差,不是算法不行,而是队名映射一开始就乱了。
四、联赛表也要统一
联赛同样要统一。
比如:
Premier League
English Premier League
英超
England Premier League
都应该映射成同一个 LeagueId。
联赛表可以叫:
Leagues
字段示例:
LeagueId
LeagueName
Country
Level
其中:
Level = 1 表示顶级联赛
Level = 2 表示次级联赛
为什么联赛统一很重要?
因为很多特征需要按联赛计算。
比如:
联赛平均进球;
联赛主场优势;
联赛平局率;
联赛总进球分布;
联赛主胜比例;
联赛风格差异。
如果联赛映射混乱,这些统计就会错。
比如把英超和英冠混在一起,模型会学到不稳定的联赛均值。
联赛不是简单背景字段,它是足球模型里的重要上下文。
五、训练数据必须以“比赛日”为时间边界
足球模型最重要的原则是:
每一场比赛的特征,只能由这场比赛之前的数据生成。
假设比赛是:
A队 vs B队
比赛时间:2025-08-20 20:00
那么这场比赛的特征只能使用:
2025-08-20 20:00 之前已经发生的数据
不能使用:
这场比赛本身的赛后数据
2025-08-21 之后的比赛
赛季结束后的总排名
赛季结束后的最终积分
这听起来简单,但实现时很容易犯错。
比如你计算球队本赛季场均进球:
错误做法:
使用整个赛季结束后的总进球 / 整个赛季场次
正确做法:
只使用这场比赛之前,该队本赛季已经完成比赛的总进球 / 已完成比赛场次
这就是滚动特征。
六、赛前排名和赛后排名不能混用
很多模型喜欢用排名。
排名当然有用。
但排名必须是赛前排名。
不能用赛后更新后的排名。
举个例子。
A队赛前排名第 6。
比赛赢了后,排名升到第 3。
如果你用第 3 名作为这场比赛的特征,那就泄漏了比赛结果。
因为排名变化部分已经包含了这场比赛的结果。
正确做法:
预测这场比赛时,使用:
赛前排名 = 第6
不能使用:
赛后排名 = 第3
同理:
赛前积分;
赛前净胜球;
赛前进球;
赛前失球;
赛前主场积分;
赛前客场积分;
都必须是比赛开始前的状态。
如果你数据库里只有赛后积分榜,就不能直接拿来训练赛前模型。
你必须按比赛时间顺序重新计算每场比赛前的积分榜。
七、近期状态特征应该怎么定义?
近期状态是足球模型常用特征。
但必须定义清楚。
比如:
最近5场积分
最近5场进球
最近5场失球
最近10场进球均值
最近10场失球均值
最近5场胜率
最近5场平局率
最近5场零封率
这些特征必须只看比赛日前已经完成的比赛。
以 A 队为例。
如果 A 队在 2025-08-20 比赛,那么最近5场必须是:
2025-08-20 之前 A队已经完成的最近5场正式比赛
不能包含 2025-08-20 这场。
这个错误非常常见。
如果写 SQL 或代码时不注意,可能会把当前比赛也算进去。
错误逻辑:
取 MatchDate <= 当前比赛时间
正确逻辑:
取 MatchDate < 当前比赛时间
这个小于号非常关键。
少一个等号,模型就可能泄漏当前比赛结果。
八、主客场特征要分开做
足球比赛里,主场和客场差异很大。
所以不要只做总均值。
应该分开做:
主队近期主场进球均值
主队近期主场失球均值
客队近期客场进球均值
客队近期客场失球均值
主队主场胜率
客队客场不败率
主队主场平局率
客队客场平局率
比如 A 队总体场均进球 1.6。
但拆开看:
主场场均进球 = 2.2
客场场均进球 = 0.9
如果 A 队本场主场作战,用总体 1.6 就不够精确。
再比如 B 队总体防守一般,但客场失球很多。
如果它本场客场作战,就要重点看客场防守。
主客场特征是足球模型里非常基础但非常重要的部分。
九、赛程数据要结构化
赛程对足球比赛影响很大。
但很多人没有把赛程做成特征。
至少要做这些字段:
HomeRestDays
AwayRestDays
RestDaysDiff
HomeMatchesLast7Days
AwayMatchesLast7Days
HomeMatchesLast14Days
AwayMatchesLast14Days
HomeIsBackToBackAway
AwayIsBackToBackAway
字段解释:
HomeRestDays:主队距离上一场比赛休息几天
AwayRestDays:客队距离上一场比赛休息几天
RestDaysDiff:主队休息天数 - 客队休息天数
HomeMatchesLast7Days:主队过去7天踢了几场
AwayMatchesLast7Days:客队过去7天踢了几场
HomeMatchesLast14Days:主队过去14天踢了几场
AwayMatchesLast14Days:客队过去14天踢了几场
HomeIsBackToBackAway:主队是否连续客场后回到本场
AwayIsBackToBackAway:客队是否连续客场
这些特征会帮助模型理解体能和赛程压力。
特别是 LightGBM 这类模型,可以学习这些特征和其他变量的交互。
比如:
休息天数少 + 客场 + 阵容深度弱,可能影响更明显。
十、Elo 评分是很好的赛前强弱特征
Elo 评分适合足球模型,因为它是滚动更新的强弱指标。
每场比赛结束后,根据结果更新球队评分。
预测下一场比赛时,使用赛前 Elo。
常用特征包括:
HomeElo
AwayElo
EloDiff = HomeElo - AwayElo
HomeEloTrend
AwayEloTrend
Elo 的优点是:
它比排名更平滑;
能跨赛季延续;
能反映长期强弱;
可以根据比赛结果滚动更新;
适合做胜平负和强弱关系特征。
但注意:
Elo 必须按时间滚动计算。
不能使用未来比赛更新后的 Elo 来预测过去比赛。
比如预测 2025-08-20 的比赛,只能用 2025-08-20 之前更新到的 Elo。
这和赛前排名逻辑一样。
十一、历史交锋可以用,但不要过度依赖
历史交锋是很多人喜欢看的数据。
但模型里要谨慎使用。
可以做一些特征:
H2HMatchesLast5Years
H2HHomeWins
H2HDraws
H2HAwayWins
H2HAvgGoals
H2HLastMeetingDays
但要注意:
历史交锋样本通常很小;
教练和球员可能已经变化;
比赛背景可能不同;
主客场可能不同;
杯赛和联赛不能简单混合;
多年以前的交锋价值很低。
所以历史交锋适合做弱特征。
不要让它权重过高。
很多模型如果过度依赖历史交锋,会学到噪音。
更合理的做法是:
历史交锋只作为辅助特征,且加入时间衰减或限制年限。
比如只看最近 3 年或 5 年。
十二、伤停和首发数据怎么处理?
伤停和首发很重要,但结构化难度高。
如果你有可靠数据源,可以做这些特征:
HomeInjuredStartersCount
AwayInjuredStartersCount
HomeMissingDefenders
AwayMissingDefenders
HomeMissingMidfielders
AwayMissingMidfielders
HomeMissingForwards
AwayMissingForwards
HomeLineupStrength
AwayLineupStrength
但这里有两个问题。
第一,伤停数据不一定完整。
不同数据源对伤停记录标准不一样。
第二,球员影响不容易量化。
缺一个替补边锋和缺主力后腰不是同一个级别。
所以伤停特征不能只看人数。
更好的方式是按位置和重要性加权。
比如:
门将缺阵权重较高
主力中卫缺阵权重较高
主力后腰缺阵权重较高
普通轮换球员缺阵权重较低
如果一开始没有可靠伤停数据,可以先不加入模型主特征,避免脏数据伤害模型。
伤停可以先用于人工分析和后续扩展。
十三、外部市场预期可以作为参考特征,但必须克制
有些模型会加入外部市场预期。
这类数据可以作为“外部概率参考”,但要克制。
可以使用的表达是:
外部预期
市场概率参考
外部定价信号
隐含概率
不要把它写成盘口技巧,也不要把它当成模型主线。
如果使用这类特征,可以处理成:
MarketHomeProb
MarketDrawProb
MarketAwayProb
MarketOver25Prob
MarketExpectationDiff
这些特征的意义是:
外部环境如何理解这场比赛的强弱和进球预期。
但要注意:
如果你的目标是训练一个纯球队能力模型,就不要过度依赖外部预期。
否则模型可能只是学习外部价格,而不是学习球队本身。
更稳妥的方式是:
先训练不含外部预期的基础模型;
再训练包含外部预期的增强模型;
比较两者校准和回测表现;
不要让外部预期掩盖模型自身能力。
十四、赛后技术统计不能直接作为赛前模型特征
这点非常重要。
赛后技术统计包括:
射门
射正
控球率
角球
犯规
传球成功率
危险进攻
xG
这些数据对赛后复盘非常有用。
但预测这场比赛时不能直接使用。
因为开赛前你不知道这场比赛会有多少射门、多少控球、多少角球。
不过,你可以把历史比赛的赛后技术统计滚动成赛前特征。
比如:
主队过去5场平均射门
主队过去5场平均射正
客队过去5场被射门均值
客队过去5场被射正均值
主队过去10场平均xG
客队过去10场平均被xG
这样是可以的。
因为这些都是比赛前已经发生的历史数据。
关键区别在于:
不能用当前比赛的赛后技术统计。
可以用当前比赛之前的历史技术统计。
十五、标签数据和特征数据必须分开存
在工程实践里,建议把数据分成几张表。
1. 原始比赛结果表
Matches
存比分、半场比分、比赛状态。
2. 赛前特征表
MatchFeatures
一场比赛一行。
字段示例:
MatchId
HomeElo
AwayElo
EloDiff
HomeGoalsAvgLast5
AwayGoalsAvgLast5
HomeConcedeAvgLast5
AwayConcedeAvgLast5
HomeRestDays
AwayRestDays
RestDaysDiff
LeagueAvgGoalsBeforeMatch
HomeRankBeforeMatch
AwayRankBeforeMatch
3. 标签表
MatchLabels
字段示例:
MatchId
FullTimeResult
TotalGoals
HomeGoals
AwayGoals
HalfFullResult
GoalBucket
为什么要分开?
因为这样能防止把标签字段误放进特征。
比如 HomeGoals 是标签来源,不应该直接出现在赛前特征表里。
清晰分表,可以降低数据泄漏风险。
十六、特征生成应该是一个“按时间滚动”的过程
正确的特征生成流程应该是这样:
1. 按 MatchDate 对比赛排序
2. 从最早的比赛开始遍历
3. 对当前比赛,先用历史数据生成赛前特征
4. 保存当前比赛的 MatchFeatures
5. 比赛结束后,再用当前比赛结果更新球队历史状态
6. 进入下一场比赛
注意顺序:
先生成特征,再更新历史。
不能先把当前比赛结果加入历史,再生成当前比赛特征。
错误顺序:
先更新当前比赛结果
再计算当前比赛近期状态
正确顺序:
先基于历史生成当前比赛特征
再用当前比赛结果更新历史状态
这个细节非常关键。
很多数据泄漏就是在这里发生的。
十七、一个最小可用训练样本长什么样?
最终你给模型训练的数据应该长这样。
每一行是一场比赛。
示例:
MatchId: 20250820001
LeagueId: EPL
MatchDate: 2025-08-20
HomeTeamId: 1001
AwayTeamId: 1002
Features:
HomeElo: 1820
AwayElo: 1690
EloDiff: 130
HomeGoalsAvgLast5: 1.80
AwayGoalsAvgLast5: 1.10
HomeConcedeAvgLast5: 0.90
AwayConcedeAvgLast5: 1.60
HomeHomeGoalsAvgLast5: 2.10
AwayAwayGoalsAvgLast5: 0.80
HomeRestDays: 6
AwayRestDays: 3
RestDaysDiff: 3
LeagueAvgGoalsBeforeMatch: 2.65
HomeRankBeforeMatch: 3
AwayRankBeforeMatch: 14
Labels:
FullTimeResult: H
HomeGoals: 2
AwayGoals: 0
TotalGoals: 2
GoalBucket: 2
模型训练时,输入 Features。
模型评估时,对比 Labels。
这就是一条干净训练样本的基本形态。
十八、数据字段不要一开始做太多
很多人一开始就想做上百个特征。
这不一定好。
特征越多,越容易:
引入脏数据;
引入未来数据;
增加过拟合风险;
让模型变得难解释;
让回测虚高。
建议一开始先做一组基础特征。
比如:
EloDiff
HomeGoalsAvgLast5
AwayGoalsAvgLast5
HomeConcedeAvgLast5
AwayConcedeAvgLast5
HomeHomeGoalsAvgLast5
AwayAwayGoalsAvgLast5
HomeRestDays
AwayRestDays
RestDaysDiff
LeagueAvgGoalsBeforeMatch
HomeRankBeforeMatch
AwayRankBeforeMatch
这十几个字段已经可以训练一个基础模型。
等流程跑通、回测可靠,再逐步扩展。
不要一开始就堆复杂字段。
足球模型成功的关键,不是特征越多越好,而是每个特征都干净、可解释、赛前可用。
十九、本章实操检查清单
在进入下一章数据清洗前,先检查这些问题。
1. 是否有统一的比赛表 Matches?
2. 是否有统一的 TeamId,而不是直接使用队名字符串?
3. 是否有统一的 LeagueId?
4. 每场比赛的 MatchDate 是否准确?
5. 是否区分赛前特征、赛后结果、未来汇总数据?
6. 近期状态是否只使用比赛日前的数据?
7. 排名和积分是否是赛前排名、赛前积分?
8. 是否避免使用赛季最终排名预测赛季中比赛?
9. 是否将标签表和特征表分开?
10. 特征生成是否按时间滚动?
11. 是否避免把当前比赛结果加入当前比赛特征?
12. 是否先从少量干净特征开始,而不是堆大量不稳定特征?
如果这些问题没有解决,不建议继续训练模型。
否则后面模型效果再好,也可能是假的。
本章小结
足球模型需要数据,但不是数据越多越好。
真正关键的是数据边界。
你必须清楚区分:
赛前特征数据
赛后结果数据
未来汇总数据
赛前特征可以作为模型输入。
赛后结果用于生成标签和复盘。
未来汇总数据必须禁止直接进入赛前特征。
一套足球模型的数据基础,至少要包括:
比赛表;
球队映射;
联赛映射;
赛前滚动特征;
结果标签;
按时间生成的训练样本。
如果数据边界不清楚,后面无论用泊松、逻辑回归还是 LightGBM,都会出问题。
下一章我们继续讲:
足球数据清洗怎么做?队名、时间、联赛、缺失值和未来数据泄漏。
本文仅供足球数据研究和模型训练学习参考,不构成任何投注建议。
