Building Models Examples
EasyHybrid.jl allows constructing diverse modeling architectures using the unified HybridModel struct. Previously, users defined bespoke structs (like LinearHM, RespirationRbQ10) for different configurations. Here we demonstrate how those legacy model architectures can be trivially constructed via HybridModel.
Setup
First, let's load our required packages:
using EasyHybrid1. Linear Hybrid Model
This is a basic model with one neural network predicting a coefficient α, and an explicit global parameter β. The equation is: ŷ = α * x + β
Process-Based Definition
linear_mechanistic(; x, α, β) = (; obs = α .* x .+ β)linear_mechanistic (generic function with 1 method)Parameter Setup
params_linear = (
α = (1.0f0, 0.0f0, 2.0f0),
β = (1.5f0, -1.0f0, 3.0f0),
)(α = (1.0f0, 0.0f0, 2.0f0), β = (1.5f0, -1.0f0, 3.0f0))HybridModel Construction
We use x as forcing data, predict α with a neural network based on some predictors a and b, and leave β as a globally optimized constant parameter.
lhm = constructHybridModel(
[:a, :b], # predictors for the NN (predicts α)
[:x], # forcing variable
[:obs], # targets
linear_mechanistic, # our mechanistic model
params_linear, # parameter container
[:α], # parameters predicted by the NN
[:β]; # globally optimized constant parameters
hidden_layers = [4, 4],
activation = tanh
)Hybrid Model (Single NN)
Neural Network:
Chain(
layer_1 = WrappedFunction(identity),
layer_2 = Dense(2 => 4, tanh), # 12 parameters
layer_3 = Dense(4 => 4, tanh), # 20 parameters
layer_4 = Dense(4 => 1), # 5 parameters
) # Total: 37 parameters,
# plus 0 states.
Configuration:
predictors = [:a, :b]
forcing = [:x]
targets = [:obs]
mechanistic_model = linear_mechanistic
neural_param_names = [:α]
global_param_names = [:β]
fixed_param_names = Symbol[]
scale_nn_outputs = false
start_from_default = true
config = (; hidden_layers = [4, 4], activation = tanh, scale_nn_outputs = false, input_batchnorm = false, start_from_default = true,)
Parameters:
┌───┬─────────┬───────┬───────┐
│ │ default │ lower │ upper │
├───┼─────────┼───────┼───────┤
│ α │ 1.0 │ 0.0 │ 2.0 │
│ β │ 1.5 │ -1.0 │ 3.0 │
└───┴─────────┴───────┴───────┘2. Respiration Rb Q10
A single NN predicting Rb for a Q10 temperature-sensitive respiration formulation. The equation is: R_soil = Rb * Q10^(0.1 * (Temp - 15))
Process-Based Definition
function mRbQ10(; Temp, Rb, Q10)
R_soil = @. Rb * Q10^(0.1f0 * (Temp - 15.0f0))
return (; R_soil)
endmRbQ10 (generic function with 1 method)Parameter Setup
params_rbq10 = (
Rb = (1.0f0, 0.0f0, 5.0f0),
Q10 = (1.5f0, 1.0f0, 3.0f0),
)(Rb = (1.0f0, 0.0f0, 5.0f0), Q10 = (1.5f0, 1.0f0, 3.0f0))HybridModel Construction
m_rbq10 = constructHybridModel(
[:SWC, :TA], # predictors for Rb
[:Temp], # forcing variable
[:R_soil], # targets
mRbQ10, # mechanistic model
params_rbq10,
[:Rb], # predicted by NN
[:Q10]; # globally optimized
hidden_layers = [8, 8]
)Hybrid Model (Single NN)
Neural Network:
Chain(
layer_1 = WrappedFunction(identity),
layer_2 = Dense(2 => 8, tanh), # 24 parameters
layer_3 = Dense(8 => 8, tanh), # 72 parameters
layer_4 = Dense(8 => 1), # 9 parameters
) # Total: 105 parameters,
# plus 0 states.
Configuration:
predictors = [:SWC, :TA]
forcing = [:Temp]
targets = [:R_soil]
mechanistic_model = mRbQ10
neural_param_names = [:Rb]
global_param_names = [:Q10]
fixed_param_names = Symbol[]
scale_nn_outputs = false
start_from_default = true
config = (; hidden_layers = [8, 8], activation = tanh, scale_nn_outputs = false, input_batchnorm = false, start_from_default = true,)
Parameters:
┌─────┬─────────┬───────┬───────┐
│ │ default │ lower │ upper │
├─────┼─────────┼───────┼───────┤
│ Rb │ 1.0 │ 0.0 │ 5.0 │
│ Q10 │ 1.5 │ 1.0 │ 3.0 │
└─────┴─────────┴───────┴───────┘3. Respiration Components
A single NN outputting 3 distinct parameters (Rb_het, Rb_root, Rb_myc).
Process-Based Definition
function rs_comp(; Temp, Rb_het, Rb_root, Rb_myc, Q10_het, Q10_root, Q10_myc)
R_het = @. Rb_het * Q10_het^(0.1f0 * (Temp - 15.0f0))
R_root = @. Rb_root * Q10_root^(0.1f0 * (Temp - 15.0f0))
R_myc = @. Rb_myc * Q10_myc^(0.1f0 * (Temp - 15.0f0))
R_soil = R_het .+ R_root .+ R_myc
return (; R_soil, R_het, R_root, R_myc)
endrs_comp (generic function with 1 method)Parameter Setup
params_rs_comp = (
Rb_het = (1.0f0, 0.0f0, 5.0f0),
Rb_root = (1.0f0, 0.0f0, 5.0f0),
Rb_myc = (1.0f0, 0.0f0, 5.0f0),
Q10_het = (1.5f0, 1.0f0, 3.0f0),
Q10_root = (1.5f0, 1.0f0, 3.0f0),
Q10_myc = (1.5f0, 1.0f0, 3.0f0),
)(Rb_het = (1.0f0, 0.0f0, 5.0f0), Rb_root = (1.0f0, 0.0f0, 5.0f0), Rb_myc = (1.0f0, 0.0f0, 5.0f0), Q10_het = (1.5f0, 1.0f0, 3.0f0), Q10_root = (1.5f0, 1.0f0, 3.0f0), Q10_myc = (1.5f0, 1.0f0, 3.0f0))HybridModel Construction
m_rs_comp = constructHybridModel(
[:SWC, :TA], # predictors for all 3 Rb parameters
[:Temp],
[:R_soil],
rs_comp,
params_rs_comp,
[:Rb_het, :Rb_root, :Rb_myc],
[:Q10_het, :Q10_root, :Q10_myc];
hidden_layers = [16, 16]
)Hybrid Model (Single NN)
Neural Network:
Chain(
layer_1 = WrappedFunction(identity),
layer_2 = Dense(2 => 16, tanh), # 48 parameters
layer_3 = Dense(16 => 16, tanh), # 272 parameters
layer_4 = Dense(16 => 3), # 51 parameters
) # Total: 371 parameters,
# plus 0 states.
Configuration:
predictors = [:SWC, :TA]
forcing = [:Temp]
targets = [:R_soil]
mechanistic_model = rs_comp
neural_param_names = [:Rb_het, :Rb_root, :Rb_myc]
global_param_names = [:Q10_het, :Q10_root, :Q10_myc]
fixed_param_names = Symbol[]
scale_nn_outputs = false
start_from_default = true
config = (; hidden_layers = [16, 16], activation = tanh, scale_nn_outputs = false, input_batchnorm = false, start_from_default = true,)
Parameters:
┌──────────┬─────────┬───────┬───────┐
│ │ default │ lower │ upper │
├──────────┼─────────┼───────┼───────┤
│ Rb_het │ 1.0 │ 0.0 │ 5.0 │
│ Rb_root │ 1.0 │ 0.0 │ 5.0 │
│ Rb_myc │ 1.0 │ 0.0 │ 5.0 │
│ Q10_het │ 1.5 │ 1.0 │ 3.0 │
│ Q10_root │ 1.5 │ 1.0 │ 3.0 │
│ Q10_myc │ 1.5 │ 1.0 │ 3.0 │
└──────────┴─────────┴───────┴───────┘4. Flux Partitioning with Multiple NNs
A multi-NN architecture predicting RUE (Radiation Use Efficiency) and Rb from different sets of predictors.
Process-Based Definition
function flux_part(; SW_IN, TA, RUE, Rb, Q10)
GPP = @. SW_IN * RUE / 12.011f0
RECO = @. Rb * Q10^(0.1f0 * (TA - 15.0f0))
NEE = RECO .- GPP
return (; NEE, GPP, RECO)
endflux_part (generic function with 1 method)Parameter Setup
params_flux = (
RUE = (1.0f0, 0.0f0, 5.0f0),
Rb = (1.0f0, 0.0f0, 5.0f0),
Q10 = (1.5f0, 1.0f0, 3.0f0),
)(RUE = (1.0f0, 0.0f0, 5.0f0), Rb = (1.0f0, 0.0f0, 5.0f0), Q10 = (1.5f0, 1.0f0, 3.0f0))HybridModel Construction
By passing a NamedTuple to predictors, HybridModel automatically provisions an independent Neural Network for each key.
predictors_multi = (
RUE = [:SWC, :TA, :SW_IN],
Rb = [:SWC, :TA],
)
m_flux = constructHybridModel(
predictors_multi, # Triggers Multi-NN construction
[:SW_IN, :TA], # Forcing variables
[:NEE], # Targets
flux_part, # Mechanistic model
params_flux,
[:Q10]; # Global parameter
hidden_layers = (RUE = [8, 8], Rb = [4, 4]), # Custom architectures per NN
activation = (RUE = Lux.sigmoid, Rb = tanh)
)Hybrid Model (Multi NN)
Neural Networks:
RUE:
Chain(
layer_1 = WrappedFunction(identity),
layer_2 = Dense(3 => 8, σ), # 32 parameters
layer_3 = Dense(8 => 8, σ), # 72 parameters
layer_4 = Dense(8 => 1), # 9 parameters
) # Total: 113 parameters,
# plus 0 states.
Rb:
Chain(
layer_1 = WrappedFunction(identity),
layer_2 = Dense(2 => 4, tanh), # 12 parameters
layer_3 = Dense(4 => 4, tanh), # 20 parameters
layer_4 = Dense(4 => 1), # 5 parameters
) # Total: 37 parameters,
# plus 0 states.
Configuration:
predictors:
RUE = [:SWC, :TA, :SW_IN]
Rb = [:SWC, :TA]
forcing = [:SW_IN, :TA]
targets = [:NEE]
mechanistic_model = flux_part
neural_param_names = [:RUE, :Rb]
global_param_names = [:Q10]
fixed_param_names = Symbol[]
scale_nn_outputs = false
start_from_default = true
config = (; hidden_layers = (RUE = [8, 8], Rb = [4, 4]), activation = (RUE = NNlib.σ, Rb = tanh), scale_nn_outputs = false, input_batchnorm = false, start_from_default = true,)
Parameters:
┌─────┬─────────┬───────┬───────┐
│ │ default │ lower │ upper │
├─────┼─────────┼───────┼───────┤
│ RUE │ 1.0 │ 0.0 │ 5.0 │
│ Rb │ 1.0 │ 0.0 │ 5.0 │
│ Q10 │ 1.5 │ 1.0 │ 3.0 │
└─────┴─────────┴───────┴───────┘5. Process-Based Model (Zero NNs)
A purely process-based configuration where all parameters are optimized globally, and no Neural Networks are built.
Process-Based Definition
function mRbQ10_0(; Temp, Rb, Q10)
R_soil = @. Rb * Q10^(0.1f0 * (Temp - 0.0f0))
return (; R_soil)
endmRbQ10_0 (generic function with 1 method)HybridModel Construction
Passing an empty Symbol[] array to predictors prevents any Neural Networks from being created.
m_pbm = constructHybridModel(
Symbol[], # No predictors -> No Neural Network
[:Temp], # Forcing
[:R_soil], # Target
mRbQ10_0,
params_rbq10,
Symbol[], # No neural params
[:Rb, :Q10] # Both are optimized as global parameters
)Hybrid Model (Single NN)
Neural Network:
Chain(
layer_1 = Lux.NoOpLayer(),
) # Total: 0 parameters,
# plus 0 states.
Configuration:
predictors = Symbol[]
forcing = [:Temp]
targets = [:R_soil]
mechanistic_model = mRbQ10_0
neural_param_names = Symbol[]
global_param_names = [:Rb, :Q10]
fixed_param_names = Symbol[]
scale_nn_outputs = false
start_from_default = true
config = (; hidden_layers = [32, 32], activation = tanh, scale_nn_outputs = false, input_batchnorm = false, start_from_default = true,)
Parameters:
┌─────┬─────────┬───────┬───────┐
│ │ default │ lower │ upper │
├─────┼─────────┼───────┼───────┤
│ Rb │ 1.0 │ 0.0 │ 5.0 │
│ Q10 │ 1.5 │ 1.0 │ 3.0 │
└─────┴─────────┴───────┴───────┘Summary
As demonstrated above, HybridModel provides a highly flexible, unified interface. By simply modifying the predictors argument and your mechanistic function, you can rapidly scale from a purely process-based model, to a single Neural Network hybrid model, all the way up to complex multi-Neural Network architectures!