HydroTuner: Usage Examples¶
This page provides practical, end-to-end examples for using the
HydroTuner to find optimal
hyperparameters for the library’s hydrogeological PINN models.
The core workflow involves three main steps: 1. Preparing your data as NumPy arrays. 2. Defining a search_space dictionary that specifies which
hyperparameters to tune.
Using the
HydroTuner.create()factory method to instantiate and run the tuner.
We will start with a comprehensive workflow for the fully-coupled
TransFlowSubsNet model.
Example 1: A Comprehensive Workflow for Tuning TransFlowSubsNet¶
In this example, we’ll configure the HydroTuner to find the best
hyperparameters for a TransFlowSubsNet model. Our search space will
be comprehensive, including architectural parameters for the data-driven
core, physical parameters for the PINN module, and optimization
parameters for the training process.
Step 1: Imports and Data Generation¶
First, we set up our environment and generate a synthetic dataset. This
dataset mimics a real-world scenario by including all the data types
that TransFlowSubsNet is designed to handle.
1import os
2import numpy as np
3import tensorflow as tf
4
5# FusionLab imports
6from fusionlab.nn.forecast_tuner import HydroTuner
7from fusionlab.nn.pinn.models import TransFlowSubsNet
8
9# --- Configuration ---
10N_SAMPLES, T_PAST, HORIZON = 500, 15, 7
11S_DIM, D_DIM, F_DIM = 4, 6, 3
12RUN_DIR = "./hydrotuner_examples"
13
14# --- Generate Dummy Data as NumPy Arrays ---
15print("Generating synthetic data for tuning...")
16inputs = {
17 # Coords (t,x,y) for the physics loss calculation
18 "coords": np.random.rand(N_SAMPLES, HORIZON, 3).astype(np.float32),
19 # Time-invariant features
20 "static_features": np.random.rand(N_SAMPLES, S_DIM).astype(np.float32),
21 # Historical time-varying features
22 "dynamic_features": np.random.rand(N_SAMPLES, T_PAST, D_DIM).astype(np.float32),
23 # Known future time-varying features
24 "future_features": np.random.rand(N_SAMPLES, HORIZON, F_DIM).astype(np.float32),
25}
26targets = {
27 # The two target variables to forecast
28 "subsidence": np.random.rand(N_SAMPLES, HORIZON, 1).astype(np.float32),
29 "gwl": np.random.rand(N_SAMPLES, HORIZON, 1).astype(np.float32)
30}
31print(f"Generated {N_SAMPLES} data samples.")
Step 2: Define the Hyperparameter Search Space¶
This dictionary is the core of our tuning experiment. It tells the
HydroTuner exactly which parameters to optimize and what values or
ranges to explore for each one. We define three types of hyperparameters:
- Architectural HPs: Control the size and capacity of the
data-driven
BaseAttentivecore.
- Physics HPs: Control the physical coefficients in the PDEs,
allowing the tuner to test fixed vs. learnable assumptions.
- Compile-time HPs: Control the optimization process, including
the learning rate and the weights of the physics loss terms.
1transflow_search_space = {
2 # --- Architectural Hyperparameters ---
3 "embed_dim": [32, 64],
4 "attention_units": [32, 64],
5 "num_heads": [2, 4],
6 "dropout_rate": {"type": "float", "min_value": 0.05, "max_value": 0.3},
7
8 # --- Physics Hyperparameters for TransFlowSubsNet ---
9 # Test if making K learnable is better than two fixed values
10 "K": ["learnable", 1e-5, 1e-4],
11 # Search for the best Ss value in a log space
12 "Ss": {"type": "float", "min_value": 1e-6, "max_value": 1e-4, "sampling": "log"},
13 # For this experiment, we'll fix C as learnable
14 "pinn_coefficient_C": ["learnable"],
15
16 # --- Compile-time Hyperparameters ---
17 "learning_rate": {"type": "choice", "values": [1e-3, 5e-4, 1e-4]},
18 # Search for the best weights for the two physics losses
19 "lambda_gw": {"type": "float", "min_value": 0.5, "max_value": 1.5},
20 "lambda_cons": {"type": "float", "min_value": 0.1, "max_value": 1.0}
21}
22print("Hyperparameter search space for TransFlowSubsNet defined.")
Step 3: Create and Run the Tuner¶
We use the HydroTuner.create() factory method. This is the
recommended approach as it simplifies setup by automatically inspecting
our data to determine the fixed parameters (like input/output
dimensions). We then call the high-level .run() method, which handles
all the underlying data conversion and launches the Keras Tuner search.
1# 1. Create the tuner instance using the factory method
2tuner = HydroTuner.create(
3 model_name_or_cls=TransFlowSubsNet,
4 inputs_data=inputs,
5 targets_data=targets,
6 search_space=transflow_search_space,
7 # Keras Tuner configuration
8 max_trials=5, # Keep low for this example; use 30-50 for real tasks
9 project_name="TransFlowSubsNet_Comprehensive_Tuning",
10 directory=RUN_DIR,
11 overwrite=True,
12 objective="val_loss" # Monitor the total validation loss
13)
14
15# 2. Run the search process
16print("\nStarting hyperparameter search for TransFlowSubsNet...")
17best_model, best_hps, tuner_instance = tuner.run(
18 inputs=inputs,
19 y=targets,
20 validation_data=(inputs, targets), # Use same data for example
21 epochs=5, # Train each trial for a few epochs
22 batch_size=64,
23 callbacks=[tf.keras.callbacks.EarlyStopping('val_loss', patience=3)]
24)
Step 4: Analyze Results and Use the Best Model¶
After the search is complete, the tuner object provides access to the best hyperparameters and the best model, which has been automatically retrained on the full dataset for you.
1print("\n--- Hyperparameter Search Complete ---")
2
3if best_hps:
4 print("\nBest Hyperparameters Found:")
5 for hp, value in best_hps.values.items():
6 # Format floats for readability
7 if isinstance(value, float):
8 print(f" - {hp}: {value:.5f}")
9 else:
10 print(f" - {hp}: {value}")
11
12 # The best_model is ready for prediction or saving
13 # best_model.save(os.path.join(RUN_DIR, "best_transflow_model.keras"))
14 # print("\nBest model saved.")
15else:
16 print("Search finished, but no best hyperparameters were found.")
Expected Output:
Starting hyperparameter search for TransFlowSubsNet...
Trial 5 Complete [00h 00m 45s]
val_loss: 0.168...
Results summary
[...]
--- Hyperparameter Search Complete ---
Best Hyperparameters Found:
- embed_dim: 32
- attention_units: 64
- num_heads: 4
- dropout_rate: 0.17581
- K: learnable
- Ss: 0.00003
- pinn_coefficient_C: learnable
- learning_rate: 0.00100
- lambda_gw: 1.25678
- lambda_cons: 0.45901
Example 2: Tuning PIHALNet¶
This example showcases the power and flexibility of the HydroTuner. We will
now tune the PIHALNet model, which has a different set of physical
parameters than TransFlowSubsNet.
The core workflow remains identical. We will reuse the same dataset from
Example 1, but we will define a new search_space tailored
specifically to the hyperparameters available in PIHALNet.
Step 1: Define a PIHALNet-Specific Search Space¶
The key to adapting the tuner is creating a search_space that matches
the target model. For PIHALNet, we are interested in tuning its
unique consolidation coefficient (\(C\)) and its single physics loss
weight (\(\lambda_{physics}\)).
Note that we omit the hyperparameters for groundwater flow (K, Ss, lambda_gw), as they are not relevant to the PIHALNet model.
1pihalnet_search_space = {
2 # --- Architectural Hyperparameters (can be different) ---
3 # For this run, let's fix the embedding dimension and explore others.
4 "embed_dim": [32],
5 "num_heads": [4, 8],
6 "dropout_rate": {"type": "float", "min_value": 0.1, "max_value": 0.5},
7
8 # --- Physics Hyperparameters for PIHALNet ---
9 # Test a few different fixed values for the consolidation coefficient C
10 "pinn_coefficient_C": [1e-3, 5e-3, 1e-2],
11
12 # --- Compile-time Hyperparameters ---
13 "learning_rate": [1e-3, 5e-4],
14 # PIHALNet uses a single physics weight, which we name `lambda_physics`
15 # Note: The tuner's `build` method will correctly pass this to the
16 # model's `compile` method, which may expect a different name
17 # like `lambda_pde`. This mapping is handled internally.
18 "lambda_physics": {"type": "float", "min_value": 0.1, "max_value": 1.0}
19}
20print("Defined a new search space tailored for PIHALNet.")
Step 2: Create and Run the Tuner for PIHALNet¶
The process is exactly the same as before. The only changes are in the
arguments passed to HydroTuner.create(): we now specify
model_name_or_cls=PIHALNet and pass our new
pihalnet_search_space.
1from fusionlab.nn.pinn.models import PIHALNet
2from tensorflow.keras.callbacks import EarlyStopping
3
4# 1. Create the tuner instance for PIHALNet
5tuner_pihal = HydroTuner.create(
6 model_name_or_cls=PIHALNet, # <-- The primary change
7 inputs_data=inputs,
8 targets_data=targets,
9 search_space=pihalnet_search_space, # <-- Use the new search space
10 max_trials=3,
11 project_name="PIHALNet_Example_Tuning",
12 directory=RUN_DIR,
13 overwrite=True
14)
15
16# 2. Run the search
17print("\nStarting tuning for PIHALNet...")
18best_model_pihal, best_hps_pihal, _ = tuner_pihal.run(
19 inputs=inputs,
20 y=targets,
21 validation_data=(inputs, targets),
22 epochs=3,
23 batch_size=64,
24 callbacks=[EarlyStopping('val_loss', patience=2)]
25)
Step 3: Analyze the PIHALNet Results¶
Finally, we inspect the output to see the best combination of
hyperparameters that the tuner discovered for the PIHALNet model.
1print("\n--- Best Hyperparameters for PIHALNet ---")
2if best_hps_pihal:
3 for hp, value in best_hps_pihal.values.items():
4 if isinstance(value, float):
5 print(f" - {hp}: {value:.5f}")
6 else:
7 print(f" - {hp}: {value}")
8else:
9 print("Search finished, but no best hyperparameters were found.")
Expected Output:
--- Best Hyperparameters for PIHALNet ---
- embed_dim: 32
- num_heads: 4
- dropout_rate: 0.25123
- pinn_coefficient_C: 0.00500
- learning_rate: 0.00100
- lambda_physics: 0.67890
These examples illustrate the power of the HydroTuner’s generic
design. The core workflow remains consistent, and you can easily adapt
it to different models by providing the appropriate model class and
defining a relevant search_space. This dramatically accelerates the
process of experimentation and optimization.