Prédire les résultats au foot

mai 10, 2026 · 7 min. de lecture
Prédiction entre Montpellier et Auxerre
projets

Présentation du projet

L’idée m’est venue après un premier projet de prédiction des élections : pourquoi ne pas appliquer les mêmes techniques au football ? Sport que je suis de près, le foot présente une richesse de données (tactiques, joueurs, forme récente, historique des confrontations) qui en fait un terrain d’expérimentation idéal pour le Machine Learning.

L’objectif initial : prédire le score d’un match retour à partir des résultats des matchs aller, avec un modèle fonctionnel déployé en production. Ce projet a évolué au fil du temps vers une architecture bien plus aboutie, intégrant progressivement des pratiques MLOps professionnelles.


V1 — Proof of Concept

Stratégie

Pour aller vite sur un premier résultat exploitable, j’ai volontairement simplifié le scope :

  • Données d’entrée : uniquement les résultats des matchs passés (pas de composition d’équipe, pas de blessés)
  • Contrainte budgétaire : utiliser une API football gratuite
  • Objectif : valider que la prédiction de score est faisable avec peu de features

Collecte de données

J’ai utilisé football-data.org, qui propose un tier gratuit suffisant pour constituer un dataset exploitable sur plusieurs saisons de Ligue 1 et autres compétitions européennes.

Persistance des données

Pour stocker et rafraîchir les données automatiquement, j’ai développé une application Java Spring Boot qui interroge l’API quotidiennement et persiste les résultats dans une base MySQL.

public void updateMatchList() throws IOException {
    MatchApi matchApi = new MatchApi();
    LocalDate yesterday = LocalDate.now().minusDays(1);
    LocalDate today = LocalDate.now();
    List<Match> refreshedMatchList = matchApi.getMatchList(yesterday, today);

    for (Match match : refreshedMatchList) {
        Optional<Match> matchInBase = matchRepository.findObjbyLegacyid(match.getId());
        Team homeTeam = getOrCreateTeam(match.getHomeTeam().getId(), match.getHomeTeam());
        Team awayTeam = getOrCreateTeam(match.getAwayTeam().getId(), match.getAwayTeam());

        match.setHomeTeam(homeTeam);
        match.setAwayTeam(awayTeam);
        LocalDate now = LocalDate.now();

        matchInBase.ifPresentOrElse(matchinbase -> {
            if (!"FINISHED".equals(matchinbase.getStatus())) {
                match.setDate_fetched(now);
                matchRepository.delete(matchinbase);
                matchRepository.save(match);
            }
        }, () -> {
            log.info("match pas en base %d", match.getId());
            matchRepository.save(match);
        });
    }
}

Modélisation

Sur un notebook Python, j’entraîne un modèle de régression pour prédire séparément le nombre de buts à domicile et à l’extérieur.

Définition des features et targets :

from sklearn.model_selection import train_test_split

X = data_encoded.drop(['nbr_goal_full_away', 'nbr_goal_full_home'], axis=1)
y_away = data_encoded['nbr_goal_full_away']
y_home = data_encoded['nbr_goal_full_home']

X_train, X_test, y_away_train, y_away_test, y_home_train, y_home_test = train_test_split(
    X, y_away, y_home, test_size=0.2, random_state=42
)

Optimisation des hyperparamètres avec RandomizedSearchCV :

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import RandomizedSearchCV

param_grid = {
    'n_estimators': [50, 100, 200, 500],
    'max_depth': [None, 10, 20, 30],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'max_features': ['auto', 'sqrt', 'log2']
}

rf = RandomForestRegressor(random_state=42)
rf_random_away = RandomizedSearchCV(rf, param_grid, n_iter=100, cv=3,
                                    verbose=2, random_state=42, n_jobs=-1)
rf_random_home = RandomizedSearchCV(rf, param_grid, n_iter=100, cv=3,
                                    verbose=2, random_state=42, n_jobs=-1)

rf_random_away.fit(X_train, y_away_train)
rf_random_home.fit(X_train, y_home_train)

J’ai opté pour la Random Forest après comparaison avec d’autres algorithmes (LinearRegression, GradientBoosting) — elle donnait les meilleurs résultats sur ce dataset avec peu de preprocessing.

Déploiement V1

Le modèle entraîné est sérialisé (joblib.dump) en fichier .pkl, récupéré automatiquement via le système d’artefacts GitLab CI/CD, puis copié sur un VPS. Une API consomme ce fichier pour exposer les prédictions, et un frontend Vue.js permettait à l’utilisateur de taper deux équipes et d’obtenir un score prédit.

L’application a été mise hors ligne suite à de nouveaux impératifs professionnels — mais ce premier cycle complet ingestion → entraînement → déploiement → exposition a validé le concept.


V2 — Refonte complète vers une architecture MLOps pro

La V1 avait des limites claires : modèle basique, peu de features, pas de monitoring, pas de versioning des données ni des modèles. La V2 est une réécriture complète, Python-first, pensée pour être industrialisable.

Objectifs de la refonte

  • Stack 100% Python : suppression du service Java Spring, plus léger et plus cohérent avec l’écosystème data/ML
  • Feature engineering enrichi : intégrer des données contextuelles (blessures, forme récente, confrontations directes)
  • Pratiques MLOps réelles : versioning des données, tracking des expérimentations, automatisation complète

Nouvelle architecture

API Football
Ingestion Python (scheduled)
Base MySQL — données brutes
Feature Engineering (calcul automatique)
    │   ├── Forme récente (5 derniers matchs)
    │   ├── Moyenne buts marqués / encaissés
    │   ├── Confrontations directes historiques
    │   └── Impact blessures (note joueur × importance)
Dataset enrichi
Entraînement + Tracking MLflow
Modèle versionné (DVC)
API FastAPI
Frontend Vue.js

Feature Engineering avancé

Contrairement à la V1 qui se basait uniquement sur les scores bruts, la V2 calcule automatiquement un ensemble de features à partir des données historiques en base :

  • Forme récente : moyenne de points sur les N derniers matchs pour chaque équipe
  • Efficacité offensive/défensive : buts marqués et encaissés en moyenne sur la saison
  • Historique des confrontations directes (head-to-head) : taux de victoire, moyenne de buts sur les duels passés entre les deux équipes
  • Avantage domicile : modélisé comme feature à part entière car statistiquement significatif

Intégration des blessures

Les données de blessures sont ingérées en brut dans la base, puis le module de feature engineering les affecte automatiquement au match correspondant. Cela permet de conserver la donnée brute tout en produisant une feature agrégée exploitable par le modèle.

La prochaine étape est de scraper les notes des joueurs blessés (style Sofascore / WhoScored) afin de pondérer l’impact d’une blessure par l’importance du joueur dans son équipe — un défenseur central titulaire indisponible n’a pas le même poids qu’un remplaçant.

Analyse des comportements de parieurs (Polymarket)

Une piste exploratoire en cours : analyser les cotes et positions des meilleurs parieurs sur Polymarket pour en extraire un signal de marché. L’hypothèse est que les marchés de prédiction agrègent une information collective parfois plus précise que les features techniques seules — une forme de wisdom of the crowd quantifiable.


Vers une stack MLOps professionnelle

C’est la partie qui me tient le plus à cœur, car c’est exactement ce qu’on attend d’un MLOps Engineer en production.

Versioning des données et des modèles avec DVC

En V1, le modèle .pkl était un artefact GitLab non tracé. En V2, j’intègre DVC (Data Version Control) pour :

  • Versionner les datasets d’entraînement (couplé à Git)
  • Reproduire n’importe quel run d’entraînement à partir d’un commit
  • Stocker les artefacts sur un remote (S3 ou VPS)
dvc init
dvc add data/matches.csv
dvc run -n train \
  -d data/matches.csv -d src/train.py \
  -o models/rf_home.pkl -o models/rf_away.pkl \
  python src/train.py

Tracking des expérimentations avec MLflow

Chaque run d’entraînement est loggué dans MLflow Tracking : hyperparamètres, métriques (MAE, RMSE), artefacts. Cela permet de comparer les expérimentations et de promouvoir le meilleur modèle en production de manière traçable.

import mlflow
import mlflow.sklearn

with mlflow.start_run():
    mlflow.log_params(best_params)
    mlflow.log_metric("mae_home", mae_home)
    mlflow.log_metric("mae_away", mae_away)
    mlflow.sklearn.log_model(rf_home, "rf_home")
    mlflow.sklearn.log_model(rf_away, "rf_away")

CI/CD GitLab — pipeline d’entraînement automatisé

stages:
  - data
  - train
  - evaluate
  - deploy

fetch_data:
  stage: data
  script:
    - python src/ingestion.py
  only:
    - schedules

train_model:
  stage: train
  script:
    - python src/train.py
  artifacts:
    paths:
      - models/

evaluate:
  stage: evaluate
  script:
    - python src/evaluate.py
  artifacts:
    reports:
      metrics:
        - metrics.json

deploy:
  stage: deploy
  script:
    - scp models/*.pkl user@vps:/app/models/
    - ssh user@vps "systemctl restart football-api"
  only:
    - main

Monitoring du modèle en production

Un point souvent négligé mais critique en MLOps : détecter le drift. Le comportement des équipes évolue (mercato, changement d’entraîneur, blessures longue durée), ce qui dégrade progressivement les performances du modèle. Je prévois d’intégrer un monitoring basique :

  • Data drift : comparer la distribution des features en production vs le dataset d’entraînement (avec evidently)
  • Performance drift : comparer les prédictions aux résultats réels au fil des journées, et déclencher un réentraînement automatique si le MAE dépasse un seuil défini

Bilan et prochaines étapes

AspectV1V2 (en cours)
Langage principalJava + PythonPython uniquement
FeaturesScores brutsForme, H2H, blessures, marché
Versioning modèleArtefact GitLab non tracéDVC + MLflow
Tracking expésAucunMLflow
Monitoring prodAucunEvidently (prévu)
DéploiementManuelCI/CD automatisé

Ce projet est pour moi un laboratoire concret pour pratiquer les patterns MLOps que je vise en production professionnelle : reproductibilité, observabilité, automatisation. La prochaine milestone est de déployer la V2 avec MLflow et DVC opérationnels, puis d’intégrer le monitoring de drift en production.

Martin Généreux
Auteurs
MLOps Engineer - Python · Docker · Kubernetes · CI/CD · Cloud
MLOps Engineer avec 4 ans d’expérience en Python, DevOps et Data Engineering. Expérience concrète dans la conception, l’industrialisation et le déploiement de pipelines ML en production. Expertise en CI/CD (GitLab), conteneurisation Docker, automatisation Ansible sur Linux et exposition de modèles via FastAPI. Bonne compréhension des architectures cloud (AWS, GCP) et des pratiques MLOps modernes : monitoring, versioning, gestion du drift. Curieux et orienté amélioration continue, je vise des environnements tech ambitieux à dimension internationale.