From d5239c6c8ac191fa8ffdc031d54d6d2fcf6188d3 Mon Sep 17 00:00:00 2001 From: nickovic Date: Sun, 26 Feb 2023 22:27:28 +0100 Subject: [PATCH 1/6] Added the GLIS sampler class. --- src/verifai/samplers/glis_optimization.py | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/verifai/samplers/glis_optimization.py diff --git a/src/verifai/samplers/glis_optimization.py b/src/verifai/samplers/glis_optimization.py new file mode 100644 index 0000000..c6c265d --- /dev/null +++ b/src/verifai/samplers/glis_optimization.py @@ -0,0 +1,55 @@ +"""GLIS Optimization sampler : Defined only for continuous domains. +For discrete inputs define another sampler""" + +from glis.solvers import GLIS +from verifai import RandomSampler, RejectionSampler, LateFeatureSampler +from verifai.samplers.domain_sampler import BoxSampler, SplitSampler + +def makeRandomSampler(domain): + """Utility function making a random sampler for a domain.""" + sampler = RandomSampler(domain) + if domain.requiresRejection: + sampler = RejectionSampler(sampler) + return sampler + +def glisSamplerFor(space, n_initial_random): + """Creates a GLIS Optimization sampler for a given space. + Uses random sampling for lengths of feature lists and any + Domains that are not continous and standardizable. + """ + + def makeDomainSampler(domain): + return SplitSampler.fromPredicate( + domain, + lambda d: d.standardizedDimension > 0, + lambda domain: GLISSampler(domain=domain, + n_initial_random=n_initial_random), + makeRandomSampler) + + return LateFeatureSampler(space, RandomSampler, makeDomainSampler) + +class GLISSampler(BoxSampler): + def __init__(self, domain, n_initial_random): + super().__init__(domain) + from numpy import array + + # Extract lower and upper bounds of Verifai ranges + lb = list() + ub = list() + for bound in self.domain.domains: + lb.append(bound.intervals[0][0]) + ub.append(bound.intervals[0][1]) + self.lb = array(lb) + self.ub = array(ub) + self.prob = GLIS(bounds=(lb, ub), n_initial_random=n_initial_random) + + + def nextVector(self, feedback=None): + if feedback is None or feedback == int(1): + x = self.prob.initialize() + else: + x = self.prob.update(feedback) + # normalize to [0,1] + x = (x - self.lb)/(self.ub - self.lb) + + return tuple(x) \ No newline at end of file From eea10e67291da651dc793d776404de13b442774a Mon Sep 17 00:00:00 2001 From: nickovic Date: Wed, 8 Mar 2023 19:14:51 +0100 Subject: [PATCH 2/6] GLIS integration: algorithms, build file --- tests/test_glisOptimization.py | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/test_glisOptimization.py diff --git a/tests/test_glisOptimization.py b/tests/test_glisOptimization.py new file mode 100644 index 0000000..b9acb26 --- /dev/null +++ b/tests/test_glisOptimization.py @@ -0,0 +1,72 @@ +from verifai.features import * +from verifai.samplers import * +from dotmap import DotMap + +import pytest +from tests.utils import sampleWithFeedback, checkSaveRestore + +pytest.importorskip('GPyOpt') + +def test_bayesianOptimization(): + carDomain = Struct({ + 'position': Box([-10,10], [-10,10], [0,1]), + 'heading': Box([0, math.pi]), + }) + + space = FeatureSpace({ + 'cars': Feature(Array(carDomain, [2])) + }) + + def f(sample): + sample = sample.cars[0].heading[0] + return abs(sample - 0.75) + + bo_params = DotMap() + bo_params.init_num = 2 + + sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, BO_params=bo_params) + + sampleWithFeedback(sampler, 3, f) + +def test_save_restore(tmpdir): + space = FeatureSpace({ + 'a': Feature(DiscreteBox([0, 12])), + 'b': Feature(Box((0, 1)), lengthDomain=DiscreteBox((1, 2))) + }) + bo_params = DotMap(init_num=2) + sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, bo_params) + + checkSaveRestore(sampler, tmpdir) + +def test_BO_oper(): + vals = [] + N = 10 + for k in range(N): + print("Testing k: ", k) + def f(sample): + x = sample.x[0] + return (6 * x - 2) ** 2 * np.sin(12 * x - 4) + + space = FeatureSpace({'x': Feature(Box([0,1]))}) + + bo_params = DotMap() + bo_params.init_num = 5 + + sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, BO_params=bo_params) + + samples = [] + y_samples = [] + + feedback = None + for i in range(20): + sample = sampler.nextSample(feedback) + samples.append(sample) + feedback = f(sample) + y_samples.append(feedback) + + min_i = np.array(y_samples).argmin() + vals.append(np.linalg.norm(samples[min_i].x[0]-0.75) < 0.1) + + assert sum(vals)/N >= 0.9 + + From 1ef5b85d951ee5117d58b4e7abe17dddd349a316 Mon Sep 17 00:00:00 2001 From: nickovic Date: Wed, 8 Mar 2023 19:15:57 +0100 Subject: [PATCH 3/6] GLIS integration: algorithms + test file --- src/verifai/samplers/glis_optimization.py | 62 ++++++++++------------- tests/test_glisOptimization.py | 42 ++------------- 2 files changed, 29 insertions(+), 75 deletions(-) diff --git a/src/verifai/samplers/glis_optimization.py b/src/verifai/samplers/glis_optimization.py index c6c265d..97d2105 100644 --- a/src/verifai/samplers/glis_optimization.py +++ b/src/verifai/samplers/glis_optimization.py @@ -2,46 +2,26 @@ For discrete inputs define another sampler""" from glis.solvers import GLIS -from verifai import RandomSampler, RejectionSampler, LateFeatureSampler -from verifai.samplers.domain_sampler import BoxSampler, SplitSampler - -def makeRandomSampler(domain): - """Utility function making a random sampler for a domain.""" - sampler = RandomSampler(domain) - if domain.requiresRejection: - sampler = RejectionSampler(sampler) - return sampler - -def glisSamplerFor(space, n_initial_random): - """Creates a GLIS Optimization sampler for a given space. - Uses random sampling for lengths of feature lists and any - Domains that are not continous and standardizable. - """ - - def makeDomainSampler(domain): - return SplitSampler.fromPredicate( - domain, - lambda d: d.standardizedDimension > 0, - lambda domain: GLISSampler(domain=domain, - n_initial_random=n_initial_random), - makeRandomSampler) - - return LateFeatureSampler(space, RandomSampler, makeDomainSampler) +from verifai.samplers.domain_sampler import BoxSampler class GLISSampler(BoxSampler): def __init__(self, domain, n_initial_random): super().__init__(domain) - from numpy import array + from numpy import array, zeros, ones # Extract lower and upper bounds of Verifai ranges - lb = list() - ub = list() - for bound in self.domain.domains: - lb.append(bound.intervals[0][0]) - ub.append(bound.intervals[0][1]) - self.lb = array(lb) - self.ub = array(ub) - self.prob = GLIS(bounds=(lb, ub), n_initial_random=n_initial_random) + dim = domain.flattenedDimension + #lb = list() + #ub = list() + self.lb = zeros(dim) + self.ub = ones(dim) + #for bound in self.domain.domains: + # lb.append(bound.intervals[0][0]) + # ub.append(bound.intervals[0][1]) + #self.lb = array(lb) + #self.ub = array(ub) + #self.prob = GLIS(bounds=(lb, ub), n_initial_random=n_initial_random) + self.prob = GLIS(bounds=(self.lb, self.ub), n_initial_random=n_initial_random) def nextVector(self, feedback=None): @@ -50,6 +30,16 @@ def nextVector(self, feedback=None): else: x = self.prob.update(feedback) # normalize to [0,1] - x = (x - self.lb)/(self.ub - self.lb) + #x = (x - self.lb)/(self.ub - self.lb) - return tuple(x) \ No newline at end of file + return tuple(x) + + def getVector(self, feedback=None): + if feedback is None or feedback == int(1): + x = self.prob.initialize() + else: + x = self.prob.update(feedback) + # normalize to [0,1] + #x = (x - self.lb)/(self.ub - self.lb) + + return tuple(x), None \ No newline at end of file diff --git a/tests/test_glisOptimization.py b/tests/test_glisOptimization.py index b9acb26..262d301 100644 --- a/tests/test_glisOptimization.py +++ b/tests/test_glisOptimization.py @@ -5,9 +5,8 @@ import pytest from tests.utils import sampleWithFeedback, checkSaveRestore -pytest.importorskip('GPyOpt') -def test_bayesianOptimization(): +def test_glisOptimization(): carDomain = Struct({ 'position': Box([-10,10], [-10,10], [0,1]), 'heading': Box([0, math.pi]), @@ -21,10 +20,7 @@ def f(sample): sample = sample.cars[0].heading[0] return abs(sample - 0.75) - bo_params = DotMap() - bo_params.init_num = 2 - - sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, BO_params=bo_params) + sampler = FeatureSampler.glisSamplerFor(space, n_initial_random=10) sampleWithFeedback(sampler, 3, f) @@ -33,40 +29,8 @@ def test_save_restore(tmpdir): 'a': Feature(DiscreteBox([0, 12])), 'b': Feature(Box((0, 1)), lengthDomain=DiscreteBox((1, 2))) }) - bo_params = DotMap(init_num=2) - sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, bo_params) + sampler = FeatureSampler.glisSamplerFor(space, n_initial_random=3) checkSaveRestore(sampler, tmpdir) -def test_BO_oper(): - vals = [] - N = 10 - for k in range(N): - print("Testing k: ", k) - def f(sample): - x = sample.x[0] - return (6 * x - 2) ** 2 * np.sin(12 * x - 4) - - space = FeatureSpace({'x': Feature(Box([0,1]))}) - - bo_params = DotMap() - bo_params.init_num = 5 - - sampler = FeatureSampler.bayesianOptimizationSamplerFor(space, BO_params=bo_params) - - samples = [] - y_samples = [] - - feedback = None - for i in range(20): - sample = sampler.nextSample(feedback) - samples.append(sample) - feedback = f(sample) - y_samples.append(feedback) - - min_i = np.array(y_samples).argmin() - vals.append(np.linalg.norm(samples[min_i].x[0]-0.75) < 0.1) - - assert sum(vals)/N >= 0.9 - From 22bf39d14e0b1d9e976fc787281d69951711340f Mon Sep 17 00:00:00 2001 From: nickovic Date: Wed, 8 Mar 2023 23:57:10 +0100 Subject: [PATCH 4/6] Finalized GLIS integration and tests --- pyproject.toml | 1 + src/verifai/samplers/feature_sampler.py | 17 +++++++ src/verifai/samplers/glis_optimization.py | 52 +++++++++---------- src/verifai/server.py | 5 ++ tests/test_glisOptimization.py | 62 +++++++++++++++++++++-- 5 files changed, 105 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1e42913..e6e0317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ scenic = "^2.1.0b4" progressbar2 = "^3.53.1" networkx = "^2.6.3" statsmodels = "^0.13.2" +glis = ">=2" GPy = {version = "^1.9.9", optional = true} GPyOpt = {version = "^1.2.6", optional = true} diff --git a/src/verifai/samplers/feature_sampler.py b/src/verifai/samplers/feature_sampler.py index 5890505..d61b615 100644 --- a/src/verifai/samplers/feature_sampler.py +++ b/src/verifai/samplers/feature_sampler.py @@ -20,6 +20,7 @@ from verifai.samplers.bayesian_optimization import BayesOptSampler from verifai.samplers.simulated_annealing import SimulatedAnnealingSampler from verifai.samplers.grid_sampler import GridSampler +from verifai.samplers.glis_optimization import GLISSampler ### Samplers defined over FeatureSpaces @@ -149,6 +150,22 @@ def makeDomainSampler(domain): makeRandomSampler) return LateFeatureSampler(space, RandomSampler, makeDomainSampler) + def glisSamplerFor(space, params): + """Creates a GLIS Optimization sampler for a given space. + Uses random sampling for lengths of feature lists and any + Domains that are not continous and standardizable. + """ + + def makeDomainSampler(domain): + return SplitSampler.fromPredicate( + domain, + lambda d: d.standardizedDimension > 0, + lambda domain: GLISSampler(domain=domain, + params=params), + makeRandomSampler) + + return LateFeatureSampler(space, RandomSampler, makeDomainSampler) + def getSample(self): """Generate a sample, along with any sampler-specific info. diff --git a/src/verifai/samplers/glis_optimization.py b/src/verifai/samplers/glis_optimization.py index 97d2105..d63e7fd 100644 --- a/src/verifai/samplers/glis_optimization.py +++ b/src/verifai/samplers/glis_optimization.py @@ -5,41 +5,35 @@ from verifai.samplers.domain_sampler import BoxSampler class GLISSampler(BoxSampler): - def __init__(self, domain, n_initial_random): + ''' + Integrates the GLIS sampler with VerifAI + --- + Parameters: + domain : FeatureSpace + params : DotMap + --- + Note: see the definition of the GLIS class for the available parameters or the GLIS documentation + https://pypi.org/project/glis/ + ''' + def __init__(self, domain, params): super().__init__(domain) - from numpy import array, zeros, ones + from numpy import zeros, ones + + self.rho = None - # Extract lower and upper bounds of Verifai ranges dim = domain.flattenedDimension - #lb = list() - #ub = list() self.lb = zeros(dim) self.ub = ones(dim) - #for bound in self.domain.domains: - # lb.append(bound.intervals[0][0]) - # ub.append(bound.intervals[0][1]) - #self.lb = array(lb) - #self.ub = array(ub) - #self.prob = GLIS(bounds=(lb, ub), n_initial_random=n_initial_random) - self.prob = GLIS(bounds=(self.lb, self.ub), n_initial_random=n_initial_random) - - - def nextVector(self, feedback=None): - if feedback is None or feedback == int(1): - x = self.prob.initialize() - else: - x = self.prob.update(feedback) - # normalize to [0,1] - #x = (x - self.lb)/(self.ub - self.lb) + self.sampler = GLIS(bounds=(self.lb, self.ub), **params) - return tuple(x) - def getVector(self, feedback=None): - if feedback is None or feedback == int(1): - x = self.prob.initialize() + def getVector(self): + if self.rho is None or self.rho == int(1): + x = self.sampler.initialize() else: - x = self.prob.update(feedback) - # normalize to [0,1] - #x = (x - self.lb)/(self.ub - self.lb) + x = self.sampler.update(self.rho) + + return tuple(x), None - return tuple(x), None \ No newline at end of file + def updateVector(self, vector, info, rho): + self.rho = rho \ No newline at end of file diff --git a/src/verifai/server.py b/src/verifai/server.py index 1298dcf..83a16d9 100644 --- a/src/verifai/server.py +++ b/src/verifai/server.py @@ -96,6 +96,11 @@ def choose_sampler(sample_space, sampler_type, sample_space, BO_params=bo_params) return 'bo', sampler + if sampler_type == 'glis': + sampler = FeatureSampler.samplerFor( + sample_space, sampler_params) + return 'glis', sampler + raise ValueError(f'unknown sampler type "{sampler_type}"') class Server: diff --git a/tests/test_glisOptimization.py b/tests/test_glisOptimization.py index 262d301..df7b1c0 100644 --- a/tests/test_glisOptimization.py +++ b/tests/test_glisOptimization.py @@ -1,6 +1,8 @@ +from glis.solvers import GLIS from verifai.features import * from verifai.samplers import * from dotmap import DotMap +from numpy import random as rdm import pytest from tests.utils import sampleWithFeedback, checkSaveRestore @@ -20,17 +22,71 @@ def f(sample): sample = sample.cars[0].heading[0] return abs(sample - 0.75) - sampler = FeatureSampler.glisSamplerFor(space, n_initial_random=10) + params = DotMap() + params.n_initial_random=10 + sampler = FeatureSampler.glisSamplerFor(space, params) - sampleWithFeedback(sampler, 3, f) + sampleWithFeedback(sampler, 10, f) def test_save_restore(tmpdir): space = FeatureSpace({ 'a': Feature(DiscreteBox([0, 12])), 'b': Feature(Box((0, 1)), lengthDomain=DiscreteBox((1, 2))) }) - sampler = FeatureSampler.glisSamplerFor(space, n_initial_random=3) + params = DotMap() + params.n_initial_random=3 + sampler = FeatureSampler.glisSamplerFor(space, params) checkSaveRestore(sampler, tmpdir) +def test_direct_vs_verifai(): + fun = lambda x: ((4.0 - 2.1 * x[0] ** 2 + x[0] ** 4 / 3.0) * + x[0] ** 2 + x[0] * x[1] + (4.0 * x[1] ** 2 - 4.0) * x[1] ** 2) + p = {'display': 0, 'n_initial_random': 3} + + # Direct GLIS approach + rdm.seed(0) + random.seed(0) + + lb = np.array([-2.0, -1.0]) + ub = np.array([2.0, 1.0]) + + prob = GLIS(bounds=(lb, ub), **p) + + X = [] + F = [] + for i in range(6): + if i == 0: + x = prob.initialize() + X.append(x.tolist()) + else: + f = fun(x) + x = prob.update(f) + X.append(x.tolist()) + F.append(f) + + # VerifAI approach + rdm.seed(0) + random.seed(0) + space = FeatureSpace({ + 'a': Feature(Box([-2.0, 2.0])), + 'b': Feature(Box([-1.0, 1.0])) + }) + + sampler = FeatureSampler.glisSamplerFor(space, p) + + X_prime = [] + F_prime = [] + for i in range(6): + if i == 0: + x_prime = sampler.nextSample(int(1)) + X_prime.append([x_prime[0][0], x_prime[1][0]]) + else: + f_prime = fun([x_prime[0][0], x_prime[1][0]]) + x_prime = sampler.nextSample(feedback=f_prime) + X_prime.append([x_prime[0][0], x_prime[1][0]]) + F_prime.append(f_prime) + + assert X == X_prime + assert F == F_prime \ No newline at end of file From 8dbbb6e78f80833f757647a88a436cd1b3da15a4 Mon Sep 17 00:00:00 2001 From: nickovic Date: Thu, 9 Mar 2023 06:19:55 +0100 Subject: [PATCH 5/6] Finalized GLIS integration and tests, with also Scenic integration --- src/verifai/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verifai/server.py b/src/verifai/server.py index 83a16d9..970cd81 100644 --- a/src/verifai/server.py +++ b/src/verifai/server.py @@ -97,7 +97,7 @@ def choose_sampler(sample_space, sampler_type, return 'bo', sampler if sampler_type == 'glis': - sampler = FeatureSampler.samplerFor( + sampler = FeatureSampler.glisSamplerFor( sample_space, sampler_params) return 'glis', sampler From ec6834cb4491af720848e0fe79b870296b5f01d5 Mon Sep 17 00:00:00 2001 From: nickovic Date: Thu, 18 May 2023 23:40:41 +0200 Subject: [PATCH 6/6] Corrected the update when the sample is rejected --- src/verifai/samplers/glis_optimization.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/verifai/samplers/glis_optimization.py b/src/verifai/samplers/glis_optimization.py index d63e7fd..be7d8a8 100644 --- a/src/verifai/samplers/glis_optimization.py +++ b/src/verifai/samplers/glis_optimization.py @@ -28,12 +28,12 @@ def __init__(self, domain, params): def getVector(self): - if self.rho is None or self.rho == int(1): - x = self.sampler.initialize() - else: - x = self.sampler.update(self.rho) + if self.rho is None: + self.x = self.sampler.initialize() + elif not self.rho == int(1): + self.x = self.sampler.update(self.rho) - return tuple(x), None + return tuple(self.x), None def updateVector(self, vector, info, rho): self.rho = rho \ No newline at end of file