Upload TCN — test RMSE 3.5771 m R² 0.334
Browse files- .gitattributes +2 -0
- README.md +98 -0
- inference.py +88 -0
- model_config.json +57 -0
- scaler_features.pkl +3 -0
- scaler_target.pkl +3 -0
- tcn_baseline_training_history.png +0 -0
- tcn_forecast_comparison.png +3 -0
- tcn_model.keras +3 -0
- tcn_residual_analysis.png +0 -0
- tcn_tuned_training_history.png +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
tcn_forecast_comparison.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
tcn_model.keras filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- time-series
|
| 5 |
+
- forecasting
|
| 6 |
+
- tcn
|
| 7 |
+
- hydrology
|
| 8 |
+
- groundwater
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# TCN Groundwater Level Forecasting — UK
|
| 12 |
+
|
| 13 |
+
A tuned Temporal Convolutional Network (TCN) for single-step monthly groundwater
|
| 14 |
+
level forecasting using meteorological variables as exogenous inputs.
|
| 15 |
+
|
| 16 |
+
## Model Details
|
| 17 |
+
|
| 18 |
+
| Parameter | Value |
|
| 19 |
+
|---|---|
|
| 20 |
+
| Architecture | TCN(nb_filters=32, kernel_size=3, dilations=[1, 2, 4, 8]) → Dense(1) |
|
| 21 |
+
| Framework | TensorFlow / Keras (keras-tcn) |
|
| 22 |
+
| Task | Single-step monthly forecasting |
|
| 23 |
+
| Lookback window | 24 months |
|
| 24 |
+
| Input features | water_level, temperature, precipitation, wind_speed |
|
| 25 |
+
| Tuning method | Bayesian Optimisation (Keras Tuner, 20 trials) |
|
| 26 |
+
|
| 27 |
+
## Data Splits
|
| 28 |
+
|
| 29 |
+
| Split | Period | Months |
|
| 30 |
+
|---|---|---|
|
| 31 |
+
| Training | 1944-01-01 → 2007-10-01 | 766 |
|
| 32 |
+
| Validation | 2007-11-01 → 2015-10-01 | 96 |
|
| 33 |
+
| Test | 2015-11-01 → 2023-10-01 | 96 |
|
| 34 |
+
|
| 35 |
+
## Best Hyperparameters
|
| 36 |
+
|
| 37 |
+
| Parameter | Value |
|
| 38 |
+
|---|---|
|
| 39 |
+
| nb_filters | 32 |
|
| 40 |
+
| kernel_size | 3 |
|
| 41 |
+
| dilations | [1, 2, 4, 8] |
|
| 42 |
+
| dropout_rate | 0.1 |
|
| 43 |
+
| learning_rate | 0.001000 |
|
| 44 |
+
| Receptive field | 61 months |
|
| 45 |
+
|
| 46 |
+
## Test Set Performance
|
| 47 |
+
|
| 48 |
+
| Metric | Value |
|
| 49 |
+
|---|---|
|
| 50 |
+
| RMSE | 3.5771 m |
|
| 51 |
+
| MAE | 2.8901 m |
|
| 52 |
+
| MAPE | 4.3138% |
|
| 53 |
+
| R² | 0.334 |
|
| 54 |
+
| NSE | 0.334 |
|
| 55 |
+
|
| 56 |
+
> This model is part of a benchmark study comparing SARIMAX, LSTM, and TCN
|
| 57 |
+
> for UK groundwater level forecasting.
|
| 58 |
+
|
| 59 |
+
## Important Note
|
| 60 |
+
|
| 61 |
+
Contemporaneous meteorological variables are used as inputs at forecast time
|
| 62 |
+
(oracle assumption). Future met values are treated as known — consistent with
|
| 63 |
+
the experimental setup used across all models in this study.
|
| 64 |
+
|
| 65 |
+
## Repository Contents
|
| 66 |
+
```
|
| 67 |
+
├── tcn_model.keras # Trained Keras TCN model
|
| 68 |
+
├── scaler_features.pkl # Feature scaler (MinMaxScaler, fit on train only)
|
| 69 |
+
├── scaler_target.pkl # Target scaler (MinMaxScaler, for inverse transform)
|
| 70 |
+
├── model_config.json # Config, hyperparameters & metrics
|
| 71 |
+
├── inference.py # Load model & generate forecasts
|
| 72 |
+
└── README.md # This file
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
## Quick Start
|
| 76 |
+
```python
|
| 77 |
+
from huggingface_hub import hf_hub_download
|
| 78 |
+
from tensorflow.keras.models import load_model
|
| 79 |
+
import joblib, pandas as pd, numpy as np
|
| 80 |
+
|
| 81 |
+
model = load_model(hf_hub_download('kozy9/GWTCN', 'tcn_model.keras'))
|
| 82 |
+
scaler_features = joblib.load(hf_hub_download('kozy9/GWTCN', 'scaler_features.pkl'))
|
| 83 |
+
scaler_target = joblib.load(hf_hub_download('kozy9/GWTCN', 'scaler_target.pkl'))
|
| 84 |
+
|
| 85 |
+
# Provide a 24-month window of features
|
| 86 |
+
X_window = pd.DataFrame({
|
| 87 |
+
'water_level' : [...], # 24 values
|
| 88 |
+
'temperature' : [...],
|
| 89 |
+
'precipitation': [...],
|
| 90 |
+
'wind_speed' : [...],
|
| 91 |
+
})
|
| 92 |
+
|
| 93 |
+
X_scaled = scaler_features.transform(X_window)
|
| 94 |
+
X_input = X_scaled.reshape(1, 24, 4)
|
| 95 |
+
y_scaled = model.predict(X_input)
|
| 96 |
+
pred = scaler_target.inverse_transform(y_scaled)[0][0]
|
| 97 |
+
print(f'Next month forecast: {pred:.2f} m')
|
| 98 |
+
```
|
inference.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
inference.py — TCN Groundwater Level Forecasting
|
| 3 |
+
=================================================
|
| 4 |
+
Usage
|
| 5 |
+
-----
|
| 6 |
+
from inference import load_model, forecast
|
| 7 |
+
model, scaler_features, scaler_target = load_model()
|
| 8 |
+
prediction = forecast(model, scaler_features, scaler_target, X_window)
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import joblib
|
| 13 |
+
import numpy as np
|
| 14 |
+
import pandas as pd
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
from tensorflow.keras.models import load_model as keras_load
|
| 17 |
+
|
| 18 |
+
MODEL_PATH = Path(__file__).parent / "tcn_model.keras"
|
| 19 |
+
SCALER_FEATURES_PATH = Path(__file__).parent / "scaler_features.pkl"
|
| 20 |
+
SCALER_TARGET_PATH = Path(__file__).parent / "scaler_target.pkl"
|
| 21 |
+
CONFIG_PATH = Path(__file__).parent / "model_config.json"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def load_model():
|
| 25 |
+
"""Load the TCN model and both scalers."""
|
| 26 |
+
model = keras_load(MODEL_PATH)
|
| 27 |
+
scaler_features = joblib.load(SCALER_FEATURES_PATH)
|
| 28 |
+
scaler_target = joblib.load(SCALER_TARGET_PATH)
|
| 29 |
+
print("TCN model and scalers loaded.")
|
| 30 |
+
return model, scaler_features, scaler_target
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def load_config():
|
| 34 |
+
with open(CONFIG_PATH) as f:
|
| 35 |
+
return json.load(f)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def forecast(model, scaler_features, scaler_target, X_window: pd.DataFrame):
|
| 39 |
+
"""
|
| 40 |
+
Predict the next month's groundwater level.
|
| 41 |
+
|
| 42 |
+
Parameters
|
| 43 |
+
----------
|
| 44 |
+
model : loaded Keras TCN model
|
| 45 |
+
scaler_features : fitted MinMaxScaler for all 4 input features
|
| 46 |
+
scaler_target : fitted MinMaxScaler for water_level target
|
| 47 |
+
X_window : DataFrame with columns [water_level, temperature,
|
| 48 |
+
precipitation, wind_speed] and exactly 24 rows
|
| 49 |
+
(the lookback window)
|
| 50 |
+
|
| 51 |
+
Returns
|
| 52 |
+
-------
|
| 53 |
+
prediction : float — forecasted water level in original units (m)
|
| 54 |
+
"""
|
| 55 |
+
cfg = load_config()
|
| 56 |
+
required = cfg['features']
|
| 57 |
+
lookback = cfg['lookback_months']
|
| 58 |
+
|
| 59 |
+
missing = [c for c in required if c not in X_window.columns]
|
| 60 |
+
if missing:
|
| 61 |
+
raise ValueError(f"X_window is missing columns: {missing}")
|
| 62 |
+
if len(X_window) != lookback:
|
| 63 |
+
raise ValueError(f"X_window must have {lookback} rows, got {len(X_window)}")
|
| 64 |
+
|
| 65 |
+
X_scaled = scaler_features.transform(X_window[required])
|
| 66 |
+
X_input = X_scaled.reshape(1, lookback, len(required))
|
| 67 |
+
|
| 68 |
+
y_scaled = model.predict(X_input, verbose=0).flatten()
|
| 69 |
+
prediction = scaler_target.inverse_transform(y_scaled.reshape(-1, 1)).flatten()[0]
|
| 70 |
+
|
| 71 |
+
return float(prediction)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
model, scaler_features, scaler_target = load_model()
|
| 76 |
+
cfg = load_config()
|
| 77 |
+
print(f"Model: {cfg['architecture']}")
|
| 78 |
+
print(f"Test RMSE: {cfg['test_metrics']['RMSE']} m")
|
| 79 |
+
|
| 80 |
+
# Dummy window — replace with real data
|
| 81 |
+
dummy = pd.DataFrame({
|
| 82 |
+
'water_level' : np.random.uniform(60, 75, 24),
|
| 83 |
+
'temperature' : np.random.uniform(3, 15, 24),
|
| 84 |
+
'precipitation': np.random.uniform(20, 120, 24),
|
| 85 |
+
'wind_speed' : np.random.uniform(10, 25, 24),
|
| 86 |
+
})
|
| 87 |
+
pred = forecast(model, scaler_features, scaler_target, dummy)
|
| 88 |
+
print(f"\nForecast (next month): {pred:.4f} m")
|
model_config.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "TCN",
|
| 3 |
+
"architecture": "TCN(nb_filters=32, kernel_size=3, dilations=[1, 2, 4, 8]) \u2192 Dense(1)",
|
| 4 |
+
"framework": "TensorFlow/Keras (keras-tcn)",
|
| 5 |
+
"task": "Single-step monthly groundwater level forecasting",
|
| 6 |
+
"features": [
|
| 7 |
+
"water_level",
|
| 8 |
+
"temperature",
|
| 9 |
+
"precipitation",
|
| 10 |
+
"wind_speed"
|
| 11 |
+
],
|
| 12 |
+
"target": "water_level",
|
| 13 |
+
"lookback_months": 24,
|
| 14 |
+
"horizon_months": 1,
|
| 15 |
+
"tuning": {
|
| 16 |
+
"method": "Bayesian Optimisation (Keras Tuner)",
|
| 17 |
+
"n_trials": 20,
|
| 18 |
+
"best_config": {
|
| 19 |
+
"nb_filters": 32,
|
| 20 |
+
"kernel_size": 3,
|
| 21 |
+
"dilations": [
|
| 22 |
+
1,
|
| 23 |
+
2,
|
| 24 |
+
4,
|
| 25 |
+
8
|
| 26 |
+
],
|
| 27 |
+
"dropout_rate": 0.1,
|
| 28 |
+
"learning_rate": 0.001,
|
| 29 |
+
"receptive_field": 61
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"data_splits": {
|
| 33 |
+
"train": {
|
| 34 |
+
"start": "1944-01-01",
|
| 35 |
+
"end": "2007-10-01",
|
| 36 |
+
"n_months": 766
|
| 37 |
+
},
|
| 38 |
+
"validation": {
|
| 39 |
+
"start": "2007-11-01",
|
| 40 |
+
"end": "2015-10-01",
|
| 41 |
+
"n_months": 96
|
| 42 |
+
},
|
| 43 |
+
"test": {
|
| 44 |
+
"start": "2015-11-01",
|
| 45 |
+
"end": "2023-10-01",
|
| 46 |
+
"n_months": 96
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"test_metrics": {
|
| 50 |
+
"RMSE": 3.5771,
|
| 51 |
+
"MAE": 2.8901,
|
| 52 |
+
"MAPE_pct": 4.3138,
|
| 53 |
+
"R2": 0.334,
|
| 54 |
+
"NSE": 0.334
|
| 55 |
+
},
|
| 56 |
+
"notes": "Scaler fitted on train only. Oracle exog assumption \u2014 contemporaneous met vars used at forecast time."
|
| 57 |
+
}
|
scaler_features.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3c0d11dcc6e97c52246a4bc0a08b926e75edaa58a268400446e4eafdd81c199a
|
| 3 |
+
size 1127
|
scaler_target.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:865d8113514357d4b75338166ebba5771c914f7cd10b5892fc2187265ae4515c
|
| 3 |
+
size 975
|
tcn_baseline_training_history.png
ADDED
|
tcn_forecast_comparison.png
ADDED
|
Git LFS Details
|
tcn_model.keras
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f7b000cf26bedc98461a4d960aa999747d38e39fe1545ddcf093f0f88cd0f537
|
| 3 |
+
size 387592
|
tcn_residual_analysis.png
ADDED
|
tcn_tuned_training_history.png
ADDED
|