--- language: fr license: mit library_name: onnxruntime tags: - onnx - flood-forecasting - hydrology - quantile-regression - time-series - lstm - attention pipeline_tag: time-series-forecasting --- # 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 | Nom | Shape | Description | |-----|-------|-------------| | `past_input` | `(batch, 72, 77)` | 72h de données passées — 11 stations × 7 variables/station (paddé à 77) | | `future_precip` | `(batch, 264)` | Précipitations prévues — 11 stations × 24h | 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 | Nom | Shape | Description | |-----|-------|-------------| | `predictions` | `(batch, 1296)` | 432 sorties × 3 quantiles (interleaved) | 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 | Code | Nom | Variables prédites | |------|-----|--------------------| | J700061001 | Janzé (Seiche) | H, Q | | J701064001 | Lac de la Valière | H | | J702401001 | Louvigné-de-Bais (Quincampoix) | H, Q | | J702403001 | Étang de Marcillé | H | | J702402001 | Rannée (Ardenne) | H, Q | | J701061001 | Vitré (Vilaine) | H, Q | | J704301001 | Pocé-les-Bois (Cantache) | H | | J705302001 | Lac de la Haute Vilaine | H | | J706062001 | Châteaubourg (Vilaine) | H, Q | | J708311001 | La Bouëxière (Illet) | H, Q | | J709063002 | Cesson-Sévigné (Vilaine) | H, Q | ## Métriques Résultats sur le jeu de test — hiver 2025-2026 (Châteaubourg, q50) : | Horizon | NSE | RMSE | |---------|------|------| | t+1h | 0.9999 | 6 mm | | t+6h | 0.9968 | 36 mm | | t+12h | 0.9884 | 69 mm | | t+24h | 0.9699 | 112 mm | Calibration des quantiles : P(y < q10) ≈ 10%, P(y < q90) ≈ 85% (cibles 10%/90%). ## Utilisation ```python 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 + delta ``` ## Données d'entraînement - **Hydrométrie** : hauteurs d'eau (H) et débits (Q) horaires depuis 2000 — [Hydro EauFrance](https://www.hydro.eaufrance.fr) - **Météo** : précipitations et humidité du sol horaires — [Open-Meteo](https://open-meteo.com) - **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](https://vilaine-amont.haruni.net) - **Code source** : [github.com/sebastien-burel/vilaine-amont](https://github.com/sebastien-burel/vilaine-amont)