sgptools 1.2.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sgptools/__init__.py +3 -4
- sgptools/core/__init__.py +1 -0
- sgptools/{models/core → core}/augmented_gpr.py +11 -17
- sgptools/{models/core → core}/augmented_sgpr.py +27 -34
- sgptools/core/osgpr.py +417 -0
- sgptools/core/transformations.py +699 -0
- sgptools/kernels/__init__.py +0 -8
- sgptools/kernels/attentive_kernel.py +214 -69
- sgptools/kernels/neural_kernel.py +268 -92
- sgptools/kernels/neural_network.py +127 -28
- sgptools/methods.py +1047 -0
- sgptools/objectives.py +275 -0
- sgptools/utils/__init__.py +0 -9
- sgptools/utils/data.py +452 -149
- sgptools/utils/gpflow.py +335 -174
- sgptools/utils/metrics.py +375 -102
- sgptools/utils/misc.py +145 -111
- sgptools/utils/tsp.py +224 -84
- sgptools-2.0.0.dist-info/METADATA +216 -0
- sgptools-2.0.0.dist-info/RECORD +23 -0
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info}/WHEEL +1 -1
- sgptools/models/__init__.py +0 -10
- sgptools/models/bo.py +0 -118
- sgptools/models/cma_es.py +0 -121
- sgptools/models/continuous_sgp.py +0 -68
- sgptools/models/core/__init__.py +0 -9
- sgptools/models/core/osgpr.py +0 -291
- sgptools/models/core/transformations.py +0 -434
- sgptools/models/greedy_mi.py +0 -115
- sgptools/models/greedy_sgp.py +0 -97
- sgptools-1.2.0.dist-info/METADATA +0 -39
- sgptools-1.2.0.dist-info/RECORD +0 -27
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info/licenses}/LICENSE.txt +0 -0
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info}/top_level.txt +0 -0
sgptools/models/bo.py
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
import numpy as np
|
16
|
-
from ..utils.metrics import get_mi
|
17
|
-
from ..utils.data import get_inducing_pts
|
18
|
-
from bayes_opt import BayesianOptimization
|
19
|
-
|
20
|
-
|
21
|
-
class BayesianOpt:
|
22
|
-
"""Class for optimizing sensor placements using Bayesian Optimization
|
23
|
-
|
24
|
-
Refer to the following papers for more details:
|
25
|
-
- UAV route planning for active disease classification [Vivaldini et al., 2019]
|
26
|
-
- Occupancy map building through Bayesian exploration [Francis et al., 2019]
|
27
|
-
|
28
|
-
Args:
|
29
|
-
X_train (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
|
30
|
-
noise_variance (float): data variance
|
31
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
32
|
-
transform (Transform): Transform object
|
33
|
-
"""
|
34
|
-
def __init__(self, X_train, noise_variance, kernel,
|
35
|
-
transform=None):
|
36
|
-
self.X_train = X_train
|
37
|
-
self.noise_variance = noise_variance
|
38
|
-
self.kernel = kernel
|
39
|
-
self.num_dim = X_train.shape[-1]
|
40
|
-
self.transform = transform
|
41
|
-
|
42
|
-
# use the boundaries of the region as the search space
|
43
|
-
self.pbounds_dim = []
|
44
|
-
for i in range(self.num_dim):
|
45
|
-
self.pbounds_dim.append((np.min(X_train[:, i]), np.max(X_train[:, i])))
|
46
|
-
|
47
|
-
def objective(self, **kwargs):
|
48
|
-
"""Objective function (GP-based Mutual Information)
|
49
|
-
|
50
|
-
Args:
|
51
|
-
x<i> (ndarray): (1, d); Current solution sensor placement location i
|
52
|
-
"""
|
53
|
-
# MI does not depend on waypoint order (reshape to -1, num_dim)
|
54
|
-
X = []
|
55
|
-
for i in range(len(kwargs)):
|
56
|
-
X.append(kwargs['x{}'.format(i)])
|
57
|
-
X = np.array(X).reshape(-1, self.num_dim)
|
58
|
-
if self.transform is not None:
|
59
|
-
X = self.transform.expand(X)
|
60
|
-
constraints_loss = self.transform.constraints(X)
|
61
|
-
|
62
|
-
try:
|
63
|
-
mi = get_mi(X, self.X_train, self.noise_variance, self.kernel)
|
64
|
-
mi += constraints_loss
|
65
|
-
mi = mi.numpy()
|
66
|
-
except:
|
67
|
-
mi = -1e4 # if the cholskey decomposition fails
|
68
|
-
return mi
|
69
|
-
|
70
|
-
def optimize(self,
|
71
|
-
num_sensors=10,
|
72
|
-
max_steps=100,
|
73
|
-
X_init=None,
|
74
|
-
init_points=10,
|
75
|
-
verbose=0,
|
76
|
-
seed=1234):
|
77
|
-
"""Optimizes the sensor placements using Bayesian Optimization without any constraints
|
78
|
-
|
79
|
-
Args:
|
80
|
-
num_sensors (int): Number of sensor locations to optimize.
|
81
|
-
max_steps (int): Maximum number of optimization steps.
|
82
|
-
X_init (ndarray): (m, d); Initial inducing points.
|
83
|
-
init_points (int): Number of random solutions used for initial exploration.
|
84
|
-
Random exploration can help by diversifying the exploration space.
|
85
|
-
verbose (int): The level of verbosity.
|
86
|
-
seed (int): The algorithm will use it to seed the randomnumber generator, ensuring replicability.
|
87
|
-
|
88
|
-
Returns:
|
89
|
-
Xu (ndarray): (m, d); Solution sensor placement locations
|
90
|
-
"""
|
91
|
-
if X_init is None:
|
92
|
-
X_init = get_inducing_pts(self.X_train, num_sensors, random=True)
|
93
|
-
else:
|
94
|
-
num_sensors = len(X_init.reshape(-1, self.num_dim))
|
95
|
-
X_init = X_init.reshape(-1)
|
96
|
-
|
97
|
-
pbounds = {}
|
98
|
-
for i in range(self.num_dim*num_sensors):
|
99
|
-
pbounds['x{}'.format(i)] = self.pbounds_dim[i%self.num_dim]
|
100
|
-
|
101
|
-
optimizer = BayesianOptimization(f=self.objective,
|
102
|
-
pbounds=pbounds,
|
103
|
-
verbose=verbose,
|
104
|
-
random_state=seed,
|
105
|
-
allow_duplicate_points=True)
|
106
|
-
optimizer.maximize(init_points=init_points,
|
107
|
-
n_iter=max_steps)
|
108
|
-
|
109
|
-
sol = []
|
110
|
-
for i in range(self.num_dim*num_sensors):
|
111
|
-
sol.append(optimizer.max['params']['x{}'.format(i)])
|
112
|
-
sol = np.array(sol).reshape(-1, self.num_dim)
|
113
|
-
if self.transform is not None:
|
114
|
-
sol = self.transform.expand(sol,
|
115
|
-
expand_sensor_model=False)
|
116
|
-
if not isinstance(sol, np.ndarray):
|
117
|
-
sol = sol.numpy()
|
118
|
-
return sol.reshape(-1, self.num_dim)
|
sgptools/models/cma_es.py
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
# Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
import cma
|
16
|
-
import numpy as np
|
17
|
-
from shapely import geometry
|
18
|
-
from ..utils.metrics import get_mi
|
19
|
-
from ..utils.data import get_inducing_pts
|
20
|
-
|
21
|
-
|
22
|
-
class CMA_ES:
|
23
|
-
"""Class for optimizing sensor placements using CMA-ES (a genetic algorithm)
|
24
|
-
|
25
|
-
Refer to the following paper for more details:
|
26
|
-
- Adaptive Continuous-Space Informative Path Planning for Online Environmental Monitoring [Hitz et al., 2017]
|
27
|
-
|
28
|
-
Args:
|
29
|
-
X_train (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
|
30
|
-
noise_variance (float): data variance
|
31
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
32
|
-
distance_budget (float): Distance budget for when treating the inducing points
|
33
|
-
as waypoints of a path
|
34
|
-
num_robots (int): Number of robots, used when modeling
|
35
|
-
multi-robot IPP with a distance budget
|
36
|
-
transform (Transform): Transform object
|
37
|
-
"""
|
38
|
-
def __init__(self, X_train, noise_variance, kernel,
|
39
|
-
distance_budget=None,
|
40
|
-
num_robots=1,
|
41
|
-
transform=None):
|
42
|
-
self.boundaries = geometry.MultiPoint([[p[0], p[1]] for p in X_train]).convex_hull
|
43
|
-
self.X_train = X_train
|
44
|
-
self.noise_variance = noise_variance
|
45
|
-
self.kernel = kernel
|
46
|
-
self.num_dim = X_train.shape[-1]
|
47
|
-
self.distance_budget = distance_budget
|
48
|
-
self.num_robots = num_robots
|
49
|
-
self.transform = transform
|
50
|
-
|
51
|
-
def update(self, noise_variance, kernel):
|
52
|
-
"""Update GP noise variance and kernel function parameters
|
53
|
-
|
54
|
-
Args:
|
55
|
-
noise_variance (float): data variance
|
56
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
57
|
-
"""
|
58
|
-
self.noise_variance = noise_variance
|
59
|
-
self.kernel = kernel
|
60
|
-
|
61
|
-
def objective(self, X):
|
62
|
-
"""Objective function (GP-based Mutual Information)
|
63
|
-
|
64
|
-
Args:
|
65
|
-
X (ndarray): (n, d); Current solution sensor placement locations
|
66
|
-
"""
|
67
|
-
# MI does not depend on waypoint order (reshape to -1, num_dim)
|
68
|
-
X = np.array(X).reshape(-1, self.num_dim)
|
69
|
-
constraints_loss = 0.0
|
70
|
-
if self.transform is not None:
|
71
|
-
X = self.transform.expand(X)
|
72
|
-
constraints_loss = self.transform.constraints(X)
|
73
|
-
|
74
|
-
try:
|
75
|
-
mi = -get_mi(X, self.X_train, self.noise_variance, self.kernel)
|
76
|
-
mi -= constraints_loss
|
77
|
-
mi = mi.numpy()
|
78
|
-
except:
|
79
|
-
mi = 0.0 # if the cholskey decomposition fails
|
80
|
-
return mi
|
81
|
-
|
82
|
-
def optimize(self,
|
83
|
-
num_sensors=10,
|
84
|
-
max_steps=5000,
|
85
|
-
tol=1e-6,
|
86
|
-
X_init=None,
|
87
|
-
verbose=0,
|
88
|
-
seed=1234):
|
89
|
-
"""Optimizes the sensor placements using CMA-ES without any constraints
|
90
|
-
|
91
|
-
Args:
|
92
|
-
num_sensors (int): Number of sensor locations to optimize
|
93
|
-
max_steps (int): Maximum number of optimization steps
|
94
|
-
tol (float): Convergence tolerance to decide when to stop optimization
|
95
|
-
X_init (ndarray): (m, d); Initial inducing points
|
96
|
-
verbose (int): The level of verbosity.
|
97
|
-
seed (int): The algorithm will use it to seed the randomnumber generator, ensuring replicability.
|
98
|
-
|
99
|
-
Returns:
|
100
|
-
Xu (ndarray): (m, d); Solution sensor placement locations
|
101
|
-
"""
|
102
|
-
sigma0 = 1.0
|
103
|
-
|
104
|
-
if X_init is None:
|
105
|
-
X_init = get_inducing_pts(self.X_train, num_sensors, random=True)
|
106
|
-
X_init = X_init.reshape(-1)
|
107
|
-
|
108
|
-
xopt, _ = cma.fmin2(self.objective, X_init, sigma0,
|
109
|
-
options={'maxfevals': max_steps,
|
110
|
-
'verb_disp': verbose,
|
111
|
-
'tolfun': tol,
|
112
|
-
'seed': seed},
|
113
|
-
restarts=5)
|
114
|
-
|
115
|
-
xopt = np.array(xopt).reshape(-1, self.num_dim)
|
116
|
-
if self.transform is not None:
|
117
|
-
xopt = self.transform.expand(xopt,
|
118
|
-
expand_sensor_model=False)
|
119
|
-
if not isinstance(xopt, np.ndarray):
|
120
|
-
xopt = xopt.numpy()
|
121
|
-
return xopt.reshape(-1, self.num_dim)
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
import numpy as np
|
16
|
-
|
17
|
-
from ..utils.data import get_inducing_pts
|
18
|
-
from ..utils.gpflow import optimize_model
|
19
|
-
|
20
|
-
from .core.augmented_sgpr import AugmentedSGPR
|
21
|
-
|
22
|
-
|
23
|
-
def continuous_sgp(num_inducing, X_train, noise_variance, kernel,
|
24
|
-
transform=None,
|
25
|
-
Xu_init=None,
|
26
|
-
Xu_time=None,
|
27
|
-
orientation=False,
|
28
|
-
**kwargs):
|
29
|
-
"""Get sensor placement solutions using the Continuous-SGP method
|
30
|
-
|
31
|
-
Refer to the following papers for more details:
|
32
|
-
- Efficient Sensor Placement from Regression with Sparse Gaussian Processes in Continuous and Discrete Spaces [[Jakkala and Akella, 2023](https://www.itskalvik.com/publication/sgp-sp/)]
|
33
|
-
- Multi-Robot Informative Path Planning from Regression with Sparse Gaussian Processes [[Jakkala and Akella, 2024](https://www.itskalvik.com/publication/sgp-ipp/)]
|
34
|
-
|
35
|
-
Args:
|
36
|
-
num_inducing (int): Number of inducing points
|
37
|
-
X_train (ndarray): (n, d); Unlabeled random sampled training points
|
38
|
-
noise_variance (float): data variance
|
39
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
40
|
-
transform (Transform): Transform object
|
41
|
-
Xu_init (ndarray): (m, d); Initial inducing points
|
42
|
-
Xu_time (ndarray): (t, d); Temporal inducing points used in spatio-temporal models
|
43
|
-
orientation (bool): If True, a additionl dimension is added to the
|
44
|
-
inducing points to represent the FoV orientation
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
sgpr (AugmentedSGPR): Optimized sparse Gaussian process model
|
48
|
-
loss (ndarray): Loss values computed during training
|
49
|
-
"""
|
50
|
-
# Generate init inducing points
|
51
|
-
if Xu_init is None:
|
52
|
-
Xu_init = get_inducing_pts(X_train, num_inducing,
|
53
|
-
orientation=orientation)
|
54
|
-
|
55
|
-
# Fit spare GP
|
56
|
-
sgpr = AugmentedSGPR((X_train, np.zeros((len(X_train), 1)).astype(X_train.dtype)),
|
57
|
-
noise_variance=noise_variance,
|
58
|
-
kernel=kernel,
|
59
|
-
inducing_variable=Xu_init,
|
60
|
-
inducing_variable_time=Xu_time,
|
61
|
-
transform=transform)
|
62
|
-
|
63
|
-
# Train the mode
|
64
|
-
loss = optimize_model(sgpr,
|
65
|
-
kernel_grad=False,
|
66
|
-
**kwargs)
|
67
|
-
|
68
|
-
return sgpr, loss
|
sgptools/models/core/__init__.py
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# sgptools/models/core/__init__.py
|
2
|
-
|
3
|
-
"""Core modules in this package:
|
4
|
-
|
5
|
-
- `augmented_gpr`: Provides a Gaussian process model with expand and aggregate functions
|
6
|
-
- `augmented_sgpr`: Provides a sparse Gaussian process model with update, expand, and aggregate functions
|
7
|
-
- `osgpr`: Provides a streaming sparse Gaussian process model along with initialization function
|
8
|
-
- `transformations`: Provides transforms to model complex sensor field of views and handle informative path planning
|
9
|
-
"""
|
sgptools/models/core/osgpr.py
DELETED
@@ -1,291 +0,0 @@
|
|
1
|
-
# Copyright 2024 The streaming_sparse_gp Contributors. All Rights Reserved.
|
2
|
-
# https://github.com/thangbui/streaming_sparse_gp/tree/master
|
3
|
-
#
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
# you may not use this file except in compliance with the License.
|
6
|
-
# You may obtain a copy of the License at
|
7
|
-
#
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
#
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
# See the License for the specific language governing permissions and
|
14
|
-
# limitations under the License.
|
15
|
-
|
16
|
-
"""Provides a streaming sparse Gaussian process model along with initialization function
|
17
|
-
"""
|
18
|
-
|
19
|
-
import tensorflow as tf
|
20
|
-
import numpy as np
|
21
|
-
|
22
|
-
import gpflow
|
23
|
-
from gpflow.inducing_variables import InducingPoints
|
24
|
-
from gpflow.models import GPModel, InternalDataTrainingLossMixin
|
25
|
-
from gpflow import covariances
|
26
|
-
from ...utils.data import get_inducing_pts
|
27
|
-
|
28
|
-
|
29
|
-
class OSGPR_VFE(GPModel, InternalDataTrainingLossMixin):
|
30
|
-
"""Online Sparse Variational GP regression model from [streaming_sparse_gp](https://github.com/thangbui/streaming_sparse_gp/tree/master)
|
31
|
-
|
32
|
-
Refer to the following paper for more details:
|
33
|
-
- Streaming Gaussian process approximations [Bui et al., 2017]
|
34
|
-
|
35
|
-
Args:
|
36
|
-
data (tuple): (X, y) ndarrays with inputs (n, d) and labels (n, 1)
|
37
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
38
|
-
mu_old (ndarray): mean of old `q(u)`; here `u` are the latents corresponding to the inducing points `Z_old`
|
39
|
-
Su_old (ndarray): posterior covariance of old `q(u)`
|
40
|
-
Kaa_old (ndarray): prior covariance of old `q(u)`
|
41
|
-
Z_old (ndarray): (m_old, d): Old initial inducing points
|
42
|
-
Z (ndarray): (m_new, d): New initial inducing points
|
43
|
-
mean_function (function): GP mean function
|
44
|
-
"""
|
45
|
-
def __init__(self, data, kernel, mu_old, Su_old, Kaa_old, Z_old, Z, mean_function=None):
|
46
|
-
self.X, self.Y = self.data = gpflow.models.util.data_input_to_tensor(data)
|
47
|
-
likelihood = gpflow.likelihoods.Gaussian()
|
48
|
-
num_latent_gps = GPModel.calc_num_latent_gps_from_data(data, kernel, likelihood)
|
49
|
-
super().__init__(kernel, likelihood, mean_function, num_latent_gps)
|
50
|
-
|
51
|
-
self.inducing_variable = InducingPoints(Z)
|
52
|
-
self.num_data = self.X.shape[0]
|
53
|
-
|
54
|
-
self.mu_old = tf.Variable(mu_old, shape=tf.TensorShape(None), trainable=False)
|
55
|
-
self.M_old = Z_old.shape[0]
|
56
|
-
self.Su_old = tf.Variable(Su_old, shape=tf.TensorShape(None), trainable=False)
|
57
|
-
self.Kaa_old = tf.Variable(Kaa_old, shape=tf.TensorShape(None), trainable=False)
|
58
|
-
self.Z_old = tf.Variable(Z_old, shape=tf.TensorShape(None), trainable=False)
|
59
|
-
|
60
|
-
def init_Z(self):
|
61
|
-
M = self.inducing_variable.Z.shape[0]
|
62
|
-
M_old = int(0.7 * M)
|
63
|
-
M_new = M - M_old
|
64
|
-
old_Z = self.Z_old.numpy()[np.random.permutation(M)[0:M_old], :]
|
65
|
-
new_Z = self.X.numpy()[np.random.permutation(self.X.shape[0])[0:M_new], :]
|
66
|
-
Z = np.vstack((old_Z, new_Z))
|
67
|
-
return Z
|
68
|
-
|
69
|
-
def update(self, data, inducing_variable=None, update_inducing=True):
|
70
|
-
"""Configure the OSGPR to adapt to a new batch of data.
|
71
|
-
Note: The OSGPR needs to be trained using gradient-based approaches after update.
|
72
|
-
|
73
|
-
Args:
|
74
|
-
data (tuple): (X, y) ndarrays with new batch of inputs (n, d) and labels (n, ndim)
|
75
|
-
inducing_variable (ndarray): (m_new, d): New initial inducing points
|
76
|
-
update_inducing (bool): Whether to update the inducing points
|
77
|
-
"""
|
78
|
-
self.X, self.Y = self.data = gpflow.models.util.data_input_to_tensor(data)
|
79
|
-
self.num_data = self.X.shape[0]
|
80
|
-
|
81
|
-
# Update the inducing points
|
82
|
-
self.Z_old.assign(self.inducing_variable.Z.numpy())
|
83
|
-
if inducing_variable is None and update_inducing:
|
84
|
-
inducing_variable = self.init_Z()
|
85
|
-
if inducing_variable is not None:
|
86
|
-
self.inducing_variable.Z.assign(inducing_variable)
|
87
|
-
|
88
|
-
# Get posterior mean and covariance for the old inducing points
|
89
|
-
mu_old, Su_old = self.predict_f(self.Z_old, full_cov=True)
|
90
|
-
self.mu_old.assign(mu_old.numpy())
|
91
|
-
self.Su_old.assign(Su_old.numpy())
|
92
|
-
|
93
|
-
# Get the prior covariance matrix for the old inducing points
|
94
|
-
Kaa_old = self.kernel(self.Z_old)
|
95
|
-
self.Kaa_old.assign(Kaa_old.numpy())
|
96
|
-
|
97
|
-
def _common_terms(self):
|
98
|
-
Mb = self.inducing_variable.num_inducing
|
99
|
-
Ma = self.M_old
|
100
|
-
# jitter = gpflow.default_jitter()
|
101
|
-
jitter = gpflow.utilities.to_default_float(1e-4)
|
102
|
-
sigma2 = self.likelihood.variance
|
103
|
-
sigma = tf.sqrt(sigma2)
|
104
|
-
|
105
|
-
Saa = self.Su_old
|
106
|
-
ma = self.mu_old
|
107
|
-
|
108
|
-
# a is old inducing points, b is new
|
109
|
-
# f is training points
|
110
|
-
# s is test points
|
111
|
-
Kbf = covariances.Kuf(self.inducing_variable, self.kernel, self.X)
|
112
|
-
Kbb = covariances.Kuu(self.inducing_variable, self.kernel, jitter=jitter)
|
113
|
-
Kba = covariances.Kuf(self.inducing_variable, self.kernel, self.Z_old)
|
114
|
-
Kaa_cur = gpflow.utilities.add_noise_cov(self.kernel(self.Z_old), jitter)
|
115
|
-
Kaa = gpflow.utilities.add_noise_cov(self.Kaa_old, jitter)
|
116
|
-
|
117
|
-
err = self.Y - self.mean_function(self.X)
|
118
|
-
|
119
|
-
Sainv_ma = tf.linalg.solve(Saa, ma)
|
120
|
-
Sinv_y = self.Y / sigma2
|
121
|
-
c1 = tf.matmul(Kbf, Sinv_y)
|
122
|
-
c2 = tf.matmul(Kba, Sainv_ma)
|
123
|
-
c = c1 + c2
|
124
|
-
|
125
|
-
Lb = tf.linalg.cholesky(Kbb)
|
126
|
-
Lbinv_c = tf.linalg.triangular_solve(Lb, c, lower=True)
|
127
|
-
Lbinv_Kba = tf.linalg.triangular_solve(Lb, Kba, lower=True)
|
128
|
-
Lbinv_Kbf = tf.linalg.triangular_solve(Lb, Kbf, lower=True) / sigma
|
129
|
-
d1 = tf.matmul(Lbinv_Kbf, Lbinv_Kbf, transpose_b=True)
|
130
|
-
|
131
|
-
LSa = tf.linalg.cholesky(Saa)
|
132
|
-
Kab_Lbinv = tf.linalg.matrix_transpose(Lbinv_Kba)
|
133
|
-
LSainv_Kab_Lbinv = tf.linalg.triangular_solve(
|
134
|
-
LSa, Kab_Lbinv, lower=True)
|
135
|
-
d2 = tf.matmul(LSainv_Kab_Lbinv, LSainv_Kab_Lbinv, transpose_a=True)
|
136
|
-
|
137
|
-
La = tf.linalg.cholesky(Kaa)
|
138
|
-
Lainv_Kab_Lbinv = tf.linalg.triangular_solve(
|
139
|
-
La, Kab_Lbinv, lower=True)
|
140
|
-
d3 = tf.matmul(Lainv_Kab_Lbinv, Lainv_Kab_Lbinv, transpose_a=True)
|
141
|
-
|
142
|
-
D = tf.eye(Mb, dtype=gpflow.default_float()) + d1 + d2 - d3
|
143
|
-
D = gpflow.utilities.add_noise_cov(D, jitter)
|
144
|
-
LD = tf.linalg.cholesky(D)
|
145
|
-
|
146
|
-
LDinv_Lbinv_c = tf.linalg.triangular_solve(LD, Lbinv_c, lower=True)
|
147
|
-
|
148
|
-
return (Kbf, Kba, Kaa, Kaa_cur, La, Kbb, Lb, D, LD,
|
149
|
-
Lbinv_Kba, LDinv_Lbinv_c, err, d1)
|
150
|
-
|
151
|
-
def maximum_log_likelihood_objective(self):
|
152
|
-
"""
|
153
|
-
Construct a tensorflow function to compute the bound on the marginal
|
154
|
-
likelihood.
|
155
|
-
"""
|
156
|
-
|
157
|
-
Mb = self.inducing_variable.num_inducing
|
158
|
-
Ma = self.M_old
|
159
|
-
jitter = gpflow.default_jitter()
|
160
|
-
# jitter = gpflow.utilities.to_default_float(1e-4)
|
161
|
-
sigma2 = self.likelihood.variance
|
162
|
-
sigma = tf.sqrt(sigma2)
|
163
|
-
N = self.num_data
|
164
|
-
|
165
|
-
Saa = self.Su_old
|
166
|
-
ma = self.mu_old
|
167
|
-
|
168
|
-
# a is old inducing points, b is new
|
169
|
-
# f is training points
|
170
|
-
Kfdiag = self.kernel(self.X, full_cov=False)
|
171
|
-
(Kbf, Kba, Kaa, Kaa_cur, La, Kbb, Lb, D, LD,
|
172
|
-
Lbinv_Kba, LDinv_Lbinv_c, err, Qff) = self._common_terms()
|
173
|
-
|
174
|
-
LSa = tf.linalg.cholesky(Saa)
|
175
|
-
Lainv_ma = tf.linalg.triangular_solve(LSa, ma, lower=True)
|
176
|
-
|
177
|
-
# constant term
|
178
|
-
bound = -0.5 * N * np.log(2 * np.pi)
|
179
|
-
# quadratic term
|
180
|
-
bound += -0.5 * tf.reduce_sum(tf.square(err)) / sigma2
|
181
|
-
# bound += -0.5 * tf.reduce_sum(ma * Sainv_ma)
|
182
|
-
bound += -0.5 * tf.reduce_sum(tf.square(Lainv_ma))
|
183
|
-
bound += 0.5 * tf.reduce_sum(tf.square(LDinv_Lbinv_c))
|
184
|
-
# log det term
|
185
|
-
bound += -0.5 * N * tf.reduce_sum(tf.math.log(sigma2))
|
186
|
-
bound += - tf.reduce_sum(tf.math.log(tf.linalg.diag_part(LD)))
|
187
|
-
|
188
|
-
# delta 1: trace term
|
189
|
-
bound += -0.5 * tf.reduce_sum(Kfdiag) / sigma2
|
190
|
-
bound += 0.5 * tf.reduce_sum(tf.linalg.diag_part(Qff))
|
191
|
-
|
192
|
-
# delta 2: a and b difference
|
193
|
-
bound += tf.reduce_sum(tf.math.log(tf.linalg.diag_part(La)))
|
194
|
-
bound += - tf.reduce_sum(tf.math.log(tf.linalg.diag_part(LSa)))
|
195
|
-
|
196
|
-
Kaadiff = Kaa_cur - tf.matmul(Lbinv_Kba, Lbinv_Kba, transpose_a=True)
|
197
|
-
Sainv_Kaadiff = tf.linalg.solve(Saa, Kaadiff)
|
198
|
-
Kainv_Kaadiff = tf.linalg.solve(Kaa, Kaadiff)
|
199
|
-
|
200
|
-
bound += -0.5 * tf.reduce_sum(
|
201
|
-
tf.linalg.diag_part(Sainv_Kaadiff) - tf.linalg.diag_part(Kainv_Kaadiff))
|
202
|
-
|
203
|
-
return bound
|
204
|
-
|
205
|
-
def predict_f(self, Xnew, full_cov=False):
|
206
|
-
"""
|
207
|
-
Compute the mean and variance of the latent function at some new points
|
208
|
-
Xnew.
|
209
|
-
"""
|
210
|
-
|
211
|
-
# jitter = gpflow.default_jitter()
|
212
|
-
jitter = gpflow.utilities.to_default_float(1e-4)
|
213
|
-
|
214
|
-
# a is old inducing points, b is new
|
215
|
-
# f is training points
|
216
|
-
# s is test points
|
217
|
-
Kbs = covariances.Kuf(self.inducing_variable, self.kernel, Xnew)
|
218
|
-
(Kbf, Kba, Kaa, Kaa_cur, La, Kbb, Lb, D, LD,
|
219
|
-
Lbinv_Kba, LDinv_Lbinv_c, err, Qff) = self._common_terms()
|
220
|
-
|
221
|
-
Lbinv_Kbs = tf.linalg.triangular_solve(Lb, Kbs, lower=True)
|
222
|
-
LDinv_Lbinv_Kbs = tf.linalg.triangular_solve(LD, Lbinv_Kbs, lower=True)
|
223
|
-
mean = tf.matmul(LDinv_Lbinv_Kbs, LDinv_Lbinv_c, transpose_a=True)
|
224
|
-
|
225
|
-
if full_cov:
|
226
|
-
Kss = self.kernel(Xnew) + jitter * tf.eye(tf.shape(Xnew)[0], dtype=gpflow.default_float())
|
227
|
-
var1 = Kss
|
228
|
-
var2 = - tf.matmul(Lbinv_Kbs, Lbinv_Kbs, transpose_a=True)
|
229
|
-
var3 = tf.matmul(LDinv_Lbinv_Kbs, LDinv_Lbinv_Kbs, transpose_a=True)
|
230
|
-
var = var1 + var2 + var3
|
231
|
-
else:
|
232
|
-
var1 = self.kernel(Xnew, full_cov=False)
|
233
|
-
var2 = -tf.reduce_sum(tf.square(Lbinv_Kbs), axis=0)
|
234
|
-
var3 = tf.reduce_sum(tf.square(LDinv_Lbinv_Kbs), axis=0)
|
235
|
-
var = var1 + var2 + var3
|
236
|
-
|
237
|
-
return mean + self.mean_function(Xnew), var
|
238
|
-
|
239
|
-
|
240
|
-
def init_osgpr(X_train,
|
241
|
-
num_inducing=10,
|
242
|
-
lengthscales=1.0,
|
243
|
-
variance=1.0,
|
244
|
-
noise_variance=0.001,
|
245
|
-
kernel=None,
|
246
|
-
ndim=1):
|
247
|
-
"""Initialize a VFE OSGPR model with an RBF kernel with
|
248
|
-
unit variance and lengthcales, and 0.001 noise variance.
|
249
|
-
Used in the Online Continuous SGP approach.
|
250
|
-
|
251
|
-
Args:
|
252
|
-
X_train (ndarray): (n, d); Unlabeled random sampled training points.
|
253
|
-
They only effect the initial inducing point locations,
|
254
|
-
i.e., limits them to the bounds of the data
|
255
|
-
num_inducing (int): Number of inducing points
|
256
|
-
lengthscales (float or list): Kernel lengthscale(s), if passed as a list,
|
257
|
-
each element corresponds to each data dimension
|
258
|
-
variance (float): Kernel variance
|
259
|
-
noise_variance (float): Data noise variance
|
260
|
-
kernel (gpflow.kernels.Kernel): gpflow kernel function
|
261
|
-
ndim (int): Number of output dimensions
|
262
|
-
|
263
|
-
Returns:
|
264
|
-
online_param (OSGPR_VFE): Initialized online sparse Gaussian process model
|
265
|
-
"""
|
266
|
-
|
267
|
-
if kernel is None:
|
268
|
-
kernel = gpflow.kernels.SquaredExponential(lengthscales=lengthscales,
|
269
|
-
variance=variance)
|
270
|
-
|
271
|
-
y_train = np.zeros((len(X_train), ndim), dtype=X_train.dtype)
|
272
|
-
Z_init = get_inducing_pts(X_train, num_inducing)
|
273
|
-
init_param = gpflow.models.SGPR((X_train, y_train),
|
274
|
-
kernel,
|
275
|
-
inducing_variable=Z_init,
|
276
|
-
noise_variance=noise_variance)
|
277
|
-
|
278
|
-
# Initialize the OSGPR model using the parameters from the SGPR model
|
279
|
-
# The X_train and y_train here will be overwritten in the online phase
|
280
|
-
X_train = np.zeros([2, X_train.shape[-1]], dtype=X_train.dtype)
|
281
|
-
y_train = np.zeros([2, ndim], dtype=X_train.dtype)
|
282
|
-
Zopt = init_param.inducing_variable.Z.numpy()
|
283
|
-
mu, Su = init_param.predict_f(Zopt, full_cov=True)
|
284
|
-
Kaa = init_param.kernel(Zopt)
|
285
|
-
online_param = OSGPR_VFE((X_train[:2], y_train[:2]),
|
286
|
-
init_param.kernel,
|
287
|
-
mu, Su[0], Kaa,
|
288
|
-
Zopt, Zopt)
|
289
|
-
online_param.likelihood.variance.assign(init_param.likelihood.variance)
|
290
|
-
|
291
|
-
return online_param
|