era5-t2m-lstm — LSTM 24 h forecast of ERA5 2 m temperature
Predicts the next 24 hours of 2 m air temperature at any ERA5 grid cell in the region below, from the previous 72 hours at that cell. One shared model for every cell; location enters through normalised latitude/longitude inputs.
Data
- Source: ERA5 reanalysis,
2m_temperature, hourly, 0.25° grid, via the Copernicus Climate Data Store. Contains modified Copernicus Climate Change Service information 2021–2025. - Region: 41.75°N–57.0°N, 95.0°W–74.25°W (62 × 84 cells).
- Splits (chronological): train 2021-01-01 → 2023-12-31, val 2024-01-01 → 2024-12-31, test 2025-01-01 → 2025-12-31.
- Normalisation: z-score with train-only mean 276.424 K, std 13.571 K (
scaler.json).
Architecture
Input (72, 7) → 2 × LSTM(128) (dropout 0.1) → Dense(24), linear.
204,312 parameters. Keras 3.13.2 / TensorFlow 2.20.0.
Test metrics (2025, every cell, t0 every 6 h, 7,525,560 windows)
| lead (h) | RMSE (K) | MAE (K) | persistence RMSE (K) |
|---|---|---|---|
| 1 | 1.505 | 1.194 | 0.879 |
| 3 | 1.946 | 1.521 | 2.649 |
| 6 | 2.578 | 1.962 | 4.453 |
| 12 | 3.561 | 2.703 | 6.311 |
| 18 | 4.072 | 3.082 | 5.735 |
| 24 | 4.481 | 3.405 | 4.979 |
| overall | 3.462 | 2.542 | 5.106 |
Skill vs persistence: 0.322 (1 − RMSE / persistence RMSE). Validation (2024): RMSE 3.256 K, skill vs persistence 0.342.
Input features (per timestep)
| # | feature |
|---|---|
| 0 | t2m_norm |
| 1 | sin_hour |
| 2 | cos_hour |
| 3 | sin_doy |
| 4 | cos_doy |
| 5 | lat_norm |
| 6 | lon_norm |
t2m_norm = (t2m_K − mean) / std; hour and day-of-year are sin/cos encoded (UTC);
lat_norm = (lat − 41.75) / 15.25, lon_norm = (lon − (-95.0)) / 20.75.
Usage
import json, numpy as np, keras
from huggingface_hub import hf_hub_download
repo = "hersxy/era5-t2m-lstm"
model = keras.saving.load_model(hf_hub_download(repo, "model.keras"), compile=False)
scaler = json.load(open(hf_hub_download(repo, "scaler.json")))
cfg = json.load(open(hf_hub_download(repo, "config.json")))
# X: float32 [batch, 72, 7] built exactly as the feature table above
y_norm = model.predict(X) # [batch, 24]
y_K = y_norm * scaler["t2m_std_K"] + scaler["t2m_mean_K"]
Limitations
- Valid only inside the training bbox and for hourly ERA5-like inputs in kelvin.
- Trained on 2021–2023; no guarantee under climate drift or extreme events outside that range.
- Univariate: uses only past temperature at the same cell plus time/location encodings — no winds, pressure, or neighbouring cells.