sebastien-burel/vilaine-amont-flood-forecast
Vilaine Amont — Flood Forecast (Station-Attention)
Modèle de prévision de crues pour les 11 stations hydrométriques du bassin Vilaine Amont (Bretagne, France), avec intervalles de confiance natifs via quantile regression.
Architecture
Per-station LSTM → Cross-station Multi-Head Attention → Quantile Regression
- Encodeur : LSTM partagé (2 couches, hidden 128) traite chaque station indépendamment (7 variables × 72 pas de temps)
- Attention : 3 couches Multi-Head Attention (8 têtes) avec résidus et LayerNorm permettent l'échange d'information entre stations
- Précipitations futures : encodeur MLP intégrant les prévisions de précipitations à 24h (11 stations × 24h = 264 valeurs)
- Décodeurs : têtes séparées pour hauteur (H) et débit (Q), chacune produisant 24 horizons × 3 quantiles
- Quantiles : q10, q50, q90 — entraînés avec pinball loss asymétrique en crue
Entrées / Sorties
Entrées
Les 7 variables par station sont : h (hauteur), q (débit), precip (précipitations), dh (variation H), dq (variation Q), soil_moisture_0_to_7cm, soil_moisture_7_to_28cm. Certaines stations remplacent q/dq par release (lâchers de barrage).
Toutes les valeurs sont normalisées min-max avec les paramètres de norm_params.json.
Sorties
Les 1296 sorties sont organisées : pour chaque station, pour chaque variable (H puis Q), pour chaque horizon (1-24h), les 3 quantiles [q10, q50, q90] sont interleaved. Le mapping exact est dans station_attn_meta.json.
Les prédictions sont des deltas normalisés — il faut les dénormaliser puis les ajouter à la dernière valeur observée pour obtenir les valeurs absolues.
Stations
Métriques
Résultats sur le jeu de test — hiver 2025-2026 (Châteaubourg, q50) :
Calibration des quantiles : P(y < q10) ≈ 10%, P(y < q90) ≈ 85% (cibles 10%/90%).
Utilisation
import numpy as np
import onnxruntime as ort
import json
# Charger le modèle et les métadonnées
session = ort.InferenceSession("station_attn.onnx")
meta = json.load(open("station_attn_meta.json"))
norm = json.load(open("norm_params.json"))
# Préparer les entrées (normalisées min-max)
past_input = np.zeros((1, 72, 77), dtype=np.float32) # 72h passées
future_precip = np.zeros((1, 264), dtype=np.float32) # précip prévues 24h
# Inférence
outputs = session.run(None, {
"past_input": past_input,
"future_precip": future_precip,
})
predictions = outputs[0] # shape (1, 1296)
# Extraire les prédictions pour Châteaubourg (H, q50, t+6h)
station = "J706062001"
h_start = meta["output_map"][station]["h_start"]
n_quantiles = meta["n_quantiles"]
horizon_idx = 5 # t+6h (0-indexed)
q50_idx = 1 # [q10, q50, q90]
delta_norm = predictions[0, (h_start + horizon_idx) * n_quantiles + q50_idx]
# Dénormaliser
h_min = norm[f"{station}_h"]["min"]
h_max = norm[f"{station}_h"]["max"]
delta = delta_norm * (h_max - h_min)
# Valeur absolue = dernière observation + delta
# prediction_h = last_observed_h + deltaDonnées d'entraînement
- Hydrométrie : hauteurs d'eau (H) et débits (Q) horaires depuis 2000 — Hydro EauFrance
- Météo : précipitations et humidité du sol horaires — Open-Meteo
- Split : train 2000–2025/06, val 2025/07–2025/12, test 2026/01+
Entraînement
- GPU : NVIDIA DGX Spark (Grace Blackwell GB10)
- Optimiseur : Adam, lr=5e-4
- Batch size : 256
- Loss : Pinball loss avec pénalité asymétrique en crue (H ≥ 800mm à Châteaubourg)
- Suréchantillonnage : crues ×2/×4/×8 selon le seuil
- Framework : PyTorch → export ONNX
Liens
- Application web : vilaine-amont.haruni.net
- Code source : github.com/sebastien-burel/vilaine-amont
