2013-04-23 17:09:52 +01:00
# Copyright (c) 2012, James Hesnsman
# Licensed under the BSD 3-clause license (see LICENSE.txt)
2015-02-27 17:55:58 +00:00
from . kern import Kern , CombinationKernel
2013-04-23 17:09:52 +01:00
import numpy as np
2014-03-13 09:57:59 +00:00
import itertools
2013-04-23 17:09:52 +01:00
def index_to_slices ( index ) :
"""
2015-04-10 15:24:28 +01:00
take a numpy array of integers ( index ) and return a nested list of slices such that the slices describe the start , stop points for each integer in the index .
2013-04-23 17:09:52 +01:00
e . g .
>> > index = np . asarray ( [ 0 , 0 , 0 , 1 , 1 , 1 , 2 , 2 , 2 ] )
returns
>> > [ [ slice ( 0 , 3 , None ) ] , [ slice ( 3 , 6 , None ) ] , [ slice ( 6 , 9 , None ) ] ]
or , a more complicated example
>> > index = np . asarray ( [ 0 , 0 , 1 , 1 , 0 , 2 , 2 , 2 , 1 , 1 ] )
returns
>> > [ [ slice ( 0 , 2 , None ) , slice ( 4 , 5 , None ) ] , [ slice ( 2 , 4 , None ) , slice ( 8 , 10 , None ) ] , [ slice ( 5 , 8 , None ) ] ]
"""
2014-05-30 18:56:23 +01:00
if len ( index ) == 0 :
return [ ]
2013-04-23 17:09:52 +01:00
#contruct the return structure
2014-05-29 10:13:16 +01:00
ind = np . asarray ( index , dtype = np . int )
2013-04-23 17:09:52 +01:00
ret = [ [ ] for i in range ( ind . max ( ) + 1 ) ]
#find the switchpoints
ind_ = np . hstack ( ( ind , ind [ 0 ] + ind [ - 1 ] + 1 ) )
switchpoints = np . nonzero ( ind_ - np . roll ( ind_ , + 1 ) ) [ 0 ]
[ ret [ ind_i ] . append ( slice ( * indexes_i ) ) for ind_i , indexes_i in zip ( ind [ switchpoints [ : - 1 ] ] , zip ( switchpoints , switchpoints [ 1 : ] ) ) ]
return ret
2014-05-09 11:14:30 +01:00
class IndependentOutputs ( CombinationKernel ) :
2013-04-23 17:09:52 +01:00
"""
2014-04-28 16:07:42 +01:00
A kernel which can represent several independent functions . this kernel
' switches off ' parts of the matrix where the output indexes are different .
The index of the functions is given by the last column in the input X the
rest of the columns of X are passed to the underlying kernel for
computation ( in blocks ) .
: param kernels : either a kernel , or list of kernels to work with . If it is
a list of kernels the indices in the index_dim , index the kernels you gave !
2013-04-23 17:09:52 +01:00
"""
2014-03-17 16:22:16 +00:00
def __init__ ( self , kernels , index_dim = - 1 , name = ' independ ' ) :
2014-04-28 16:07:42 +01:00
assert isinstance ( index_dim , int ) , " IndependentOutputs kernel is only defined with one input dimension being the index "
if not isinstance ( kernels , list ) :
2014-03-17 16:22:16 +00:00
self . single_kern = True
self . kern = kernels
kernels = [ kernels ]
else :
self . single_kern = False
self . kern = kernels
super ( IndependentOutputs , self ) . __init__ ( kernels = kernels , extra_dims = [ index_dim ] , name = name )
2014-03-13 09:57:59 +00:00
self . index_dim = index_dim
2013-04-23 17:09:52 +01:00
2014-02-24 13:51:03 +00:00
def K ( self , X , X2 = None ) :
2014-03-13 09:07:27 +00:00
slices = index_to_slices ( X [ : , self . index_dim ] )
2014-03-27 13:10:51 +00:00
kerns = itertools . repeat ( self . kern ) if self . single_kern else self . kern
2013-04-23 17:09:52 +01:00
if X2 is None :
2014-02-24 13:51:03 +00:00
target = np . zeros ( ( X . shape [ 0 ] , X . shape [ 0 ] ) )
2014-03-27 13:10:51 +00:00
[ [ target . __setitem__ ( ( s , ss ) , kern . K ( X [ s , : ] , X [ ss , : ] ) ) for s , ss in itertools . product ( slices_i , slices_i ) ] for kern , slices_i in zip ( kerns , slices ) ]
2013-04-23 17:09:52 +01:00
else :
2014-03-13 09:07:27 +00:00
slices2 = index_to_slices ( X2 [ : , self . index_dim ] )
2014-02-24 13:51:03 +00:00
target = np . zeros ( ( X . shape [ 0 ] , X2 . shape [ 0 ] ) )
2014-03-27 13:10:51 +00:00
[ [ target . __setitem__ ( ( s , s2 ) , kern . K ( X [ s , : ] , X2 [ s2 , : ] ) ) for s , s2 in itertools . product ( slices_i , slices_j ) ] for kern , slices_i , slices_j in zip ( kerns , slices , slices2 ) ]
2014-02-24 13:51:03 +00:00
return target
def Kdiag ( self , X ) :
2014-03-13 09:07:27 +00:00
slices = index_to_slices ( X [ : , self . index_dim ] )
2014-03-27 13:10:51 +00:00
kerns = itertools . repeat ( self . kern ) if self . single_kern else self . kern
2014-02-24 13:51:03 +00:00
target = np . zeros ( X . shape [ 0 ] )
2014-03-27 13:10:51 +00:00
[ [ np . copyto ( target [ s ] , kern . Kdiag ( X [ s ] ) ) for s in slices_i ] for kern , slices_i in zip ( kerns , slices ) ]
2014-02-24 13:51:03 +00:00
return target
def update_gradients_full ( self , dL_dK , X , X2 = None ) :
2014-03-13 09:07:27 +00:00
slices = index_to_slices ( X [ : , self . index_dim ] )
2015-04-10 15:24:28 +01:00
if self . single_kern :
2014-03-27 13:10:51 +00:00
target = np . zeros ( self . kern . size )
kerns = itertools . repeat ( self . kern )
2015-04-10 15:24:28 +01:00
else :
2014-03-27 13:10:51 +00:00
kerns = self . kern
target = [ np . zeros ( kern . size ) for kern , _ in zip ( kerns , slices ) ]
2014-03-17 16:22:16 +00:00
def collate_grads ( kern , i , dL , X , X2 ) :
kern . update_gradients_full ( dL , X , X2 )
if self . single_kern : target [ : ] + = kern . gradient
else : target [ i ] [ : ] + = kern . gradient
2013-04-23 17:09:52 +01:00
if X2 is None :
2014-03-27 13:10:51 +00:00
[ [ collate_grads ( kern , i , dL_dK [ s , ss ] , X [ s ] , X [ ss ] ) for s , ss in itertools . product ( slices_i , slices_i ) ] for i , ( kern , slices_i ) in enumerate ( zip ( kerns , slices ) ) ]
2013-04-23 17:09:52 +01:00
else :
2014-03-13 09:07:27 +00:00
slices2 = index_to_slices ( X2 [ : , self . index_dim ] )
2014-03-27 13:10:51 +00:00
[ [ [ collate_grads ( kern , i , dL_dK [ s , s2 ] , X [ s ] , X2 [ s2 ] ) for s in slices_i ] for s2 in slices_j ] for i , ( kern , slices_i , slices_j ) in enumerate ( zip ( kerns , slices , slices2 ) ) ]
2015-04-10 15:24:28 +01:00
if self . single_kern :
2015-03-06 17:07:35 +00:00
self . kern . gradient = target
else :
[ kern . gradient . __setitem__ ( Ellipsis , target [ i ] ) for i , [ kern , _ ] in enumerate ( zip ( kerns , slices ) ) ]
2013-04-23 17:09:52 +01:00
2014-02-24 13:51:03 +00:00
def gradients_X ( self , dL_dK , X , X2 = None ) :
2014-03-14 12:32:47 +00:00
target = np . zeros ( X . shape )
2014-03-27 13:10:51 +00:00
kerns = itertools . repeat ( self . kern ) if self . single_kern else self . kern
2013-04-23 17:09:52 +01:00
if X2 is None :
2014-03-17 16:22:16 +00:00
# TODO: make use of index_to_slices
2015-04-10 15:24:28 +01:00
# FIXME: Broken as X is already sliced out
2015-06-23 01:26:52 -07:00
print ( " Warning, gradients_X may not be working, I believe X has already been sliced out by the slicer! " )
2014-03-17 16:22:16 +00:00
values = np . unique ( X [ : , self . index_dim ] )
slices = [ X [ : , self . index_dim ] == i for i in values ]
[ target . __setitem__ ( s , kern . gradients_X ( dL_dK [ s , s ] , X [ s ] , None ) )
2014-03-27 13:10:51 +00:00
for kern , s in zip ( kerns , slices ) ]
2014-03-17 16:22:16 +00:00
#slices = index_to_slices(X[:,self.index_dim])
2015-04-10 15:24:28 +01:00
#[[np.add(target[s], kern.gradients_X(dL_dK[s,s], X[s]), out=target[s])
2014-03-27 13:10:51 +00:00
# for s in slices_i] for kern, slices_i in zip(kerns, slices)]
2014-03-17 16:22:16 +00:00
#import ipdb;ipdb.set_trace()
#[[(np.add(target[s ], kern.gradients_X(dL_dK[s ,ss],X[s ], X[ss]), out=target[s ]),
# np.add(target[ss], kern.gradients_X(dL_dK[ss,s ],X[ss], X[s ]), out=target[ss]))
2014-03-27 13:10:51 +00:00
# for s, ss in itertools.combinations(slices_i, 2)] for kern, slices_i in zip(kerns, slices)]
2013-04-23 17:09:52 +01:00
else :
2014-03-17 16:22:16 +00:00
values = np . unique ( X [ : , self . index_dim ] )
slices = [ X [ : , self . index_dim ] == i for i in values ]
slices2 = [ X2 [ : , self . index_dim ] == i for i in values ]
[ target . __setitem__ ( s , kern . gradients_X ( dL_dK [ s , : ] [ : , s2 ] , X [ s ] , X2 [ s2 ] ) )
2014-03-27 13:10:51 +00:00
for kern , s , s2 in zip ( kerns , slices , slices2 ) ]
2014-03-17 16:22:16 +00:00
# TODO: make work with index_to_slices
#slices = index_to_slices(X[:,self.index_dim])
#slices2 = index_to_slices(X2[:,self.index_dim])
2014-03-27 13:10:51 +00:00
#[[target.__setitem__(s, target[s] + kern.gradients_X(dL_dK[s,s2], X[s], X2[s2])) for s, s2 in itertools.product(slices_i, slices_j)] for kern, slices_i,slices_j in zip(kerns, slices,slices2)]
2014-02-24 13:51:03 +00:00
return target
def gradients_X_diag ( self , dL_dKdiag , X ) :
2014-03-13 09:07:27 +00:00
slices = index_to_slices ( X [ : , self . index_dim ] )
2014-03-27 13:10:51 +00:00
kerns = itertools . repeat ( self . kern ) if self . single_kern else self . kern
2014-02-24 13:51:03 +00:00
target = np . zeros ( X . shape )
2014-03-27 13:10:51 +00:00
[ [ target . __setitem__ ( s , kern . gradients_X_diag ( dL_dKdiag [ s ] , X [ s ] ) ) for s in slices_i ] for kern , slices_i in zip ( kerns , slices ) ]
2014-02-24 13:51:03 +00:00
return target
2014-03-13 10:23:07 +00:00
def update_gradients_diag ( self , dL_dKdiag , X ) :
slices = index_to_slices ( X [ : , self . index_dim ] )
2014-03-27 13:10:51 +00:00
kerns = itertools . repeat ( self . kern ) if self . single_kern else self . kern
2014-03-17 16:22:16 +00:00
if self . single_kern : target = np . zeros ( self . kern . size )
2014-03-27 13:10:51 +00:00
else : target = [ np . zeros ( kern . size ) for kern , _ in zip ( kerns , slices ) ]
2014-03-17 16:22:16 +00:00
def collate_grads ( kern , i , dL , X ) :
kern . update_gradients_diag ( dL , X )
if self . single_kern : target [ : ] + = kern . gradient
else : target [ i ] [ : ] + = kern . gradient
2014-03-27 13:10:51 +00:00
[ [ collate_grads ( kern , i , dL_dKdiag [ s ] , X [ s , : ] ) for s in slices_i ] for i , ( kern , slices_i ) in enumerate ( zip ( kerns , slices ) ) ]
2015-03-06 17:07:35 +00:00
if self . single_kern : self . kern . gradient = target
2014-03-27 13:10:51 +00:00
else : [ kern . gradient . __setitem__ ( Ellipsis , target [ i ] ) for i , [ kern , _ ] in enumerate ( zip ( kerns , slices ) ) ]
2014-03-17 16:22:16 +00:00
2014-04-29 16:50:27 +01:00
class Hierarchical ( CombinationKernel ) :
2014-02-24 13:51:03 +00:00
"""
2014-04-28 16:07:42 +01:00
A kernel which can represent a simple hierarchical model .
2014-02-24 13:51:03 +00:00
See Hensman et al 2013 , " Hierarchical Bayesian modelling of gene expression time
series across irregularly sampled replicates and clusters "
http : / / www . biomedcentral . com / 1471 - 2105 / 14 / 252
2014-04-28 16:07:42 +01:00
To construct this kernel , you must pass a list of kernels . the first kernel
will be assumed to be the ' base ' kernel , and will be computed everywhere .
For every additional kernel , we assume another layer in the hierachy , with
a corresponding column of the input matrix which indexes which function the
data are in at that level .
2014-02-24 13:51:03 +00:00
2014-04-28 16:07:42 +01:00
For more , see the ipython notebook documentation on Hierarchical
covariances .
2014-02-24 13:51:03 +00:00
"""
2014-04-28 16:07:42 +01:00
def __init__ ( self , kernels , name = ' hierarchy ' ) :
assert all ( [ k . input_dim == kernels [ 0 ] . input_dim for k in kernels ] )
assert len ( kernels ) > 1
self . levels = len ( kernels ) - 1
input_max = max ( [ k . input_dim for k in kernels ] )
super ( Hierarchical , self ) . __init__ ( kernels = kernels , extra_dims = range ( input_max , input_max + len ( kernels ) - 1 ) , name = name )
2014-02-24 16:19:03 +00:00
def K ( self , X , X2 = None ) :
2014-04-28 16:07:42 +01:00
K = self . parts [ 0 ] . K ( X , X2 ) # compute 'base' kern everywhere
slices = [ index_to_slices ( X [ : , i ] ) for i in self . extra_dims ]
2014-02-24 16:19:03 +00:00
if X2 is None :
2014-04-29 16:50:27 +01:00
[ [ [ np . add ( K [ s , s ] , k . K ( X [ s ] , None ) , K [ s , s ] ) for s in slices_i ] for slices_i in slices_k ] for k , slices_k in zip ( self . parts [ 1 : ] , slices ) ]
2014-02-24 16:19:03 +00:00
else :
2014-04-29 16:50:27 +01:00
slices2 = [ index_to_slices ( X2 [ : , i ] ) for i in self . extra_dims ]
[ [ [ np . add ( K [ s , ss ] , k . K ( X [ s ] , X2 [ ss ] ) , K [ s , ss ] ) for s , ss in zip ( slices_i , slices_j ) ] for slices_i , slices_j in zip ( slices_k1 , slices_k2 ) ] for k , slices_k1 , slices_k2 in zip ( self . parts [ 1 : ] , slices , slices2 ) ]
2014-04-28 16:07:42 +01:00
return K
2014-02-24 16:19:03 +00:00
2014-04-29 16:50:27 +01:00
def Kdiag ( self , X ) :
return np . diag ( self . K ( X ) )
2014-05-14 10:04:58 +01:00
def gradients_X ( self , dL_dK , X , X2 = None ) :
raise NotImplementedError
2014-02-24 16:19:03 +00:00
def update_gradients_full ( self , dL_dK , X , X2 = None ) :
2014-04-29 16:50:27 +01:00
slices = [ index_to_slices ( X [ : , i ] ) for i in self . extra_dims ]
2014-02-24 16:19:03 +00:00
if X2 is None :
2014-04-29 16:50:27 +01:00
self . parts [ 0 ] . update_gradients_full ( dL_dK , X , None )
for k , slices_k in zip ( self . parts [ 1 : ] , slices ) :
2014-02-24 16:19:03 +00:00
target = np . zeros ( k . size )
2014-04-29 16:50:27 +01:00
def collate_grads ( dL , X , X2 , target ) :
2014-02-24 16:19:03 +00:00
k . update_gradients_full ( dL , X , X2 )
2014-04-29 16:50:27 +01:00
target + = k . gradient
[ [ collate_grads ( dL_dK [ s , s ] , X [ s ] , None , target ) for s in slices_i ] for slices_i in slices_k ]
k . gradient [ : ] = target
2014-02-24 16:19:03 +00:00
else :
2014-04-29 16:50:27 +01:00
raise NotImplementedError
2014-02-24 16:19:03 +00:00
2013-04-23 17:09:52 +01:00