利用友谊赛数据做预测的完整案例
下面我用一个足球比赛预测的案例,展示从数据到模型的完整流程,友谊赛数据的特点是:噪声大、动机弱、参考价值低,所以关键是要"数据清洗 + 特征工程 + 合理建模"。

项目背景与思路
目标:用历史友谊赛数据,预测某场比赛的胜平负(或比分)。
难点:
- 友谊赛球队可能不派主力、试阵容
- 球员动机弱,结果随机性大
- 不同时期球队实力变化大
思路:不能单纯用友谊赛当"真实实力",要构造加权特征 + 状态特征。
数据准备
假设 CSV 格式如下(matches.csv):
date,home_team,away_team,home_goals,away_goals,is_friendly,competition 2023-03-20,巴西,阿根廷,1,2,1,友谊赛 2023-06-15,法国,德国,3,0,1,友谊赛 2023-09-10,西班牙,意大利,2,1,0,欧国联 ...
Java 实现:从数据到预测
数据模型
public class Match {
public LocalDate date;
public String homeTeam;
public String awayTeam;
public int homeGoals;
public int awayGoals;
public boolean isFriendly;
public Match(LocalDate d, String h, String a, int hg, int ag, boolean f) {
this.date = d; this.homeTeam = h; this.awayTeam = a;
this.homeGoals = hg; this.awayGoals = ag; this.isFriendly = f;
}
}
加载 CSV
import java.io.*;
import java.nio.file.*;
import java.time.LocalDate;
import java.util.*;
public class DataLoader {
public static List<Match> load(String path) throws IOException {
List<Match> list = new ArrayList<>();
List<String> lines = Files.readAllLines(Paths.get(path));
for (int i = 1; i < lines.size(); i++) {
String[] p = lines.get(i).split(",");
if (p.length < 6) continue;
list.add(new Match(
LocalDate.parse(p[0]),
p[1], p[2],
Integer.parseInt(p[3]),
Integer.parseInt(p[4]),
p[5].equals("1")
));
}
list.sort(Comparator.comparing(m -> m.date));
return list;
}
}
特征工程(核心!)
关键思想:友谊赛权重低,近期比赛权重高。
public class FeatureExtractor {
// 计算球队在指定日期前的"实力分"
// 公式:加权进球差 - 加权失球差,权重 = 时间衰减 × 赛事权重
public static double teamStrength(String team, LocalDate before,
List<Match> history) {
double score = 0, weightSum = 0;
LocalDate now = before;
for (Match m : history) {
if (!m.date.isBefore(before)) break;
if (!m.homeTeam.equals(team) && !m.awayTeam.equals(team)) continue;
// 时间衰减:越近权重越高(半衰期 180 天)
long days = java.time.temporal.ChronoUnit.DAYS.between(m.date, now);
double timeW = Math.exp(-days / 180.0);
// 赛事权重:友谊赛低
double compW = m.isFriendly ? 0.4 : 1.0;
int gf = m.homeTeam.equals(team) ? m.homeGoals : m.awayGoals;
int ga = m.homeTeam.equals(team) ? m.awayGoals : m.homeGoals;
double diff = gf - ga;
score += timeW * compW * diff;
weightSum += timeW * compW;
}
return weightSum > 0 ? score / weightSum : 0;
}
// 构造 X 特征向量
public static double[] buildFeatures(Match m, List<Match> history) {
double homeStr = teamStrength(m.homeTeam, m.date, history);
double awayStr = teamStrength(m.awayTeam, m.date, history);
double strengthDiff = homeStr - awayStr;
// 近5场友谊赛胜率(同队)
double homeForm = recentForm(m.homeTeam, m.date, history, 5);
double awayForm = recentForm(m.awayTeam, m.date, history, 5);
return new double[]{
1.0, // bias
strengthDiff, // 实力差
homeStr, // 主队实力
awayStr, // 客队实力
homeForm - awayForm,// 近期状态差
1.0 // 主场优势常量
};
}
private static double recentForm(String team, LocalDate before,
List<Match> history, int k) {
List<Match> recent = new ArrayList<>();
for (Match m : history) {
if (!m.date.isBefore(before)) break;
if (m.homeTeam.equals(team) || m.awayTeam.equals(team)) recent.add(m);
}
int n = Math.min(k, recent.size());
if (n == 0) return 0;
int win = 0;
for (int i = recent.size() - n; i < recent.size(); i++) {
Match m = recent.get(i);
int gf = m.homeTeam.equals(team) ? m.homeGoals : m.awayGoals;
int ga = m.homeTeam.equals(team) ? m.awayGoals : m.homeGoals;
if (gf > ga) win++;
else if (gf == ga) win += 0;
}
return (double) win / n;
}
}
逻辑回归模型(手写梯度下降)
public class LogisticRegression {
private double[] w;
private final double lr;
private final int epochs;
public LogisticRegression(int dim, double lr, int epochs) {
this.w = new double[dim];
this.lr = lr;
this.epochs = epochs;
}
private static double sigmoid(double z) {
return 1.0 / (1.0 + Math.exp(-z));
}
public void fit(List<double[]> X, List<Integer> y) {
int n = X.size();
for (int e = 0; e < epochs; e++) {
double[] grad = new double[w.length];
for (int i = 0; i < n; i++) {
double[] xi = X.get(i);
double pred = sigmoid(dot(w, xi));
double err = pred - y.get(i);
for (int j = 0; j < w.length; j++) {
grad[j] += err * xi[j];
}
}
for (int j = 0; j < w.length; j++) {
w[j] -= lr * grad[j] / n;
}
if (e % 50 == 0) {
System.out.printf("Epoch %d loss=%.4f%n", e, logLoss(X, y));
}
}
}
public double predictProba(double[] x) {
return sigmoid(dot(w, x));
}
private double dot(double[] a, double[] b) {
double s = 0;
for (int i = 0; i < a.length; i++) s += a[i] * b[i];
return s;
}
private double logLoss(List<double[]> X, List<Integer> y) {
double loss = 0;
for (int i = 0; i < X.size(); i++) {
double p = predictProba(X.get(i));
p = Math.max(1e-9, Math.min(1 - 1e-9, p));
loss -= y.get(i) * Math.log(p) + (1 - y.get(i)) * Math.log(1 - p);
}
return loss / X.size();
}
}
训练 + 预测主流程
public class Predictor {
public static void main(String[] args) throws Exception {
List<Match> all = DataLoader.load("matches.csv");
List<double[]> X = new ArrayList<>();
List<Integer> Y = new ArrayList<>();
// 只训练"友谊赛",二分类:1=主队胜,0=客队不败
for (Match m : all) {
if (m.isFriendly) {
double[] f = FeatureExtractor.buildFeatures(m, all);
X.add(f);
Y.add(m.homeGoals > m.awayGoals ? 1 : 0);
}
}
System.out.println("训练样本数: " + X.size());
LogisticRegression model = new LogisticRegression(X.get(0).length, 0.05, 500);
model.fit(X, Y);
// 预测新比赛
Match test = new Match(LocalDate.now(), "巴西", "德国", 0, 0, true);
double p = model.predictProba(FeatureExtractor.buildFeatures(test, all));
System.out.printf("巴西 vs 德国 主胜概率: %.2f%%%n", p * 100);
}
}
进阶方案(三分类 / 比分预测)
| 目标 | 方法 |
|---|---|
| 胜平负 | Softmax 回归 / 随机森林 |
| 进球数 | 泊松回归(Poisson Regression),主客各建模 λ |
| 比分 | 双泊松模型 + Dixon-Coles 修正 |
| 概率校准 | Isotonic Regression / Platt Scaling |
泊松模型提示(Java 可用 Apache Commons Math 的 PoissonDistribution):
double lambdaHome = 1.2 + 0.3 * strengthDiff; double lambdaAway = 1.0 - 0.25 * strengthDiff; PoissonDistribution ph = new PoissonDistribution(lambdaHome); PoissonDistribution pa = new PoissonDistribution(lambdaAway); // P(2:1) = ph.probability(2) * pa.probability(1)
工程上的关键经验
- 友谊赛必须降权 —— 直接混用会严重污染模型(我用
4权重效果不错)。 - 时间衰减 —— 半衰期 90~180 天效果好;球队实力非线性变化。
- 主场优势 —— 独立特征,不要只靠实力差。
- 别过拟合友谊赛 —— 用真实赛事(世预赛、欧洲杯)做验证集。
- 概率校准 —— 输出概率时用 Brier Score 评估,而非只用准确率。
- 可尝试:XGBoost4J、Smile 库、DL4J(深度网络),但对小数据不如逻辑回归。
完整依赖建议
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<!-- 可选:机器学习库 -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-core</artifactId>
<version>3.0.0</version>
</dependency>
总结一句话:友谊赛数据不能当"真实实力"用,必须通过时间衰减 + 赛事权重转化成"状态特征",再用逻辑回归或泊松模型做预测,最后用真实赛事做校验。
需要我给出完整的可编译 Maven 工程 或 Dixon-Coles 比分预测的实现吗?