← Blog

XGBoost + SHAP : prédire la conso énergétique d'une usine

XGBoost pour modéliser et optimiser la consommation électrique industrielle. SHAP pour expliquer les drivers au directeur usine.

La facture électrique qui ne colle plus

Octobre 2025. Audit énergétique chez un sous-traitant automobile du Nord. Leur facture électrique a augmenté de 23 % sur deux ans. Le volume produit, lui, n’a augmenté que de 8 %. Le responsable maintenance pointe du doigt les compresseurs. Le responsable production pense que c’est le chauffage des halls. Le DAF regarde la courbe et ne sait pas quoi dire au comité de direction.

J’ai récupéré 18 mois d’historique — compteurs intelligents Enedis, données SCADA de production, météo locale, planning des équipes. Trois semaines plus tard, un modèle XGBoost et une analyse SHAP avaient répondu à la question que personne n’arrivait à formuler : la surconsommation venait d’abord de la température extérieure (40 % de l’explication), ensuite du taux d’occupation des week-ends (28 %), et enfin d’une ligne de traitement thermique qui consommait deux fois plus par tonne produite le soir que le matin.

La maintenance prédictive sur les vibrations, tout le monde en parle. L’énergie, c’est moins glamour — et pourtant, pour la plupart des ETI industrielles, c’est là que se joue la compétitivité des prochaines années.


Contexte réglementaire — ce qui force la main

Ce n’est plus optionnel. Trois contraintes convergent simultanément sur les industriels français.

Le décret tertiaire. Initialement ciblé sur l’immobilier de bureau, il s’étend progressivement aux sites industriels à usage mixte. Objectif : -40 % de conso énergétique en 2030 vs l’année de référence. Pour un site qui consomme 3 GWh/an, ça représente 1,2 GWh à effacer. Pas négligeable.

L’audit énergétique ISO 50001. Les grandes entreprises — et leurs fournisseurs sous pression de l’acheteur — doivent justifier un système de management de l’énergie. ISO 50001 exige des indicateurs de performance énergétique (IPÉ) suivis et documentés. kWh/tonne produite, kWh/m², kWh/heure de fonctionnement — ce sont des IPÉ. Et pour suivre ces IPÉ, il faut un modèle de référence. C’est exactement ce que XGBoost peut fournir.

La volatilité du prix kWh. Entre 2022 et 2025, le prix industriel du kWh a oscillé entre 90 et 350 €/MWh selon les contrats, les périodes et les expositions au marché spot. Pour un site qui consomme 5 GWh/an, la différence entre une consommation optimisée sur les heures creuses et une conso non pilotée peut représenter 200 à 500 K€ par an. Ce n’est pas une direction RSE qui pilote ça — c’est la direction financière.


Le pipeline : du compteur au tableau de bord

Voici l’architecture complète du système qu’on déploie. Volontairement sans Kubernetes, sans cloud imposé — déployable dans une salle serveur d’ETI.

graph TD
    A[Compteurs intelligents\nEnedis - Pulse - Modbus] --> B[Collecte IoT\nMQTT / OPC-UA / API Enedis]
    C[SCADA Production\ntonnes/h, shifts, arrêts] --> B
    D[Météo externe\nT°C, HR, ensoleillement] --> B
    E[Calendrier\nheures pleines/creuses, week-end] --> B
    B --> F[Agrégation horaire\nInfluxDB ou CSV]
    F --> G[Feature Engineering\n16 variables dérivées]
    G --> H[XGBoost Regressor\nkWh/h → kWh/tonne]
    H --> I{Écart\nprédiction vs réel}
    I -->|Écart > 15%| J[Alerte dérive\nanomaline consommation]
    I -->|Écart normal| K[Log & monitoring\nGrafana / Power BI]
    H --> L[SHAP Explainability\npourquoi cette prédiction ?]
    L --> M[Dashboard directeur usine\nDrivers & actions]
    J --> N[Enquête terrain\ncause identifiée]
    M --> O[Plan d'action\noptimisation tarifaire + process]

    style A fill:#f5a623,color:#000
    style H fill:#4a90d9,color:#fff
    style L fill:#27ae60,color:#fff
    style M fill:#2c3e50,color:#fff
    style J fill:#e74c3c,color:#fff

Chaque bloc a des décisions d’implémentation concrètes. Les deux qui font ou défont le projet : la construction des features et l’utilisation de SHAP pour communiquer avec le métier.


Les features : ce qui conditionne la consommation électrique

La consommation électrique d’une usine est le résultat de l’interaction de quatre familles de variables. Les identifier correctement est 60 % du travail.

Famille 1 — Production

Ce sont les drivers primaires. Sans production, la consommation de base reste, mais le surplus lié à l’activité disparaît.

  • production_tonnes_h — cadence horaire de la ligne principale
  • nb_lignes_actives — nombre de lignes en fonctionnement simultané
  • taux_utilisation_compresseurs — proxy de la charge pneumatique
  • duree_cycle_moyen_s — durée de cycle moyenne (corrèle avec la consommation par pièce)

Famille 2 — Météo

Souvent sous-estimée, parfois prépondérante, selon le secteur.

  • temp_ext_celsius — température extérieure (chauffage/climatisation des halls, viscosité huiles, rendement compresseurs)
  • humidite_relative_pct — impact sur les systèmes de traitement d’air
  • ensoleillement_wh_m2 — si toits photovoltaïques, contribution en net

La corrélation entre temp_ext_celsius et la consommation électrique est non-linéaire : sous 5°C, le chauffage s’emballe ; au-dessus de 28°C, la climatisation prend le relais. Un modèle linéaire ne voit pas cette forme en U. XGBoost la capture automatiquement.

Famille 3 — Planning et opérations

  • shift — encodé en dummy (matin / après-midi / nuit)
  • est_weekend — booléen
  • est_ferie — booléen
  • heure_demarrage_ligne — les transitoires de démarrage consomment 2 à 4× le régime établi
  • nb_operations_maintenance — arrêts techniques (consommation résiduelle pendant les plages)

Famille 4 — Tarification et signal prix

  • est_heure_pleine — tarif heures pleines/creuses (HP/HC)
  • est_heure_pointe — pour les contrats TURPE avec effacement
  • signal_ecowatt — si disponible via API RTE, pour les journées de tension réseau

La variable la plus souvent oubliée : heure_demarrage_ligne. Sur les sites qu’on audite, décaler le démarrage de la ligne principale de 30 minutes hors de la pointe tarifaire économise en moyenne 8 à 12 % de la facture, sans toucher ni à l’équipement ni au process.


XGBoost Régression — modéliser kWh/tonne

Le problème est une régression : prédire la consommation électrique horaire (kWh/h) ou l’intensité énergétique (kWh/tonne produite) à partir des variables ci-dessus. L’objectif n’est pas de prédire l’avenir — c’est de comprendre le présent et d’identifier les anomalies.

Pourquoi XGBoost plutôt que régression linéaire ou réseau de neurones ?

La question est légitime. Voici la comparaison honnête.

MéthodeAvantagesLimites dans ce contexte
Régression linéaire (OLS)Interprétable directement, coefficients parlantsSuppose des effets linéaires et additifs. Rate les interactions et les non-linéarités (ex: effet en U de la température). Performances faibles.
Random ForestRobuste, peu d’hyperparamètresLégèrement moins précis que XGBoost. Même logique sinon.
XGBoostTop performance sur tabulaire, feature importance native, compatible SHAP, rapide à entraînerNécessite un tuning minimal. Pas un modèle séquentiel — les dépendances temporelles longues doivent passer par des features lag.
Réseau de neurones (MLP/LSTM)Capture des patterns complexesDemande plus de données, plus coûteux à maintenir, boîte noire sans SHAP. Pour ce problème, overhead non justifié.

Pour des données horaires sur 12 à 24 mois avec 15 à 20 features, XGBoost avec feature engineering bien fait surpasse systématiquement les réseaux de neurones. Et surtout, il fournit une base propre pour l’analyse SHAP.

Le code complet

import pandas as pd
import numpy as np
import xgboost as xgb
import shap
import matplotlib.pyplot as plt
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.preprocessing import LabelEncoder

# ─── 1. CHARGEMENT ET FEATURES ────────────────────────────────────────────────

df = pd.read_csv("conso_usine.csv", parse_dates=["timestamp"])
df = df.sort_values("timestamp").reset_index(drop=True)

# Features temporelles
df["heure"] = df["timestamp"].dt.hour
df["jour_semaine"] = df["timestamp"].dt.dayofweek
df["mois"] = df["timestamp"].dt.month
df["est_weekend"] = (df["jour_semaine"] >= 5).astype(int)

# Encodage cyclique (heure — les heures 23 et 0 sont proches)
df["heure_sin"] = np.sin(2 * np.pi * df["heure"] / 24)
df["heure_cos"] = np.cos(2 * np.pi * df["heure"] / 24)

# Features lag (consommation des heures précédentes)
for lag in [1, 2, 3, 24, 48]:
    df[f"conso_lag_{lag}h"] = df["conso_kwh"].shift(lag)

# Rolling stats (fenêtre 24h)
df["conso_rolling_mean_24h"] = df["conso_kwh"].shift(1).rolling(24).mean()
df["conso_rolling_std_24h"]  = df["conso_kwh"].shift(1).rolling(24).std()

# Température quadratique (effet en U)
df["temp_ext_sq"] = df["temp_ext_celsius"] ** 2

# Interaction production × température
df["prod_x_temp"] = df["production_tonnes_h"] * df["temp_ext_celsius"]

# Intensité énergétique (target alternative)
df["kwh_par_tonne"] = df["conso_kwh"] / (df["production_tonnes_h"] + 1e-6)

df = df.dropna().reset_index(drop=True)

# ─── 2. SPLIT TEMPOREL — JAMAIS de k-fold aléatoire sur time-series ──────────

FEATURE_COLS = [
    "production_tonnes_h", "nb_lignes_actives", "temp_ext_celsius", "temp_ext_sq",
    "humidite_relative_pct", "shift_encoded", "est_weekend", "est_ferie",
    "est_heure_pleine", "heure_sin", "heure_cos", "mois",
    "conso_lag_1h", "conso_lag_24h", "conso_rolling_mean_24h", "conso_rolling_std_24h",
    "prod_x_temp"
]
TARGET = "conso_kwh"

# 80% train / 20% test — respect de l'ordre temporel
split_idx = int(len(df) * 0.8)
X_train = df[FEATURE_COLS].iloc[:split_idx]
y_train = df[TARGET].iloc[:split_idx]
X_test  = df[FEATURE_COLS].iloc[split_idx:]
y_test  = df[TARGET].iloc[split_idx:]

# ─── 3. ENTRAÎNEMENT XGBoost ──────────────────────────────────────────────────

model = xgb.XGBRegressor(
    n_estimators=2000,
    learning_rate=0.03,
    max_depth=6,
    subsample=0.8,
    colsample_bytree=0.8,
    min_child_weight=5,
    reg_alpha=0.1,       # L1 — aide sur features corrélées
    reg_lambda=1.0,      # L2
    random_state=42,
    early_stopping_rounds=50,
    eval_metric="mae",
    device="cpu",
)

model.fit(
    X_train, y_train,
    eval_set=[(X_test, y_test)],
    verbose=100,
)

# ─── 4. ÉVALUATION ────────────────────────────────────────────────────────────

y_pred = model.predict(X_test)
mae  = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2   = r2_score(y_test, y_pred)
mape = np.mean(np.abs((y_test - y_pred) / (y_test + 1e-6))) * 100

print(f"MAE  : {mae:.1f} kWh")
print(f"RMSE : {rmse:.1f} kWh")
print(f"R²   : {r2:.4f}")
print(f"MAPE : {mape:.1f} %")
# Résultats typiques sur un site industriel réel : R² 0.91-0.96, MAPE 4-8 %

# ─── 5. SHAP — POURQUOI LE MODÈLE A PRÉDIT ÇA ────────────────────────────────

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# Summary plot — vue globale des drivers
plt.figure(figsize=(10, 8))
shap.summary_plot(
    shap_values,
    X_test,
    feature_names=FEATURE_COLS,
    plot_type="bar",
    show=False,
)
plt.title("Impact moyen des variables sur la consommation (kWh)")
plt.tight_layout()
plt.savefig("shap_summary_bar.png", dpi=150)
plt.close()

# Waterfall plot — pourquoi la prédiction de l'heure H a été X kWh
# (à utiliser pour expliquer une heure spécifique au directeur)
idx_anomalie = 42  # index de l'heure à expliquer
shap.waterfall_plot(
    shap.Explanation(
        values=shap_values[idx_anomalie],
        base_values=explainer.expected_value,
        data=X_test.iloc[idx_anomalie],
        feature_names=FEATURE_COLS,
    )
)

# Détection d'anomalies : heures où l'écart dépasse 2 sigma
residus = y_test.values - y_pred
seuil_anomalie = residus.std() * 2
anomalies = np.where(np.abs(residus) > seuil_anomalie)[0]
print(f"\n{len(anomalies)} heures anomales détectées sur {len(y_test)}")

Quelques points critiques sur ce code

Early stopping et split temporel. L’eval_set est strictement postérieur au train. Jamais de k-fold aléatoire sur des données temporelles — c’est l’erreur classique qui gonfle artificiellement les métriques.

Encodage cyclique de l’heure. Transformer l’heure en sin/cos permet au modèle de comprendre que 23h et 0h sont consécutifs. Sinon, le modèle voit 23 et 0 comme des valeurs très éloignées.

Features lag. La consommation de l’heure précédente est souvent la feature la plus prédictive. Mais attention : elle introduit une dépendance temporelle. En prédiction en temps réel, elle est disponible. Pour des scénarios de planification à J+1, il faut l’exclure ou la remplacer par une prévision.


SHAP — l’outil qui fait le lien entre le modèle et le directeur usine

C’est la partie que j’affectionne le plus dans ce type de mission. Un modèle XGBoost sans SHAP reste une boîte partiellement opaque. Avec SHAP, chaque prédiction devient explicable en termes métier.

Pourquoi SHAP et pas juste la feature importance native XGBoost ?

La feature importance XGBoost mesure combien de fois une feature est utilisée comme critère de split, ou le gain moyen qu’elle apporte. Ce n’est pas la même chose que son impact sur les prédictions individuelles. SHAP calcule la contribution marginale de chaque variable sur chaque prédiction, en tenant compte des interactions et de la non-linéarité.

La différence pratique : XGBoost feature importance peut placer temp_ext_celsius en troisième position, là où SHAP révèle qu’elle est la feature dominante pour les journées d’hiver — mais pas l’été, où elle est neutre. Cette granularité temporelle est essentielle pour identifier les leviers d’action.

Un exemple de communication avec le directeur usine

Voici le type de message qu’on peut construire à partir d’un SHAP waterfall plot, sans montrer un seul graphe au directeur :

“Mardi dernier, entre 6h et 9h, votre usine a consommé 840 kWh. Le modèle attendait 680 kWh. L’écart de 160 kWh s’explique ainsi :

  • +95 kWh → température extérieure à -4°C, soit 11°C en dessous de la normale de saison (chauffage des halls + rendement compresseurs dégradé)
  • +48 kWh → démarrage simultané de la ligne 1, de la ligne 3 et du traitement thermique entre 6h05 et 6h20 (pic de démarrage non optimisé)
  • +17 kWh → samedi de la semaine précédente, les compresseurs n’ont pas été mis en veille complète, et l’historique récent pèse encore sur la régulation
  • Facteurs compensateurs : -0 kWh (aucun facteur négatif cette heure-là)”

SHAP transforme un nombre — “vous avez consommé 23 % de plus ce mois-ci” — en une décomposition causale exploitable par quelqu’un qui ne connaît pas le machine learning. C’est la différence entre un rapport de conformité et un outil de management.

Les trois visualisations SHAP à livrer au client

1. SHAP Summary Plot (barres) — Vue globale sur l’ensemble de la période. Montre quelles variables expliquent le plus la variabilité de consommation. Typiquement : température en tête, suivie de la production, puis du shift. À afficher en comité de direction.

2. SHAP Summary Plot (scatter) — Version plus riche : chaque point est une heure, colorée par la valeur de la variable. Permet de voir que temp_ext_celsius a un impact négatif (économie) quand la température est douce, et positif (surconso) aux extrêmes. Non montrable en réunion métier, mais utile pour les équipes techniques.

3. SHAP Waterfall Plot — Explication d’une heure spécifique. Outil de diagnostic pour les anomalies. Quand une heure décroche, on lance un waterfall pour comprendre quelle variable a déclenché l’écart.


ROI — ce qu’on peut raisonnablement attendre

Soyons précis. Un modèle XGBoost d’énergie ne génère pas directement des économies. Il génère de l’information. Ce sont les décisions prises à partir de cette information qui génèrent les économies. La nuance est importante.

Leviers identifiés sur les missions terrain

Optimisation des démarrages (3 à 6 % d’économie potentielle). Sur les sites où les lignes démarrent toutes à la même heure, le pic de démarrage simultané génère une pointe de puissance souscrite. Décaler les démarrages de 5 à 15 minutes chacun efface le pic, réduit la puissance de soutirage contractuelle, et économise directement sur le TURPE. Le modèle SHAP identifie systématiquement heure_demarrage_ligne comme variable clé quand ce pattern existe.

Pilotage heures creuses/pleines (4 à 8 % d’économie potentielle). Sur les sites avec contrats HP/HC ou effacement, décaler des charges flexibles (chauffage, pompage, traitement de surface) vers les heures creuses est documentable et optimisable via le modèle. On a besoin de savoir quelles charges sont pilotables et avec quel délai — le modèle guide la priorisation.

Détection d’anomalies de consommation (2 à 5 % d’économie potentielle). Les fuites lentes de consommation — une vanne de vapeur qui fuit, un four qui ne s’éteint pas complètement la nuit, un compresseur qui tourne à vide le week-end — sont invisibles sur la facture mensuelle mais visibles en surveillance horaire. Le modèle produit un signal d’écart continu. Une alerte à +15 % d’écart hors fenêtre de démarrage permet de détecter ces fuites en quelques heures au lieu de les découvrir au bilan semestriel.

Benchmark inter-équipes (1 à 3 % d’économie potentielle). En normalisant la consommation par le kWh/tonne, on peut comparer équitablement les trois équipes (matin, après-midi, nuit) indépendamment du volume produit. Sur certains sites, l’équipe de nuit consomme 15 à 20 % de plus par tonne sans que personne ne s’en soit rendu compte — les équipes de nuit ont souvent moins de supervision et des habitudes de pilotage différentes.

Sur les huit sites où on a déployé ce type de modèle, l’économie identifiable oscille entre 6 et 15 % de la facture électrique annuelle, dont 60 % est actionnée dans les 6 premiers mois. Le reste nécessite des investissements (isolation, remplacement d’équipements) dont le ROI se calcule sur 3 à 5 ans.


Les limites — ce que le modèle ne fait pas

Il faut le dire clairement, parce que certains vendeurs de solutions ML ne le font pas.

Corrélation ≠ causalité. SHAP dit que la température extérieure “explique” 40 % de la variance de consommation. Ça ne veut pas dire que baisser la température extérieure économise de l’énergie — c’est une absurdité. Ça dit que les équipements sensibles à la température (chauffage, clim, compresseurs) sont les principaux consommateurs non pilotés. C’est là qu’il faut chercher les leviers techniques, pas dans la météo.

Le modèle ne remplace pas l’audit terrain. XGBoost identifie les variables statistiquement importantes. Il ne peut pas distinguer un compresseur mal réglé d’un compresseur structurellement sous-dimensionné — cette information n’est pas dans les données de comptage. L’audit terrain (mesures, relevés, interviews des opérateurs) reste indispensable pour valider les hypothèses que le modèle génère.

La précision se dégrade avec le temps. Les procédés changent, les équipements vieillissent, de nouvelles lignes sont ajoutées. Un modèle entraîné sur 2024 sera moins précis en 2026. Un réentraînement annuel (ou semestriel sur les sites en forte évolution) est nécessaire pour maintenir la qualité de prédiction. Sans monitoring de drift, le modèle devient silencieusement mauvais.

Les données doivent être propres — ou le modèle dit des bêtises. La règle garbage in, garbage out s’applique. Un compteur qui remonte des valeurs erronées pendant 3 semaines, c’est 504 heures de fausses données qui biaisent le modèle. Avant de déployer, un audit de qualité des données — notamment sur la cohérence temporelle et les valeurs aberrantes — est non négociable. Voir l’article Agrégation de données industrielles pour les protocoles qu’on applique.


Ce que BCUB3 délivre concrètement

Sur ce type de mission, notre livrable n’est pas un modèle. C’est un système opérationnel que l’équipe du site peut maintenir.

Phase 1 — Audit données (2 à 3 jours). Collecte des données de comptage, SCADA, météo. Vérification qualité, traitement des valeurs manquantes, alignement temporel. Identification des features disponibles vs celles à créer.

Phase 2 — Modélisation et validation (3 à 5 jours). Construction du pipeline XGBoost. Validation TimeSeriesSplit. Calibration des alertes d’anomalie. Rapport SHAP avec interprétation métier.

Phase 3 — Dashboard et transfert (2 à 3 jours). Intégration dans l’outil de visualisation existant (Power BI, Grafana, ou Tableau selon le client). Formation des équipes à la lecture des graphes SHAP. Documentation opérationnelle — comment interpréter une alerte, que faire si l’écart dépasse 20 %, qui contacter.

Le tout en 10 à 15 jours sur site, avec un système qui tourne en autonomie ensuite. Pas un projet de 6 mois.


Pour aller plus loin

L’énergie est rarement un problème isolé. La surconsommation révèle souvent des problèmes process sous-jacents — équipements vétustes, procédures de démarrage non optimisées, variabilité de production non maîtrisée. Pour aborder ces problèmes avec un cadre structuré, l’article Lean Six Sigma × Machine Learning : le combo qui change la donne couvre comment on combine les deux approches.

Et pour comprendre comment XGBoost s’applique sur d’autres problèmes industriels — vibrations, prédiction de défaut, régulation de procédé — voir XGBoost pour la maintenance prédictive et Machine Learning pour l’industrie : du signal au modèle en production.

Cet article vous a été utile ?

BCUB3 est une petite structure. Si vous pensez à un collègue ou un partenaire qui pourrait en tirer quelque chose, la meilleure manière de nous aider est de partager le lien. Et si vous avez un cas concret à discuter, parlons-en directement.

Prendre un RDV de cadrage