diff --git a/ipynb/Tutorial_DEMachineLearning.ipynb b/ipynb/Tutorial_DEMachineLearning.ipynb index fb7936e..3297a61 100644 --- a/ipynb/Tutorial_DEMachineLearning.ipynb +++ b/ipynb/Tutorial_DEMachineLearning.ipynb @@ -222,7 +222,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.8.5" } }, "nbformat": 4, diff --git a/sparseSpACE/HP_Optimization.py b/sparseSpACE/HP_Optimization.py new file mode 100644 index 0000000..b4e8f0d --- /dev/null +++ b/sparseSpACE/HP_Optimization.py @@ -0,0 +1,576 @@ +import sparseSpACE.DEMachineLearning as deml +import sparseSpACE +import numpy as np +import scipy as sp +import math +import random +import itertools +from scipy.optimize import fmin +from scipy.optimize import minimize +import matplotlib.pyplot as plt + + +class HP_Optimization: + """Constructor of HP_Optimization class + :param function: The function to be optimized / find the maximum of + :param hp_space: Defines the possible input values of the function. Input values are assumed to be numerical. + Array that contains another array for each parameter. Can be one of three types: + 1.["interval", min, max] + 2.["interval_int", min, max] + 3.["list", value_1, value_2, ..., value_n] + :param f_max: Optional. Maximal value of the function. Optimization is stopped if this value is reached. + """ + def __init__(self, function, hp_space, f_max = None): + self.function = function + self.hp_space = hp_space + self.check_hp_space() + self.f_max = f_max + self.r = 3.0 + self.delta = 0.1 + self.a = 1.0 + self.b = 1.0 + self.l = 0.5 + + """checks whether hp_space fulfills certain criteria (e.g. enough values, minmax -> max=min, min=max) or adds default values when needed. + """ + def check_hp_space(self): + if(len(self.hp_space)<1): + print("Please enter values for hp_space! Using default value ['list', 1], but will likely throw errors") + self.hp_space.append(["list", 1]) + return 0 + for i in range(0, len(self.hp_space)): + if(len(self.hp_space[i])<2): + print("Please enter at least a descriptor and one value for hp_space index " + str(i)) + print("Default value ['list', 1] will be used") + self.hp_space[i] = ["list", 1] + elif(self.hp_space[i][0]=="interval"): + if(len(self.hp_space[i])<3): + self.hp_space[i].append(self.hp_space[i][1]) + elif(self.hp_space[i][1]>self.hp_space[i][2]): + self.hp_space[i] = ["interval", self.hp_space[i][2], self.hp_space[i][1]] + else: + self.hp_space[i] = ["interval", self.hp_space[i][1], self.hp_space[i][2]] + elif(self.hp_space[i][0]=="interval_int"): + if(len(self.hp_space[i])<3): + self.hp_space[i] = ["interval_int", round(self.hp_space[i][1]), round(self.hp_space[i][1])] + elif(self.hp_space[i][1]>self.hp_space[i][2]): + self.hp_space[i] = ["interval_int", round(self.hp_space[i][2]), round(self.hp_space[i][1])] + else: + self.hp_space[i] = ["interval_int", round(self.hp_space[i][1]), round(self.hp_space[i][2])] + elif(self.hp_space[i][0]=="list"): + self.hp_space[i] = self.hp_space[i] + else: + print("Unknown descriptor for hp_space at index " + str(i) +". Using ['list', ]") + self.hp_space[i] = ["list", self.hp_space[i][1]] + + """Perform Grid Optimization + :param interval_levels: Optional. Default value: 4. If no search space is given, search space is generated by taking a subset + of values for each parameter and taking the cartesian product of these subsets. + For "list"-type parameters are values are taken. From "interval"-type parameters roughly "interval_levels"+1 many + evenly split values are taken (e.g. from ["interval", 0, 10] take [0, 1, 2, ..., 10]). + If taking this many values not possible (e.g. 3 integers between 0 and 1), takes as many values as possible + :param search_space: Optional. Directly give all the parameter values that should be tried for grid search. + : + """ + def perform_GO(self, interval_levels = 4, search_space = None): + #for storing the current best evaluation + best_evaluation = None + #storing the parameters of the best evaluation + best_x = None + #storing all x and their respective y + x_set = [] + y_set = [] + if(search_space == None): + search_space = self.cart_prod_hp_space(interval_levels) + x_set = search_space + for x in search_space: + cur_evaluation = self.perform_evaluation_at(x) + y_set.append(cur_evaluation) + if(best_evaluation == None or cur_evaluation>best_evaluation): + best_evaluation = cur_evaluation + best_x = x + if(best_evaluation==self.f_max): + print("The specified maximum of f has been reached. Stopping GO.") + break + print("The best evaluation found with grid search is " + str(best_evaluation) + " at " + str(best_x)) + return best_x, best_evaluation, x_set, y_set + + #creates the search space as explained in explanation of param interval_levels of method perform_GO + def cart_prod_hp_space(self, interval_levels): + res = [] + if (interval_levels == None): + interval_levels = 1 + for i in range (0, len(self.hp_space)): + new = [] + if(len(self.hp_space[i])<2): + print("please enter at least 1 value for hp_space list") + new.append(1) + elif(self.hp_space[i][0] == "list"): + new = self.hp_space[i].copy() + new.remove("list") + elif(len(self.hp_space[i])<3): + print("please enter at least 1 value for hp_space interval. Using the value given") + new.append(self.hp_space[i][1]) + elif(self.hp_space[i][0] == "interval"): + h = self.hp_space[i][2]-self.hp_space[i][1] + dh = h + if(interval_levels > 0): + dh = h / interval_levels + #currently adds 1 more than interval_levels so that both borders are included - is there a better way to do this? + if(h == 0): + new.append(self.hp_space[i][1]) + else: + for j in range (0, interval_levels+1): + new.append(self.hp_space[i][1]+j*dh) + elif(self.hp_space[i][0] == "interval_int"): + h = int(self.hp_space[i][2]-self.hp_space[i][1]) + dh = h + if (interval_levels>0): + dh = h /interval_levels + #currently adds 1 more than interval_levels so that both borders are included - is there a better way to do this? + if(h == 0): + new.append(int(self.hp_space[i][1])) + else: + if(dh<1): + dh = 1 + #may yield different values than expected due to rounding + if(interval_levels == 0): + new.append(int(self.hp_space[i][1])) + else: + for j in range (self.hp_space[i][1], self.hp_space[i][2]+1, int(dh)): + new.append(int(j)) + else: + print("please enter valid types for hp_space! Using the first value given") + new.append(self.hp_space[i][1]) + res.append(new) + res_cart = list(itertools.product(*res)) + return res_cart + + """Performs random optimization + :param amt_it: the amount of iterations random search is performed for + """ + def perform_RO(self, amt_it: int): + best_evaluation = 0 + best_x = None + x_set = [] + y_set = [] + for i in range (0, amt_it, 1): + x = self.create_random_x() + new_eval = self.perform_evaluation_at(x) + x_set.append(x) + y_set.append(new_eval) + if new_eval>best_evaluation: + best_x = x + best_evaluation = new_eval + if(best_evaluation == self.f_max): + print("The specified maximum of f has been reached. Stopping RO at iteration " + str(i)) + break + print("Best evaluation in " + str(amt_it) + " random steps: " + str(best_evaluation) + " at " + str(best_x)) + return best_x, best_evaluation, x_set, y_set + + #returns evaluation of the given function for certain Parameters on a certain data set + def perform_evaluation_at(self, params): + res = self.function(params) + return res + + + """performs Bayesian Optimization + :param amt_it: The amount of iterations Bayesian Optimization is performed for AFTER creating the initial evidence set + :param r: Parameter for the computation of beta + :param delta: see r + :param a: see r + :param b: see r + """ + def perform_BO(self, amt_it: int, r: float = 3.0, delta: float = 0.1, a: float = 1.0, b: float = 1.0): + self.r = r + self.delta = delta + self.a = a + self.b = b + #notes how many x values currently are in the evidence set - starts with amt_HP+1 + amt_HP = len(self.hp_space) + cur_amt_x: int = amt_HP+1 + x_ret = None + y_ret = None + print("Starting Bayesian Optimization with " + str(amt_it) + " iterations!") + C = self.create_evidence_set(amt_it, amt_HP) + C_x=C[0] + C_y=C[1] + #For easier checking in what iterations random x has been used + used_random_x = [] + for i in range (0, amt_it, 1): + beta = self.get_beta(i+1) + l = self.get_l_k(i) + if(np.linalg.det(self.get_cov_matrix(C_x, cur_amt_x))==0): + print("With the nex x value added the Covariance Matrix is singular.\nBayesian Optimization can not be continued and will be ended here, at the start of iteration " + str(i)) + break + if((self.get_cov_matrix(C_x, cur_amt_x) == np.identity(len(C_x))*(cur_amt_x+10)).all()): + print("cov_matr has been modified to be Id*scalar! This means it was singular -> ending at iteration " + str(i)) + break + + #new x value that will be evaluated and added to the evidence set + new_x=self.acq_x(beta, l, C_x, C_y, cur_amt_x) + new_x_rd = self.round_x(new_x) + old_x_rd = None + old_beta_and_l = [beta, l] + #erstelle neues beta und l und berechne neuen wert damit + amt_tries = 0 + while(self.check_if_in_array(new_x_rd, C_x)): + print("x is already included in C") + old_x_rd = new_x_rd.copy() + new_beta_and_l = self.get_new_beta_and_l(beta, cur_amt_x, new_x, C_x, C_y, amt_tries = amt_tries) + amt_tries += 1 + new_x = self.acq_x(new_beta_and_l[0], new_beta_and_l[1], C_x, C_y, cur_amt_x) + new_x_rd = self.round_x(new_x) + if(old_x_rd==new_x_rd and amt_tries >= 3): + new_x = self.acq_x(0, 0.5, C_x, C_y, cur_amt_x) + new_x_rd = self.round_x(new_x) + if(self.check_if_in_array(new_x_rd, C_x)): + used_random_x.append(i) + while(self.check_if_in_array(new_x_rd, C_x) and amt_tries < 13): + new_x_rd = self.create_random_x() + amt_tries += 1 + break + if(old_x_rd==new_x_rd or self.check_if_in_array(new_x_rd, C_x)): + print("No new x has been found. Ending BO at iteration " + str(i)) + break + C_x[cur_amt_x] = new_x_rd + C_y[cur_amt_x] = self.perform_evaluation_at(new_x_rd) + cur_amt_x += 1 + if(C_y[cur_amt_x-1]==self.f_max): + print("The specified maximum of f has been reached. Stopping BO at iteration " + str(i)) + break + #ends everything if cov_matr is singular + if(np.linalg.det(self.get_cov_matrix(C_x, cur_amt_x)) == 0): + print("With the nex x value added the Covariance Matrix is singular.\nBayesian Optimization can not be continued and will be ended here, after " + str(i) + " iterations") + break + if((self.get_cov_matrix(C_x, cur_amt_x) == np.identity(len(C_x))*(cur_amt_x+10)).all()): + print("cov_matr has been modified to be Id*scalar! This means it was singular -> ending at iteration " + str(i)) + break + + for i in range(0, cur_amt_x, 1): + if(y_ret == None or y_ret1 + y[i] = self.perform_evaluation_at(x[i]) + + return x, y + + #returns a random x, that fullfills criteria specified in hp_space + def create_random_x(self): + res = [] + for i in range (0, len(self.hp_space)): + new_x = 1 + if (len(self.hp_space[i])<2): + print("Please enter at least 1 value for hp_space list!") + elif (self.hp_space[i][0] == "list"): + sel = self.hp_space[i].copy() + sel.remove("list") + new_x = random.choice(sel) + elif (len(self.hp_space[i])<3): + print("Please enter at least 2 values for hp_space interval! Using the 1 value given") + new_x = self.hp_space[i][1] + elif (self.hp_space[i][0] == "interval"): + new_x = random.uniform(self.hp_space[i][1], self.hp_space[i][2]) + elif (self.hp_space[i][0] == "interval_int"): + new_x = int(random.randrange(int(self.hp_space[i][1]), int(self.hp_space[i][2]+1))) + #max+1 because otherwise second border value is not included + else: + print("Unknown type of space! Using first value given for index " + str(i)) + new_x = self.hp_space[i][1] + res.append(new_x) + return res + + #checks if array has the element x, returns True if yes. Caution: If x has same length but different dimension to the elements of array this may give unintended results + def check_if_in_array(self, x, array): + if(len(x) != len(array[0])): + return False + for i in range(0, len(array), 1): + if((x == array[i]).all()): + return True + break + return False + + #calculates and returns current beta. t is current iteration + def get_beta(self, t: int): + r: float = self.r + d: int = len(self.hp_space) + delta: float = self.delta + a: float = self.a + b: float = self.b + beta = 2*math.log(t**2*2*math.pi**2/(3*delta))+2*d*math.log(t**2*d*b*r*math.sqrt(math.log(4*d*a/delta))) + return beta + + #TODO: get optimized l_k in each iteration + def get_l_k(self, t: int): + return self.l + def set_l_k(self, l: float): + self.l = l + + #gets new beta and l if old beta and l give values that are already in C + def get_new_beta_and_l(self, cur_beta: float, cur_amt_x, cur_x, C_x, C_y, amt_tries: int=0): + if(np.linalg.det(self.get_cov_matrix(C_x, cur_amt_x))==0): + print("Getting new beta and l, but suddenly the cov matr is singular??") + #making upper bounds dependable on current values so bounds are never too low + beta_h = cur_beta+(100*2**amt_tries) + l_h = self.l_k+(50*2**amt_tries) + #penalty p(x) is 0 if rd(x) is in ev set C_x, constant otherwise. Is increased on each try to find new beta and l + p = lambda x: self.check_if_in_array(self.round_x(x), C_x)*(200*2**amt_tries) + #gets the x value for certain l (z[1]) and beta (z[0]) + new_x = lambda z: self.acq_x(z[0], z[1], C_x, C_y, cur_amt_x) + #for g: x[0] is \beta+d\beta, x[1] is l. Also d\beta = \beta+d\beta-\beta + g = lambda x: (x[0]-cur_beta)+np.linalg.norm(cur_x-new_x(x))+p(new_x(x)) + bounds_g = ((cur_beta, beta_h), (self.l_k, l_h)) + #due to numerical inaccuracies (or a bug?) a matrix might become singular with new beta and l + result = minimize(g, [cur_beta, self.l_k], method='L-BFGS-B', bounds=bounds_g).x + #result is in the form [new beta, new l] + return result + + """returns the bounds in which a new x may lie, as well as an x0 where to start the search for a new x. + Bounds are derived from hp_space, x0 is the smallest value within the bounds. + """ + def get_bounds_and_x0(self, offset = 0): + bounds = [] + x0 = np.zeros(len(self.hp_space)) + for i in range(0, len(self.hp_space)): + new = [0, 1] + if(len(self.hp_space[i])<2): + print("please enter at least 1 value for hp_space list. Using default values [0, 1]") + elif(self.hp_space[i][0] == "list"): + max = self.hp_space[i][1] + min = max + for j in range (2, len(self.hp_space[i])): + if(self.hp_space[i][j]max): + max = self.hp_space[i][j] + new = [min-offset, max+offset] + elif(len(self.hp_space[i])<3): + print("please enter at least 2 values for hp_space interval. Using the one value given") + new = [self.hp_space[i][1]-offset, self.hp_space[i][1]+offset] + elif(self.hp_space[i][0] == "interval"): + new = self.hp_space[i].copy() + new.remove("interval") + new = [new[0]-offset, new[1]+offset] + elif(self.hp_space[i][0] == "interval_int"): + new = self.hp_space[i].copy() + new.remove("interval_int") + new = [new[0]-offset, new[1]+offset] + else: + print("please enter a valid hp_space. Using first value given") + new = [self.hp_space[i][1]-offset, self.hp_space[i][1]+offset] + bounds.append(new) + x0[i] = new[0] + return bounds, x0 + + #acquire new x for current l, beta, C_x, cur_amt_x, using GP-UCB + def acq_x(self, beta: float, l: float, C_x, C_y, cur_amt_x): + self.l_k=l + K_matr = self.get_cov_matrix(C_x, cur_amt_x) + if(np.linalg.det(K_matr) == 0): + print("Covariance Matrix is singular!") + mu = lambda x: self.get_cov_vector(C_x, x, cur_amt_x).dot(np.linalg.inv(K_matr).dot(C_y)) + sigma_sqrd = lambda x: self.cov(x, x)-self.get_cov_vector(C_x, x, cur_amt_x).dot(np.linalg.inv(K_matr).dot(self.get_cov_vector(C_x, x, cur_amt_x))) + #takes sqrt of abs(sigma_sqrd) bc otherwise fmin gives an error - might be an overall warning sign tho + sigma = lambda x: math.sqrt(abs(sigma_sqrd(x))) + alpha = lambda x: mu(x)+(math.sqrt(abs(beta)))*sigma(x) + #negates alpha because maximum needs to be found + alpha_neg = lambda x: -alpha(x) + bounds_and_x0 = self.get_bounds_and_x0() #bounds search space to vicinity of useful values + bounds_an = bounds_and_x0[0] + #problem: Nelder-Mead (same as fmin) cannot handle bounds -> use L-BFGS-B + x0 = bounds_and_x0[1] + new_x=minimize(alpha_neg, x0, method='L-BFGS-B', bounds=bounds_an).x + return new_x + + #rounds x so that x_rd fulfills criteria stated in hp_space + def round_x(self, x): + if len(x) < len(self.hp_space): + print("Input too short! Returning default values rd(0) for missing values") + for k in range (len(x), len(self.hp_space)): + x.append(0) + if len(x) > len(self.hp_space): + print("Input too long! Cropping") + new_x_rd = [] + for i in range (0, len(self.hp_space)): + new_x = 1 + if(len(self.hp_space[i])<2): + print("Please enter at least 1 value for hp_space list. Index: " + str(i) + ": " + str(self.hp_space[i])) + elif(self.hp_space[i][0] == "list"): + new_x = self.hp_space[i][1] + dis = abs(x[i]-self.hp_space[i][1]) + #takes the value in the list that is least away from x + for j in range (2, len(self.hp_space[i])): + cur_dis = abs(x[i]-self.hp_space[i][j]) + if(cur_dis < dis): + dis = cur_dis + new_x = self.hp_space[i][j] + elif(len(self.hp_space[i])<3): + print("Please enter at least 2 values for hp_space interval. Using the one value given") + new_x = self.hp_space[i][1] + elif(self.hp_space[i][0] == "interval"): + if(x[i]self.hp_space[i][2]): + new_x = self.hp_space[i][2] + else: + new_x = x[i] + elif(self.hp_space[i][0] == "interval_int"): + if(x[i]self.hp_space[i][2]): + new_x = int(self.hp_space[i][2]) + else: + new_x = round(x[i]) + else: + print("Unknown type of space! Using first value given for index " + str(i)) + new_x = self.hp_space[i][1] + new_x_rd.append(new_x) + return new_x_rd + +class Optimize_Classification: + """Constructor of the Optimize_Classification class. + + Takes in: + :param data_name: The name of a data set to create such a data set. + Options are: moons, blobs, circles, classification, gaussian_quantiles, + digits, iris, breast_cancer, wine. Can also take in a dataset directly. + :param data: Optional. If no data set is given here data is created by name, using "moons" as default + :param max_lv: Parameter of classification and class_dim_wise that are not otimized + but can be specified here + :param max_evals: see max_lv + """ + def __init__(self, data_name: str = "moons", data = None, samples: int = 500, dimension: int = 2, labels: int = 6, max_lv: int = 3, max_evals: int = 256): + self.samples = samples + self.dimension = dimension + self.labels = labels + if(data == None): + self.data = self.create_data(data_name) + else: + self.data = data + self.max_lv = max_lv + self.max_evals = max_evals + self.classification_space = [["interval", 0, 20], ["list", 0, 1], ["list", 1], ["list", 0, 1]] + self.class_dim_wise_space = [["interval", 0, 20], ["list", 0, 1], ["list", 1], ["list", 0, 1, 2], ["interval", 0, 1], ["list", 0, 1], ["list", 0, 1], ["interval_int", 2, self.max_evals]] + + def create_data(self, name: str = "moons"): + dataset = deml.datasets.make_moons(n_samples=self.samples, noise=0.15, random_state=1) + if(name == "blobs"): + dataset = deml.datasets.make_blobs(n_samples=self.samples, n_features=self.dimension, centers=self.labels, random_state=1) + elif(name == "circles"): + dataset = deml.datasets.make_circles(n_samples=self.samples, noise=0.05, random_state=1) + elif(name == "classification"): + dataset = deml.datasets.make_classification(n_samples=self.samples, n_features=self.dimension, n_redundant=0, n_clusters_per_class=1, n_informative=2, n_classes=(self.labels if self.labels < 4 else 4), random_state=1) + elif(name == "gaussian_quantiles"): + dataset = deml.datasets.make_gaussian_quantiles(n_samples=self.samples, n_features=self.dimension, n_classes=self.labels, random_state=1) + elif(name == "digits"): + dataset = deml.datasets.load_digits(return_X_y=True) # hint: try only with max_level <= 3 + elif(name == "iris"): + dataset = deml.datasets.load_iris(return_X_y=True) + elif(name == "breast_cancer"): + dataset = deml.datasets.load_breast_cancer(return_X_y=True) # hint: try only with max_level <= 4 + elif(name == "wine"): + dataset = deml.datasets.load_wine(return_X_y=True) + elif(name != "moons"): + print("This name for the data set is unknown, using moons.\nPlease check that the name is not capitalized, words are separated by _, and that it is written correctly") + data = deml.DataSet(dataset, name="data_"+name) + return data + + """Perform classification with certain HP-input and return evaluation + :param params: array containing all parameters for classification + params[0]: lambd_exp: used to calculate lambda = 10**(-lambd_exp) + params[1]: massl + params[2]: min_lv + params[3]: ove_vs_others + """ + def pea_classification(self, params): + if(len(params)<4): + print("too little parameters for pea_classification. Returning 0.0") + return 0.0 + params = [params[0], int(params[1]), int(params[2]), int(params[3])] + cur_data = self.data.copy() + classification = deml.Classification(cur_data, split_percentage=0.8, split_evenly=True, shuffle_data=False) + classification.perform_classification(masslumping=params[1], lambd=10**(-params[0]), minimum_level=params[2], maximum_level=self.max_lv, one_vs_others=params[3], print_metrics=False) + evaluation = classification.evaluate() + return evaluation["Percentage correct"] + + """Perform dimension wise classification with certain HP-input and return evaluation + :param params: array containing all parameters for dim wise classification + params[0]: lambd_exp: used to calculate lambda = 10**(-lambd_exp) + params[1]: massl + params[2]: min_lv + params[3]: ove_vs_others / error calculator. + case 0: ovo=false, ec=none; + case1: ovo=true, ec=none; + case2: ovo=true, ec=error_calculator + params[4]: margin + params[5]: rebalancing + params[6]: use_relative_surplus + params[7]: max_evaluations + """ + def pea_classification_dimension_wise(self, params): + if(len(params)<4): + print("too little parameters for pea_classification. Returning 0.0") + return 0.0 + params = [float(params[0]), int(params[1]), int(params[2]), int(params[3]), float(params[4]), int(params[5]), int(params[6]), int(params[7])] + cur_data = self.data.copy() + error_calculator=sparseSpACE.ErrorCalculator.ErrorCalculatorSingleDimMisclassificationGlobal() + ec = None + ovo = False + if(params[3] == 2): + ec = error_calculator + ovo = True + elif(params[3] == 1): + ovo = True + classification = deml.Classification(cur_data, split_percentage=0.8, split_evenly=True, shuffle_data=False) + classification.perform_classification_dimension_wise(masslumping=params[1], lambd=10**(-params[0]), minimum_level=params[2], maximum_level=self.max_lv, one_vs_others=ovo, error_calculator = ec, margin = params[4], rebalancing = params[5], use_relative_surplus = params[6], max_evaluations = params[7], print_metrics=False) + evaluation = classification.evaluate() + return evaluation["Percentage correct"] + + diff --git a/test/test_HP_Optimization.py b/test/test_HP_Optimization.py new file mode 100644 index 0000000..1ebd4e5 --- /dev/null +++ b/test/test_HP_Optimization.py @@ -0,0 +1,103 @@ +import unittest +import sparseSpACE +import sparseSpACE.HP_Optimization as hpo +import numpy as np +import math + +class TestHP_Optimization(unittest.TestCase): + def test_HPO(self): + OC = hpo.Optimize_Classification(data_name = "moons") + HPO = hpo.HP_Optimization(OC.pea_classification, OC.classification_space) + #test check_if_in_array (only works with np.array) + array = np.array([[0, 1, 1, 1], [7, 3, 6, 5], [4.2, 3.3, 5.2, 9], [7, 7, 7, 7]]) + b = HPO.check_if_in_array([7, 7, 7, 7], array) + print("b = " + str(b)) + self.assertTrue(b) + self.assertTrue(HPO.check_if_in_array([4.2, 3.3, 5.2, 9], array)) + self.assertFalse(HPO.check_if_in_array([1, 0, 0, 0], array)) + #test get_beta + beta_1 = HPO.get_beta(1) + self.assertAlmostEqual(beta_1, 34.74985580606742) + #test cov + v1 = np.array([1, 2]) + v2 = np.array([3, 4]) + res = HPO.cov(v1, v2) + self.assertAlmostEqual(res, math.exp(-16)) + #test cov_matrix + C_x=np.array([[1, 2], [3, 4]]) + K = HPO.get_cov_matrix(C_x, 2) + control_K = np.array([[1, math.exp(-16)], [math.exp(-16), 1]]) + np.testing.assert_almost_equal(K, control_K) + #test cov_vector + C_x=np.array([[1, 2], [5, 6]]) + x = np.array([3, 4]) + v = HPO.get_cov_vector(C_x, x, 2) + control_v = np.array([math.exp(-16), math.exp(-64)]) + np.testing.assert_almost_equal(v, control_v) + #test check_hp_space + simple_function = lambda x: x[0]+x[1] + simple_space = [] + cmp_space = [["list", 1]] + HPO_simple = hpo.HP_Optimization(simple_function, simple_space) + self.assertEqual(HPO_simple.hp_space, cmp_space) + simple_space = [["interval", 5, 3.3], [7], ["interval_int", 4.2, 1.1], ["interval", 7]] + cmp_space = [["interval", 3.3, 5], ["list", 1], ["interval_int", 1, 4], ["interval", 7, 7]] + HPO_simple = hpo.HP_Optimization(simple_function, simple_space) + self.assertEqual(HPO_simple.hp_space, cmp_space) + simple_space = [["list", 0.42, 3.3], ["interval", 2, 4], ["interval_int", 2, 4]] + HPO_simple = hpo.HP_Optimization(simple_function, simple_space) + self.assertEqual(HPO_simple.hp_space, [["list", 0.42, 3.3], ["interval", 2, 4], ["interval_int", 2, 4]]) + #test cart_prod_hp_space + cart_prod = HPO_simple.cart_prod_hp_space(1) + cmp_cart_prod = [(0.42, 2.0, 2), (0.42, 2.0, 4), (0.42, 4.0, 2), (0.42, 4.0, 4), (3.3, 2.0, 2), (3.3, 2.0, 4), (3.3, 4.0, 2), (3.3, 4.0, 4)] + self.assertEqual(cart_prod, cmp_cart_prod) + #test perform_eval_at + res = HPO_simple.perform_evaluation_at([0.42, 4.0, 2]) + self.assertEqual(res, 4.42) + res2 = HPO_simple.perform_evaluation_at([42, 1337]) + self.assertEqual(res2, 1379) + #test GO + simple_space = [["list", 0.42, 1, 2.5, 3.3], ["list", 2, 4]] + HPO_simple = hpo.HP_Optimization(simple_function, simple_space) + res_GO = HPO_simple.perform_GO() + np.testing.assert_almost_equal(res_GO[0], [3.3, 4]) + self.assertAlmostEqual(res_GO[1], 7.3) + #test round x + simple_space = [["list", 0.42, 1, 2.5, 3.3], ["interval", 2, 4], ["interval_int", 1, 5]] + HPO_simple = hpo.HP_Optimization(simple_function, simple_space) + x = [0.4, 3.33, 2.7] + x_rd = HPO_simple.round_x(x) + y = [2.5, 5, 5] + y_rd = HPO_simple.round_x(y) + self.assertEqual(x_rd, [0.42, 3.33, 3]) + self.assertEqual(y_rd, [2.5, 4, 5]) + #test get random x + x = HPO_simple.create_random_x() + x_rd = HPO_simple.round_x(x) + self.assertEqual(x, x_rd) + #test create_evidence_set + C = HPO_simple.create_evidence_set(5, 3) + self.assertEqual(len(C[0]), 9) + self.assertEqual(len(C[0][0]), 3) + self.assertEqual(HPO_simple.perform_evaluation_at(C[0][0]), C[1][0]) + cmp = [0, 0, 0] + np.testing.assert_equal(C[0][7], cmp) + #test get_bounds_and_x0 + #bounds are supposed to be smallest and biggest possible values, x0 consists of smallest values + bounds_and_x0 = HPO_simple.get_bounds_and_x0(0) + self.assertEqual(bounds_and_x0[0][0][0], 0.42) + self.assertEqual(bounds_and_x0[0][0][1], 3.3) + self.assertEqual(bounds_and_x0[0][1][0], 2) + self.assertEqual(bounds_and_x0[0][1][1], 4) + self.assertEqual(bounds_and_x0[0][2][0], 1) + self.assertEqual(bounds_and_x0[0][2][1], 5) + + self.assertEqual(bounds_and_x0[1][0], 0.42) + self.assertEqual(bounds_and_x0[1][1], 2) + self.assertEqual(bounds_and_x0[1][2], 1) + + def simple_function(self, x): + return x[0]+x[1] + +if __name__ == '__main__': + unittest.main()