RobustNeuralNetworks.AbstractLBDN
RobustNeuralNetworks.AbstractREN
RobustNeuralNetworks.DiffLBDN
RobustNeuralNetworks.DiffREN
RobustNeuralNetworks.LBDN
RobustNeuralNetworks.REN
RobustNeuralNetworks.SandwichFC
RobustNeuralNetworks.WrapREN
Model Wrappers
Lipschitz-Bounded Deep Networks
RobustNeuralNetworks.AbstractLBDN
— Typeabstract type AbstractLBDN{T, L} end
Explicit 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 F
Construct 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 end
Explicit 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 T
Construct 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 T
Construct 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.0034
See also AbstractREN
, REN
, and DiffREN
.