2014-02-19 15:00:48 +00:00
# Copyright (c) 2012, GPy authors (see AUTHORS.txt).
# Licensed under the BSD 3-clause license (see LICENSE.txt)
import sys
2014-03-11 10:24:15 +00:00
import numpy as np
2014-03-11 16:24:09 +00:00
from . . . core . parameterization . parameterized import Parameterized
from kernel_slice_operations import KernCallsViaSlicerMeta
2014-03-07 16:59:41 +00:00
from . . . util . caching import Cache_this
2014-02-19 15:00:48 +00:00
2014-03-11 16:24:09 +00:00
2014-03-11 10:24:15 +00:00
2014-02-19 15:00:48 +00:00
class Kern ( Parameterized ) :
2014-03-11 16:24:09 +00:00
#===========================================================================
2014-03-25 16:59:52 +00:00
# This adds input slice support. The rather ugly code for slicing can be
2014-03-11 16:24:09 +00:00
# found in kernel_slice_operations
2014-04-15 16:47:34 +01:00
__metaclass__ = KernCallsViaSlicerMeta
2014-03-11 16:24:09 +00:00
#===========================================================================
2014-03-27 17:12:17 +00:00
_support_GPU = False
2014-04-07 10:25:16 +01:00
def __init__ ( self , input_dim , active_dims , name , useGPU = False , * a , * * kw ) :
2014-02-19 15:00:48 +00:00
"""
The base class for a kernel : a positive definite function
which forms of a covariance function ( kernel ) .
2014-04-16 10:12:02 +01:00
input_dim :
is the number of dimensions to work on . Make sure to give the
tight dimensionality of inputs .
2014-04-16 12:19:40 +01:00
You most likely want this to be the integer telling the number of
2014-04-16 10:12:02 +01:00
input dimensions of the kernel .
If this is not an integer ( ! ) we will work on the whole input matrix X ,
and not check whether dimensions match or not ( ! ) .
active_dims :
is the active_dimensions of inputs X we will work on .
All kernels will get sliced Xes as inputs , if active_dims is not None
if active_dims is None , slicing is switched off and all X will be passed through as given .
2014-03-14 09:18:08 +00:00
: param int input_dim : the number of input dimensions to the function
2014-04-16 10:12:02 +01:00
: param array - like | slice | None active_dims : list of indices on which dimensions this kernel works on , or none if no slicing
2014-02-19 15:00:48 +00:00
Do not instantiate .
"""
2014-02-21 10:38:11 +00:00
super ( Kern , self ) . __init__ ( name = name , * a , * * kw )
2014-04-16 10:12:02 +01:00
try :
self . input_dim = int ( input_dim )
2014-04-16 12:19:40 +01:00
self . active_dims = active_dims # if active_dims is not None else slice(0, input_dim, 1)
2014-04-16 10:12:02 +01:00
except TypeError :
# input_dim is something else then an integer
self . input_dim = input_dim
if active_dims is not None :
print " WARNING: given input_dim= {} is not an integer and active_dims= {} is given, switching off slicing "
self . active_dims = None
2014-04-16 09:49:35 +01:00
if self . active_dims is not None and self . input_dim is not None :
assert isinstance ( self . active_dims , ( slice , list , tuple , np . ndarray ) ) , ' active_dims needs to be an array-like or slice object over dimensions, {} given ' . format ( self . active_dims . __class__ )
if isinstance ( self . active_dims , slice ) :
self . active_dims = slice ( self . active_dims . start or 0 , self . active_dims . stop or self . input_dim , self . active_dims . step or 1 )
active_dim_size = int ( np . round ( ( self . active_dims . stop - self . active_dims . start ) / self . active_dims . step ) )
elif isinstance ( self . active_dims , np . ndarray ) :
#assert np.all(self.active_dims >= 0), 'active dimensions need to be positive. negative indexing is not allowed'
assert self . active_dims . ndim == 1 , ' only flat indices allowed, given active_dims.shape= {} , provide only indexes to the dimensions (columns) of the input ' . format ( self . active_dims . shape )
active_dim_size = self . active_dims . size
else :
active_dim_size = len ( self . active_dims )
assert active_dim_size == self . input_dim , " input_dim= {} does not match len(active_dim)= {} , active_dims= {} " . format ( self . input_dim , active_dim_size , self . active_dims )
2014-03-12 12:03:37 +00:00
self . _sliced_X = 0
2014-03-27 17:12:17 +00:00
self . useGPU = self . _support_GPU and useGPU
2014-03-11 10:24:15 +00:00
2014-03-14 09:18:08 +00:00
@Cache_this ( limit = 10 )
2014-03-07 16:59:41 +00:00
def _slice_X ( self , X ) :
2014-04-16 10:12:02 +01:00
return X [ : , self . active_dims ]
2014-03-11 10:24:15 +00:00
2014-02-21 09:14:31 +00:00
def K ( self , X , X2 ) :
2014-03-11 10:24:15 +00:00
"""
Compute the kernel function .
: param X : the first set of inputs to the kernel
: param X2 : ( optional ) the second set of arguments to the kernel . If X2
is None , this is passed throgh to the ' part ' object , which
handLes this as X2 == X .
"""
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-03-07 16:59:41 +00:00
def Kdiag ( self , X ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-02-26 08:23:46 +00:00
def psi0 ( self , Z , variational_posterior ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-02-26 08:23:46 +00:00
def psi1 ( self , Z , variational_posterior ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-02-26 08:23:46 +00:00
def psi2 ( self , Z , variational_posterior ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-02-20 14:24:41 +00:00
def gradients_X ( self , dL_dK , X , X2 ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-03-07 16:59:41 +00:00
def gradients_X_diag ( self , dL_dKdiag , X ) :
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-03-12 12:03:37 +00:00
2014-03-10 11:14:19 +00:00
def update_gradients_diag ( self , dL_dKdiag , X ) :
""" update the gradients of all parameters when using only the diagonal elements of the covariance matrix """
raise NotImplementedError
2014-02-24 21:16:26 +00:00
def update_gradients_full ( self , dL_dK , X , X2 ) :
2014-02-19 15:00:48 +00:00
""" Set the gradients of all parameters when doing full (N) inference. """
raise NotImplementedError
2014-03-14 09:18:08 +00:00
2014-02-25 17:15:38 +00:00
def update_gradients_expectations ( self , dL_dpsi0 , dL_dpsi1 , dL_dpsi2 , Z , variational_posterior ) :
"""
Set the gradients of all parameters when doing inference with
uncertain inputs , using expectations of the kernel .
2014-02-26 08:23:46 +00:00
The esential maths is
dL_d { theta_i } = dL_dpsi0 * dpsi0_d { theta_i } +
dL_dpsi1 * dpsi1_d { theta_i } +
dL_dpsi2 * dpsi2_d { theta_i }
2014-02-25 17:15:38 +00:00
"""
2014-02-19 15:00:48 +00:00
raise NotImplementedError
2014-02-26 08:23:46 +00:00
2014-02-25 17:15:38 +00:00
def gradients_Z_expectations ( self , dL_dpsi1 , dL_dpsi2 , Z , variational_posterior ) :
2014-02-26 08:23:46 +00:00
"""
Returns the derivative of the objective wrt Z , using the chain rule
through the expectation variables .
"""
2014-02-21 09:14:31 +00:00
raise NotImplementedError
2014-02-26 08:23:46 +00:00
2014-02-25 17:15:38 +00:00
def gradients_qX_expectations ( self , dL_dpsi0 , dL_dpsi1 , dL_dpsi2 , Z , variational_posterior ) :
"""
Compute the gradients wrt the parameters of the variational
distruibution q ( X ) , chain - ruling via the expectations of the kernel
"""
2014-02-21 09:14:31 +00:00
raise NotImplementedError
2014-02-25 17:15:38 +00:00
2014-02-26 14:30:28 +00:00
def plot ( self , * args , * * kwargs ) :
"""
See GPy . plotting . matplot_dep . plot
"""
assert " matplotlib " in sys . modules , " matplotlib package has not been imported. "
2014-03-03 15:07:52 +00:00
from . . . plotting . matplot_dep import kernel_plots
2014-02-26 14:30:28 +00:00
kernel_plots . plot ( self , * args )
2014-02-24 14:47:43 +00:00
def plot_ARD ( self , * args , * * kw ) :
2014-02-26 08:21:14 +00:00
"""
See : class : ` ~ GPy . plotting . matplot_dep . kernel_plots `
"""
import sys
2014-02-19 15:00:48 +00:00
assert " matplotlib " in sys . modules , " matplotlib package has not been imported. "
2014-02-21 17:53:44 +00:00
from . . . plotting . matplot_dep import kernel_plots
2014-02-24 14:47:43 +00:00
return kernel_plots . plot_ARD ( self , * args , * * kw )
2014-02-25 17:15:38 +00:00
2014-02-24 14:47:43 +00:00
def input_sensitivity ( self ) :
"""
Returns the sensitivity for each dimension of this kernel .
"""
2014-03-10 08:21:13 +00:00
return np . zeros ( self . input_dim )
2014-02-25 17:15:38 +00:00
2014-02-19 15:00:48 +00:00
def __add__ ( self , other ) :
""" Overloading of the ' + ' operator. for more control, see self.add """
return self . add ( other )
2014-03-11 10:24:15 +00:00
def add ( self , other , name = ' add ' ) :
2014-02-19 15:00:48 +00:00
"""
Add another kernel to this one .
: param other : the other kernel to be added
: type other : GPy . kern
"""
assert isinstance ( other , Kern ) , " only kernels can be added to kernels... "
from add import Add
2014-03-18 17:41:08 +00:00
return Add ( [ self , other ] , name = name )
2014-02-19 15:00:48 +00:00
def __mul__ ( self , other ) :
""" Here we overload the ' * ' operator. See self.prod for more information """
return self . prod ( other )
2014-03-12 13:23:01 +00:00
def __pow__ ( self , other ) :
"""
Shortcut for tensor ` prod ` .
"""
assert self . active_dims == range ( self . input_dim ) , " Can only use kernels, which have their input_dims defined from 0 "
assert other . active_dims == range ( other . input_dim ) , " Can only use kernels, which have their input_dims defined from 0 "
other . active_dims + = self . input_dim
return self . prod ( other )
2014-02-19 15:00:48 +00:00
2014-03-13 13:13:15 +00:00
def prod ( self , other , name = ' mul ' ) :
2014-02-19 15:00:48 +00:00
"""
2014-02-26 08:23:46 +00:00
Multiply two kernels ( either on the same space , or on the tensor
product of the input space ) .
2014-02-19 15:00:48 +00:00
: param other : the other kernel to be added
: type other : GPy . kern
: param tensor : whether or not to use the tensor space ( default is false ) .
: type tensor : bool
"""
assert isinstance ( other , Kern ) , " only kernels can be added to kernels... "
from prod import Prod
2014-03-13 11:01:48 +00:00
#kernels = []
#if isinstance(self, Prod): kernels.extend(self._parameters_)
#else: kernels.append(self)
#if isinstance(other, Prod): kernels.extend(other._parameters_)
#else: kernels.append(other)
return Prod ( [ self , other ] , name )
2014-03-11 10:24:15 +00:00
class CombinationKernel ( Kern ) :
2014-03-14 09:18:08 +00:00
"""
Abstract super class for combination kernels .
A combination kernel combines ( a list of ) kernels and works on those .
Examples are the HierarchicalKernel or Add and Prod kernels .
"""
def __init__ ( self , kernels , name , extra_dims = [ ] ) :
"""
Abstract super class for combination kernels .
A combination kernel combines ( a list of ) kernels and works on those .
Examples are the HierarchicalKernel or Add and Prod kernels .
: param list kernels : List of kernels to combine ( can be only one element )
: param str name : name of the combination kernel
: param array - like | slice extra_dims : if needed extra dimensions for the combination kernel to work on
"""
2014-03-11 10:24:15 +00:00
assert all ( [ isinstance ( k , Kern ) for k in kernels ] )
2014-03-27 13:08:54 +00:00
input_dim , active_dims = self . get_input_dim_active_dims ( kernels , extra_dims )
2014-03-13 11:01:48 +00:00
# initialize the kernel with the full input_dim
2014-03-14 10:55:16 +00:00
super ( CombinationKernel , self ) . __init__ ( input_dim , active_dims , name )
2014-03-14 09:18:08 +00:00
self . extra_dims = extra_dims
2014-03-11 10:24:15 +00:00
self . add_parameters ( * kernels )
@property
def parts ( self ) :
return self . _parameters_
2014-03-27 13:08:54 +00:00
def get_input_dim_active_dims ( self , kernels , extra_dims = None ) :
2014-04-16 09:49:35 +01:00
#active_dims = reduce(np.union1d, (np.r_[x.active_dims] for x in kernels), np.array([], dtype=int))
#active_dims = np.array(np.concatenate((active_dims, extra_dims if extra_dims is not None else [])), dtype=int)
2014-04-16 12:19:40 +01:00
input_dim = np . array ( [ k . input_dim for k in kernels ] )
if np . all ( input_dim [ 0 ] == input_dim ) :
input_dim = input_dim [ 0 ]
2014-04-16 09:49:35 +01:00
active_dims = None
2014-03-27 13:08:54 +00:00
return input_dim , active_dims
2014-03-11 16:24:09 +00:00
def input_sensitivity ( self ) :
2014-04-28 15:13:58 +01:00
raise NotImplementedError ( " Choose the kernel you want to get the sensitivity for. You need to override the default behaviour for getting the input sensitivity to be able to get the input sensitivity. For sum kernel it is the sum of all sensitivities, TODO: product kernel? Other kernels?, also TODO: shall we return all the sensitivities here in the combination kernel? So we can combine them however we want? This could lead to just plot all the sensitivities here... " " )