From 982a9577e595b8f50fc22790590915ef33b4fe6a Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Wed, 9 Aug 2017 02:45:53 +0200 Subject: [PATCH 01/11] Refactoring DataScaler to parameterized --- GPflowOpt/scaling.py | 61 ++++++++++++++------------------------ testing/test_datascaler.py | 8 ++--- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/GPflowOpt/scaling.py b/GPflowOpt/scaling.py index baa4f08..bc2d451 100644 --- a/GPflowOpt/scaling.py +++ b/GPflowOpt/scaling.py @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from GPflow.param import DataHolder, AutoFlow, Parameterized -from GPflow.model import Model, GPModel +from GPflow.param import DataHolder, AutoFlow +from GPflow.model import GPModel from GPflow import settings import numpy as np from .transforms import LinearTransform, DataTransform from .domain import UnitCube +from .models import ModelWrapper float_type = settings.dtypes.float_type -class DataScaler(GPModel): +class DataScaler(ModelWrapper): """ Model-wrapping class, primarily intended to assure the data in GPflow models is scaled. One DataScaler wraps one GPflow model, and can scale the input as well as the output data. By default, if any kind of object attribute @@ -59,13 +60,9 @@ def __init__(self, model, domain=None, normalize_Y=False): :param normalize_Y: (default: False) enable automatic scaling of output values to zero mean and unit variance. """ - # model sanity checks - assert (model is not None) + # model sanity checks, slightly stronger conditions than the wrapper assert (isinstance(model, GPModel)) - self._parent = None - - # Wrap model - self.wrapped = model + super(DataScaler, self).__init__(model) # Initial configuration of the datascaler n_inputs = model.X.shape[1] @@ -74,34 +71,8 @@ def __init__(self, model, domain=None, normalize_Y=False): self._normalize_Y = normalize_Y self._output_transform = LinearTransform(np.ones(n_outputs), np.zeros(n_outputs)) - # The assignments in the constructor of GPModel take care of initial re-scaling of model data. - super(DataScaler, self).__init__(model.X.value, model.Y.value, None, None, 1, name=model.name+"_datascaler") - del self.kern - del self.mean_function - del self.likelihood - - def __getattr__(self, item): - """ - If an attribute is not found in this class, it is searched in the wrapped model - """ - return self.wrapped.__getattribute__(item) - - def __setattr__(self, key, value): - """ - If setting :attr:`wrapped` attribute, point parent to this object (the datascaler) - """ - if key is 'wrapped': - object.__setattr__(self, key, value) - value.__setattr__('_parent', self) - return - - super(DataScaler, self).__setattr__(key, value) - - def __eq__(self, other): - return self.wrapped == other - - def __str__(self, prepend=''): - return self.wrapped.__str__(prepend) + self.X = model.X.value + self.Y = model.Y.value @property def input_transform(self): @@ -216,6 +187,20 @@ def build_predict(self, Xnew, full_cov=False): f, var = self.wrapped.build_predict(self.input_transform.build_forward(Xnew), full_cov=full_cov) return self.output_transform.build_backward(f), self.output_transform.build_backward_variance(var) + @AutoFlow((float_type, [None, None])) + def predict_f(self, Xnew): + """ + Compute the mean and variance of held-out data at the points Xnew + """ + return self.build_predict(Xnew) + + @AutoFlow((float_type, [None, None])) + def predict_f_full_cov(self, Xnew): + """ + Compute the mean and variance of held-out data at the points Xnew + """ + return self.build_predict(Xnew, full_cov=True) + @AutoFlow((float_type, [None, None])) def predict_y(self, Xnew): """ @@ -230,6 +215,6 @@ def predict_density(self, Xnew, Ynew): """ Compute the (log) density of the data Ynew at the points Xnew """ - mu, var = self.build_predict(Xnew) + mu, var = self.wrapped.build_predict(self.input_transform.build_forward(Xnew)) Ys = self.output_transform.build_forward(Ynew) return self.likelihood.predict_density(mu, var, Ys) diff --git a/testing/test_datascaler.py b/testing/test_datascaler.py index 0abe6fd..13c9b41 100644 --- a/testing/test_datascaler.py +++ b/testing/test_datascaler.py @@ -80,7 +80,7 @@ def test_enabling_transforms(self): def test_predict_scaling(self): m = self.create_parabola_model() - n = DataScaler(self.create_parabola_model(), self.domain) + n = DataScaler(self.create_parabola_model(), self.domain, normalize_Y=True) m.optimize() n.optimize() @@ -103,6 +103,6 @@ def test_predict_scaling(self): Yt = parabola2d(Xt) #+ np.random.rand(20, 1) * 0.05 fr = m.predict_density(Xt, Yt) fs = n.predict_density(Xt, Yt) - print(fr) - print(fs) - np.testing.assert_allclose(fr, fs, rtol=1e-3) + #print(fr) + #print(fs) + np.testing.assert_allclose(fr, fs, rtol=1e-2) From 2cc18555345783666e6e22d03ffa80c68e643f85 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Wed, 9 Aug 2017 07:14:39 +0200 Subject: [PATCH 02/11] Bugfix in modelwrapper, make sure compile flag is set correctly --- GPflowOpt/models.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 GPflowOpt/models.py diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py new file mode 100644 index 0000000..9e95d8a --- /dev/null +++ b/GPflowOpt/models.py @@ -0,0 +1,64 @@ +# Copyright 2017 Joachim van der Herten +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from GPflow.param import Parameterized +from GPflow.model import Model + + +class ModelWrapper(Parameterized): + """ + Modelwrapper class + """ + def __init__(self, model): + """ + :param model: model to be wrapped + """ + super(ModelWrapper, self).__init__() + + # Wrap model + assert (isinstance(model, Model)) + self.wrapped = model + + def __getattr__(self, item): + """ + If an attribute is not found in this class, it is searched in the wrapped model + """ + + # Exception for AF storages, if a method with the same name exists in this class, do not find the cache + # in the wrapped model. + if item.endswith('_AF_storage'): + method = item[1:].rstrip('_AF_storage') + if method in dir(self): + raise AttributeError("{0} has no attribute {1}".format(self.__class__.__name__, item)) + + return getattr(self.wrapped, item) + + def __setattr__(self, key, value): + """ + If setting :attr:`wrapped` attribute, point parent to this object (the datascaler) + """ + if key is 'wrapped': + object.__setattr__(self, key, value) + value.__setattr__('_parent', self) + return + + if key is '_needs_recompile': + setattr(self.wrapped, key, value) + + super(ModelWrapper, self).__setattr__(key, value) + + def __eq__(self, other): + return self.wrapped == other + + def __str__(self, prepend=''): + return self.wrapped.__str__(prepend) From 948965d0a0da7a7814cb7b57433df32665fd0b0e Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Wed, 9 Aug 2017 08:12:04 +0200 Subject: [PATCH 03/11] Adding tests for the wrapping super class --- GPflowOpt/__init__.py | 1 + GPflowOpt/models.py | 1 + testing/test_datascaler.py | 6 +----- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/GPflowOpt/__init__.py b/GPflowOpt/__init__.py index 89b71d8..e83d54b 100644 --- a/GPflowOpt/__init__.py +++ b/GPflowOpt/__init__.py @@ -21,3 +21,4 @@ from . import scaling from . import objective from . import pareto +from . import models diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index 9e95d8a..78c3424 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -54,6 +54,7 @@ def __setattr__(self, key, value): if key is '_needs_recompile': setattr(self.wrapped, key, value) + return super(ModelWrapper, self).__setattr__(key, value) diff --git a/testing/test_datascaler.py b/testing/test_datascaler.py index 13c9b41..cd4a4ed 100644 --- a/testing/test_datascaler.py +++ b/testing/test_datascaler.py @@ -26,8 +26,6 @@ def test_object_integrity(self): Xs, Ys = m.X.value, m.Y.value n = DataScaler(m, self.domain) - self.assertEqual(n.wrapped, m) - self.assertEqual(m._parent, n) self.assertTrue(np.allclose(Xs, n.X.value)) self.assertTrue(np.allclose(Ys, n.Y.value)) @@ -100,9 +98,7 @@ def test_predict_scaling(self): self.assertTrue(np.allclose(fr, fs, atol=1e-3)) self.assertTrue(np.allclose(vr, vs, atol=1e-3)) - Yt = parabola2d(Xt) #+ np.random.rand(20, 1) * 0.05 + Yt = parabola2d(Xt) fr = m.predict_density(Xt, Yt) fs = n.predict_density(Xt, Yt) - #print(fr) - #print(fs) np.testing.assert_allclose(fr, fs, rtol=1e-2) From 44720791e15d7f5871a0108602d79100f2ac8446 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Wed, 9 Aug 2017 08:12:34 +0200 Subject: [PATCH 04/11] Forgot adding the file --- testing/test_modelwrapper.py | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 testing/test_modelwrapper.py diff --git a/testing/test_modelwrapper.py b/testing/test_modelwrapper.py new file mode 100644 index 0000000..eccbb25 --- /dev/null +++ b/testing/test_modelwrapper.py @@ -0,0 +1,71 @@ +import GPflowOpt +import unittest +import GPflow +import numpy as np + +float_type = GPflow.settings.dtypes.float_type + + +class MethodOverride(GPflowOpt.models.ModelWrapper): + + def __init__(self, m): + super(MethodOverride, self).__init__(m) + self.A = GPflow.param.DataHolder(np.array([1.0])) + + @GPflow.param.AutoFlow((float_type, [None, None])) + def predict_f(self, Xnew): + """ + Compute the mean and variance of held-out data at the points Xnew + """ + m, v = self.build_predict(Xnew) + return self.A * m, v + + +class TestModelWrapper(unittest.TestCase): + + def simple_model(self): + x = np.random.rand(10,2) * 2 * np.pi + y = np.sin(x[:,[0]]) + m = GPflow.gpr.GPR(x,y, kern=GPflow.kernels.RBF(1)) + return m + + def test_object_integrity(self): + m = self.simple_model() + w = GPflowOpt.models.ModelWrapper(m) + self.assertEqual(w.wrapped, m) + self.assertEqual(m._parent, w) + self.assertEqual(w.optimize, m.optimize) + + def test_optimize(self): + m = self.simple_model() + w = GPflowOpt.models.ModelWrapper(m) + logL = m.compute_log_likelihood() + self.assertTrue(np.allclose(logL, w.compute_log_likelihood())) + + # Check if compiled & optimized, verify attributes are set in the right object. + w.optimize(maxiter=5) + self.assertTrue(hasattr(m, '_minusF')) + self.assertFalse('_minusF' in w.__dict__) + self.assertGreater(m.compute_log_likelihood(), logL) + + def test_af_storage_detection(self): + # Regression test for a bug with predict_f/predict_y... etc. + m = self.simple_model() + x = np.random.rand(10,2) + m.predict_f(x) + self.assertTrue(hasattr(m, '_predict_f_AF_storage')) + w = MethodOverride(m) + self.assertFalse(hasattr(w, '_predict_f_AF_storage')) + w.predict_f(x) + self.assertTrue(hasattr(w, '_predict_f_AF_storage')) + + def test_set_recompile(self): + # Regression test for setting recompile in the wrong object + m = self.simple_model() + w = GPflowOpt.models.ModelWrapper(m) + w._needs_recompile = False + self.assertFalse('_needs_recompile' in w.__dict__) + self.assertTrue('_needs_recompile' in m.__dict__) + self.assertFalse(w._needs_recompile) + self.assertFalse(m._needs_recompile) + From 8e7d9b3f8ee60e013f5178b2a14f06ec8ec487b0 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Thu, 10 Aug 2017 03:25:42 +0200 Subject: [PATCH 05/11] Enable wrapping of modelwrappers --- GPflowOpt/models.py | 2 +- testing/test_modelwrapper.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index 78c3424..aa2abac 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -26,7 +26,7 @@ def __init__(self, model): super(ModelWrapper, self).__init__() # Wrap model - assert (isinstance(model, Model)) + assert isinstance(model, (Model, ModelWrapper)) self.wrapped = model def __getattr__(self, item): diff --git a/testing/test_modelwrapper.py b/testing/test_modelwrapper.py index eccbb25..3b02296 100644 --- a/testing/test_modelwrapper.py +++ b/testing/test_modelwrapper.py @@ -69,3 +69,14 @@ def test_set_recompile(self): self.assertFalse(w._needs_recompile) self.assertFalse(m._needs_recompile) + def test_double_wrap(self): + m = self.simple_model() + n = GPflowOpt.models.ModelWrapper(MethodOverride(m)) + n.optimize(maxiter=10) + Xt = np.random.rand(10,2) + n.predict_f(Xt) + self.assertFalse('_predict_f_AF_storage' in n.__dict__) + self.assertTrue('_predict_f_AF_storage' in n.wrapped.__dict__) + self.assertFalse('_predict_f_AF_storage' in n.wrapped.wrapped.__dict__) + + From e423653e657e432280d9383edd57e20fb510b2a4 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Thu, 10 Aug 2017 03:44:35 +0200 Subject: [PATCH 06/11] Documentation --- GPflowOpt/models.py | 14 ++++++++++---- doc/source/interfaces.rst | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index aa2abac..e5c845c 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -17,7 +17,13 @@ class ModelWrapper(Parameterized): """ - Modelwrapper class + Class for fast implementation of a wrapper for models defined in GPflow. Once wrapped, all lookups for attributes + which are not found in the wrapper class are automatically forwarded to the wrapped model. + + To influence the I/O of methods on the wrapped class, simply implement the method in the wrapper and call the + appropriate methods on the wrapped class. Specific logic is included to make sure that if AutoFlow methods are + influenced following this pattern, the original AF storage (if existing) is unaffected and a new storage is added + to the subclass. """ def __init__(self, model): """ @@ -25,15 +31,14 @@ def __init__(self, model): """ super(ModelWrapper, self).__init__() - # Wrap model assert isinstance(model, (Model, ModelWrapper)) + #: Wrapped model self.wrapped = model def __getattr__(self, item): """ If an attribute is not found in this class, it is searched in the wrapped model """ - # Exception for AF storages, if a method with the same name exists in this class, do not find the cache # in the wrapped model. if item.endswith('_AF_storage'): @@ -45,7 +50,8 @@ def __getattr__(self, item): def __setattr__(self, key, value): """ - If setting :attr:`wrapped` attribute, point parent to this object (the datascaler) + 1) If setting :attr:`wrapped` attribute, point parent to this object (the datascaler). + 2) If setting the recompilation attribute, always do this on the wrapped class. """ if key is 'wrapped': object.__setattr__(self, key, value) diff --git a/doc/source/interfaces.rst b/doc/source/interfaces.rst index 6756e40..86b0b0f 100644 --- a/doc/source/interfaces.rst +++ b/doc/source/interfaces.rst @@ -36,3 +36,11 @@ Transform :special-members: .. autoclass:: GPflowOpt.transforms.DataTransform :special-members: + +ModelWrapper +------------ +.. automodule:: GPflowOpt.models + :special-members: +.. autoclass:: GPflowOpt.models.ModelWrapper + :members: + :special-members: From 2bf2c4451396b41d9116ba93c33598cfa0f62e80 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Sun, 13 Aug 2017 17:14:47 +0200 Subject: [PATCH 07/11] Solving asserts, and allow modelwrappers as input in acquisition --- GPflowOpt/acquisition/acquisition.py | 6 +++++- GPflowOpt/acquisition/ei.py | 1 - GPflowOpt/scaling.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/GPflowOpt/acquisition/acquisition.py b/GPflowOpt/acquisition/acquisition.py index 907ef8a..aa48e7f 100644 --- a/GPflowOpt/acquisition/acquisition.py +++ b/GPflowOpt/acquisition/acquisition.py @@ -14,8 +14,10 @@ from ..scaling import DataScaler from ..domain import UnitCube +from ..models import ModelWrapper from GPflow.param import Parameterized, AutoFlow, ParamList +from GPflow.model import Model from GPflow import settings import numpy as np @@ -48,7 +50,9 @@ def __init__(self, models=[], optimize_restarts=5): :param optimize_restarts: number of optimization restarts to use when training the models """ super(Acquisition, self).__init__() - self._models = ParamList([DataScaler(m) for m in np.atleast_1d(models).tolist()]) + models = np.atleast_1d(models) + assert all(isinstance(model, (Model, ModelWrapper))for model in models) + self._models = ParamList([DataScaler(m) for m in models]) self._default_params = list(map(lambda m: m.get_free_state(), self._models)) assert (optimize_restarts >= 0) diff --git a/GPflowOpt/acquisition/ei.py b/GPflowOpt/acquisition/ei.py index 90507cb..2c9ce87 100644 --- a/GPflowOpt/acquisition/ei.py +++ b/GPflowOpt/acquisition/ei.py @@ -57,7 +57,6 @@ def __init__(self, model): :param model: GPflow model (single output) representing our belief of the objective """ super(ExpectedImprovement, self).__init__(model) - assert (isinstance(model, Model)) self.fmin = DataHolder(np.zeros(1)) self.setup() diff --git a/GPflowOpt/scaling.py b/GPflowOpt/scaling.py index bc2d451..141cb57 100644 --- a/GPflowOpt/scaling.py +++ b/GPflowOpt/scaling.py @@ -61,7 +61,6 @@ def __init__(self, model, domain=None, normalize_Y=False): variance. """ # model sanity checks, slightly stronger conditions than the wrapper - assert (isinstance(model, GPModel)) super(DataScaler, self).__init__(model) # Initial configuration of the datascaler From 0301ffaa163f037bf999ea93522ba9d054e0f0c9 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Sun, 13 Aug 2017 21:55:50 +0200 Subject: [PATCH 08/11] Improving setattr in modelwrapper to cope with some inconsistensies due to multiple wrap operations --- GPflowOpt/models.py | 25 +++++++++++++++++++----- testing/test_modelwrapper.py | 38 +++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index e5c845c..bd46ecb 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -45,7 +45,6 @@ def __getattr__(self, item): method = item[1:].rstrip('_AF_storage') if method in dir(self): raise AttributeError("{0} has no attribute {1}".format(self.__class__.__name__, item)) - return getattr(self.wrapped, item) def __setattr__(self, key, value): @@ -58,11 +57,27 @@ def __setattr__(self, key, value): value.__setattr__('_parent', self) return - if key is '_needs_recompile': - setattr(self.wrapped, key, value) - return + try: + # If attribute is in this object, set it. Test by using getattribute instead of hasattr to avoid lookup in + # wrapped object. + self.__getattribute__(key) + super(ModelWrapper, self).__setattr__(key, value) + except AttributeError: + # Attribute is not in wrapper. + # In case no wrapped object is set yet (e.g. constructor), set in wrapper. + if 'wrapped' not in self.__dict__: + super(ModelWrapper, self).__setattr__(key, value) + return - super(ModelWrapper, self).__setattr__(key, value) + if hasattr(self, key): + # Now use hasattr, we know getattribute already failed so if it returns true, it must be in the wrapped + # object. Hasattr is called on self instead of self.wrapped to account for the different handling of + # AF storages. + # Prefer setting the attribute in the wrapped object if exists. + setattr(self.wrapped, key, value) + else: + # If not, set in wrapper nonetheless. + super(ModelWrapper, self).__setattr__(key, value) def __eq__(self, other): return self.wrapped == other diff --git a/testing/test_modelwrapper.py b/testing/test_modelwrapper.py index 3b02296..aa2cfa1 100644 --- a/testing/test_modelwrapper.py +++ b/testing/test_modelwrapper.py @@ -20,6 +20,22 @@ def predict_f(self, Xnew): m, v = self.build_predict(Xnew) return self.A * m, v + @property + def X(self): + return self.wrapped.X + + @X.setter + def X(self, Xc): + self.wrapped.X = Xc + + @property + def foo(self): + return 1 + + @foo.setter + def foo(self, val): + self.wrapped.foo = val + class TestModelWrapper(unittest.TestCase): @@ -59,8 +75,8 @@ def test_af_storage_detection(self): w.predict_f(x) self.assertTrue(hasattr(w, '_predict_f_AF_storage')) - def test_set_recompile(self): - # Regression test for setting recompile in the wrong object + def test_set_wrapped_attributes(self): + # Regression test for setting certain keys in the right object m = self.simple_model() w = GPflowOpt.models.ModelWrapper(m) w._needs_recompile = False @@ -73,10 +89,26 @@ def test_double_wrap(self): m = self.simple_model() n = GPflowOpt.models.ModelWrapper(MethodOverride(m)) n.optimize(maxiter=10) - Xt = np.random.rand(10,2) + Xt = np.random.rand(10, 2) n.predict_f(Xt) self.assertFalse('_predict_f_AF_storage' in n.__dict__) self.assertTrue('_predict_f_AF_storage' in n.wrapped.__dict__) self.assertFalse('_predict_f_AF_storage' in n.wrapped.wrapped.__dict__) + n = MethodOverride(GPflowOpt.models.ModelWrapper(m)) + Xn = np.random.rand(10, 2) + Yn = np.random.rand(10, 1) + n.X = Xn + n.Y = Yn + self.assertTrue(np.allclose(Xn, n.wrapped.wrapped.X.value)) + self.assertTrue(np.allclose(Yn, n.wrapped.wrapped.Y.value)) + self.assertFalse('Y' in n.wrapped.__dict__) + self.assertFalse('X' in n.wrapped.__dict__) + + n.foo = 5 + self.assertTrue('foo' in n.wrapped.__dict__) + self.assertFalse('foo' in n.wrapped.wrapped.__dict__) + + + From 53c8e960c2383473b0086c67a9ec568f62034f2c Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Tue, 15 Aug 2017 12:54:47 +0200 Subject: [PATCH 09/11] Fixed naming of modelwrapper objects, and solved PR review comments --- GPflowOpt/acquisition/acquisition.py | 4 +++- GPflowOpt/models.py | 26 ++++++++++++++++---------- GPflowOpt/transforms.py | 5 ----- testing/test_modelwrapper.py | 9 +++++++++ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/GPflowOpt/acquisition/acquisition.py b/GPflowOpt/acquisition/acquisition.py index 34c8272..69d4b80 100644 --- a/GPflowOpt/acquisition/acquisition.py +++ b/GPflowOpt/acquisition/acquisition.py @@ -50,7 +50,9 @@ def __init__(self, models=[], optimize_restarts=5): :param optimize_restarts: number of optimization restarts to use when training the models """ super(Acquisition, self).__init__() - self._models = ParamList([DataScaler(m) for m in np.atleast_1d(models).tolist()]) + models = np.atleast_1d(models) + assert all(isinstance(model, (Model, ModelWrapper)) for model in models) + self._models = ParamList([DataScaler(m) for m in models]) assert (optimize_restarts >= 0) self.optimize_restarts = optimize_restarts diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index bd46ecb..de66526 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -17,13 +17,13 @@ class ModelWrapper(Parameterized): """ - Class for fast implementation of a wrapper for models defined in GPflow. Once wrapped, all lookups for attributes - which are not found in the wrapper class are automatically forwarded to the wrapped model. + Class for fast implementation of a wrapper for models defined in GPflow. - To influence the I/O of methods on the wrapped class, simply implement the method in the wrapper and call the - appropriate methods on the wrapped class. Specific logic is included to make sure that if AutoFlow methods are - influenced following this pattern, the original AF storage (if existing) is unaffected and a new storage is added - to the subclass. + Once wrapped, all lookups for attributes which are not found in the wrapper class are automatically forwarded + to the wrapped model. To influence the I/O of methods on the wrapped class, simply implement the method in the + wrapper and call the appropriate methods on the wrapped class. Specific logic is included to make sure that if + AutoFlow methods are influenced following this pattern, the original AF storage (if existing) is unaffected and a + new storage is added to the subclass. """ def __init__(self, model): """ @@ -49,8 +49,12 @@ def __getattr__(self, item): def __setattr__(self, key, value): """ - 1) If setting :attr:`wrapped` attribute, point parent to this object (the datascaler). - 2) If setting the recompilation attribute, always do this on the wrapped class. + 1) If setting :attr:`wrapped` attribute, point parent to this object (the ModelWrapper). + 2) Setting attributes in the right objects. The following rules are processed in order: + (a) If attribute exists in wrapper, set in wrapper. + (b) If not object wrapped yet, set in wrapper. + (c) If attribute is found in the wrapped object, set it there. This rule is ignored for AF storages. + (d) Set attribute in wrapper. """ if key is 'wrapped': object.__setattr__(self, key, value) @@ -82,5 +86,7 @@ def __setattr__(self, key, value): def __eq__(self, other): return self.wrapped == other - def __str__(self, prepend=''): - return self.wrapped.__str__(prepend) + @Parameterized.name.getter + def name(self): + name = super(ModelWrapper, self).name + return ".".join([name, str.lower(self.__class__.__name__)]) diff --git a/GPflowOpt/transforms.py b/GPflowOpt/transforms.py index 4d5b82d..c87fac3 100644 --- a/GPflowOpt/transforms.py +++ b/GPflowOpt/transforms.py @@ -61,9 +61,6 @@ def __invert__(self): """ raise NotImplementedError - def __str__(self): - raise NotImplementedError - class LinearTransform(DataTransform): """ @@ -155,5 +152,3 @@ def __invert__(self): A_inv = np.linalg.inv(self.A.value.T) return LinearTransform(A_inv, -np.dot(self.b.value, A_inv)) - def __str__(self): - return 'XA + b' diff --git a/testing/test_modelwrapper.py b/testing/test_modelwrapper.py index aa2cfa1..439858e 100644 --- a/testing/test_modelwrapper.py +++ b/testing/test_modelwrapper.py @@ -109,6 +109,15 @@ def test_double_wrap(self): self.assertTrue('foo' in n.wrapped.__dict__) self.assertFalse('foo' in n.wrapped.wrapped.__dict__) + def test_name(self): + n = GPflowOpt.models.ModelWrapper(self.simple_model()) + self.assertEqual(n.name, 'unnamed.modelwrapper') + p = GPflow.param.Parameterized() + p.model = n + self.assertEqual(n.name, 'model.modelwrapper') + n = MethodOverride(self.simple_model()) + self.assertEqual(n.name, 'unnamed.methodoverride') + From 01e6f6499f4d2c75303c96a3b3ab53e8cdf8678d Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Tue, 15 Aug 2017 13:32:01 +0200 Subject: [PATCH 10/11] Small documentation fix --- GPflowOpt/scaling.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/GPflowOpt/scaling.py b/GPflowOpt/scaling.py index 141cb57..a8dee0e 100644 --- a/GPflowOpt/scaling.py +++ b/GPflowOpt/scaling.py @@ -25,9 +25,10 @@ class DataScaler(ModelWrapper): """ - Model-wrapping class, primarily intended to assure the data in GPflow models is scaled. One DataScaler wraps one - GPflow model, and can scale the input as well as the output data. By default, if any kind of object attribute - is not found in the datascaler object, it is searched on the wrapped model. + Model-wrapping class, primarily intended to assure the data in GPflow models is scaled. + + One DataScaler wraps one GPflow model, and can scale the input as well as the output data. By default, + if any kind of object attribute is not found in the datascaler object, it is searched on the wrapped model. The datascaler supports both input as well as output scaling, although both scalings are set up differently: From c2cc6f5ac3b217bebc09d3df3f1a31ed56bbc0b8 Mon Sep 17 00:00:00 2001 From: Joachim van der Herten Date: Wed, 16 Aug 2017 21:09:29 +0200 Subject: [PATCH 11/11] Fixed typo in documentation --- GPflowOpt/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPflowOpt/models.py b/GPflowOpt/models.py index de66526..0cdda58 100644 --- a/GPflowOpt/models.py +++ b/GPflowOpt/models.py @@ -52,7 +52,7 @@ def __setattr__(self, key, value): 1) If setting :attr:`wrapped` attribute, point parent to this object (the ModelWrapper). 2) Setting attributes in the right objects. The following rules are processed in order: (a) If attribute exists in wrapper, set in wrapper. - (b) If not object wrapped yet, set in wrapper. + (b) If no object has been wrapped (wrapper is None), set attribute in the wrapper. (c) If attribute is found in the wrapped object, set it there. This rule is ignored for AF storages. (d) Set attribute in wrapper. """