diff --git a/docs/ZOOpt/Parameters-in-ZOOpt.rst b/docs/ZOOpt/Parameters-in-ZOOpt.rst index 00f49ff..c09fa34 100644 --- a/docs/ZOOpt/Parameters-in-ZOOpt.rst +++ b/docs/ZOOpt/Parameters-in-ZOOpt.rst @@ -137,6 +137,9 @@ Parameter class that implements the member function ``check(self, optcontent)``, which will be invoked at each iteration of the optimization. The optimization algorithm will stop in advance if ``stopping_criterion.check()`` returns True. - ``seed`` sets the seed of all generated random numbers used in ZOOpt. - ``parallel`` and ``server_num`` are set for parallel optimization. +- ``shrinking_rate`` is the region shrinking rate of ``RACE-CARS``. +- ``shrinking_freq`` is the region shrinking freqency of ``RACE-CARS``. +- ``max_shrinking_times`` is the maximum shrinking times of ``RACE-CARS``. @@ -275,3 +278,15 @@ the variance of the projection matrix A. By default, ``withdraw_alpha`` equals to ``Dimension(1, [[-1, 1]], [True])`` and ``variance_A`` equals to ``1/d`` (``d`` is the dimension size of the ``low_dimension``). In most cases, it's not necessary for users to provide them. +the population set. + +Optimize a function leveraging randomized region shrinking +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +ZOOpt implements an acceleration for sequential ``RACOS`` utilizing randomized region shrinking (``RACE-CARS``). + +.. code:: python + + parameter = Parameter(budget=3000, shrinking_rate=0.9, shrinking_freq=0.01) + +In ZOOpt, the ``RACE-CARS`` acceleration is automatically enabled when both ``shrinking_rate`` (controls how aggressively the search space shrinks) and ``shrinking_freq`` (determines how often shrinking occurs) parameters are specified. Additionally, ``max_shrinking_times`` can be configured to cap the total number of space reductions during optimization. \ No newline at end of file diff --git a/zoopt/__init__.py b/zoopt/__init__.py index cb34c30..c11c361 100644 --- a/zoopt/__init__.py +++ b/zoopt/__init__.py @@ -1,6 +1,6 @@ -from zoopt.dimension import Dimension, Dimension2, ValueType -from zoopt.objective import Objective -from zoopt.opt import Opt -from zoopt.parameter import Parameter -from zoopt.solution import Solution -from zoopt.exp_opt import ExpOpt +from .dimension import Dimension, Dimension2, ValueType +from .objective import Objective +from .opt import Opt +from .parameter import Parameter +from .solution import Solution +from .exp_opt import ExpOpt diff --git a/zoopt/algos/opt_algorithms/racos/racecars.py b/zoopt/algos/opt_algorithms/racos/racecars.py new file mode 100644 index 0000000..62fe5c1 --- /dev/null +++ b/zoopt/algos/opt_algorithms/racos/racecars.py @@ -0,0 +1,233 @@ +""" +This module contains the class SRacos, which is the sequential version of Racos (a classification based optimization algorithm). + +Author: + Yu-ren Liu + +Updated by: + Ze-Wen Li +""" + +import time + +import numpy as np +from copy import deepcopy + +from zoopt.algos.opt_algorithms.racos.racos_classification import RacosClassification +from zoopt.algos.opt_algorithms.racos.racos_common import RacosCommon +from zoopt.utils.tool_function import ToolFunction +from zoopt import Objective, Solution, Parameter + + +class RaceCars(RacosCommon): + """ + The class SRacos represents Sequential Racos algorithm. It's inherited from RacosCommon. + """ + + def __init__(self): + """ + Initialization. + """ + RacosCommon.__init__(self) + return + + def opt(self, objective: Objective, parameter: Parameter, strategy='WR', ub=1): + """ + SRacos optimization. + + :param objective: an Objective object + :param parameter: a Parameter object + :param strategy: replace strategy + :param ub: uncertain bits, which is a parameter of SRacos + :return: Optimization result + """ + self.clear() + self.set_objective(objective) + self.set_parameters(parameter) + self.init_attribute() + stopping_criterion = self._parameter.get_stopping_criterion() + i = 0 + k = 1 + iteration_num = self._parameter.get_budget() - self._parameter.get_train_size() + time_log1 = time.time() + max_distinct_repeat_times = 100 + current_not_distinct_times = 0 + shrinking_rate = self._parameter.get_shrinking_rate() + shrinking_freq = self._parameter.get_shrinking_freq() + max_shrinking_times = self._parameter.get_max_shrinking_times() + dim = deepcopy(self._objective.get_dim()) + while i < iteration_num: + sampled_data = self._positive_data + self._negative_data + if max_shrinking_times is not None: + if k <= max_shrinking_times: + if np.random.rand() <= shrinking_freq: + self._objective.shrink_dim(self._best_solution.get_x(), dim, shrinking_rate, k) + k += 1 + else: + if np.random.rand() <= shrinking_freq: + self._objective.shrink_dim(self._best_solution.get_x(), dim, shrinking_rate, k) + k += 1 + if np.random.rand() < self._parameter.get_probability(): + classifier = RacosClassification( + self._objective.get_dim(), self._positive_data, self._negative_data, ub) + classifier.mixed_classification() + solution, distinct_flag = self.distinct_sample_classifier( + classifier, sampled_data, True, self._parameter.get_train_size()) + else: + solution, distinct_flag = self.distinct_sample(dim, sampled_data) + # panic stop + if solution is None: + ToolFunction.log(" [break loop] because solution is None") + return self._best_solution + if distinct_flag is False: + current_not_distinct_times += 1 + if current_not_distinct_times >= max_distinct_repeat_times: + ToolFunction.log( + "[break loop] because distinct_flag is false too much times") + return self._best_solution + else: + continue + # evaluate the solution + objective.eval(solution) + # show best solution + times = i + self._parameter.get_train_size() + 1 + self.show_best_solution(parameter.get_intermediate_result(), times, parameter.get_intermediate_freq()) + bad_ele = self.replace(self._positive_data, solution, 'pos') + self.replace(self._negative_data, bad_ele, 'neg', strategy) + self._best_solution = self._positive_data[0] + if i == 4: + time_log2 = time.time() + expected_time = (self._parameter.get_budget() - self._parameter.get_train_size()) * \ + (time_log2 - time_log1) / 5 + if self._parameter.get_time_budget() is not None: + expected_time = min( + expected_time, self._parameter.get_time_budget()) + if expected_time > 5: + m, s = divmod(expected_time, 60) + h, m = divmod(m, 60) + ToolFunction.log( + 'expected remaining running time: %02d:%02d:%02d' % (h, m, s)) + # time budget check + if self._parameter.get_time_budget() is not None: + if (time.time() - time_log1) >= self._parameter.get_time_budget(): + ToolFunction.log('time_budget runs out') + return self._best_solution + # terminal_value check + if self._parameter.get_terminal_value() is not None: + if self._best_solution.get_value() <= self._parameter.get_terminal_value(): + ToolFunction.log('terminal function value reached') + return self._best_solution + if stopping_criterion.check(self) is True: + return self._best_solution + i += 1 + return self._best_solution + + def replace(self, iset, x, iset_type, strategy='WR'): + """ + Replace a solution(chosen by strategy) in iset with x. + + :param iset: a solution list + :param x: a Solution object + :param iset_type: 'pos' or 'neg' + :param strategy: 'WR': worst replace or 'RR': random replace or 'LM': replace the farthest solution + :return: the replaced solution + """ + if strategy == 'WR': + return self.strategy_wr(iset, x, iset_type) + elif strategy == 'RR': + return self.strategy_rr(iset, x) + elif strategy == 'LM': + best_sol = min(iset, key=lambda x: x.get_value()) + return self.strategy_lm(iset, best_sol, x) + + def binary_search(self, iset, x, begin, end): + """ + Find the first element larger than x. + + :param iset: a solution set + :param x: a Solution object + :param begin: begin position + :param end: end position + :return: the index of the first element larger than x + """ + x_value = x.get_value() + if x_value <= iset[begin].get_value(): + return begin + if x_value >= iset[end].get_value(): + return end + 1 + if end == begin + 1: + return end + mid = begin + (end - begin) // 2 + if x_value <= iset[mid].get_value(): + return self.binary_search(iset, x, begin, mid) + else: + return self.binary_search(iset, x, mid, end) + + def strategy_wr(self, iset, x, iset_type): + """ + Replace the worst solution in iset. + + :param iset: a solution set + :param x: a Solution object + :param iset_type: 'pos' or 'neg' + :return: the worst solution + """ + if iset_type == 'pos': + index = self.binary_search(iset, x, 0, len(iset) - 1) + iset.insert(index, x) + worst_ele = iset.pop() + else: + worst_ele, worst_index = Solution.find_maximum(iset) + if worst_ele.get_value() > x.get_value(): + iset[worst_index] = x + else: + worst_ele = x + return worst_ele + + def strategy_rr(self, iset, x): + """ + Replace a random solution in iset. + + :param iset: a solution set + :param x: a Solution object + :return: the replaced solution + """ + len_iset = len(iset) + replace_index = np.random.randint(0, len_iset) + replace_ele = iset[replace_index] + iset[replace_index] = x + return replace_ele + + # + def strategy_lm(self, iset, best_sol, x): + """ + Replace the farthest solution from best_sol + + :param iset: a solution set + :param best_sol: the best solution, distance between solution in iset and best_sol will be computed + :param x: a Solution object + :return: the farthest solution (has the largest margin) in iset + """ + farthest_dis = 0 + farthest_index = 0 + for i in range(len(iset)): + dis = self.distance(iset[i].get_x(), best_sol.get_x()) + if dis > farthest_dis: + farthest_dis = dis + farthest_index = i + farthest_ele = iset[farthest_index] + iset[farthest_index] = x + return farthest_ele + + @staticmethod + def distance(x, y): + """ + Get the distance between the list x and y + :param x: a list + :param y: a list + :return: Euclidean distance + """ + dis = 0 + for i in range(len(x)): + dis += (x[i] - y[i])**2 + return np.sqrt(dis) \ No newline at end of file diff --git a/zoopt/algos/opt_algorithms/racos/racos_classification.py b/zoopt/algos/opt_algorithms/racos/racos_classification.py index 0ab521b..92e2477 100644 --- a/zoopt/algos/opt_algorithms/racos/racos_classification.py +++ b/zoopt/algos/opt_algorithms/racos/racos_classification.py @@ -8,12 +8,17 @@ Ze-Wen Li """ -from zoopt.dimension import Dimension, Dimension2 +from zoopt.dimension import Dimension, Dimension2, ValueType from zoopt.utils.tool_function import ToolFunction import copy import numpy as np from zoopt.utils.zoo_global import gl +def sol_in_regs(sol, regs): + for i in range(len(sol)): + if sol[i] < regs[i][0] or sol[i] > regs[i][1]: + return False + return True class RacosClassification: """ @@ -42,7 +47,6 @@ def __init__(self, dim, positive, negative, ub=1): for i in range(dim.get_size()): temp = [regions[i][0], regions[i][1]] self.__sample_region.append(temp) - return def reset_classifier(self): """ @@ -66,8 +70,11 @@ def mixed_classification(self): :return: no return value """ if type(self.__solution_space) == Dimension: - self.__x_positive = self.__positive_solution[np.random.randint( + while True: + self.__x_positive = self.__positive_solution[np.random.randint( 0, len(self.__positive_solution))] + if sol_in_regs(self.__x_positive.get_x(), self.__sample_region): + break len_negative = len(self.__negative_solution) index_set = list(range(self.__solution_space.get_size())) remain_index_set = list(range(self.__solution_space.get_size())) @@ -76,7 +83,7 @@ def mixed_classification(self): while len_negative > 0: if len(remain_index_set) == 0: ToolFunction.log('ERROR: sampled two same solutions, please raise issues on github') - k = remain_index_set[np.random.randint(0, len(remain_index_set))] + k = remain_index_set[np.random.randint(0, len(remain_index_set))] # randomly choose a index k x_pos_k = self.__x_positive.get_x_index(k) # continuous if types[k] is True: @@ -85,7 +92,7 @@ def mixed_classification(self): x_neg_k = x_negative.get_x_index(k) if x_pos_k < x_neg_k: r = np.random.uniform(x_pos_k, x_neg_k) - if r < self.__sample_region[k][1]: + if r <= self.__sample_region[k][1]: self.__sample_region[k][1] = r i = 0 while i < len_negative: @@ -98,7 +105,7 @@ def mixed_classification(self): i += 1 else: r = np.random.uniform(x_neg_k, x_pos_k) - if r > self.__sample_region[k][0]: + if r >= self.__sample_region[k][0]: self.__sample_region[k][0] = r i = 0 while i < len_negative: @@ -118,7 +125,7 @@ def mixed_classification(self): if x_pos_k < x_neg_k: # different from continuous version r = np.random.randint(x_pos_k, x_neg_k) - if r < self.__sample_region[k][1]: + if r <= self.__sample_region[k][1]: self.__sample_region[k][1] = r i = 0 while i < len_negative: @@ -131,7 +138,7 @@ def mixed_classification(self): i += 1 else: r = np.random.randint(x_neg_k, x_pos_k + 1) - if r > self.__sample_region[k][0]: + if r >= self.__sample_region[k][0]: self.__sample_region[k][0] = r i = 0 while i < len_negative: @@ -162,20 +169,23 @@ def mixed_classification(self): self.set_uncertain_bit(index_set) return elif type(self.__solution_space) == Dimension2: - self.__x_positive = self.__positive_solution[np.random.randint(0, len(self.__positive_solution))] - len_negative = len(self.__negative_solution) + while True: + self.__x_positive = self.__positive_solution[np.random.randint( + 0, len(self.__positive_solution))] + if sol_in_regs(self.__x_positive.get_x(), self.__sample_region): + break index_set = list(range(self.__solution_space.get_size())) remain_index_set = list(range(self.__solution_space.get_size())) - types = self.__solution_space.get_types() + types = self.__solution_space.get_types() # continuous, discrete, grid order_or_precision = self.__solution_space.get_order_or_precision() while len_negative > 0: if len(remain_index_set) == 0: ToolFunction.log('ERROR: sampled two same solutions, please raise issues on github') - k = remain_index_set[np.random.randint(0, len(remain_index_set))] + k = remain_index_set[np.random.randint(0, len(remain_index_set))] # randomly choose a index k x_pos_k = self.__x_positive.get_x_index(k) # continuous - if types[k]: + if types[k] == ValueType.CONTINUOUS: x_negative = self.__negative_solution[np.random.randint(0, len_negative)] x_neg_k = x_negative.get_x_index(k) @@ -205,7 +215,7 @@ def mixed_classification(self): self.__negative_solution[len_negative] = itemp else: i += 1 - # discrete + # discrete and grid else: if order_or_precision[k] is True: x_negative = self.__negative_solution[np.random.randint(0, len_negative)] @@ -236,7 +246,7 @@ def mixed_classification(self): self.__negative_solution[len_negative] = itemp else: i += 1 - else: + else: # for unordered discrete or grid valuetype delete = 0 i = 0 while i < len_negative: @@ -285,9 +295,12 @@ def rand_sample(self): x = copy.deepcopy(self.__x_positive.get_x()) for index in self.__label_index: # continuous - if self.__solution_space.get_type(index): + if self.__solution_space.get_type(index) == ValueType.CONTINUOUS: x[index] = round(np.random.uniform(self.__sample_region[index][0], self.__sample_region[index][1]), gl.float_precisions[index]) + # grid + elif self.__solution_space.get_type(index) == ValueType.GRID: + x[index] = np.random.choice(self.__sample_region[index], 1)[0] # discrete else: x[index] = np.random.randint(self.__sample_region[index][0], self.__sample_region[index][1] + 1) diff --git a/zoopt/algos/opt_algorithms/racos/racos_common.py b/zoopt/algos/opt_algorithms/racos/racos_common.py old mode 100755 new mode 100644 index 024b25f..961f8d6 --- a/zoopt/algos/opt_algorithms/racos/racos_common.py +++ b/zoopt/algos/opt_algorithms/racos/racos_common.py @@ -27,6 +27,9 @@ def __init__(self): # Solution set # Random sampled solutions construct self._data self._data = [] + # Save solutions with distinct x for tune init + self._init_data = [] + self._need_copy = True # self._positive_data are best-positive_size solutions set self._positive_data = [] # self._negative_data are the other solutions @@ -152,8 +155,28 @@ def tune_init_attribute(self): :return: sample x """ self._parameter.set_negative_size(self._parameter.get_train_size() - self._parameter.get_positive_size()) - x, distinct_flag = self.distinct_sample(self._objective.get_dim(), self._data, data_num=1) - return x + if self._need_copy: + self._data_temp = copy.deepcopy(self._parameter.get_init_samples()) + self._need_copy = False + self._iteration_num = self._parameter.get_train_size() + if self._data_temp is not None and self._best_solution is None: + size = min(len(self._data_temp), self._iteration_num) + if size > 0: + if isinstance(self._data_temp[0], Solution) is False: + x = self._objective.construct_solution(self._data_temp[0]) + else: + x = self._data_temp[0] + del self._data_temp[0] + self._iteration_num -= 1 + self._init_data.append(x) + if math.isnan(x.get_value()): + return x, True + else: + return self.tune_init_attribute() + x, distinct_flag = self.distinct_sample(self._objective.get_dim(), self._init_data, data_num=1) + if distinct_flag: + self._init_data.append(x) + return x, distinct_flag def selection(self): """ @@ -167,10 +190,9 @@ def selection(self): """ new_data = sorted(self._data, key=lambda x: x.get_value()) - self._data = new_data[0:self._parameter.get_train_size()] - self._positive_data = new_data[0: self._parameter.get_positive_size()] - self._negative_data = new_data[ - self._parameter.get_positive_size(): self._parameter.get_train_size()] + self._data = new_data[0: self._parameter.get_train_size()] + self._positive_data = self._data[0: self._parameter.get_positive_size()] + self._negative_data = self._data[self._parameter.get_positive_size():] self._best_solution = self._positive_data[0] return diff --git a/zoopt/algos/opt_algorithms/racos/racos_optimization.py b/zoopt/algos/opt_algorithms/racos/racos_optimization.py index a56aa09..c09eeed 100644 --- a/zoopt/algos/opt_algorithms/racos/racos_optimization.py +++ b/zoopt/algos/opt_algorithms/racos/racos_optimization.py @@ -8,6 +8,7 @@ from zoopt.algos.noise_handling.ssracos import SSRacos from zoopt.algos.opt_algorithms.racos.racos import Racos from zoopt.algos.opt_algorithms.racos.sracos import SRacos +from zoopt.algos.opt_algorithms.racos.racecars import RaceCars from zoopt.algos.opt_algorithms.racos.asracos import ASRacos @@ -52,6 +53,8 @@ def opt(self, objective, parameter, strategy='WR'): if parameter.get_sequential(): if parameter.get_noise_handling() is True and parameter.get_suppression() is True: self.__algorithm = SSRacos() + elif parameter.get_region_shrinking(): + self.__algorithm = RaceCars() else: self.__algorithm = SRacos() self.__best_solution = self.__algorithm.opt( diff --git a/zoopt/algos/opt_algorithms/racos/sracos.py b/zoopt/algos/opt_algorithms/racos/sracos.py index 93f606f..71c45af 100644 --- a/zoopt/algos/opt_algorithms/racos/sracos.py +++ b/zoopt/algos/opt_algorithms/racos/sracos.py @@ -143,7 +143,7 @@ def binary_search(self, iset, x, begin, end): return end + 1 if end == begin + 1: return end - mid = (begin + end) // 2 + mid = begin + (end - begin) // 2 if x_value <= iset[mid].get_value(): return self.binary_search(iset, x, begin, mid) else: @@ -224,7 +224,7 @@ class SRacosTune(RacosCommon): The class SRacosTune represents Sequential Racos algorithm for Tune. It's inherited from RacosCommon. """ - def __init__(self, dimension, parameter): + def __init__(self, dimension, parameter, **kwargs): """ Initialization. @@ -237,10 +237,12 @@ def __init__(self, dimension, parameter): objective = Objective(None, dimension) self.set_objective(objective) self.set_parameters(parameter) + self._parameter.set_server_num(kwargs['parallel_num']) self.init_num = 0 self.complete_num = 0 self.semaphore = 1 # control init + self.live_num = 0 self.ub = self._parameter.get_uncertain_bits() if self.ub is None: self.ub = self.choose_ub(self.get_objective()) @@ -254,14 +256,22 @@ def suggest(self): if self.semaphore == 0: return + solution = None + if self.init_num < self._parameter.get_train_size(): # for init - solution = self.tune_init_attribute() + solution, distinct_flag = self.tune_init_attribute() + if distinct_flag is False: + return "FINISHED" + self.live_num += 1 elif self.init_num == self._parameter.get_train_size(): self.semaphore = 0 self.init_num += 1 return - else: - solution = self.sample_solution(self.ub) + elif self.live_num < self._parameter.get_server_num(): + solution, distinct_flag = self.sample_solution(self.ub) + if distinct_flag is False: + return "FINISHED" + self.live_num += 1 self.init_num += 1 return solution @@ -275,16 +285,16 @@ def complete(self, solution, result): :return: best solution so far """ self.complete_num += 1 + self.live_num -= 1 + solution.set_value(result) if self.complete_num < self._parameter.get_train_size(): - solution.set_value(result) - self._data.append(solution) - elif self.complete_num == self._parameter.get_train_size(): - solution.set_value(result) self._data.append(solution) self.selection() + elif self.complete_num == self._parameter.get_train_size(): + best_solution_so_far = self.update_classifier(solution) self.semaphore = 1 + return best_solution_so_far else: - solution.set_value(result) best_solution_so_far = self.update_classifier(solution) return best_solution_so_far @@ -296,18 +306,16 @@ def sample_solution(self, ub=1): :return: a solution containing trial """ sampled_data = self._positive_data + self._negative_data - if np.random.random() < self._parameter.get_probability(): + if np.random.random() < self._parameter.get_probability(): # exploitation classifier = RacosClassification( self._objective.get_dim(), self._positive_data, self._negative_data, ub) classifier.mixed_classification() solution, distinct_flag = self.distinct_sample_classifier( classifier, sampled_data, True, self._parameter.get_train_size()) - else: + else: # exploration solution, distinct_flag = self.distinct_sample(self._objective.get_dim(), sampled_data) - sampled_data.append(solution) - - return solution + return solution, distinct_flag def update_classifier(self, solution, strategy='WR'): stopping_criterion = self._parameter.get_stopping_criterion() @@ -360,7 +368,7 @@ def binary_search(self, iset, x, begin, end): return end + 1 if end == begin + 1: return end - mid = (begin + end) // 2 + mid = begin + (end - begin) // 2 if x_value <= iset[mid].get_value(): return self.binary_search(iset, x, begin, mid) else: diff --git a/zoopt/dimension.py b/zoopt/dimension.py index b613558..22c2765 100644 --- a/zoopt/dimension.py +++ b/zoopt/dimension.py @@ -8,8 +8,8 @@ Ze-Wen Li """ -from zoopt.utils.zoo_global import gl -from zoopt.utils.tool_function import ToolFunction +from .utils.zoo_global import gl +from .utils.tool_function import ToolFunction import numpy as np import copy @@ -91,7 +91,7 @@ def set_dimension_size(self, size): self._size = size return - def set_region(self, index, reg, ty): + def set_region(self, index, reg, ty=True): if index > self._size - 1: ToolFunction.log('dimension.py: index out of bound') return @@ -99,7 +99,7 @@ def set_region(self, index, reg, ty): self._types[index] = ty return - def set_regions(self, regs, tys): + def set_regions(self, regs, tys=True): if self.judge_match(self._size, regs, tys) is False: return self._regions = regs @@ -219,6 +219,7 @@ def print_dim(self): class ValueType(enumerate): + GRID = 2 CONTINUOUS = 1 DISCRETE = 0 @@ -238,14 +239,16 @@ def __init__(self, dim_list=[]): e.g.: (ValueType.CONTINUOUS, [0, 1], 1e-6) for discrete dimension: (type, range, has_partial_order) e.g.: (ValueType.DISCRETE, [0, 1], False) + for grid dimension: (type, values) + e.g.: (ValueType.GRID, ['first value', 'second value']) """ gl.float_precisions = [] self._size = len(dim_list) self._regions = list(map(lambda x: x[1], dim_list)) - # True means continuous, False means discrete - self._types = list(map(lambda x: x[0] == True, dim_list)) - self._order_or_precision = list(map(lambda x: x[2], dim_list)) + self._types = list(map(lambda x: x[0], dim_list)) + self._order_or_precision = list(map(lambda x: x[2] if len(x) == 3 else None, + dim_list)) # Note: for grid valuetype, len(x)=2 for _dim in dim_list: if _dim[0] == ValueType.CONTINUOUS: @@ -347,7 +350,9 @@ def rand_sample(self): """ x = [] for i in range(self._size): - if self._types[i]: + if self._types[i] == ValueType.GRID: + value = np.random.choice(self._regions[i], 1)[0] + elif self._types[i] == ValueType.CONTINUOUS: value = round(np.random.uniform(self._regions[i][0], self._regions[i][1]), gl.float_precisions[i]) else: value = np.random.randint(self._regions[i][0], self._regions[i][1] + 1) @@ -364,8 +369,10 @@ def limited_space(self): """ number = 1 for i in range(self._size): - if self._types[i]: + if self._types[i] == ValueType.CONTINUOUS: return False, 0 + elif self._types[i] == ValueType.GRID: + number *= len(self._regions[i]) else: number *= self._regions[i][1] - self._regions[i][0] + 1 return True, number diff --git a/zoopt/exp_opt.py b/zoopt/exp_opt.py index 5f505e3..c8ae505 100644 --- a/zoopt/exp_opt.py +++ b/zoopt/exp_opt.py @@ -4,10 +4,10 @@ Author: Yu-Ren Liu """ -from zoopt.utils.zoo_global import gl -from zoopt.opt import Opt +from .utils.zoo_global import gl +from .opt import Opt import matplotlib.pyplot as plt -from zoopt.utils.tool_function import ToolFunction +from .utils.tool_function import ToolFunction import numpy as np diff --git a/zoopt/objective.py b/zoopt/objective.py index 0a8ebb9..f61803a 100644 --- a/zoopt/objective.py +++ b/zoopt/objective.py @@ -5,9 +5,10 @@ Yu-Ren Liu, Xiong-Hui Chen """ -from zoopt.solution import Solution -from zoopt.utils.zoo_global import pos_inf -from zoopt.utils.tool_function import ToolFunction +from .solution import Solution +from .utils.zoo_global import pos_inf +from .utils.tool_function import ToolFunction +from copy import deepcopy import numpy as np @@ -27,6 +28,7 @@ def __init__(self, func=None, dim=None, constraint=None, resample_func=None): """ self.__func = func self.__dim = dim + self.__dim_type = dim.get_types() # the function for inheriting solution attachment self.__inherit = self.default_inherit self.__post_inherit = self.default_post_inherit @@ -136,6 +138,19 @@ def set_dim(self, dim): def get_dim(self): return self.__dim + + def shrink_dim(self, center, dim, shrinking_rate, shrinking_times): + for i in range(self.__dim.get_size()): + dim_reg = deepcopy(dim.get_region(i)) + dim_reg_size = dim_reg[1] - dim_reg[0] + if self.__dim_type[i] is True: + left = max(dim_reg[0], center[i]-shrinking_rate**shrinking_times*0.5*dim_reg_size) + right = min(dim_reg[1], center[i]+shrinking_rate**shrinking_times*0.5*dim_reg_size) + self.__dim.set_region(i, [left, right]) + else: + left = max(dim_reg[0], center[i]-int(shrinking_rate**shrinking_times*0.5*dim_reg_size)) + right = min(dim_reg[1], center[i]+int(shrinking_rate**shrinking_times*0.5*dim_reg_size)) + self.__dim.set_region(i, [left, right], ty=False) def set_inherit_func(self, inherit_func): self.__inherit = inherit_func diff --git a/zoopt/opt.py b/zoopt/opt.py index 44650ea..9db9afd 100644 --- a/zoopt/opt.py +++ b/zoopt/opt.py @@ -4,12 +4,11 @@ Author: Yu-Ren Liu """ -from zoopt.algos.opt_algorithms.paretoopt.pareto_optimization import ParetoOptimization - -from zoopt.algos.high_dimensionality_handling.sre_optimization import SequentialRandomEmbedding -from zoopt.algos.opt_algorithms.racos.racos_optimization import RacosOptimization -from zoopt.utils.tool_function import ToolFunction -from zoopt.utils.zoo_global import gl +from .algos.opt_algorithms.paretoopt.pareto_optimization import ParetoOptimization +from .algos.high_dimensionality_handling.sre_optimization import SequentialRandomEmbedding +from .algos.opt_algorithms.racos.racos_optimization import RacosOptimization +from .utils.tool_function import ToolFunction +from .utils.zoo_global import gl class Opt: @@ -37,7 +36,7 @@ def min(objective, parameter): result = None if constraint is not None and ((algorithm is None) or (algorithm == "poss")): optimizer = ParetoOptimization() - elif constraint is None and ((algorithm is None) or (algorithm == "racos") or (algorithm == "sracos")) or (algorithm == "ssracos"): + elif constraint is None and ((algorithm is None) or (algorithm == "racos") or (algorithm == "sracos")) or (algorithm == "racecars") or (algorithm == "ssracos"): optimizer = RacosOptimization() else: ToolFunction.log( diff --git a/zoopt/parameter.py b/zoopt/parameter.py old mode 100755 new mode 100644 index f535598..fc21fff --- a/zoopt/parameter.py +++ b/zoopt/parameter.py @@ -7,8 +7,8 @@ import sys import math -from zoopt.dimension import Dimension -from zoopt.utils.tool_function import ToolFunction +from .dimension import Dimension +from .utils.tool_function import ToolFunction class DefaultStoppingCriterion: @@ -42,7 +42,8 @@ def __init__(self, algorithm=None, budget=0, exploration_rate=0.01, init_samples noise_handling=False, resampling=False, suppression=False, ponss=False, ponss_theta=None, ponss_b=None, non_update_allowed=500, resample_times=100, balance_rate=0.5, high_dim_handling=False, reducedim=False, num_sre=5, low_dimension=None, withdraw_alpha=Dimension(1, [[-1, 1]], [True]), variance_A=None, - stopping_criterion=DefaultStoppingCriterion(), seed=None, parallel=False, server_num=1): + stopping_criterion=DefaultStoppingCriterion(), seed=None, parallel=False, server_num=1, + shrinking_rate=None, shrinking_freq=None, max_shrinking_times=None): """ Initialization. @@ -88,6 +89,10 @@ def __init__(self, algorithm=None, budget=0, exploration_rate=0.01, init_samples :param parallel: whether to use parallel optimization. :param server_num: the number of servers started for parallel optimization. + :param shrinking_rate: the shrinking rate of region shrinking method + :param shrinking_freq: the frequency of shrinking + :param max_shrinking_times: the maximum shrinking times + """ self.__algorithm = algorithm self.__budget = budget if noise_handling is False or resampling is False else (budget / resample_times) @@ -106,6 +111,12 @@ def __init__(self, algorithm=None, budget=0, exploration_rate=0.01, init_samples self.__negative_size = 0 self.__probability = 1 - exploration_rate + # for region shrinking + self.__region_shrinking = True if shrinking_rate is not None else False + self.__shrinking_rate = shrinking_rate + self.__shrinking_freq = shrinking_freq + self.__max_shrinking_times = max_shrinking_times + # for intermediate result self.__intermediate_result = intermediate_result tmp_freq = math.floor(intermediate_freq) @@ -340,4 +351,29 @@ def get_server_num(self): return self.server_num def set_server_num(self, server_num): - self.server_num = server_num \ No newline at end of file + self.server_num = server_num + + def get_shrinking_rate(self): + return self.__shrinking_rate + + def set_shrinking_rate(self, shrinking_rate): + self.__shrinking_rate = shrinking_rate + + def get_shrinking_freq(self): + return self.__shrinking_freq + + def set_shrinking_freq(self, shrinking_freq): + self.__shrinking_freq = shrinking_freq + + def set_region_shrinking(self, region_shrinking): + self.__region_shrinking = region_shrinking + + def get_region_shrinking(self): + return self.__region_shrinking + + def get_max_shrinking_times(self): + return self.__max_shrinking_times + + def set_max_shrinking_times(self, max_shrinking_times): + self.__max_shrinking_times = max_shrinking_times + \ No newline at end of file diff --git a/zoopt/solution.py b/zoopt/solution.py index a61887d..3e5b4ee 100644 --- a/zoopt/solution.py +++ b/zoopt/solution.py @@ -3,12 +3,15 @@ Author: Yu-Ren Liu, Xiong-Hui Chen + +Updated by: + Ze-Wen Li """ import copy import numpy as np -from zoopt.utils.tool_function import ToolFunction -from zoopt.utils.zoo_global import pos_inf, neg_inf, nan, gl +from .utils.tool_function import ToolFunction +from .utils.zoo_global import pos_inf, neg_inf, nan, gl class Solution: @@ -82,14 +85,25 @@ def is_equal(self, sol): """ sol_x = sol.get_x() sol_value = sol.get_value() - if sol_value != nan and self.__value != nan: - if abs(self.__value - sol_value) > gl.precision: + if sol_value is not nan and self.__value is not nan: + if abs(self.__value - sol_value) >= gl.precision: return False if len(self.__x) != len(sol_x): return False for i in range(len(self.__x)): - if abs(self.__x[i] - sol_x[i]) > gl.precision: + if not type(self.__x[i]) == type(sol_x[i]): return False + else: + if gl.float_precisions: # for Dimension2 class + if gl.float_precisions[i] is not None: # CONTINUOUS + if abs(self.__x[i] - sol_x[i]) >= pow(10, -1 * gl.float_precisions[i]): + return False + else: # DISCRETE or GRID + if not self.__x[i] == sol_x[i]: + return False + else: # for Dimension class + if abs(self.__x[i] - sol_x[i]) >= gl.precision: + return False return True def exist_equal(self, sol_set):