EasyHybrid.jl
Documentation for EasyHybrid.jl.
EasyHybrid.EasyHybrid Module
EasyHybrid.jlEasyHybrid is a Julia package for hybrid machine learning models, combining neural networks and traditional statistical methods. It provides tools for data preprocessing, model training, and evaluation, making it easier to build and deploy hybrid models.
The hybrid model combines a neural network h(x; θ), with inputs x and learnable parameters θ, together with a mechanistic model M(·, z; ϕ) driven by forcings z and parameterized by ϕ, where ϕ may be known, learned from data, or fixed.
- Documentation https://earthyscience.github.io/EasyHybrid.jl/
EasyHybrid.DataConfig Type
Configuration for data preparation and loading.
Controls array types, observation shuffling, data splitting, cross-validation, and sequence construction for time-series training.
sourceEasyHybrid.HybridModel Type
HybridModel{T, P} <: LuxCore.AbstractLuxContainerLayer{(:NNs,)}A unified hybrid model struct that handles both single and multi neural network architectures. It combines predictive neural networks (NNs) with a mechanistic_model to form a differentiable hybrid model.
EasyHybrid.HybridModel Method
(m::HybridModel)(ds_k::Tuple, ps, st)Forward pass of the hybrid model. Evaluates the neural networks to predict parameters, merges them with scaled global parameters and fixed parameters, and executes the mechanistic model. Returns a tuple (out, st_new).
EasyHybrid.InputBatchNorm Type
InputBatchNorm(chs; kwargs...)A wrapper around BatchNorm that handles 3D sequence input (features, timesteps, batch).
Lux's BatchNorm expects channels in the penultimate dimension, which works for 2D input (features, batch) but fails for 3D input (features, timesteps, batch) where features are in dim 1. This wrapper reshapes 3D input to 2D (features, timesteps * batch) before normalization, then reshapes back. For 2D input, delegates directly to BatchNorm.
EasyHybrid.LoggingLoss Type
LoggingLossA structure to define a logging loss function for hybrid models.
Arguments
loss_types: A vector of loss specifications (Symbol, Function or Tuple)Symbol: predefined loss, e.g.
:mseFunction: custom loss function, e.g.
custom_lossTuple: function with args/kwargs:
(f, args): positional args, e.g.(weighted_loss, (0.5,))(f, kwargs): keyword args, e.g.(scaled_loss, (scale=2.0,))(f, args, kwargs): both, e.g.(complex_loss, (0.5,), (scale=2.0,))
training_loss: The loss specification to use during training (same format as above)extra_loss: Optional function(ŷ, ps; kwargs...) -> NamedTuple(or splattable collection) added to training loss (default:nothing)agg: Function to aggregate losses across targets, e.g.sumormeantrain_mode: If true, usestraining_loss; otherwise usesloss_types.
Examples
# Simple predefined loss
logging = LoggingLoss(
loss_types=[:mse, :mae],
training_loss=:mse
)
# Custom loss function
custom_loss(ŷ, y) = mean(abs2, ŷ .- y)
logging = LoggingLoss(
loss_types=[:mse, custom_loss],
training_loss=custom_loss
)
# With arguments/kwargs
weighted_loss(ŷ, y, w) = w * mean(abs2, ŷ .- y)
scaled_loss(ŷ, y; scale=1.0) = scale * mean(abs2, ŷ .- y)
logging = LoggingLoss(
loss_types=[:mse, (weighted_loss, (0.5,)), (scaled_loss, (scale=2.0,))],
training_loss=(weighted_loss, (0.5,))
)EasyHybrid.ParameterContainer Type
ParameterContainer{NT <: NamedTuple, T}A container for holding the parameter definitions of a model, including their default values, lower bounds, and upper bounds.
sourceEasyHybrid.PerTarget Type
PerTarget(losses)A wrapper to indicate that a tuple of losses should be applied on a per-target basis.
sourceEasyHybrid.RecurrenceOutputDense Type
RecurrenceOutputDense(in_dims => out_dims, [activation])A layer that wraps a Dense layer to handle sequence outputs from Recurrence layers.
When a Recurrence layer has return_sequence=true, it outputs a tuple/vector of arrays (one per timestep). This layer broadcasts the Dense operation over each timestep and reshapes the result to (features, timesteps, batch) format.
Arguments
in_dims::Int: Input dimension (should match Recurrence output dimension)out_dims::Int: Output dimensionactivation: Activation function (default:identity)
Example
# Instead of manually creating:
broadcast_layer = @compact(; layer = Dense(15 => 15)) do x
y = map(layer, x)
@return permutedims(stack(y; dims = 3), (1, 3, 2))
end
# Simply use:
Chain(
Recurrence(LSTMCell(15 => 15), return_sequence = true),
RecurrenceOutputDense(15 => 15)
)EasyHybrid.TrainConfig Type
Configuration for training a hybrid model.
Controls all aspects of the training process including optimization, loss computation, data handling, output, and visualization.
sourceEasyHybrid.TrainResults Type
Output of train, containing the full training history, model state, and diagnostics.
EasyHybrid.WrappedTuples Type
WrappedTuples(vec::Vector{<:NamedTuple})Wraps a vector of named tuples to allow dot-access to each field as a vector.
sourceEasyHybrid._apply_loss Function
_apply_loss(ŷ, y, y_nan, loss_spec)Helper function to apply the appropriate loss function based on the specification type.
Arguments
ŷ: Predictions for a single targety: Target values for a single targety_nan: NaN mask for a single targetloss_spec: Loss specification (Symbol, Function, or Tuple)
Returns
- Computed loss value
EasyHybrid._build_optim_loss Method
Build the scalar loss closure consumed by Optimization.jl, called as loss_fn(p, data). data is an already device-placed / Array-converted batch (shape ((x, forcings), (y, mask))), produced once per (mini)batch by collect_dim_data in the caller — not inside this closure. Keeping the data prep out of the loss is important: L-BFGS line searches call the objective many times per iteration, so re-running collect_dim_data (NamedTuple rebuilds, Array copies, gdev transfers) on every evaluation was a major slowdown, especially on the minibatch path. It also keeps the closure trivially Zygote-differentiable (no pairs(...)/∇map in the AD tape).
EasyHybrid._compute_loss Function
_compute_loss(ŷ, y, y_nan, targets, loss_spec, agg::Function)
_compute_loss(ŷ, y, y_nan, targets, loss_types::Vector, agg::Function)Compute the loss for the given predictions and targets using the specified training loss (or vector of losses) type and aggregation function.
Arguments:
ŷ: Predicted values.y: Target values.y_nan: Mask for NaN values.targets: The targets for which the loss is computed.loss_spec: The loss type to use during training, e.g.,:mse.loss_types::Vector: A vector of loss types to compute, e.g.,[:mse, :mae].agg::Function: The aggregation function to apply to the computed losses, e.g.,sumormean.
Returns a single loss value if loss_spec is provided, or a NamedTuple of losses for each type in loss_types.
EasyHybrid._get_target_nan Function
_get_target_nan(y_nan, target)Helper function to extract target-specific values from y_nan. Supports NamedTuple, KeyedArray, AbstractDimArray, and callables (functions).
EasyHybrid._get_target_y Function
_get_target_y(y, target)Helper function to extract target-specific values from y, handling cases where y can be a tuple of (y_obs, y_sigma). Supports NamedTuple, KeyedArray, AbstractDimArray, Tuple, and callables (functions).
EasyHybrid._init_nn_params Method
_init_nn_params(rng, m::HybridModel{<:Any, <:NamedTuple})Initialize parameters for a multi-neural network architecture. Returns a NamedTuple containing the initialized parameters for each sub-network.
EasyHybrid._init_nn_params Method
_init_nn_params(rng, m::HybridModel{<:Any, <:Vector})Initialize parameters for a single-neural network architecture. Returns a NamedTuple containing a single ps field with the network's parameters.
EasyHybrid._init_nn_states Method
_init_nn_states(rng, m::HybridModel{<:Any, <:NamedTuple})Initialize states for a multi-neural network architecture. Returns a NamedTuple containing the initialized states for each sub-network.
EasyHybrid._init_nn_states Method
_init_nn_states(rng, m::HybridModel{<:Any, <:Vector})Initialize states for a single-neural network architecture. Returns a NamedTuple containing a single st_nn field with the network's states.
EasyHybrid._is_named_leaf Method
_is_named_leaf(name, child, key) -> BoolReturn true when child is an array leaf whose parent field is key (e.g. a Dense layer's weight matrix).
EasyHybrid._run_minibatch! Method
Explicit "repeated minibatch" driver for the full_batch = false Optimization path (Le et al., 2011, ICML, §4.2). For each of cfg.nepochs outer passes we iterate a reshuffled DataLoader; on every fixed minibatch we run cfg.inner_maxiters optimizer iterations warm-started from the current ps, then resample. A validation EpochSnapshot (history / early-stopping / dashboard / checkpoint) is built once per outer pass, so patience is counted in outer passes — consistent with the Optimisers.jl loop.
maxiters / epochs from solve_kwargs are dropped (the per-minibatch budget is cfg.inner_maxiters and the pass count is cfg.nepochs); any remaining solve_kwargs (e.g. g_abstol, f_reltol) are forwarded to each inner solve.
EasyHybrid._run_nn Method
_run_nn(m::HybridModel{<:Any, <:NamedTuple}, ds_k::Tuple, ps, st)Execute the forward pass for a multi-neural network architecture. Applies each sub-network to its specific predictors, and applies scaling to the outputs if required. Returns scaled parameter values, updated states, and raw network outputs.
sourceEasyHybrid._run_nn Method
_run_nn(m::HybridModel{<:Any, <:Vector}, ds_k::Tuple, ps, st)Execute the forward pass for a single-neural network architecture. Applies the neural network to the given predictors, slices the output for multiple predicted parameters, and scales them if required. Returns scaled parameter values, updated states, and raw network outputs.
sourceEasyHybrid._train Method
_train(model, data, train_cfg, data_cfg, solve_kwargs)Dispatcher used by train(...): routes to the original 4-arg _train body (the Lux.Training / Optimisers.jl loop) when train_cfg.opt isa Optimisers.AbstractRule, or to _train_optimization (which delegates batch iteration to Optimization.jl) otherwise. solve_kwargs are forwarded to solve(...) on the Optimization.jl branch and warned about on the Optimisers.jl branch.
EasyHybrid._train_optimization Method
_train_optimization(model, data, train_cfg, data_cfg, solve_kwargs)Optimization.jl-based driver dispatched from _train whenever train_cfg.opt is not an Optimisers.AbstractRule (e.g. Optim.LBFGS() / Optimization.LBFGS()); see the SciML minibatching tutorial.
Two modes, selected by train_cfg.full_batch:
full_batch = true: pass the full training set as a single tuple to oneOptimizationProblemand a singlesolve(...). Batch-method idiom (the recommended L-BFGS setup): the objective is a single consistent function.solve_kwargs(e.g.maxiters,g_abstol,f_reltol) are splatted intosolve, andtrain_cfg.eval_everybuilds a validationEpochSnapshotevery N solver iterations via the callback.full_batch = false: explicit "repeated minibatch" loop grounded in Le et al., 2011 (On Optimization Methods for Deep Learning, ICML, §4.2). For each oftrain_cfg.nepochsouter passes we iterate a (reshuffled)DataLoaderand, on each fixed minibatch, runtrain_cfg.inner_maxitersoptimizer iterations (solve(...; maxiters = inner_maxiters)), warm-starting the next minibatch from the current parameters. Holding the minibatch fixed for a few iterations keeps the objective (and L-BFGS curvature pairs / line search) consistent — naive one-step-per-minibatch L-BFGS does not converge. A validationEpochSnapshotis built once per outer pass. Optimization.jl's ownDataLoaderiteration is not used here because it only applies to theOptimisers.jl-style solvers, not Optim.jl's L-BFGS.
Both modes honour train_cfg.promote_f64: promote ps to Float64 before optimization (workaround for Lux.jl#1260).
EasyHybrid.build_opt_state Method
build_opt_state(opt, ps::NamedTuple; default_rule = Optimisers.Adam())Build the optimizer state tree consumed by Lux.Training.TrainState / Optimisers.update!. Three forms of opt are accepted:
opt::Optimisers.AbstractRule— single rule applied to the whole parameter tree (delegates toOptimisers.setup(opt, ps)).opt::NamedTupleofOptimisers.AbstractRules — each rule is wired to the matching top-level branch ofpsviaOptimisers.setup(rule, ps[name]). Branches missing fromoptusedefault_rule.opt::NamedTupleof pre-built state trees (already returned by a priorOptimisers.setup) — used as-is. Form 2 and 3 can be mixed in the sameNamedTuple.
The returned state tree has the same top-level keys as ps.
Example
ps, _ = LuxCore.setup(rng, hybrid_model) # (; Rb = NN_ps, RUE = NN_ps, Q10 = [v])
# Form 2 — preferred, lets the framework call `Optimisers.setup`:
opt_state = build_opt_state(
(; Rb = Adam(1e-3), RUE = Adam(1e-3), Q10 = Descent(1e-2)),
ps,
)EasyHybrid.build_parameter_matrix Method
build_parameter_matrix(parameter_defaults_and_bounds::NamedTuple)Build a ComponentArray matrix from a NamedTuple containing parameter defaults and bounds.
This function converts a NamedTuple where each value is a tuple of (default, lower, upper) bounds into a ComponentArray with named axes for easy parameter management in hybrid models.
Arguments
parameter_defaults_and_bounds::NamedTuple: A NamedTuple where each key is a parameter name and each value is a tuple of (default, lower, upper) for that parameter.
Returns
ComponentArray: A 2D ComponentArray with:Row axis: Parameter names (from the NamedTuple keys)
Column axis: Bound types (:default, :lower, :upper)
Data: The parameter values organized in a matrix format
Example
# Define parameter defaults and bounds
parameter_defaults_and_bounds = (
θ_s = (0.464f0, 0.302f0, 0.700f0), # Saturated water content [cm³/cm³]
h_r = (1500.0f0, 1500.0f0, 1500.0f0), # Pressure head at residual water content [cm]
α = (log(0.103f0), log(0.01f0), log(7.874f0)), # Shape parameter [cm⁻¹]
n = (log(3.163f0 - 1), log(1.100f0 - 1), log(20.000f0 - 1)), # Shape parameter [-]
)
# Build the ComponentArray
parameter_matrix = build_parameter_matrix(parameter_defaults_and_bounds)
# Access specific parameter bounds
parameter_matrix.θ_s.default # Get default value for θ_s
parameter_matrix[:, :lower] # Get all lower bounds
parameter_matrix[:, :upper] # Get all upper boundsNotes
The function expects each value in the NamedTuple to be a tuple with exactly 3 elements
The order of bounds is always (default, lower, upper)
The resulting ComponentArray can be used for parameter optimization and constraint handling
EasyHybrid.compute_loss Method
compute_loss(HM, x, (y_t, y_nan), ps, st, logging::LoggingLoss)Main loss function for hybrid models that handles both training and evaluation modes.
Arguments
HM: The hybrid model (AbstractLuxContainerLayer or specific model type)x: Input data for the model(y_t, y_nan): Tuple containing target values and NaN mask functions/arraysps: Model parametersst: Model statelogging: LoggingLoss configuration
Returns
In training mode (
logging.train_mode = true):(loss_value, st): Single loss value and updated state
In evaluation mode (
logging.train_mode = false):(loss_values, st, ŷ): NamedTuple of losses, state and predictions
EasyHybrid.constructHybridModel Method
constructHybridModel(predictors::NamedTuple, forcing, targets, mechanistic_model, parameters, global_param_names; kwargs...)Construct a HybridModel with multiple neural network architectures. A separate neural network is built for each key in the predictors NamedTuple.
Arguments:
predictors::NamedTuple: A NamedTuple where keys are network names, and values are vectors of predictor variables for that network.forcing: Variables passed directly to the mechanistic model.targets: The target variables to predict.mechanistic_model: A function implementing the process-based model.parameters: A parameter container defining defaults, lowers, and uppers.global_param_names: Names of the parameters to be globally optimized.kwargs: Additional configuration.hidden_layersandactivationcan also be NamedTuples to configure each network independently.
EasyHybrid.constructHybridModel Method
constructHybridModel(predictors::Vector{Symbol}, forcing, targets, mechanistic_model, parameters, neural_param_names, global_param_names; kwargs...)Construct a HybridModel with a single neural network architecture predicting all neural_param_names from the predictors.
Arguments:
predictors::Vector{Symbol}: Variables used as inputs to the neural network.forcing: Variables passed directly to the mechanistic model.targets: The target variables to predict.mechanistic_model: A function implementing the process-based model.parameters: A parameter container defining defaults, lowers, and uppers.neural_param_names: Names of the parameters to be predicted by the neural network.global_param_names: Names of the parameters to be globally optimized.kwargs: Additional configuration likehidden_layers,activation,scale_nn_outputs, etc.
EasyHybrid.constructNNModel Method
constructNNModel(predictors, targets; hidden_layers, activation, scale_nn_outputs)Main constructor: hidden_layers can be either • a Vector{Int} of sizes, or • a Chain of hidden-layer Dense blocks.
EasyHybrid.extract_weights Method
extract_weights(ps; key=:weight) -> Vector{AbstractArray}Walk the parameter tree ps (a ComponentArray, NamedTuple, or any nested combination of them) and return all leaf arrays whose immediate parent field name equals key.
Defaults to :weight, so you get the weight matrices of Dense/Conv layers and skip biases, BatchNorm scale/bias, running statistics in st, and any scalar global parameters.
The returned arrays are views/aliases into ps. When ps is the argument the autodiff is differentiating w.r.t., gradients of any function of these views flow back into the trainable weights.
EasyHybrid.filter_sequences Method
filter_sequences(x, y) -> (x_filtered, y_filtered)Drop 3rd-dim samples where any predictor is NaN or all targets are NaN. Accepts x as a 3D array or (x_array, forcings) Tuple.
EasyHybrid.get_layer_dim Method
get_layer_dim(layer, type::Symbol)Helper function to extract the input or output dimensions of a Lux layer. This function uses multiple dispatch to safely pull dimensions from various container types.
sourceEasyHybrid.get_loss_value Method
get_loss_value(losses, loss_spec, agg)Extract loss value from losses based on the loss specification type.
Arguments
losses: NamedTuple containing loss valuesloss_spec: Loss specification (Symbol, Function or Tuple)agg: Aggregation function name as Symbol
Returns
- Loss value for the specified loss function
Examples
# Symbol case
val = get_loss_value(losses, :mse, :sum)
# Function case
custom_loss(ŷ, y) = mean(abs2, ŷ .- y)
val = get_loss_value(losses, custom_loss, :mean)
# Tuple case
weighted_loss(ŷ, y, w) = w * mean(abs2, ŷ .- y)
val = get_loss_value(losses, (weighted_loss, (0.5,)), :sum)EasyHybrid.get_mechanistic_model_config Method
get_mechanistic_model_config(f::Function)Build an OrderedDict describing a function for YAML output: its name, and for each Method, its source file, starting line, and the full source text extracted from disk by parsing one complete expression starting at that line. The single-method case is flattened so the YAML stays compact.
Used to record mechanistic_model in the saved config so the exact function definition (not just its name) is preserved alongside the run.
EasyHybrid.get_parameters_config Method
get_parameters_config(pc::ParameterContainer)Serialize the parameter table (default, lower, upper per parameter) of a ParameterContainer into a nested OrderedDict suitable for YAML output. Without this, only the compact show of the struct (e.g. ParameterContainer(RUE, Rb, Q10)) would be written, which drops all of the actual default and bound values.
EasyHybrid.get_prediction_target_names Method
get_prediction_target_names(hm)Utility function to extract predictor/forcing and target names from a hybrid model.
Arguments:
hm: The Hybrid Model
Returns a tuple of (predictors_forcing, targets) names.
sourceEasyHybrid.initialize_plotting_observables Method
initialize_plotting_observables(init_ŷ_train, init_ŷ_val, y_train, y_val, l_init_train, l_init_val, training_loss, agg, monitor_names, target_names)Initialize plotting observables for training visualization if the Makie extension is loaded.
sourceEasyHybrid.is_optimisers_rule Method
is_optimisers_rule(opt) -> BoolReturn true when opt originates from the Optimisers.jl package (e.g. Adam, AdamW, RMSProp, OptimiserChain), or when opt is a NamedTuple describing a per-branch optimizer (see is_per_branch_opt).
The check on single rules is by source package (nameof(parentmodule(typeof(opt))) === :Optimisers) rather than by isa Optimisers.AbstractRule, because in some package combinations Optim.jl optimizers were observed to satisfy the AbstractRule test and get misrouted to the Lux.Training loop.
The Lux.Training-based loop dispatches on this; everything else (including Optim.jl and Optimization.jl optimizers) is routed through the Optimization.jl driver.
EasyHybrid.is_per_branch_opt Method
is_per_branch_opt(opt) -> BoolReturn true when opt is a NamedTuple describing a per-branch optimizer specification — i.e. one of:
a
NamedTupleofOptimisers.AbstractRules (e.g.(; Rb = Adam(1e-3), Q10 = Descent(1e-2))), ora
NamedTupleof pre-built optimizer state trees as returned byOptimisers.setup(rule, ps_branch), ora mix of the two.
Branches of the parameter tree not listed in opt fall back to the default rule Adam() (see build_opt_state).
This is detected purely by opt isa NamedTuple; the per-branch dispatch is checked again per-leaf when build_opt_state walks the spec.
EasyHybrid.kwargs_to_configs Method
kwargs_to_configs(save_ps, kwargs) -> (TrainConfig, DataConfig, NamedTuple)Build a fresh (TrainConfig, DataConfig) pair from a flat collection of kwargs. Kwargs are split between the two configs based on fieldnames(TrainConfig) and fieldnames(DataConfig); anything left over is returned as the third element and forwarded to solve(...) on the Optimization.jl path (or warned about on the Optimisers.jl path — see _train).
save_ps is the deprecated positional argument from train(model, data, save_ps; ...); when non-empty it is forwarded as tracked_params on the resulting TrainConfig.
EasyHybrid.load_timeseries_netcdf Method
load_timeseries_netcdf(path::AbstractString; timedim::AbstractString = "time") -> DataFrameReads a NetCDF file where all data variables are 1D over the specified timedim and returns a tidy DataFrame with one row per time step.
Only includes variables whose sole dimension is
timedim.Does not attempt to parse or convert time units; all columns are read as-is.
EasyHybrid.loss_fn Function
loss_fn(ŷ, y, y_nan, loss_type)Compute the loss for given predictions and targets using various loss specifications.
Arguments
ŷ: Predicted valuesy: Target valuesy_nan: Mask for NaN valuesloss_type: One of the following:Val(:rmse): Root Mean Square ErrorVal(:mse): Mean Square ErrorVal(:mae): Mean Absolute ErrorVal(:pearson): Pearson correlation coefficientVal(:r2): R-squaredVal(:pearsonLoss): 1 - Pearson correlation coefficientVal(:nseLoss): 1 - NSE::Function: Custom loss function with signaturef(ŷ, y)::Tuple{Function, Tuple}: Custom loss with argsf(ŷ, y, args...)::Tuple{Function, NamedTuple}: Custom loss with kwargsf(ŷ, y; kwargs...)::Tuple{Function, Tuple, NamedTuple}: Custom loss with bothf(ŷ, y, args...; kwargs...)
Examples
# Predefined loss
loss = loss_fn(ŷ, y, y_nan, Val(:mse))
# Custom loss function
custom_loss(ŷ, y) = mean(abs2, ŷ .- y)
loss = loss_fn(ŷ, y, y_nan, custom_loss)
# With positional arguments
weighted_loss(ŷ, y, w) = w * mean(abs2, ŷ .- y)
loss = loss_fn(ŷ, y, y_nan, (weighted_loss, (0.5,)))
# With keyword arguments
scaled_loss(ŷ, y; scale=1.0) = scale * mean(abs2, ŷ .- y)
loss = loss_fn(ŷ, y, y_nan, (scaled_loss, (scale=2.0,)))
# With both args and kwargs
complex_loss(ŷ, y, w; scale=1.0) = scale * w * mean(abs2, ŷ .- y)
loss = loss_fn(ŷ, y, y_nan, (complex_loss, (0.5,), (scale=2.0,)))You can define additional predefined loss functions by adding more methods:
import EasyHybrid: loss_fn
function EasyHybrid.loss_fn(ŷ, y, y_nan, ::Val{:nse})
return 1 - sum((ŷ[y_nan] .- y[y_nan]).^2) / sum((y[y_nan] .- mean(y[y_nan])).^2)
endEasyHybrid.make_folds Method
make_folds(df::DataFrame; k::Int=5, shuffle=true) -> Vector{Int}Assigns each observation in the DataFrame df to one of k folds for cross-validation.
Arguments
df::DataFrame: The input DataFrame whose rows are to be split into folds.k::Int=5: Number of folds to create.shuffle=true: Whether to shuffle the data before assigning folds.
Returns
folds::Vector{Int}: A vector of lengthnrow(df)where each entry is an integer in1:kindicating the fold assignment for that observation.
EasyHybrid.override_config Method
override_config(cfg, overrides::NamedTuple)Return a new cfg of the same type with the fields named in overrides replaced. Works with any @kwdef struct (e.g. TrainConfig, DataConfig).
EasyHybrid.override_configs Method
override_configs(train_cfg, data_cfg, kwargs) -> (TrainConfig, DataConfig, NamedTuple)Return (train_cfg′, data_cfg′, solve_kwargs) where any field present in kwargs overrides the corresponding field of train_cfg/data_cfg. Fields not mentioned in kwargs are kept as-is. Anything left over is returned as solve_kwargs and forwarded to solve(...) on the Optimization.jl path (or warned about on the Optimisers.jl path — see _train).
EasyHybrid.prepare_data Function
prepare_data(hm, data::DataFrame; array_type=:KeyedArray, drop_missing_rows=true)
prepare_data(hm, data::KeyedArray)
prepare_data(hm, data::AbstractDimArray)
prepare_data(hm, data::Tuple)Prepare data for training by extracting predictor/forcing and target variables based on the hybrid model's configuration.
Arguments:
hm: The Hybrid Modeldata: The input data, which can be a DataFrame, KeyedArray, or DimensionalData array.array_type: (DataFrame only) Output array type::KeyedArray(default) or:DimArray.drop_missing_rows: (DataFrame only) Iftrue(default), drop rows where any predictor is NaN or all targets are NaN.
Returns:
If
datais a DataFrame: a tuple of (predictors_forcing, targets) as KeyedArrays or DimArrays depending onarray_type.If
datais a KeyedArray: a tuple of (predictors_forcing, targets) as KeyedArrays.If
datais an AbstractDimArray: a tuple of (predictors_forcing, targets) as DimArrays.If
datais already a Tuple, it is returned as-is.
EasyHybrid.scale_single_param Method
scale_single_param(name, raw_val, parameters)Scale a single parameter using the sigmoid scaling function.
sourceEasyHybrid.scale_single_param_minmax Method
scale_single_param_minmax(name, hm::AbstractHybridModel)Scale a single parameter using the minmax scaling function.
sourceEasyHybrid.split_data Function
split_data(data, hybridModel; split_by_id=nothing, shuffleobs=false, split_data_at=0.8, kwargs...)
split_data(data::Union{DataFrame, KeyedArray}, hybridModel; split_by_id=nothing, shuffleobs=false, split_data_at=0.8, folds=nothing, val_fold=nothing, kwargs...)
split_data(data::AbstractDimArray, hybridModel; split_by_id=nothing, shuffleobs=false, split_data_at=0.8, kwargs...)
split_data(data::Tuple, hybridModel; split_by_id=nothing, shuffleobs=false, split_data_at=0.8, kwargs...)
split_data(data::Tuple{Tuple, Tuple}, hybridModel; kwargs...)Split data into training and validation sets, either randomly, by grouping by ID, or using external fold assignments.
Arguments:
data: The data to split, which can be a DataFrame, KeyedArray, AbstractDimArray, or TuplehybridModel: The hybrid model object used for data preparationsplit_by_id=nothing: Eithernothingfor random splitting, aSymbolfor column-based splitting, or anAbstractVectorfor custom ID-based splittingshuffleobs=false: Whether to shuffle observations during splittingsplit_data_at=0.8: Ratio of data to use for trainingfolds: Vector or column name of fold assignments (1..k), one per sample/column for k-fold cross-validationval_fold: The validation fold to use whenfoldsis providedsequence_kwargs=nothing: NamedTuple of keyword arguments forwarded tosplit_into_sequences(e.g.(; input_window=10, output_window=1, output_shift=1, lead_time=2)). When set, data is windowed into 3D sequences before splitting.
Behavior:
For DataFrame/KeyedArray: Supports random splitting, ID-based splitting, and external fold assignments
For AbstractDimArray/Tuple: Random splitting only after data preparation
For pre-split Tuple{Tuple, Tuple}: Returns input unchanged
Returns:
((x_train, y_train), (x_val, y_val)): Tuple containing training and validation data pairs
EasyHybrid.split_data Method
split_data(df::DataFrame, target, xvars, seqID; f=0.8, batchsize=32, shuffle=true, partial=true)
sourceEasyHybrid.split_data Method
split_data(df::DataFrame, target, xvars; f=0.8, batchsize=32, shuffle=true, partial=true)
sourceEasyHybrid.split_into_sequences Method
split_into_sequences(x, y; input_window=5, output_window=1, output_shift=1, lead_time=1)Slide a (input_window + lead_time) window over 2D (feature, time) arrays to produce 3D (feature, time, batch) tensors for sequence-to-sequence training.
Arguments:
x: 2D input array(feature, time), or a(x_array, forcings)Tuple fromprepare_data.y: 2D target array(target, time), or aNamedTupleof 1D target vectors.input_window: number of input time steps per sample.output_window: number of target time steps per sample.output_shift: stride between consecutive samples.lead_time: gap between end of input window and end of output window.
Returns:
(X, Y)as 3D arrays. Whenxis a Tuple, returns((X, forcings_windowed), Y).
EasyHybrid.split_seq2seq Method
split_seq2seq(x, forcings, y; enc_window, dec_window, lead_time=1, stride=1)
split_seq2seq(x, y; enc_window, dec_window, lead_time=1, stride=1)Slides a window over N-dimensional arrays (where time is the last dimension) to generate (Encoder Input, Decoder Input, Target) tuples for Sequence-to-Sequence Modeling.
Arguments:
x: Historical covariates array(..., Features, Time).forcings: Known future covariates array(..., Features, Time). Can be omitted if not used.y: Target array(..., Features, Time).enc_window: Length of the historical past to feed the Encoder.dec_window: Length of the future horizon to feed the Decoder and predict.lead_time: Gap between the end of the encoder window and start of the decoder window. Uselead_time=1for Forecasting (predicting next steps). Uselead_time=0for Regression (predicting concurrently with the last encoder step).stride: Step size between consecutive sliding windows.
Returns:
enc_x: Array of shape(..., Features, enc_window, Batch)dec_x: Array of shape(..., Features, dec_window, Batch)(ornothingifforcingsis omitted)y_target: Array of shape(..., Features, dec_window, Batch)
EasyHybrid.toDataFrame Method
toDataFrame(arr, target_names)Extract specific target variables from a labeled array into a DataFrame with _pred suffix.
Arguments
arr: A labeled array or NamedTuple-like object with property accesstarget_names: Vector of target variable names to extract
Returns
DataFramewith columns named<target>_predfor each target
EasyHybrid.toDataFrame Method
toDataFrame(nt::NamedTuple, target_names, y_ref::NamedTuple)Extract targets from prediction NamedTuple nt into a DataFrame with _pred suffix. When the prediction has more rows than the reference y_ref (e.g. input_window > output_window), the last output_window rows are selected before flattening.
EasyHybrid.toDataFrame Method
toDataFrame(nt::NamedTuple)Convert a NamedTuple of arrays (vectors or matrices) into a DataFrame. Matrix values are flattened via vec so each key becomes a single column.
EasyHybrid.toDataFrame Method
toDataFrame(arr::Union{KeyedArray{T, 2}, AbstractDimArray{T, 2}}, cols_dim=:variable, index_dim=:batch_size; index_col=:index)Convert a 2D labeled array (KeyedArray or DimArray) to a DataFrame.
Arguments
arr: The 2D labeled array to convertcols_dim: Dimension name to use as DataFrame columns (default::variable)index_dim: Dimension name to use as DataFrame row index (default::batch_size)index_col: Name for the index column in the result (default::index)
Returns
DataFramewith columns fromcols_dimkeys and an index column fromindex_dimkeys
EasyHybrid.toDataFrame Method
toDataFrame(arr::AbstractLabeledArray{T, 3}, cols_dim=:variable, index_dim=:batch_size; slice_dim=:time, index_col=:index)Convert a 3D labeled array (KeyedArray or DimArray) to a Dict of DataFrames, one per slice.
Arguments
arr: The 3D labeled array to convertcols_dim: Dimension name to use as DataFrame columns (default::variable)index_dim: Dimension name to use as DataFrame row index (default::batch_size)slice_dim: Dimension name to slice along (default::time)index_col: Name for the index column in each result DataFrame (default::index)
Returns
Dict{Any, DataFrame}mapping slice keys to DataFrames
EasyHybrid.toNamedTuple Method
toNamedTuple(ka::KeyedArray, variable::Symbol) Extract a single variable from a KeyedArray and return it as a vector.
Arguments:
ka: The KeyedArray or DimArray to unpackvariable: Symbol representing the variable to extract
Returns:
- Vector containing the variable data
Example:
# Extract just SW_IN from an array
sw_in = toNamedTuple(ds, :SW_IN)EasyHybrid.toNamedTuple Method
toNamedTuple(ka::Union{KeyedArray, AbstractDimArray}, variables::Vector{Symbol})Extract specified variables from a KeyedArray or DimArray and return them as a NamedTuple of vectors.
Arguments:
ka: The KeyedArray or DimArray to unpackvariables: Vector of symbols representing the variables to extract
Returns:
- NamedTuple with variable names as keys and vectors as values
Example:
# Extract SW_IN and TA from an array
data = toNamedTuple(ds, [:SW_IN, :TA])
sw_in = data.SW_IN
ta = data.TAEasyHybrid.toNamedTuple Method
toNamedTuple(ka::KeyedArray) Extract all variables from a KeyedArray and return them as a NamedTuple of vectors.
Arguments:
ka: The KeyedArray to unpack
Returns:
- NamedTuple with all variable names as keys and vectors as values
Example:
# Extract all variables from an array
data = toNamedTuple(ds)
# Access individual variables
sw_in = data.SW_IN
ta = data.TA
nee = data.NEEEasyHybrid.to_keyedArray Function
tokeyedArray(dfg::Union{Vector,GroupedDataFrame{DataFrame}}, vars=All())
sourceEasyHybrid.train Method
train(model, data; train_cfg::TrainConfig = TrainConfig(), data_cfg::DataConfig = DataConfig())
train(model, data; kwargs...)Train a hybrid model using the provided data.
Two equivalent calling styles are supported:
- Typed configs — pass complete
TrainConfig/DataConfigobjects:
train(model, data;
train_cfg = TrainConfig(nepochs=100, batchsize=32),
data_cfg = DataConfig(split_data_at=0.8),
)- Flat kwargs — pass
TrainConfig/DataConfigfield names directly:
train(model, data; nepochs=100, batchsize=32, split_data_at=0.8)The two styles can also be mixed; flat kwargs override the corresponding fields of the supplied train_cfg / data_cfg:
train(model, data; train_cfg = TrainConfig(nepochs=100), nepochs = 10) # nepochs = 10Returns nothing if data preparation fails (zero-size dimension in training or validation data).
Arguments
model: The hybrid model to train.data: Training data, a singleDimArray, a singleDataFrame, or a singleKeyedArray.
Keyword Arguments
train_cfg: Training configuration. SeeTrainConfigfor all options.data_cfg: Data preparation configuration. SeeDataConfigfor all options.Any other kwargs are forwarded as overrides to
train_cfg/data_cfg.
Returns
A TrainResults with the following fields:
train_losses: Per-epoch training losses.val_losses: Per-epoch validation losses.snapshots: Model parameter snapshots taken during training.train_obs_pred: Observed vs. predicted values on the training set.val_obs_pred: Observed vs. predicted values on the validation set.train_diffs: Additional diagnostic variables computed on the training set.val_diffs: Additional diagnostic variables computed on the validation set.ps: Final (or best) model parameters.st: Final (or best) model state.best_epoch: Epoch at which the best validation loss was achieved.best_loss: Best validation loss recorded during training.
EasyHybrid.tune Method
tune(hybrid_model, data, mspec::ModelSpec; kwargs...)
tune(hybrid_model, data; kwargs...)
tune(hybrid_model, data, train_cfg::TrainConfig; data_cfg::DataConfig = DataConfig(), kwargs...)Construct a new hybrid model from hybrid_model plus hyperparameters, then call train.
Returns a TrainResults (or nothing if data preparation fails, as in train).
EasyHybrid.weight_l2 Method
weight_l2(ps; key=:weight, normalize=false) -> RealSum of squared Frobenius norms over all parameter arrays in ps whose immediate parent field name is key (default :weight).
With normalize=true, returns the mean squared weight (sum divided by the number of scalar weights), so the value is independent of network width/depth.
Unlike sum(abs2, extract_weights(ps)), this fuses the tree walk with the reduction so it is safe to use inside Zygote-differentiated losses, e.g.:
extra_loss = (ŷ, ps) -> (; l2_Rb = λ * weight_l2(ps.Rb; normalize=true),)When ps is the loss function argument, gradients flow into the weight arrays.
EasyHybrid.@hybrid Macro
@hybrid ModelName α β γMacro to define hybrid model structs with arbitrary numbers of physical parameters.
This defines a struct with:
Default fields:
NN(neural network),predictors,forcing,targets.Additional physical parameters, i.e.,
α β γ.
Examples
@hybrid MyModel α β γ
@hybrid FluidModel (:viscosity, :density)
@hybrid SimpleModel :a :bEasyHybrid.Transformers.EncoderDecoderModel Type
EncoderDecoderModel(; in_features, dec_features, d_model, enc_layers, dec_layers, n_heads, n_kv_heads=n_heads, out_features, norm_eps=1.0f-5, dropout_rate=0.0f0, stem=nothing)Creates a sequence-to-sequence Encoder-Decoder Transformer using GroupedQueryAttention for self-attention and MultiHeadSelfAttention for cross-attention.
Arguments
in_features: Number of input features for the encoderdec_features: Number of input features for the decoder (shifted targets or covariates)d_model: Dimensionality of the model's hidden statesenc_layers: Number of Transformer blocks in the encoderdec_layers: Number of Transformer blocks in the decodern_heads: Number of query attention headsn_kv_heads: Number of key/value attention heads (defaults ton_heads)out_features: Dimensionality of the final output projectionnorm_eps: Epsilon value for RMSNorm stabilitydropout_rate: Dropout probability applied to attention and feedforward layersstem: Optional Lux layer to apply as a feature extractor before encoder embedding
Returns
- An
EncoderDecoderModelcontainer layer
EasyHybrid.Transformers.EncoderDecoderModel Method
(m::EncoderDecoderModel)(enc_x, dec_x, ps, st; enc_causal=false, dec_causal=true)Forward pass for the sequence-to-sequence EncoderDecoderModel.
Arguments
m: TheEncoderDecoderModelenc_x: Encoder input sequence datadec_x: Decoder input sequence data (e.g. shifted targets)ps: Model parametersst: Model stateenc_causal: Boolean kwarg (defaultfalse). Iftrue, applies causal masking to the encoder.dec_causal: Boolean kwarg (defaulttrue). Iftrue, applies causal masking to the decoder self-attention.
Returns
(out, new_st): A tuple containing the model predictions and updated state
EasyHybrid.Transformers.FeatureEmbedding Type
FeatureEmbedding(in_features::Int, d_model::Int)Creates a FeatureEmbedding layer for continuous/multivariate sequences. Unlike standard TokenEmbeddings (which use dictionaries for discrete tokens), this projects continuous features into the hidden d_model dimension via a Dense layer.
Arguments
in_features: Number of input features/covariates per timestepd_model: Dimensionality of the model's hidden states
Returns
- A
FeatureEmbeddingcontainer layer
EasyHybrid.Transformers.FeatureEmbedding Method
(m::FeatureEmbedding)(x, ps, st)Forward pass for the continuous FeatureEmbedding.
Arguments
m: TheFeatureEmbeddinglayerx: Input sequence data of shape(in_features, seq_len, batch)ps: Model parametersst: Model state
Returns
(y, st_out): Projected features of shape(d_model, seq_len, batch)and updated state
EasyHybrid.Transformers.FeedForward Type
FeedForward(dim::Int; multiple_of::Int=256, ffn_dim_multiplier::Union{Float64, Nothing}=nothing, dropout_rate=0.0f0)Implements a SwiGLU FeedForward network. This block projects the input to a higher dimension, applies a Swish (SiLU) gating mechanism, and projects it back to the original dimension, offering better gradient flow than standard GELU MLPs.
Arguments
dim: Dimensionality of the model's hidden states (d_model)multiple_of: Ensures the hidden dimension is a multiple of this value for hardware efficiencyffn_dim_multiplier: Optional multiplier to explicitly scale the hidden dimensiondropout_rate: Dropout probability applied to the final projection
Returns
- A
FeedForwardcontainer layer
EasyHybrid.Transformers.FeedForward Method
(m::FeedForward)(x, ps, st)Forward pass for the SwiGLU FeedForward network.
Arguments
m: TheFeedForwardlayerx: Input sequence data of shape(d_model, seq_len, batch)ps: Model parametersst: Model state
Returns
(y, st_out): Projected sequence and updated state
EasyHybrid.Transformers.GroupedQueryAttention Type
GroupedQueryAttention(dim::Int, n_heads::Int, n_kv_heads::Int; dropout_rate=0.0f0)Implements Grouped Query Attention (GQA). GQA reduces memory and computational overhead during autoregressive generation by sharing key and value projections across multiple query heads.
sourceEasyHybrid.Transformers.GroupedQueryAttention Method
(m::GroupedQueryAttention)(x, ps, st; context=nothing, mask=nothing)Standard forward pass for Grouped Query Attention without KVCache (e.g. for ViT or standard training).
Arguments
m: TheGroupedQueryAttentionlayerx: Input sequence data of shape(dim, seq_len, batch)ps: Model parametersst: Model statecontext: Optional context sequence for cross-attentionmask: Optional attention mask
Returns
(out, st_out): Attended sequence and updated state
EasyHybrid.Transformers.GroupedQueryAttention Method
GroupedQueryAttention(dim::Int, n_heads::Int, n_kv_heads::Int; dropout_rate=0.0f0)Implements Grouped Query Attention (GQA). GQA reduces memory and computational overhead during autoregressive generation by sharing key and value projections across multiple query heads.
Arguments
dim: Dimensionality of the model's hidden states (d_model)n_heads: Number of query attention headsn_kv_heads: Number of key/value attention headsdropout_rate: Dropout probability applied to the final attention projection
Returns
- A
GroupedQueryAttentioncontainer layer
EasyHybrid.Transformers.MultiHeadSelfAttention Type
MultiHeadSelfAttention(d_model, n_heads; dropout_rate=0.0f0)Standard Multi-Head Self Attention, primarily used for Cross-Attention in Encoder-Decoder architectures.
Arguments
d_model: Dimensionality of the model's hidden statesn_heads: Number of attention headsdropout_rate: Dropout probability applied to the final attention projection
Returns
- A
MultiHeadSelfAttentioncontainer layer
EasyHybrid.Transformers.MultiHeadSelfAttention Method
(m::MultiHeadSelfAttention)(x, ps, st; context=nothing, mask=nothing)Forward pass for standard Multi-Head Self Attention.
Arguments
m: TheMultiHeadSelfAttentionlayerx: Input sequence data of shape(d_model, seq_len, batch)(used for Queries, and Keys/Values ifcontextisnothing)ps: Model parametersst: Model statecontext: Optional context sequence of shape(d_model, context_len, batch)for cross-attention (Keys/Values)mask: Optional attention mask
Returns
(out, st_out): Attended sequence and updated state
EasyHybrid.Transformers.PatchEmbedding Type
PatchEmbedding(patch_size::Tuple, in_channels::Int, d_model::Int; ndims::Int=2)Creates a PatchEmbedding for Vision Transformers using a Convolutional layer.
Arguments
patch_size: Tuple defining the patch dimensionsin_channels: Number of input channelsd_model: Dimensionality of the model's hidden statesndims: 2 for spatial grids (images), 3 for spatio-temporal grids (video/climate)
Returns
- A
PatchEmbeddingcontainer layer
EasyHybrid.Transformers.PatchEmbedding Method
(m::PatchEmbedding)(x, ps, st)Forward pass for PatchEmbedding.
Arguments
m: ThePatchEmbeddinglayerx: Input data of shape(W, H, C, B)for 2D or(W, H, T, C, B)for 3Dps: Model parametersst: Model state
Returns
(y, st_out): Flattened sequence of patches of shape(d_model, seq_len, batch)and updated state
EasyHybrid.Transformers.PatchUnEmbedding Type
PatchUnEmbedding(patch_size::Tuple, d_model::Int, out_channels::Int, grid_size::Tuple; ndims::Int=2)Creates a PatchUnEmbedding layer to reconstruct spatial or spatio-temporal grids from a sequence of patches. This is the reverse of PatchEmbedding.
Arguments
patch_size: Tuple defining the patch dimensionsd_model: Dimensionality of the model's hidden statesout_channels: Number of output channels for the reconstructed gridgrid_size: The number of patches in each spatial/temporal dimension (e.g.,(W', H')for 2D).ndims: 2 for spatial grids (images), 3 for spatio-temporal grids (video/climate)
Returns
- A
PatchUnEmbeddingcontainer layer
EasyHybrid.Transformers.PatchUnEmbedding Method
(m::PatchUnEmbedding)(x, ps, st)Forward pass for PatchUnEmbedding.
Arguments
m: ThePatchUnEmbeddinglayerx: Input sequence of patches of shape(d_model, seq_len, batch)ps: Model parametersst: Model state
Returns
(out, st_out): Reconstructed grid of shape(W, H, C, B)for 2D or(W, H, T, C, B)for 3D and updated state
EasyHybrid.Transformers.PositionEmbedding Type
PositionEmbedding(max_positions::Integer, d_model::Integer; dim::Int=2)Creates a learned additive Positional Embedding layer. Typically used when RoPE is disabled.
Arguments
max_positions: The maximum sequence length supportedd_model: Dimensionality of the model's hidden statesdim: The dimension along which to add the embeddings (default2forseq_len)
Returns
- A
PositionEmbeddinglayer
EasyHybrid.Transformers.PositionEmbedding Method
(m::PositionEmbedding)(x, ps, st)Forward pass for the additive PositionEmbedding.
Arguments
m: ThePositionEmbeddinglayerx: Hidden states of shape(d_model, seq_len, batch)ps: Model parametersst: Model state
Returns
(y, st): Hidden states with positional embeddings added, and updated state
EasyHybrid.Transformers.PrefixTokens Type
PrefixTokens(embed_dim::Int; use_cls_token::Bool=true, n_register_tokens::Int=0)Creates a layer that maintains learned [CLS] and [REGISTER] tokens. During the forward pass, these tokens are repeated for the batch size and prepended to the sequence.
Arguments
embed_dim: Dimensionality of the model's hidden statesuse_cls_token: If true, includes a single [CLS] tokenn_register_tokens: Number of [REGISTER] (storage) tokens to include
Returns
- A
PrefixTokenslayer
EasyHybrid.Transformers.TransformerBlock Type
TransformerBlock(dim, n_heads, n_kv_heads; norm_eps=1.0f-5, cross_attention=false, dropout_rate=0.0f0)Creates a State-of-the-Art Transformer Block using RMSNorm, SwiGLU FeedForward, and GroupedQueryAttention. If cross_attention is true, an additional MultiHeadSelfAttention block is added for Encoder-Decoder architectures.
Arguments
dim: Dimensionality of the model's hidden states (d_model)n_heads: Number of query attention headsn_kv_heads: Number of key/value attention headsnorm_eps: Epsilon value for RMSNorm stabilitycross_attention: Iftrue, adds a cross-attention layer (e.g. for decoders)dropout_rate: Dropout probability applied to attention and feedforward layerslayer_scale_init: Initial value for LayerScale (e.g., 1e-5). If nothing, LayerScale is not used.
Returns
- A
TransformerBlockcontainer layer
EasyHybrid.Transformers.TransformerBlock Method
(m::TransformerBlock)(x, ps, st; cosf=nothing, sinf=nothing, context=nothing, mask=nothing)Forward pass for a single TransformerBlock.
Arguments
m: TheTransformerBlocklayerx: Input sequence dataps: Model parametersst: Model statecosf: Optional precomputed cosine frequencies for RoPEsinf: Optional precomputed sine frequencies for RoPEcontext: Optional context sequence for cross-attentionmask: Optional attention mask
Returns
(y, st_out): Processed sequence and updated state
EasyHybrid.Transformers.TransformerModel Type
TransformerModel(; in_features, d_model, n_layers, n_heads, n_kv_heads=n_heads, out_features, dropout_rate=0.0f0, stem=nothing, max_positions=nothing, norm_eps=1.0f-5)Creates a continuous sequence TransformerModel using GroupedQueryAttention and RMSNorm. Optionally accepts a stem (e.g. a CNN or LSTM) to act as a feature extractor before embedding.
Arguments
in_features: Number of input features/covariates per timestepd_model: Dimensionality of the model's hidden statesn_layers: Number of Transformer blocksn_heads: Number of query attention headsn_kv_heads: Number of key/value attention heads (defaults ton_heads)out_features: Dimensionality of the final output projectiondropout_rate: Dropout probability applied to attention and feedforward layersstem: Optional Lux layer to apply as a feature extractor before embeddingmax_positions: Unused placeholder for additive embeddings if needed laternorm_eps: Epsilon value for RMSNorm stabilitylayer_scale_init: Initial value for LayerScale (e.g., 1e-5). If nothing, LayerScale is not used.
Returns
- A
TransformerModelcontainer layer
EasyHybrid.Transformers.TransformerModel Method
(m::TransformerModel)(x, ps, st; causal=false)Forward pass for the continuous sequence TransformerModel.
Arguments
m: TheTransformerModelmodelx: Input sequence data of shape(in_features, seq_len, batch)or(spatial..., batch)if stem is usedps: Model parametersst: Model statecausal: Boolean kwarg (defaultfalse). Iftrue, applies a causal upper-triangular mask to prevent peeking into the future.
Returns
(y, st_out): A tuple containing the model predictions and updated state
EasyHybrid.Transformers.TransformerStack Type
TransformerStack(layers::Union{Vector, Tuple})A sequential container for Transformer blocks. While Lux.Chain passes inputs through a sequence of layers, TransformerStack is specifically designed to correctly propagate arbitrary keyword arguments (such as mask, cache, and RoPE frequencies) down to each individual block, which is required for advanced attention mechanisms.
Arguments
layers: A Tuple or Vector ofTransformerBlocklayers
Returns
- A
TransformerStackcontainer layer
EasyHybrid.Transformers.TransformerStack Method
(m::TransformerStack)(x, ps, st; kwargs...)Forward pass for the TransformerStack. Passes x sequentially through each TransformerBlock in the stack. Arbitrary keyword arguments (like mask, cache, start_pos, cosf, sinf, context) are correctly propagated to every block.
Arguments
m: TheTransformerStackcontainerx: Input sequence dataps: Model parametersst: Model statekwargs...: Optional arguments passed down to the blocks
Returns
(y, st_new): Processed sequence and updated state
EasyHybrid.Transformers.VisionToVisionModel Type
VisionToVisionModel(; patch_size, grid_size, in_channels, out_channels, d_model, n_layers, n_heads, n_kv_heads=n_heads, max_positions, ndims=2, use_rope=false, dropout_rate=0.0f0, stem=nothing, norm_eps=1.0f-5)Creates a Vision-to-Vision Transformer (e.g. for Image-to-Image regression or Grid-to-Grid forecasting). The input grid is processed via PatchEmbedding and the output sequence is reconstructed back into a grid via PatchUnEmbedding.
Arguments
patch_size: Tuple defining the spatial/spatio-temporal dimensions of each patchgrid_size: Tuple defining the number of patches in each dimension (e.g.,(W', H'))in_channels: Number of input channelsout_channels: Number of output channels for the reconstructed gridd_model: Dimensionality of the model's hidden statesn_layers: Number of Transformer blocksn_heads: Number of query attention headsn_kv_heads: Number of key/value attention heads (defaults ton_heads)max_positions: Maximum number of positions for additive positional embeddingsndims: Number of spatial/spatio-temporal dimensions (2 for spatial, 3 for spatio-temporal)use_rope: If true, uses Rotary Positional Embeddings instead of additive embeddingsdropout_rate: Dropout probabilitystem: Optional Lux layer to apply before patch embeddingnorm_eps: Epsilon value for RMSNorm stabilityuse_cls_token: If true, prepends a [CLS] token.n_register_tokens: Number of [REGISTER] tokens to prepend.layer_scale_init: Initial value for LayerScale (e.g., 1e-5). If nothing, LayerScale is not used.
Returns
- A
VisionToVisionModelcontainer layer
EasyHybrid.Transformers.VisionToVisionModel Method
(m::VisionToVisionModel)(x, ps, st)Forward pass for the Vision-to-Vision Transformer model.
Arguments
m: TheVisionToVisionModelmodelx: Input grid of shape(W, H, C, B)for 2D or(W, H, T, C, B)for 3Dps: Model parametersst: Model state
Returns
(y, st_out): Predicted output grid of shape(W, H, out_channels, B)(or 3D equivalent) and updated state
EasyHybrid.Transformers.VisionTransformer Type
VisionTransformer(; patch_size, in_channels, d_model, n_layers, n_heads, n_kv_heads=n_heads, max_positions, num_classes, ndims=2, use_rope=false, dropout_rate=0.0f0, stem=nothing, norm_eps=1.0f-5)Creates a Vision Transformer. If stem is provided, it acts as a Hybrid feature extractor before the PatchEmbedding.
Arguments
patch_size: Tuple defining the spatial/spatio-temporal dimensions of each patchin_channels: Number of input channels (e.g., 1 for single variable, 3 for RGB)d_model: Dimensionality of the model's hidden statesn_layers: Number of Transformer blocksn_heads: Number of query attention headsn_kv_heads: Number of key/value attention heads (defaults ton_headsfor standard attention, less for GQA)max_positions: Maximum number of positions for additive positional embeddingsnum_classes: Dimensionality of the final output (e.g., number of classes for classification)ndims: Number of spatial/spatio-temporal dimensions (2 for spatial, 3 for spatio-temporal)use_rope: If true, uses Rotary Positional Embeddings instead of additive embeddingsdropout_rate: Dropout probability applied to attention and feedforward layersstem: Optional Lux layer to apply before patch embedding (e.g., a CNN for Hybrid ViT)norm_eps: Epsilon value for RMSNorm stabilityuse_cls_token: If true, prepends a [CLS] token and uses it for the final output instead of GAP.n_register_tokens: Number of [REGISTER] tokens to prepend.layer_scale_init: Initial value for LayerScale (e.g., 1e-5). If nothing, LayerScale is not used.
Returns
- A
VisionTransformercontainer layer
EasyHybrid.Transformers.VisionTransformer Method
(m::VisionTransformer)(x, ps, st)Forward pass for the Vision Transformer model.
Arguments
m: TheVisionTransformermodelx: Input data of shape(W, H, C, B)for 2D or(W, H, T, C, B)for 3Dps: Model parametersst: Model state
Returns
(y, st_out): A tuple containing the model predictions (logits) and updated state
EasyHybrid.Transformers.VisionEncoderDecoderModel Method
VisionEncoderDecoderModel(; patch_size, in_channels, dec_features, d_model, enc_layers, dec_layers, n_heads, n_kv_heads=n_heads, out_features, ndims=2, norm_eps=1.0f-5, dropout_rate=0.0f0, stem=nothing)Creates a sequence-to-sequence Encoder-Decoder Transformer where the Encoder processes spatial or spatio-temporal data (via PatchEmbedding) and the Decoder processes continuous sequential covariates (via FeatureEmbedding).
Arguments
patch_size: Tuple defining the spatial/spatio-temporal dimensions of each encoder patchin_channels: Number of input channels for the encoder griddec_features: Number of input features for the decoder (shifted targets or covariates)d_model: Dimensionality of the model's hidden statesenc_layers: Number of Transformer blocks in the encoderdec_layers: Number of Transformer blocks in the decodern_heads: Number of query attention headsn_kv_heads: Number of key/value attention heads (defaults ton_heads)out_features: Dimensionality of the final output projectionndims: Number of spatial/spatio-temporal dimensions for the encoder (2 or 3)norm_eps: Epsilon value for RMSNorm stabilitydropout_rate: Dropout probability applied to attention and feedforward layersstem: Optional Lux layer to apply as a feature extractor before patch embedding
Returns
- A
EncoderDecoderModelconfigured for vision-to-sequence tasks
EasyHybrid.Transformers.VisionToVisionEncoderDecoderModel Method
VisionToVisionEncoderDecoderModel(; patch_size, grid_size, in_channels, dec_channels, out_channels, d_model, enc_layers, dec_layers, n_heads, n_kv_heads=n_heads, ndims=2, norm_eps=1.0f-5, dropout_rate=0.0f0, stem=nothing)Creates a sequence-to-sequence Encoder-Decoder Transformer where BOTH inputs and outputs are spatial or spatio-temporal grids. The Encoder processes the historical maps, the Decoder processes known future covariate maps, and the Output predicts target future maps.
Arguments
patch_size: Tuple defining the spatial/spatio-temporal dimensions of each patchgrid_size: Tuple defining the number of patches in each dimension (e.g.,(W', H'))in_channels: Number of input channels for the encoder griddec_channels: Number of input channels for the decoder grid (covariates)out_channels: Number of output channels for the reconstructed prediction gridd_model: Dimensionality of the model's hidden statesenc_layers: Number of Transformer blocks in the encoderdec_layers: Number of Transformer blocks in the decodern_heads: Number of query attention headsn_kv_heads: Number of key/value attention headsndims: Number of spatial/spatio-temporal dimensions (2 or 3)norm_eps: Epsilon value for RMSNorm stabilitydropout_rate: Dropout probabilitystem: Optional Lux layer to apply as a feature extractor before patch embedding
Returns
- A
EncoderDecoderModelconfigured for grid-to-grid forecasting tasks
EasyHybrid.Transformers.apply_rotary_embeddings Method
apply_rotary_embeddings(x::AbstractArray{T, 4}, cosf, sinf) where {T}Applies the precomputed rotary positional embeddings to a 4D tensor x.
How it works: It splits the head_dim (feature dimension) in half and applies a 2D rotation. Because it directly rotates the hidden representations in the complex plane, the dot product between any Query and Key during attention will naturally decay based on their relative distance in the sequence. This injects highly effective, shift-invariant positional context without adding any learned parameters to the network.
Arguments
x: Input tensor of shape(head_dim, n_heads, seq_len, batch)(either Queries or Keys)cosf: Precomputed cosine frequencies fromprecompute_rope_freqssinf: Precomputed sine frequencies fromprecompute_rope_freqs
Returns
- A rotated tensor of the same shape as
x
EasyHybrid.Transformers.extract_features Method
extract_features(m::Union{VisionTransformer, VisionToVisionModel}, x, ps, st; n_blocks::Union{Int, Nothing} = 1, blocks::Union{AbstractVector{Int}, Nothing} = nothing)Extracts the spatial/spatio-temporal features from intermediate blocks of the VisionTransformer. This is particularly useful when using a pretrained LingBot-Vision or DINOv2 model as a frozen feature extractor for downstream dense prediction tasks (like depth estimation or segmentation).
Arguments
m: TheVisionTransformerorVisionToVisionModellayerx: Input data of shape(W, H, C, B)for 2D or(W, H, T, C, B)for 3Dps: Model parametersst: Model staten_blocks: The number of intermediate blocks to extract from the end of the transformer (default: 1).blocks: Specific block indices to extract (e.g.[1, 3, 5]). If provided,n_blocksis ignored.
Returns
- A tuple of tensors, one for each extracted block, reshaped back into spatial grids of shape
(W', H', d_model, B)for 2D or(W', H', T', d_model, B)for 3D.
EasyHybrid.Transformers.precompute_rope_freqs Method
precompute_rope_freqs(x::AbstractArray, head_dim::Int, max_seq_len::Int; theta::Float32 = 10_000f0)Precomputes the trigonometric frequencies (cosine and sine) needed for Rotary Positional Embeddings (RoPE).
Why RoPE matters: Unlike standard additive positional embeddings (which encode absolute positions as fixed, learned vectors), RoPE encodes relative positional information directly into the attention mechanism's Queries and Keys using complex rotations. This allows the model to:
Extrapolate to sequence lengths (or spatial grids) longer than those seen during training.
Intuitively understand the relative distance between tokens, making it incredibly robust for spatial-temporal grids and continuous time-series where boundaries might shift.
Arguments
head_dim: The dimensionality of each attention headmax_seq_len: The maximum sequence length to precompute frequencies fortheta: The base for the inverse frequency computation (default10_000f0)
Returns
(cosf, sinf): A tuple of cosine and sine frequency matrices, each of shape(head_dim/2, max_seq_len)