A Data-Driven Framework for Certifying Safety in Systems with Black-Box Stochastic Dynamics
By Oliver Schön, Zhengang Zhong, Sadegh Soudjani
How to learn a safety certificate for a system with unknown stochastic dynamics?
Loading diagram...
Loading diagram...
Loading diagram...
Unknown: we don't know the model
Unknown + Noisy: we don't know the model & the output is stochastic
More formally, we are interfacing with
such that the, given the current state xt∈X and control input ut∈U, the next state xt+1∈X is given by
xt+1=f(xt,ut,wt),wt∼PwSystem safety can be proven via a barrier certificate.
More formally, given
we want to learn a function B:X→R and constants γ≥η≥0, c≥0 such that
(i) (ii) (iii) (iv) ∀x∈X∀x0∈X0∀xu∈Xu∀x∈XB(x)≥0B(x0)≤ηB(xu)≥γE[B(xt+1)∣X=x]−B(x)≤cIf the barrier exists, for the horizon T∈N we have
P(System safe)≥1−γη−cTThe main obstacle to learning the barrier is the unknown, stochastic, non-linear dynamics.
We must gather information by collecting data from the system via sampling N∈N times
Loading diagram...
A symmetric positive definite function k:X×X→R defines a reproducing kernel Hilbert space (RKHS) HkX of functions f:X→R.
Kernels have a useful reproducing property:
f(x)=⟨f,k(x,⋅)⟩HkX∀x∈X,f∈HkXTip
If the dynamics ∈HkX, we can reproduce them via k.
Multiple kernels exist, each with different properties.
Given two RKHSs HkX and HkY, the CME of a conditional probability measure P:X×B(Y)→[0,1] is an X-measurable random variable taking values in HkY given by
μkY∣kX(P)(⋅):=EP[ϕkY(Y)∣X=⋅]Using the CME, we can rewrite the last barrier condition as
(iv) ∀x∈X⟨B,μX+∣X(f)(x)⟩Hk−B(x)≤c−constantεC∥B∥HkThe robustness is given by the last term. It depends on the maximum frequency we need to capture the transition function.
The Gaussian kernel can be approximated via a truncated Fourier series expansion
f∈HkX⟹f(x)≈bTϕ(x)=bTq0q1cos(ω1x)q1sin(ω1x)…qncos(ωnx)qnsin(ωnx)with n∈N,b,q,ω∈Rn.
We want to create a tool called Lucid that provides probabilistic guarantees with respect to a system's properties.
It will leverage the techniques we just presented.
It should be easy to use and install without sacrificing performance.
Loading diagram...
Define the system and sample some points.
N = 1000
# Transition function f
f = lambda x: ...
# State space
X_bounds = RectSet((-3, -2), (2.5, 1))
# Initial set
X_i = MultiSet(
RectSet((1, -0.5), (2, 0.5)),
RectSet((-1.8, -0.1), (-1.2, 0.1)),
RectSet((-1.4, -0.5), (-1.2, 0.1)),
)
# Unsafe set
X_u = MultiSet(
RectSet((0.4, 0.1), (0.6, 0.5)),
RectSet((0.4, 0.1), (0.8, 0.3))
)
x_samples = X_bounds.sample(N)
# We get the next state by applying f
xp_samples = f(x_samples)
Loading diagram...
Sample some points to train the model to predict the truncated feature map.
# Create a truncated Fourier feature map
tffm = TruncatedFourierFeatureMap(
num_frequencies=6, # Include the 0 frequency
sigma_l=[0.1, 0.2],
sigma_f=1,
x_limits=X_bounds,
)
# Apply the feature map to the samples
xp_features = tffm(xp_samples)
# Train the model so that it can predict the feature map
model = KernelRidgeRegressor(
kernel=GaussianKernel(
sigma_l=[0.1, 0.2],
sigma_f=1
),
regularisation=1e-6
)
model.fit(x_samples, xp_features)
Loading diagram...
We can now use the model to learn the barrier on a lattice of points.
# Create a lattice of points
# It must be at least 2 times
# the number of frequencies (Nyquist theorem)
x_lattice = X_bounds.lattice(6 * 2, True)
xi_lattice = X_init.lattice(6 * 2, True)
xu_lattice = X_unsafe.lattice(6 * 2, True)
# Obtain the feature maps on both lattices
x_f_lattice = tffm(x_lattice)
xp_f_lattice = model.predict(x_lattice)
xi_f_lattice = tffm(xi_lattice)
xu_f_lattice = tffm(xu_lattice)
# Efficiently gather more points using the FFT
x_f_l_upsampled = fft_upsample(x_f_lattice, 2)
xp_f_l_upsampled = fft_upsample(xp_f_lattice, 2)
xi_f_l_upsampled = fft_upsample(xi_f_lattice, 2)
xu_f_l_upsampled = fft_upsample(xu_f_lattice, 2)
Loading diagram...
We can finally learn the barrier by solving a linear program.
# Define the callback function
cb = lambda success, obj_val, sol, eta, c, norm: ...
# Initialise and run the LP
xp_f_l_upsampled = fft_upsample(xp_f_lattice, 2)
x_f_l_upsampled = fft_upsample(x_f_lattice, 2)
xi_f_l_upsampled = fft_upsample(xi_f_lattice, 2)
xu_f_l_upsampled = fft_upsample(xu_f_lattice, 2)
GurobiLinearOptimiser(T=10,
gamma=1,
epsilon=0,
sigma_f=1.0
).solve(
xi_f_l_upsampled,
xu_f_l_upsampled,
x_f_l_upsampled,
xp_f_l_upsampled,
...
cb,
)
Loading diagram...
A lot to do!