Interfaces
The primary objects in Stheno are some special subtypes of AbstractGP
. There are three primary concrete subtypes of AbstractGP
:
AtomicGP
: an atomic Gaussian process given by wrapping anAbstractGP
.CompositeGP
: a Gaussian process composed of otherAtomicGP
s andCompositeGP
s, whose properties are determined recursively from the GPs of which it is composed.GaussianProcessProbabilisticProgramme
/GPPP
: a Gaussian process comprisingAtomicGP
s andCompositeGP
s. This is the primary piece of functionality that users should interact with.
Each of these types implements the Internal AbstractGPs API.
This documentation provides the information necessary to understand the internals of Stheno, and to extend it with custom functionality.
diag methods
It is crucial for pseudo-point methods, and for the computation of marginal statistics at a reasonable scale, to be able to compute the diagonal of a given covariance matrix in linear time in the size of its inputs. This, in turn, necessitates that the diagonal of a given cross-covariance matrix can also be computed efficiently as the evaluation of covariance matrices often rely on the evaluation of cross-covariance matrices. As such, we have the following functions in addition to the AbstractGPs API implemented for AtomicGP
s and CompositeGP
s:
Function | Brief description |
---|---|
var(f, x) | diag(cov(f, x)) |
var(f, x, x′) | diag(cov(f, x, x′)) |
var(f, f′, x, x′) | diag(cov(f, f′, x, x′)) |
The second and third rows of the table only make sense when length(x) == length(x′)
, of course.
AtomicGP
We can construct a AtomicGP
in the following manner:
f = atomic(GP(m, k), gpc)
where m
is its MeanFunction
, k
its Kernel
. gpc
is a GPC
object that handles some book-keeping, and is discussed in more depth below.
The AbstractGP
interface is implemented for AtomicGP
s in terms of the AbstractGP
that they atomic. So if you want a particular behaviour, just make sure that the AbstractGP
that you atomic has the functionality you want.
Aside: Example Kernel implementation
It's straightforward to implement a new kernel yourself: simply following the instructions in KernelFunctions.jl and it'll work fine with GPs in Stheno.jl.
GPC
This book-keeping object doesn't matter from a user's perspective but, unfortunately, we currently expose it to users. Fortunately, it's straightforward to work with. Say you wish to construct a collection of processes:
# THIS WON'T WORK
f = GP(mf, kf)
g = GP(mg, kg)
h = f + g
# THIS WON'T WORK
You should write
# THIS IS GOOD. PLEASE DO THIS
gpc = GPC()
f = atomic(GP(mf, kf), gpc)
g = atomic(GP(mg, kg), gpc)
h = f + g
# THIS IS GOOD. PLEASE DO THIS
The rule is simple: when constructing GPs that you plan to make interact later in your program, construct them using the same gpc
object. For example, DON'T do the following:
# THIS IS BAD. PLEASE DON'T DO THIS
f = atomic(GP(mf, kf), GPC())
g = atomic(GP(mg, kg), GPC())
h = f + g
# THIS IS BAD. PLEASE DON'T DO THIS
The mistake here is to construct a separate GPC
object for each GP
. Hopefully, the code errors, but might yield incorrect results.
CompositeGP
CompositeGP
s are constructed as affine transformations of CompositeGP
s and AtomicGP
s. We describe the implemented transformations below. You can add additional transformations – see Custom Affine Transformations for an a worked example.
Addition
Given AbstractGP
s f
and g
, we define
h = f + g
to be the CompositeGP
sastisfying h(x) = f(x) + g(x)
for all x
.
Multiplication
Multiplication of AbstractGP
s is undefined since the product of two Gaussian random variables is not itself Gaussian. However, we can scale an AbstractGP
by either a constant or (deterministic) function.
h = c * f
h = sin * f
will both work, and produce the result that h(x) = c * f(x)
or h(x) = sin(x) * f(x)
.
Composition
h = f ∘ g
for some deterministic function g
is the composition of f
with g
. i.e. h(x) = f(g(x))
.
cross
h = cross([f, g])
for WrappedGPs
and CompositeGP
s f
and g
. Think of cross
as having stacked f
and g
together, so that you can work with their joint.
For example, if you wanted to sample jointly from f
and g
at locations x_f
and x_g
, you could write something like
fg = cross([f, g])
x_fg = BlockData([x_f, x_g])
rand(fg(x_fg, 1e-6))
This particular function isn't part of the user-facing API because it isn't generally needed. It is, however, used extensively in the implementation of the GaussianProcessProbabilisticProgramme
.
GPPP
The GaussianProcessProbabilisticProgramme
is another AbstractGP
which enables provides a thin layer of convenience functionality on top of AtomicGP
s and CompositeGP
s, and is the primary way in which it is intended that users will interact with this package.
A GPPP
like this
f = @gppp let
f1 = GP(SEKernel())
f2 = GP(Matern52Kernel())
f3 = f1 + f2
end
is equivalent to manually constructing a GPPP
using AtomicGP
s and CompositeGP
s:
gpc = GPC()
f1 = atomic(GP(SEKernel()), gpc)
f2 = atomic(GP(SEKernel()), gpc)
f3 = f1 + f2
f = Stheno.GPPP((f1=f1, f2=f2, f3=f3), gpc)
If you take a look at the gaussian_process_probabilistic_programming.jl
source, you'll see that it's essentially just the above, and an implementation of the AbstractGP
s API on top of a GPPP
.
A GPPP
is a single GP on an extended input domain: