Comment estimer le temps d’une tâche avant son exécution ? Ce projet Python part d’un CSV de tâches déjà terminées, entraîne XGBoost et exporte les prédictions sur des tâches gardées à l’écart. Vous obtenez les données, le script complet et les résultats de référence pour reproduire chaque étape.
Le scénario est pédagogique : les 1 000 tâches et leurs durées sont entièrement synthétiques. Elles n’ont pas été relevées sur un serveur. L’objectif est d’apprendre à construire et vérifier le parcours CSV → modèle → résultat, sans promettre une précision sur une infrastructure réelle.
Télécharger le projet complet gratuit (ZIP, 26 Ko) : script, CSV, dépendances, guide de lancement, générateur des données et résultats de référence. Aucun compte n’est nécessaire pour ce téléchargement.
Exécuté le 14 septembre 2026 avec Python 3.12.14, XGBoost 3.4.1, scikit-learn 1.9.1 et NumPy 2.5.3. Le calcul utilise le CPU et fonctionne localement après installation des dépendances.
Ce que le programme va produire
Le CSV contient des caractéristiques connues avant une tâche et sa durée observée après exécution. Le script apprend sur 600 lignes, utilise 200 autres lignes pour l’arrêt anticipé et mesure le résultat sur les 200 dernières lignes réservées au test.
Le dossier de sortie contient predictions_test.csv, rapport.json et modele.json. Vous pourrez retrouver chaque tâche grâce à son identifiant et comparer XGBoost à un premier repère : prédire toujours la durée moyenne de l’apprentissage.
Ce cas complète le guide XGBoost en régression. Le guide explique l’algorithme ; ce projet s’occupe aussi du contrat de fichier, des erreurs de saisie et de la conservation des résultats.
1. Comprendre les colonnes du CSV
| Colonne | Signification | Rôle |
|---|---|---|
id |
Identifiant unique d’une tâche | Traçabilité ; exclu des entrées du modèle. |
taille_mo |
Taille des données d’entrée en Mo | Variable explicative positive. |
nb_fichiers |
Nombre de fichiers à traiter | Entier positif. |
compression |
Compression prévue, 0 ou 1 | Variable explicative binaire. |
workers |
Nombre de workers configurés avant la tâche | Entier positif. |
duree_s |
Durée observée en secondes | Cible à apprendre, jamais une entrée. |
Le fichier fourni commence ainsi :
id,taille_mo,nb_fichiers,compression,workers,duree_s T0001,423.260,52,0,8,23.799 T0002,923.823,49,1,2,101.062 T0003,1527.678,38,1,2,170.201 T0004,1842.213,129,0,2,164.168
Le générateur invente une relation avec interactions entre taille, compression et workers, plus du bruit. Il produit des tailles entre 10 et 2 000 Mo, 1 à 150 fichiers et un nombre de workers parmi 1, 2, 4 et 8. Il ne simule pas fidèlement un logiciel particulier. La formule complète se trouve dans generer_donnees.py ; le CSV est déjà prêt à utiliser.
Le lecteur exige ces six colonnes, des identifiants uniques, au moins 50 lignes et des valeurs numériques finies. Il refuse les cellules vides et les champs supplémentaires. Le seuil de 50 lignes sert uniquement à garder des ensembles non minuscules dans cet exercice ; il ne prouve pas qu’un jeu réel est suffisant.
2. Installer les dépendances et lancer le script
Extrayez le ZIP et ouvrez PowerShell dans le dossier contenant predire_durees.py. Avec Python installé, lancez :
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe predire_durees.py taches_synthetiques.csv resultats
Sous macOS ou Linux, créez l’environnement avec python3 -m venv .venv, puis utilisez .venv/bin/python à la place de .\.venv\Scripts\python.exe. Une connexion sert à installer les paquets ; le script ne transmet ensuite pas votre fichier à une API.
Le nom resultats doit désigner un nouveau dossier. Pour un second essai, utilisez resultats_2. Cette règle protège les résultats précédents et le CSV d’origine.
3. Garder un test indépendant de l’apprentissage
Le découpage aléatoire fixe est adapté aux observations indépendantes de notre générateur. La validation guide l’arrêt anticipé, tandis que le test reste en dehors de fit. La liste des variables FEATURES exclut explicitement l’identifiant et la durée cible.
Le modèle de référence DummyRegressor apprend uniquement la moyenne des durées de l’apprentissage. Il sert à vérifier si les variables permettent de faire mieux qu’une constante. C’est un point de départ, pas un comparatif complet : une régression linéaire et d’autres méthodes mériteraient une évaluation séparée avant un choix métier.
XGBoost dispose d’un plafond de 800 arbres. La configuration d’arrêt anticipé laisse 30 itérations sans amélioration de la RMSE de validation. Dans l’exécution fournie, 757 arbres sont retenus. Le script ne choisit aucun paramètre en regardant les scores du test.
4. Consulter le script complet
Le code ci-dessous est identique au fichier de l’archive. Vous pouvez le déplier pour étudier le contrôle des entrées, la séparation des indices et l’export :
Afficher le script Python complet
"""Projet pédagogique : évaluer une durée en secondes sur un CSV étiqueté."""
import argparse
import csv
import hashlib
import json
from pathlib import Path
import numpy as np
import sklearn
import xgboost
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, root_mean_squared_error
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor
FEATURES = ("taille_mo", "nb_fichiers", "compression", "workers")
COLUMNS = ("id", *FEATURES, "duree_s")
def lire_csv(path):
with Path(path).open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames is None or len(reader.fieldnames) != len(COLUMNS) or set(reader.fieldnames) != set(COLUMNS):
raise ValueError("Colonnes attendues : " + ", ".join(COLUMNS))
rows = list(reader)
if len(rows) < 50:
raise ValueError("Au moins 50 lignes sont nécessaires pour cet exercice.")
ids, values, seen = [], [], set()
for line, row in enumerate(rows, start=2):
if None in row or any(v is None or not v.strip() for v in row.values()):
raise ValueError(f"Ligne {line} : cellule vide ou nombre de champs incorrect.")
ident = row["id"]
if ident.strip() in seen:
raise ValueError(f"Ligne {line} : identifiant dupliqué.")
seen.add(ident.strip())
try:
vals = [float(row[c]) for c in (*FEATURES, "duree_s")]
except ValueError as exc:
raise ValueError(f"Ligne {line} : nombre invalide.") from exc
size, files, compression, workers, duration = vals
if not np.isfinite(vals).all() or min(size, files, workers, duration) <= 0:
raise ValueError(f"Ligne {line} : valeurs finies et positives requises.")
if files != int(files) or workers != int(workers) or compression not in (0, 1):
raise ValueError(f"Ligne {line} : fichiers/workers entiers, compression 0 ou 1.")
ids.append(ident)
values.append(vals)
values = np.asarray(values, dtype=np.float64)
return ids, values[:, :-1], values[:, -1]
def separer(n):
trainval, test = train_test_split(np.arange(n), test_size=0.2, random_state=42)
train, val = train_test_split(trainval, test_size=0.25, random_state=42)
return train, val, np.sort(test)
def entrainer(X, y, train, val):
baseline = DummyRegressor(strategy="mean").fit(X[train], y[train])
model = XGBRegressor(
objective="reg:squarederror", n_estimators=800, learning_rate=0.05,
max_depth=3, min_child_weight=5, subsample=0.9, colsample_bytree=1.0,
reg_lambda=1.0, tree_method="hist", n_jobs=1, random_state=42,
eval_metric="rmse", early_stopping_rounds=30,
)
model.fit(X[train], y[train], eval_set=[(X[val], y[val])], verbose=False)
return model, baseline
def mesures(y, pred):
return {"mae_s": float(mean_absolute_error(y, pred)),
"rmse_s": float(root_mean_squared_error(y, pred))}
def executer(source, destination):
source, destination = Path(source), Path(destination)
if destination.exists():
raise FileExistsError("Choisissez un nouveau dossier de sortie.")
ids, X, y = lire_csv(source)
train, val, test = separer(len(y))
model, baseline = entrainer(X, y, train, val)
pred, ref = model.predict(X[test]), baseline.predict(X[test])
report = {
"donnees": "Origine à documenter ; le CSV fourni est entièrement synthétique.",
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
"features_ordre": list(FEATURES), "target": "duree_s",
"effectifs": {"apprentissage": len(train), "validation": len(val), "test": len(test)},
"ids_separation": {k: [ids[i] for i in indices] for k, indices in
[("apprentissage", train), ("validation", val), ("test", test)]},
"arbres_retenus": model.best_iteration + 1,
"iterations_executees": len(model.evals_result()["validation_0"]["rmse"]),
"test": {"moyenne_apprentissage": mesures(y[test], ref), "xgboost": mesures(y[test], pred)},
"baseline_constante_s": float(baseline.constant_.ravel()[0]),
"versions": {"numpy": np.__version__, "scikit-learn": sklearn.__version__, "xgboost": xgboost.__version__},
}
destination.mkdir(parents=True, exist_ok=False)
model.save_model(destination / "modele.json")
(destination / "rapport.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
with (destination / "predictions_test.csv").open("x", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["id", "duree_reelle_s", "prediction_xgb_s", "prediction_baseline_s", "erreur_xgb_s"])
for i, prediction, constant in zip(test, pred, ref):
writer.writerow([ids[i], f"{y[i]:.6f}", f"{prediction:.6f}", f"{constant:.6f}", f"{prediction-y[i]:.6f}"])
print("Lignes :", len(y), "| apprentissage / validation / test :", len(train), len(val), len(test))
for name, metrics in report["test"].items():
print(f"{name} : MAE {metrics['mae_s']:.3f} s ; RMSE {metrics['rmse_s']:.3f} s")
print("Arbres retenus :", report["arbres_retenus"])
print("Résultats :", destination)
return report
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("csv", type=Path)
parser.add_argument("sortie", type=Path)
args = parser.parse_args()
try:
executer(args.csv, args.sortie)
except (ValueError, OSError) as exc:
parser.exit(2, f"Erreur : {exc}\n")
5. Lire le résultat obtenu
Lignes : 1000 | apprentissage / validation / test : 600 200 200 moyenne_apprentissage : MAE 52.538 s ; RMSE 64.925 s xgboost : MAE 2.595 s ; RMSE 3.386 s Arbres retenus : 757
Sur ces 200 tâches de test, l’erreur absolue moyenne de XGBoost est d’environ 2,595 secondes. Sa RMSE atteint 3,386 secondes ; les erreurs importantes y pèsent davantage. La référence constante a ici une RMSE de 64,925 secondes. Ces résultats illustrent une relation synthétique construite pour être apprenable. Ils ne démontrent ni un gain économique ni une précision attendue en production.

Le fichier exporté commence ainsi :
id,duree_reelle_s,prediction_xgb_s,prediction_baseline_s,erreur_xgb_s T0011,146.102000,147.857452,94.452168,1.755452 T0024,110.169000,115.237511,94.452168,5.068511 T0026,136.848000,128.107864,94.452168,-8.740136 T0031,115.985000,117.729912,94.452168,1.744912
erreur_xgb_s est la prédiction moins la durée réelle : une valeur positive signifie une surestimation. Les 200 lignes conservent l’ordre relatif du CSV source. Il n’y a pas de prédictions exportées pour les lignes d’apprentissage ou de validation, ce qui évite de mélanger leurs résultats avec le test.
Le rapport garde les identifiants des trois ensembles, les métriques, la moyenne de référence, l’ordre des variables, les versions et l’empreinte du CSV. La sauvegarde du modèle permet de le recharger avec XGBRegressor.load_model. L’ordre des variables doit rester identique lors de futures prédictions ; les réglages complets du programme restent documentés dans le script.
6. Comprendre les erreurs courantes
- « Colonnes attendues » : vérifiez la première ligne et le séparateur virgule. Un export avec des points-virgules doit être converti explicitement.
- « Nombre invalide » : utilisez un point décimal et retirez les unités textuelles des cellules numériques.
- « Cellule vide » : corrigez l’entrée ou définissez un traitement des absences avant d’adapter ce projet. Le script ne supprime aucune ligne silencieusement.
- « Identifiant dupliqué » : chaque tâche doit pouvoir être retrouvée sans ambiguïté. Deux exécutions distinctes ont besoin de deux identifiants.
- « Nouveau dossier de sortie » : changez le nom de destination pour préserver l’essai précédent. Après une erreur d’écriture, un dossier peut être incomplet ; choisissez aussi un nouveau nom.
7. Trois exercices pour aller plus loin
- Ouvrez
predictions_test.csvet retrouvez la tâche dont l’erreur absolue est la plus élevée. Comparez ses caractéristiques à celles du reste du test. - À partir de
rapport.json, vérifiez que les listes d’identifiants d’apprentissage, de validation et de test sont disjointes et couvrent les 1 000 tâches. - Préparez un nouveau jeu final tenu à l’écart, puis comparez une autre configuration ou une régression linéaire. Choisissez les réglages sur la validation et utilisez le nouveau test seulement à la fin.
Le projet a été contrôlé notamment sur la conservation des identifiants, la reproductibilité, le refus des données invalides et le rechargement du modèle. Un test change fortement les durées du jeu de test : cela ne modifie ni les prédictions apprises ni l’itération retenue. L’archive extraite reproduit aussi les sorties de référence.
Adapter le projet à de vraies tâches
Avant d’utiliser des historiques réels, définissez le moment exact où la prédiction doit être disponible. Excluez les informations connues seulement après la tâche. Si plusieurs lignes proviennent du même serveur ou d’exécutions proches dans le temps, un découpage aléatoire peut donner une image trop favorable : réservez une période future ou des groupes réellement séparés.
Vérifiez aussi les tâches annulées, les changements de matériel et les nouvelles charges. Ce script attend une durée observée pour évaluer chaque ligne ; il ne fournit pas encore une commande destinée aux tâches futures sans étiquette. Une extension devra définir leur schéma, contrôler les plages et surveiller les erreurs après exécution.
Enfin, une relation entre workers et durée ne prouve pas qu’ajouter des workers accélérera une tâche réelle : charge concurrente, disque et mémoire peuvent intervenir. La régression prédit dans un contexte observé ; elle ne remplace pas une expérience permettant de mesurer l’effet d’un changement.
Continuer le parcours CSV et Python
Télécharger les fichiers gratuits de ce projet, puis retrouver les explications des paramètres dans le guide XGBoost régression.
Pour apprendre à examiner des lignes inhabituelles sans cible à prédire, essayez le projet CSV avec Isolation Forest. Notre sélection de ressources Python et de livres par niveau complète les prérequis.

