java案例如何利用友谊赛数据做预测?

wen java案例 4

利用友谊赛数据做预测的完整案例

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

java案例如何利用友谊赛数据做预测?


项目背景与思路

目标:用历史友谊赛数据,预测某场比赛的胜平负(或比分)。

难点

  • 友谊赛球队可能不派主力、试阵容
  • 球员动机弱,结果随机性大
  • 不同时期球队实力变化大

思路:不能单纯用友谊赛当"真实实力",要构造加权特征 + 状态特征


数据准备

假设 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)

工程上的关键经验

  1. 友谊赛必须降权 —— 直接混用会严重污染模型(我用 4 权重效果不错)。
  2. 时间衰减 —— 半衰期 90~180 天效果好;球队实力非线性变化。
  3. 主场优势 —— 独立特征,不要只靠实力差。
  4. 别过拟合友谊赛 —— 用真实赛事(世预赛、欧洲杯)做验证集。
  5. 概率校准 —— 输出概率时用 Brier Score 评估,而非只用准确率。
  6. 可尝试: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 比分预测的实现吗?

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