RobustNeuralNetworks.AbstractLBDNRobustNeuralNetworks.AbstractRENRobustNeuralNetworks.DiffLBDNRobustNeuralNetworks.DiffRENRobustNeuralNetworks.LBDNRobustNeuralNetworks.RENRobustNeuralNetworks.SandwichFCRobustNeuralNetworks.WrapREN
Model Wrappers
Lipschitz-Bounded Deep Networks
RobustNeuralNetworks.AbstractLBDN — Typeabstract type AbstractLBDN{T, L} endExplicit parameterisation for Lipschitz-bounded deep networks.
(m::AbstractLBDN)(u::AbstractVecOrMat)Call and AbstractLBDN model given inputs u.
If arguments are matrices, each column must be a vector of inputs (allows batch simulations).
Examples
This example creates a dense LBDN using DenseLBDNParams and calls the model with some randomly generated inputs.
using Random
using RobustNeuralNetworks
# Setup
rng = Xoshiro(42)
batches = 10
γ = 20.0
# Model with 4 inputs, 1 ouput, 4 hidden layers
nu, ny = 4, 1
nh = [5, 10, 5, 15]
lbdn_ps = DenseLBDNParams{Float64}(nu, nh, ny, γ; rng)
lbdn = LBDN(lbdn_ps)
# Evaluate model with a batch of random inputs
u = 10*randn(rng, nu, batches)
y = lbdn(u)
println(round.(y; digits=2))
# output
[-1.11 -1.01 -0.07 -2.25 -4.22 -1.76 -3.82 -1.13 -11.85 -3.01]RobustNeuralNetworks.DiffLBDN — TypeDiffLBDN(ps::AbstractLBDNParams)Construct a differentiable LBDN from its direct parameterisation.
DiffLBDN is an alternative to LBDN that computes the explicit parameterisation ExplicitLBDNParams each time the model is called, rather than storing it in the LBDN object.
This model wrapper is easiest to use if you plan to update the model parameters after every call of the model. It can be trained just like any other Flux.jl model and does not need to be re-created if the trainable parameters are updated (unlike LBDN).
However, it is slow and computationally inefficient if the model is called many times before updating the parameters (eg: in reinforcement learning).
Examples
The syntax to construct a DiffLBDN is identical to that of an LBDN.
using RobustNeuralNetworks
nu, nh, ny, γ = 1, [10, 20], 1
lbdn_params = DenseLBDNParams{Float64}(nu, nh, ny, γ)
model = DiffLBDN(lbdn_params)See also AbstractLBDN, LBDN, SandwichFC.
RobustNeuralNetworks.LBDN — TypeLBDN(ps::AbstractLBDNParams)Construct an LBDN from its direct parameterisation.
This constructor takes a direct parameterisation of LBDN (eg: a DenseLBDNParams instance) and converts it to a callable explicit parameterisation of the LBDN. An example can be found in the docs for AbstractLBDN.
See also AbstractLBDN, DiffLBDN.
RobustNeuralNetworks.SandwichFC — TypeSandwichFC((in, out), σ::F; <keyword arguments>) where FConstruct a non-expansive "sandwich layer" for a dense (fully-connected) LBDN.
A non-expensive layer is a layer with a Lipschitz bound of exactly 1. This layer is provided as a Lipschitz-bounded alternative to Flux.Dense. Its interface and usage was intentionally designed to be very similar to make it easy to use for anyone familiar with Flux.
Arguments
(in, out)::Pair{<:Integer, <:Integer}: Input and output sizes of the layer.σ::F=identity: Activation function.
Keyword arguments
init::Function=Flux.glorot_normal: Initialisation function for all weights and bias vectors.bias::Bool=true: Include bias vector or not.output_layer::Bool=false: Just the output layer of a dense LBDN or regular sandwich layer.T::DataType=Float32: Data type for weight matrices and bias vectors.rng::AbstractRNG = Random.GLOBAL_RNG: rng for model initialisation.
Examples
We can build a dense LBDN directly using SandwichFC layers. The model structure is described in Equation 8 of Wang & Manchester (2023).
using Flux
using Random
using RobustNeuralNetworks
# Random seed for consistency
rng = Xoshiro(42)
# Model specification
nu = 1 # Number of inputs
ny = 1 # Number of outputs
nh = fill(16,2) # 2 hidden layers, each with 16 neurons
γ = 5 # Lipschitz bound of 5.0
# Set up dense LBDN model
model = Flux.Chain(
(x) -> (√γ * x),
SandwichFC(nu => nh[1], relu; T=Float64, rng),
SandwichFC(nh[1] => nh[2], relu; T=Float64, rng),
(x) -> (√γ * x),
SandwichFC(nh[2] => ny; output_layer=true, T=Float64, rng),
)
# Evaluate on dummy inputs
u = 10*randn(rng, nu, 10)
y = model(u)
println(round.(y;digits=2))
# output
[4.13 4.37 3.22 8.38 4.15 3.71 0.7 2.04 1.78 2.64]See also DenseLBDNParams, DiffLBDN.
Recurrent Equilibrium Networks
RobustNeuralNetworks.AbstractREN — Typeabstract type AbstractREN endExplicit parameterisation for recurrent equilibrium networks.
(m::AbstractREN)(xt::AbstractVecOrMat, ut::AbstractVecOrMat)Call an AbstractREN model given internal states xt and inputs ut.
If arguments are matrices, each column must be a vector of states or inputs (allows batch simulations).
Examples
This example creates a contracting REN using ContractingRENParams and calls the model with some randomly generated inputs.
using Random
using RobustNeuralNetworks
# Setup
rng = Xoshiro(42)
batches = 10
nu, nx, nv, ny = 4, 2, 20, 1
# Construct a REN
contracting_ren_ps = ContractingRENParams{Float64}(nu, nx, nv, ny; rng)
ren = REN(contracting_ren_ps)
# Some random inputs
x0 = init_states(ren, batches; rng)
u0 = randn(rng, ren.nu, batches)
# Evaluate the REN over one timestep
x1, y1 = ren(x0, u0)
println(round.(y1;digits=2))
# output
[-1.49 0.75 1.34 -0.23 -0.84 0.38 0.79 -0.1 0.72 0.54]RobustNeuralNetworks.DiffREN — TypeDiffREN(ps::AbstractRENParams{T}) where TConstruct a differentiable REN from its direct parameterisation.
DiffREN is an alternative to REN and WrapREN that computes the explicit parameterisation ExplicitRENParams every time the model is called, rather than storing it in the REN object.
This model wrapper is easiest to use if you plan to update the model parameters after every call of the model. It an be trained just like any other Flux.jl model (unlike WrapREN) and does not need to be re-created if the trainable parameters are updated (unlike REN).
However, it is slow and computationally inefficient if the model is called many times before updating the parameters (eg: in reinforcement learning).
Examples
The syntax to construct a DiffREN is identical to that of a REN.
using RobustNeuralNetworks
nu, nx, nv, ny = 1, 10, 20, 1
ren_params = ContractingRENParams{Float64}(nu, nx, nv, ny)
model = DiffREN(ren_params)See also AbstractREN, REN, and WrapREN.
RobustNeuralNetworks.REN — TypeREN(ps::AbstractRENParams{T}) where TConstruct a REN from its direct parameterisation.
This constructor takes a direct parameterisation of REN (eg: a GeneralRENParams instance) and converts it to a callable explicit parameterisation of the REN. An example can be found in the docs for AbstractREN.
See also AbstractREN, WrapREN, and DiffREN.
RobustNeuralNetworks.WrapREN — TypeWrapREN(ps::AbstractRENParams{T}) where T[NOTE:] THIS IS A LEGACY WRAPPER AND WILL BE REMOVED IN A FUTURE RELEASE.
Construct REN wrapper from its direct parameterisation.
WrapREN is an alternative to REN that stores the AbstractRENParams and ExplicitRENParams within the same object. This means that a new REN object does not have to be created each time the parameters are updated. Explicit REN parameters must be updated by the user if the direct parameters have changed.
Note that WrapREN cannot be used with Flux.jl, since it relies on mutating the WrapREN instance.
Examples
In this example, we create a REN satisfying some generic behavioural constraints and demonstrate how to update the REN wrapper if model parameters are changed.
using LinearAlgebra
using Random
using RobustNeuralNetworks
# Setup
rng = Xoshiro(42)
batches = 10
nu, nx, nv, ny = 4, 10, 20, 2
Q = Matrix{Float64}(-I(ny))
R = 0.1^2 * Matrix{Float64}(I(nu))
S = zeros(Float64, nu, ny)
# Construct a REN
ren_ps = GeneralRENParams{Float64}(nu, nx, nv, ny, Q, S, R; rng)
ren = WrapREN(ren_ps)
# Some dummy inputs
x0 = init_states(ren, batches; rng)
u0 = randn(rng, ren.nu, batches)
# Evaluate the REN over one timestep
x1, y1 = ren(x0, u0)
# Update the model after changing a parameter
ren.params.direct.B2 .*= rand(rng, size(ren.params.direct.B2)...)
update_explicit!(ren)
println(round(ren.explicit.B2[10];digits=4))
# output
-0.0034See also AbstractREN, REN, and DiffREN.