TP4 Parties 2-4 : pipeline.py (split -> train --register -> promote) + workflow Forgejo Actions (PR -> main) + requirements

This commit is contained in:
Johan LEROY
2026-07-23 15:19:40 +02:00
parent e063effc5d
commit 47109efad9
3 changed files with 134 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
name: pipeline
# Partie 4 : le pipeline ML est rejoue automatiquement a chaque Pull Request
# vers la branche principale (Q4.1). Autres declencheurs possibles (Q4.4) :
# push sur main, tag de release, planification (schedule/cron), declenchement
# manuel (workflow_dispatch).
on:
pull_request:
branches: [main]
jobs:
train-and-register:
runs-on: docker
# Le runner (mode docker, reseau mlops-net) lance le job dans ce conteneur.
# On y monte en lecture seule les donnees source du fil rouge et le magasin
# de certificats du host (pour faire confiance a la CA interne ENI MLOps).
container:
image: node:20-bookworm
volumes:
- /data/modelling:/data/modelling:ro
- /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro
steps:
- name: Recuperation du code
uses: actions/checkout@v4
- name: Installation de Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
- name: Installation des dependances
run: pip install -r requirements.txt
- name: Execution du pipeline (split -> train --register -> promote)
run: python pipeline.py
env:
# Serveur MLflow (basic auth) : URLs HTTPS via Caddy, resolues sur mlops-net.
MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}
MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_TRACKING_USERNAME }}
MLFLOW_TRACKING_PASSWORD: ${{ secrets.MLFLOW_TRACKING_PASSWORD }}
MLFLOW_EXPERIMENT_NAME: ${{ secrets.MLFLOW_EXPERIMENT_NAME }}
# Artefacts sur S3 Garage (log_model).
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
MLFLOW_S3_ENDPOINT_URL: ${{ secrets.MLFLOW_S3_ENDPOINT_URL }}
# CA interne ENI MLOps (requests/boto3 n'utilisent pas le store systeme par defaut).
REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
AWS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
# Import du package lab depuis la racine du depot.
PYTHONPATH: ${{ github.workspace }}

70
pipeline.py Normal file
View File

@@ -0,0 +1,70 @@
"""Pipeline ML automatise : split -> entrainement (--register) -> promotion.
Orchestre en une seule commande (`python pipeline.py`) les etapes du fil rouge
menant a la mise a disposition du modele dans le MLflow Model Registry :
1. split : (re)cree data/{train,validation,test}.parquet depuis /data/modelling
(strategie de split figee dans lab/constants.py) ;
2. train : entraine la strategie de service (`full`) et enregistre une
nouvelle version du modele dans le Registry (log_model + --register) ;
3. promote : (re)pointe l'alias `champion` vers cette nouvelle version.
Chaque etape est un sous-processus lance avec `check=True` : si une etape echoue,
le pipeline s'arrete immediatement avec un code de sortie non nul et n'execute
aucune etape suivante (Partie 2, Q2.2). Le pipeline est reexecutable sans effet
de bord indesirable (idempotence, Q2.3) : le split est deterministe, `--register`
empile proprement une nouvelle version, et l'alias `champion` est repointe
(jamais duplique).
Les etapes reutilisent telles quelles les CLI du package `lab/` (memes commandes
qu'une execution manuelle des TP precedents) : le pipeline ne fait que les
enchainer et calculer le numero de la version a promouvoir.
"""
import logging
import subprocess
import sys
from mlflow import MlflowClient
from lab import constants
logging.basicConfig(level=logging.INFO, format="%(asctime)s [pipeline] %(message)s")
logger = logging.getLogger("pipeline")
def run_step(title: str, module_args: list[str]) -> None:
"""Execute une etape (module `lab`) dans un sous-processus ; stoppe si elle echoue."""
logger.info("=== ETAPE %s : python -m %s", title, " ".join(module_args))
subprocess.run([sys.executable, "-m", *module_args], check=True)
def latest_model_version(name: str) -> int:
"""Numero de la derniere version enregistree du modele (celle qui vient d'etre creee)."""
client = MlflowClient()
versions = client.search_model_versions(f"name = '{name}'")
if not versions:
raise RuntimeError(f"Aucune version enregistree pour le modele '{name}'.")
return max(int(mv.version) for mv in versions)
def main() -> None:
strategy = constants.SERVING_STRATEGY
name = constants.REGISTERED_MODEL_NAME
alias = constants.MODEL_ALIAS
# 1. Preparation des jeux de donnees (deterministe).
run_step("split", ["lab.split.cli"])
# 2. Entrainement + enregistrement d'une nouvelle version dans le Model Registry.
run_step("train", ["lab.modeling.cli", strategy.value, "--register"])
# 3. Promotion : repointer l'alias vers la version qui vient d'etre enregistree.
version = latest_model_version(name)
run_step("promote", ["lab.registry.cli", "promote", "--version", str(version), "--alias", alias])
logger.info("Pipeline termine avec succes : %s v%s -> alias '%s'", name, version, alias)
if __name__ == "__main__":
main()

14
requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
# Dependances runtime du pipeline (Partie 4 : "installer les dependances").
# Versions figees sur l'environnement de reference de la VM (venv /opt/venvs/mlops,
# Python 3.14) pour garantir la reproductibilite et la coherence avec le serveur
# MLflow (3.13.0) et l'API de service (chargement du modele enregistre).
mlflow==3.13.0
scikit-learn==1.9.0
scipy==1.17.1
numpy==2.4.6
pandas==2.3.3
pyarrow==24.0.0
typer==0.26.7
boto3==1.43.0
botocore==1.43.0
s3fs==2026.6.0