LSTM networks module
LSTM networks are complex Deep Learning models that are extremely powerful at detecting patterns from various time series, and especially good at finding relationships between various timeseries. This is perfect in a hydrological modelling context, as these models can learn patterns between meteorological variables (such as precipitation, temperature and wind speed timeseries) and streamflow. Furthermore, when we also add catchment descriptors, LSTM models can learn hydrograph dynamics according to catchment attributes such as the area, slope, land-use and other such descriptors.
This page demonstrates how to use xhydro to perform simple LSTM modelling on local (one) and regional (multiple) catchments. However, LSTM models are infinitely flexible and it would be a monumental task to expose all possible hyperparameters and modelling decisions through an interface. Therefore, this package can be seen as a starting point to develop custom models on custom data. Eventually, users will want to add codes, models and methods to the package. Official models from research
projects could be added, and any model used to generate operational datasets could be implemented as well. For the time being, we will use extremely simple models to show how the code works, and we can then let users explore and add functionalities as needed.
[1]:
import os
import pooch
import tensorflow.keras.backend as K
import xarray as xr
from xhydro_lstm.lstm_controller import (
control_local_lstm_training,
control_regional_lstm_training,
)
2024-07-03 14:26:44.776201: I tensorflow/core/util/port.cc:113] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
2024-07-03 14:26:44.801639: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 AVX512F AVX512_VNNI AVX512_BF16 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2024-07-03 14:26:45.225103: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
Example for application on a single catchment
For this example, we will explore some existing data for a single catchment and see how we can use it for LSTM modelling. We will first get the data from the xhydro-testdata test data repository, and we will then process the data while explaining what is going on in the backend. This package is optimized for use with .nc data, and more precisely xarray.Dataset formats.
[2]:
# Get data on the xhydro-testdata repository
input_data_filename_local = pooch.retrieve(
url="https://github.com/hydrologie/xhydro-testdata/raw/main/data/LSTM_data/single_watershed.nc",
known_hash="md5:b1dfe4641321f63fb9198e9538fd679b",
)
ds_local = xr.open_dataset(input_data_filename_local)
display(ds_local)
<xarray.Dataset> Size: 1MB
Dimensions: (time: 12419, watershed: 1)
Coordinates:
* time (time) datetime64[ns] 99kB 1979-01-01 ... 2012-12-31
* watershed (watershed) <U7 28B '01BG005'
Data variables: (12/19)
t2m (time, watershed) float64 99kB ...
tp (time, watershed) float64 99kB ...
sd (time, watershed) float64 99kB ...
sp (time, watershed) float64 99kB ...
d2m (time, watershed) float64 99kB ...
tcc (time, watershed) float64 99kB ...
... ...
latitude (watershed) float64 8B ...
forest (watershed) float64 8B ...
crops (watershed) float64 8B ...
shrubs (watershed) float64 8B ...
elevation (watershed) float64 8B ...
slope (watershed) float64 8B ...We can see that there are two types of variables:variables that have a “time” dimension (i.e. timeseries), and variables that do not:
Those that have a “time” dimension are considered “dynamic features” as they evolve in time (such as weather data).
The others that do not have a “time” dimension are deemed “static features” as they describe the catchment in a fixed manner and do not evolve in time (such as catchment area and soil properties).
The dynamic features are instrumental in establishing relationships between observed weather and streamflow. However, static features are only used when building and training regional models such that the LSTM model can modulate flows and hydrographs as a function of catchment attributes. For a single catchment, these attributes are constant and the relationship between meteorology and hydrology do not require modulating according to catchment properties.
IMPORTANT
This file MUST contain the observed streamflow (Target variable) under the name
qobs.This file MUST contain the drainage area of the catchment under the name
drainage_area.
For completeness, let’s take a look at the regional (i.e. multi-basin) dataset that we will use later:
[3]:
# Get data from the xhydro-testdata repository
input_data_filename_regional = pooch.retrieve(
url="https://github.com/hydrologie/xhydro-testdata/raw/main/data/LSTM_data/multiple_watersheds.nc",
known_hash="md5:31e1ae3ffcfd14d6a92faefd3d8bd57a",
)
ds_regional = xr.open_dataset(input_data_filename_regional)
display(ds_regional)
<xarray.Dataset> Size: 3MB
Dimensions: (time: 12419, watershed: 3)
Coordinates:
* time (time) datetime64[ns] 99kB 1979-01-01 ... 2012-12-31
* watershed (watershed) <U7 84B '01BG005' '01BG009' '02QC009'
Data variables: (12/19)
t2m (time, watershed) float64 298kB ...
tp (time, watershed) float64 298kB ...
sd (time, watershed) float64 298kB ...
sp (time, watershed) float64 298kB ...
d2m (time, watershed) float64 298kB ...
tcc (time, watershed) float64 298kB ...
... ...
latitude (watershed) float64 24B ...
forest (watershed) float64 24B ...
crops (watershed) float64 24B ...
shrubs (watershed) float64 24B ...
elevation (watershed) float64 24B ...
slope (watershed) float64 24B ...We can see that in this case, we still have some variables with (and without) the “time” dimension. However, we can see that there are other variables with the “watershed” dimension. This refers to the watershed from within the regional model.
The LSTM network package provided in xhydro uses a controller function to manage data inputs and formats. These will be described below, but it is important to note that users should consider developing their own functions to process data in specific ways according to their needs. Let’s explore the LSTM controller function. Note that there are two functions:
One for the local catchment modelling (control_local_lstm_training)
One for the regional catchment modelling (control_regional_lstm_training)
We first start with the local model. There are a series of inputs to provide (training data, some hyperparameters, general controls), some of which are optional. Here is a short summary of each input:
Parameters / inputs
input_data_filename [str] Path to the netcdf file containing the required input and target data for the LSTM. The ncfile must contain a dataset named “qobs” and “drainage_area” for the code to work, as these are required as target and scaling for training, respectively.
dynamic_var_tags [list of str] List of dataset variables to use in the LSTM model training. Must be part of the input_data_filename dataset.
qsim_pos [list of bool] List of same length as dynamic_var_tags. Should be set to all False EXCEPT where the dynamic_var_tags refer to flow simulations (ex: simulations from a hydrological model). Those should be set to True, because we will scale them according to the catchment size.
batch_size [int, Optional] Number of data points to use in training. Datasets are often way too big to train in a single batch on a single GPU or CPU, meaning that the dataset must be divided into smaller batches. This has an impact on the training performance and final model skill, and should be handled accordingly. Defaults to
32if unspecified by the user.epochs [int, Optional] Number of training evaluations. Larger number of epochs means more model iterations and deeper training. At some point, training will stop due to a stop in validation skill improvement. Defaults to
200if unspecified by the user.window_size [int, Optional] Number of days of look-back for training and model simulation. LSTM requires a large backwards-looking window to allow the model to learn from long-term weather patterns and history to predict the next day’s streamflow. Usually set to 365 days to get one year of previous data. This makes the model heavier and longer to train but can improve results. Defaults to
365if unspecified by the user.train_pct [int, Optional] Percentage of days from the dataset to use as training. The higher, the better for model training skill, but it is important to keep a decent amount for validation and testing. Defaults to
60if unspecified by the user.valid_pct [int, Optional] Percentage of days from the dataset to use as validation. The sum of train_pct and valid_pct needs to be less than 100, such that the remainder can be used for testing. A good starting value is 20%. Validation is used as the stopping criteria during training. When validation stops improving, then the model is overfitting and training is stopped. Defaults to
20if unspecified by the user.use_cpu [bool, Optional] Flag to force the training and simulations to be performed on the CPU rather than on the GPU(s). Must be performed on a CPU that has AVX and AVX2 instruction sets, or tensorflow will fail. CPU training is very slow and should only be used as a last resort (such as for CI testing and debugging). Defaults to
Trueif unspecified by the user, to maximize compatibility.use_parallel [bool, Optional] Flag to make use of multiple GPUs to accelerate training further. Models trained on multiple GPUs can have larger batch_size values as different batches can be run on different GPUs in parallel. Speedup is not linear as there is overhead related to the management of datasets, batches, the gradient merging and other steps. Still very useful and should be used when possible. Defaults to
Falseif unspecified by the user.do_train [bool, Optional] Indicate that the code should perform the training step. This is not required as a pre-trained model could be used to perform a simulation by passing an existing model in “name_of_saved_model”. Defaults to
Trueif unspecified by the user.model_structure [str, Optional] This is where we can define which LSTM model structure we desire from within the LSTM_static.py function, which contains all the available model structures. Users that want to contribute new model structures should do so here. By default, we use dummy models, but we can recommend some simple starter models such as
["simple_local_lstm", "simple_regional_lstm"]depending on if the model is local or regional.do_simulation [bool, Optional] Indicate that simulations should be performed to obtain simulated streamflow and KGE metrics on the watersheds of interest, using the “name_of_saved_model” pre-trained model. If set to True and ‘do_train’ is True, then the new trained model will be used instead. Defaults to
Trueif unspecified by the user.training_func [str, Optional] Name of the objective function used for training. For a regional model, it is highly recommended to use the scaled nse_loss variable that uses the standard deviation of streamflow as inputs. For a local model, the
kgefunction is preferred. Defaults tokgeif unspecified by the user. Can be one of["kge", "nse_scaled"].filename_base [str] Name of the trained model that will be trained if it does not already exist. Do not add the “.h5” extension, it will be added automatically. Defaults to
LSTM_resultsif unspecified by the user.simulation_phases [list of str] List of periods to generate the simulations. Can contain
['train','valid','test','full'], corresponding to the training, validation, testing and complete periods, respectively.name_of_saved_model [str] Path to the model that has been pre-trained if required for simulations.
Returns
kge_results [array-like] Kling-Gupta Efficiency metric values for each of the watersheds in the input_data_filename ncfile after running in simulation mode (thus after training). Contains n_watersheds items, each containing 4 values representing the KGE values in training, validation, testing and full period, respectively. If one or more simulation phases are not requested, the items will be set to None.
flow_results [array-like] Streamflow simulation values for each of the watersheds in the input_data_filename ncfile after running in simulation mode (thus after training). Contains n_watersheds items, each containing 4x 2D-arrays representing the observed and simulation series in training, validation, testing and full period, respectively. If one or more simulation phases are not requested, the items will be set to None.
name_of_saved_model [str] Path to the model that has been trained, or to the pre-trained model if it already existed.
We will now define these inputs for use in the local LSTM model.
[4]:
# Define some of the basic required inputs.
# The path to the input data file containing flow, weather data (dynamic features) and catchment descriptors
# (static features).
input_data_filename = input_data_filename_local
# Names of the dynamic features to be used to train the data, from the input_data_filename dataset. We are only
# using two here to keep things simple and to match the LSTM model complexity.
dynamic_var_tags = ["t2m", "tp"]
# Scale variable according to area. Used for simulated flow inputs. Ensure the same order as dynamic_var_tags.
qsim_pos = [False, False]
# Perform the training of the model. We will select "True" for this first step.
do_train = True
# Define the model structure. We do not want to use the dummy model setup here, so let's use a more appropriate model
model_structure = "simple_local_lstm"
# Also perform a simulation after the model is trained. We will select "True" to simulate flows on the periods
# identified in the "simulation_phases" parameter.
do_simulation = True
# The phases on which we want to perform the simulations. Can be a list containing any combination of
# ['train','valid','test','full'].
simulation_phases = ["test"]
# Optional, and only needed if we intend to do a simulation using a pre-trained LSTM model. We can then provide
# the name of the trained model to use for simulation.
name_of_saved_model = None
# batch size used in the training - multiple of 32 is ideal for efficiency but not critical.
batch_size = 256
# Number of epoch to train the LSTM model. Keeping this very short for this example and the model will not converge.
epochs = 5
# Number of time steps (days) to use in the LSTM model
window_size = 180
# Percentage of days to use from the training data for training the LSTM model.
train_pct = 70
# Percentage of days to use from the training data for training the LSTM model.
valid_pct = 15
# Use CPU as GPU is not guaranteed to be installed with CUDA/CuDNN etc. Much slower, but more compatible.
use_cpu = True
# Use multiple GPUs in parallel if available. Highly suggested for larger models, but useless if using CPU.
use_parallel = False
# The objective function to use for training. Since we are using a local model, use the KGE directly
training_func = "kge"
# The name of the output files to be generated after simulations
filename_base = "./LSTM_results_local_"
We can now run the model and get results:
WARNING: There is a high likelihood that the model will not converge and return nan values for the loss and val_loss metrics. If this happens, the model with restart, and do so until the model converges (or run indefinitely). When it does converge, there is a good chance that the results will be poor. This is because this test example has too few data to properly converge, to keep these examples small and easy to run.
[5]:
K.clear_session()
KGE_results, flow_results, name_of_saved_model = control_local_lstm_training(
input_data_filename=input_data_filename,
dynamic_var_tags=dynamic_var_tags,
qsim_pos=qsim_pos,
batch_size=batch_size,
epochs=epochs,
window_size=window_size,
train_pct=train_pct,
valid_pct=valid_pct,
use_cpu=use_cpu,
use_parallel=use_parallel,
do_train=do_train,
model_structure=model_structure,
do_simulation=do_simulation,
training_func=training_func,
filename_base=filename_base,
simulation_phases=simulation_phases,
name_of_saved_model=name_of_saved_model,
)
USING CPU
Epoch 1/5
2024-07-03 14:26:46.560537: E external/local_xla/xla/stream_executor/cuda/cuda_driver.cc:282] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected
/home/richard/miniconda3/envs/xhydrolstm/lib/python3.11/site-packages/keras/src/layers/activations/leaky_relu.py:41: UserWarning: Argument `alpha` is deprecated. Use `negative_slope` instead.
warnings.warn(
/home/richard/miniconda3/envs/xhydrolstm/lib/python3.11/site-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
self._warn_if_super_not_called()
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 111ms/step - loss: nan
Epoch 1: val_loss did not improve from inf
33/33 ━━━━━━━━━━━━━━━━━━━━ 6s 125ms/step - loss: nan - val_loss: nan - learning_rate: 5.0000e-04
Epoch 2/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 108ms/step - loss: nan
Epoch 2: val_loss did not improve from inf
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 116ms/step - loss: nan - val_loss: nan - learning_rate: 5.0000e-04
Epoch 3/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 105ms/step - loss: nan
Epoch 3: val_loss did not improve from inf
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 114ms/step - loss: nan - val_loss: nan - learning_rate: 5.0000e-04
Epoch 4/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 106ms/step - loss: nan
Epoch 4: val_loss did not improve from inf
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 115ms/step - loss: nan - val_loss: nan - learning_rate: 5.0000e-04
Epoch 5/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 107ms/step - loss: nan
Epoch 5: val_loss did not improve from inf
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 116ms/step - loss: nan - val_loss: nan - learning_rate: 5.0000e-04
USING CPU
Epoch 1/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 112ms/step - loss: 1.3023
Epoch 1: val_loss improved from inf to 1.10500, saving model to /tmp/tmpi9opkmt_/LSTM_results_local_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 6s 128ms/step - loss: 1.3011 - val_loss: 1.1050 - learning_rate: 5.0000e-04
Epoch 2/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 111ms/step - loss: 1.0814
Epoch 2: val_loss improved from 1.10500 to 1.03765, saving model to /tmp/tmpi9opkmt_/LSTM_results_local_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 120ms/step - loss: 1.0807 - val_loss: 1.0376 - learning_rate: 5.0000e-04
Epoch 3/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 108ms/step - loss: 1.0267
Epoch 3: val_loss improved from 1.03765 to 1.02908, saving model to /tmp/tmpi9opkmt_/LSTM_results_local_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 117ms/step - loss: 1.0266 - val_loss: 1.0291 - learning_rate: 5.0000e-04
Epoch 4/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 112ms/step - loss: 1.0166
Epoch 4: val_loss improved from 1.02908 to 1.02367, saving model to /tmp/tmpi9opkmt_/LSTM_results_local_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 121ms/step - loss: 1.0166 - val_loss: 1.0237 - learning_rate: 5.0000e-04
Epoch 5/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 105ms/step - loss: 1.0150
Epoch 5: val_loss improved from 1.02367 to 1.02104, saving model to /tmp/tmpi9opkmt_/LSTM_results_local_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 4s 114ms/step - loss: 1.0150 - val_loss: 1.0210 - learning_rate: 5.0000e-04
7/7 ━━━━━━━━━━━━━━━━━━━━ 0s 55ms/step
We can now explore the results. A quick reminder: The KGE_results and flow_results are lists of size 4, reflecting results for the training, validation, testing and full periods, respectively. Since we only asked for the “test” simulation to be performed, we will only receive results in the 3rd element in the list:
[6]:
# Show the KGE results
print("The KGE results are:")
display(KGE_results)
print("\nThe simulated flows are:")
display(flow_results)
print("\nThe trained model has been saved at the following location:")
display(name_of_saved_model)
The KGE results are:
[None, None, -0.0144405131655152, None]
The simulated flows are:
[None,
None,
array([[218. , 191. , 180. , ..., 12.1 ,
12.6 , 12.7 ],
[ 0.34784153, 0.34113994, 0.3369055 , ..., 0. ,
0. , 0. ]], dtype=float32),
None]
The trained model has been saved at the following location:
'/tmp/tmpi9opkmt_/LSTM_results_local_.keras'
Now that the model has been trained, we can use the controller to perform a simulation by changing just a few elements to the inputs:
[7]:
# Define the variables that need to be changed:
# Perform the training of the model. We will select "False" now that we have a saved trained model
do_train = False
# The phases on which we want to perform the simulations. Can be a list containing any combination of
# ['train','valid','test','full'].
simulation_phases = ["train", "valid", "test", "full"]
# The name of the saved model from the previous training.
name_of_saved_model = name_of_saved_model
# And run the model
KGE_results_sim, flow_results_sim, _ = control_local_lstm_training(
input_data_filename=input_data_filename,
dynamic_var_tags=dynamic_var_tags,
qsim_pos=qsim_pos,
batch_size=batch_size,
epochs=epochs,
window_size=window_size,
train_pct=train_pct,
valid_pct=valid_pct,
use_cpu=use_cpu,
use_parallel=use_parallel,
do_train=do_train,
model_structure=model_structure,
do_simulation=do_simulation,
training_func=training_func,
filename_base=filename_base,
simulation_phases=simulation_phases,
name_of_saved_model=name_of_saved_model,
)
print("The KGE results are:")
display(KGE_results_sim)
print("\nThe simulated flows are:")
display(flow_results_sim)
1/33 ━━━━━━━━━━━━━━━━━━━━ 6s 205ms/step
/home/richard/miniconda3/envs/xhydrolstm/lib/python3.11/site-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
self._warn_if_super_not_called()
33/33 ━━━━━━━━━━━━━━━━━━━━ 2s 44ms/step
8/8 ━━━━━━━━━━━━━━━━━━━━ 1s 53ms/step
WARNING:tensorflow:5 out of the last 42 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x7fde48c5fce0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
7/7 ━━━━━━━━━━━━━━━━━━━━ 1s 57ms/step
WARNING:tensorflow:5 out of the last 16 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x7fde48c5d8a0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
48/48 ━━━━━━━━━━━━━━━━━━━━ 2s 44ms/step
The KGE results are:
[-0.009339690893735542,
-0.017701832438825615,
-0.0144405131655152,
-0.011388125559591344]
The simulated flows are:
[array([[2.7400000e+01, 2.6600000e+01, 2.5500000e+01, ..., 1.0600000e+02,
1.2000000e+02, 1.0600000e+02],
[5.3376455e-02, 5.4626308e-02, 5.4637779e-02, ..., 3.1778562e-01,
2.7539933e-01, 2.3393394e-01]], dtype=float32),
array([[12.6 , 12.3 , 12. , ..., 57.3 ,
51.7 , 47.3 ],
[ 0. , 0. , 0. , ..., 0.15638022,
0.14065447, 0.11017577]], dtype=float32),
array([[218. , 191. , 180. , ..., 12.1 ,
12.6 , 12.7 ],
[ 0.34784153, 0.34113994, 0.3369055 , ..., 0. ,
0. , 0. ]], dtype=float32),
array([[27.4 , 26.6 , 25.5 , ..., 12.6 ,
12.7 , 12.3 ],
[ 0.05337645, 0.05462631, 0.05463778, ..., 0. ,
0. , 0. ]], dtype=float32)]
Regional model
We can now apply the exact same concept to the regional model dataset that contained multiple watersheds. We will first define the input parameters just like for the local model:
[8]:
# Define some of the basic required inputs.
# The path to the input data file containing flow, weather data (dynamic features) and catchment descriptors
# (static features).
input_data_filename = input_data_filename_regional
# Names of the dynamic features to be used to train the data, from the input_data_filename dataset. We are only
# using two here to keep things simple and to match the LSTM model complexity.
dynamic_var_tags = ["t2m", "tp"]
# Scale variable according to area. Used for simulated flow inputs. Ensure the same order as dynamic_var_tags.
qsim_pos = [False, False]
# Names of the static features to be used from the input dataset. These are used to regionalize flows between catchments
# based on their attributes. Here we only use 3 to keep things simple. They will not be useful as our dataset only
# contains 3 watersheds as well, so there is not enough data to learn the relationships correctly.
static_var_tags = ["drainage_area", "forest", "slope"]
# Perform the training of the model. We will select "True" for this first step.
do_train = True
# Define the model structure. We do not want to use the dummy model setup here, so let's use a more appropriate model
model_structure = "simple_regional_lstm"
# Also perform a simulation after the model is trained. We will select "True" to simulate flows on the periods
# identified in the "simulation_phases" parameter.
do_simulation = True
# The phases on which we want to perform the simulations. Can be a list containing any combination of
# ['train','valid','test','full'].
simulation_phases = ["test"]
# Optional, and only needed if we intend to do a simulation using a pre-trained LSTM model. We can then provide
# the name of the trained model to use for simulation.
name_of_saved_model = None
# batch size used in the training - multiple of 32 is ideal for efficiency but not critical.
batch_size = 256
# Number of epoch to train the LSTM model. Keeping this very short for this example and the model will not converge.
epochs = 5
# Number of time steps (days) to use in the LSTM model
window_size = 180
# Percentage of days to use from the training data for training the LSTM model.
train_pct = 70
# Percentage of days to use from the training data for training the LSTM model.
valid_pct = 15
# Use CPU as GPU is not guaranteed to be installed with CUDA/CuDNN etc. Much slower, but more compatible.
use_cpu = True
# Use multiple GPUs in parallel if available. Highly suggested for larger models, but useless if using CPU.
use_parallel = False
# The objective function to use for training. Since we are using a local model, use the KGE directly
training_func = "nse_scaled"
# The name of the output files to be generated after simulations
filename_base = "./LSTM_results_regional_"
And now we run the model for training:
[9]:
K.clear_session()
KGE_results, flow_results, name_of_saved_model = control_regional_lstm_training(
input_data_filename=input_data_filename,
dynamic_var_tags=dynamic_var_tags,
qsim_pos=qsim_pos,
static_var_tags=static_var_tags,
batch_size=batch_size,
epochs=epochs,
window_size=window_size,
train_pct=train_pct,
valid_pct=valid_pct,
use_cpu=use_cpu,
use_parallel=use_parallel,
do_train=do_train,
model_structure=model_structure,
do_simulation=do_simulation,
training_func=training_func,
filename_base=filename_base,
simulation_phases=simulation_phases,
name_of_saved_model=name_of_saved_model,
)
Currently working on watershed no: 0
Currently working on watershed no: 0
USING CPU
Epoch 1/5
/home/richard/miniconda3/envs/xhydrolstm/lib/python3.11/site-packages/keras/src/layers/activations/leaky_relu.py:41: UserWarning: Argument `alpha` is deprecated. Use `negative_slope` instead.
warnings.warn(
/home/richard/miniconda3/envs/xhydrolstm/lib/python3.11/site-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
self._warn_if_super_not_called()
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 407ms/step - loss: 1.6288
Epoch 1: val_loss improved from inf to 0.77263, saving model to /tmp/tmp855g032j/LSTM_results_regional_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 16s 447ms/step - loss: 1.6132 - val_loss: 0.7726 - learning_rate: 5.0000e-04
Epoch 2/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 400ms/step - loss: 0.7536
Epoch 2: val_loss improved from 0.77263 to 0.49094, saving model to /tmp/tmp855g032j/LSTM_results_regional_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 14s 435ms/step - loss: 0.7507 - val_loss: 0.4909 - learning_rate: 5.0000e-04
Epoch 3/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 399ms/step - loss: 0.3557
Epoch 3: val_loss improved from 0.49094 to 0.41243, saving model to /tmp/tmp855g032j/LSTM_results_regional_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 14s 434ms/step - loss: 0.3551 - val_loss: 0.4124 - learning_rate: 5.0000e-04
Epoch 4/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 397ms/step - loss: 0.2678
Epoch 4: val_loss improved from 0.41243 to 0.36246, saving model to /tmp/tmp855g032j/LSTM_results_regional_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 14s 432ms/step - loss: 0.2681 - val_loss: 0.3625 - learning_rate: 5.0000e-04
Epoch 5/5
33/33 ━━━━━━━━━━━━━━━━━━━━ 0s 397ms/step - loss: 0.2566
Epoch 5: val_loss improved from 0.36246 to 0.34656, saving model to /tmp/tmp855g032j/LSTM_results_regional_.keras
33/33 ━━━━━━━━━━━━━━━━━━━━ 14s 432ms/step - loss: 0.2564 - val_loss: 0.3466 - learning_rate: 5.0000e-04
Currently working on watershed no: 0
7/7 ━━━━━━━━━━━━━━━━━━━━ 1s 176ms/step
Similarly to the previous model, we can now get the overall results
[10]:
# Show the KGE results
print("The KGE results are:")
display(KGE_results)
print("\nThe simulated flows are:")
display(flow_results)
print("\nThe trained model has been saved at the following location:")
display(name_of_saved_model)
The KGE results are:
[[None, None, 0.7028731164011819, None]]
The simulated flows are:
[[None,
None,
array([[217.99998446, 190.99999739, 179.9999865 , ..., 12.09999971,
12.6000001 , 12.6999995 ],
[168.16178894, 163.36073303, 157.63221741, ..., 15.97389889,
18.29801369, 18.19104004]]),
None]]
The trained model has been saved at the following location:
'/tmp/tmp855g032j/LSTM_results_regional_.keras'