def params_to_skopt(param_space: ParamSpace): """ Converts a parameter space to a list of Dimention objects that can be used with a skopt Optimizer. A skopt Optimizer only receives 3 types of Dimensions: Categorical, Real, or Integer we convert parameters from our parameter space into one of those 3 types. Note that we only convert parameters that have either bounds or with a categorical domain with more than 1 value. If we have constant values in our parameter space, these don't need to be optimized anyway. Another function is provided to convert skopt output values back into a dictionary with a full configuration according to the parameter space (@see values_to_params). Args: param_space: a ParameterSpace where we can get the domain of each parameter Returns: a list of Dimension that can be passed to a skopt Optimizer """ dimensions = [] for param_name in param_space.param_names(): domain_param = param_space.domain(param_name) domain = domain_param["domain"] dtype = DTypes.from_type(domain_param["dtype"]) if len(domain) > 1: if dtype == DTypes.INT: low = min(domain) high = max(domain) dimensions.append(Integer(low, high, name=param_name)) elif dtype == DTypes.FLOAT: low = min(domain) high = max(domain) prior = domain_param.get("prior", None) dimensions.append(Real(low, high, prior=prior, name=param_name)) elif dtype == DTypes.CATEGORICAL: prior = domain_param.get("prior", None) dimensions.append( Categorical(domain, prior, transform="onehot", name=param_name)) return dimensions
def run(params, module, workers, gpu, n, surrogate, acquisition, name, plot, out, sync, kappa, xi, kuma): logger = logging.getLogger(__name__) handler = logging.FileHandler('{name}.log'.format(name=name), delay=True) handler.setLevel(logging.ERROR) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) opt_results = None out_file = None try: if gpu: # detecting available gpus with load < 0.1 gpu_ids = [g.id for g in GPUtil.getGPUs() if g.load < 0.2] num_workers = min(workers, len(gpu_ids)) if num_workers <= 0: sys.exit(1) else: num_workers = min(workers, mp.cpu_count()) logger.log(logging.DEBUG, "Spawning {} workers".format(num_workers)) if num_workers <= 0: logger.log(logging.ERROR, "--workers cannot be 0") sys.exit(1) # prepare output file out_file_name = '{}_configurations.csv'.format(name) out = out_file_name if out is None else out if out is not None and os.path.isdir(out): out_file_path = os.path.join(out, out_file_name) else: out_file_path = out out_dir = os.path.abspath(os.path.join(out_file_path, os.pardir)) out_file_path = os.path.join(out_dir, out_file_name) param_space = ParamSpace(params) dimensions = params_to_skopt(param_space) optimizer_dims = [d.name for d in dimensions] acquisition_kwargs = None if acquisition == "LCB": acquisition_kwargs = {'kappa': kappa} elif acquisition == "EI": acquisition_kwargs = {'xi': xi} optimizer = Optimizer(dimensions=dimensions, acq_func_kwargs=acquisition_kwargs, base_estimator=surrogate, acq_func=acquisition) out_file = open(out_file_path, 'w') out_writer = csv.DictWriter(out_file, fieldnames=param_space.param_names() + ["id", "evaluation"]) out_writer.writeheader() # setup process pool and queues # manager = mp.Manager() config_queue = Queue() result_queue = Queue() error_queue = Queue() terminate_flags = [Event() for _ in range(num_workers)] processes = [ Process(target=worker, args=(i, module, config_queue, result_queue, error_queue, terminate_flags[i])) for i in range(num_workers) ] configs = [] scores = {} # get initial points at random and submit one job per worker submit(num_workers, optimizer, optimizer_dims, configs, param_space, config_queue) # cfg_if: score num_completed = 0 pending = len(configs) cancel = False for p in processes: p.daemon = True p.start() if plot: fig = plt.gcf() fig.show() fig.canvas.draw() progress_bar = tqdm(total=n, leave=True) if kuma: update_progress_kuma(progress_bar) while num_completed < n and not cancel: try: res = result_queue.get(timeout=1) pid, cfg_id, result = res if not isinstance(result, Exception): cfg = configs[cfg_id] # convert dictionary to x vector that optimizer takes x = [cfg[param] for param in optimizer_dims] # store scores for each config scores[cfg_id] = result out_row = dict(cfg) out_row["evaluation"] = result out_writer.writerow(out_row) # make sure we can see the results in the file as we run the optimizer out_file.flush() opt_results = optimizer.tell(x, result) num_completed += 1 pending -= 1 if plot: plots.plot_convergence(opt_results) fig.canvas.draw() # sync submission of jobs means we wait for all workers to finish if sync and pending == 0: if num_completed != n: num_submit = min(num_workers, n - num_completed) submit(num_submit, optimizer, optimizer_dims, configs, param_space, config_queue) pending = num_submit else: terminate_flags[pid].set() # async submission of jobs: as soon as we receive one result we submit the next if not sync: if (num_completed + pending) != n: submit(1, optimizer, optimizer_dims, configs, param_space, config_queue) pending += 1 else: # signal the current worker for termination terminate_flags[pid].set() progress_bar.update() progress_bar.set_postfix( {"best solution ": opt_results["fun"]}) if kuma: update_progress_kuma(progress_bar) else: _, cfg_id_err, err = error_queue.get() logger.error("configuration {} failed".format(cfg_id_err)) logger.error(err) cancel = True except Empty: pass # try to wait for process termination for process in processes: process.join(timeout=0.5) if process.is_alive(): process.terminate() progress_bar.close() except TomlDecodeError as e: logger.error(traceback.format_exc()) print("\n\n[Invalid parameter file] TOML decode error:\n {}".format(e), file=sys.stderr) except ParamDecodeError as e: logger.error(traceback.format_exc()) print("\n\n[Invalid parameter file]\n {}".format(e), file=sys.stderr) except Exception as e: logger.error(traceback.format_exc()) raise e except KeyboardInterrupt: pass finally: # debugging if opt_results is not None and plot: plt_file = '{}_convergence.pdf'.format(name) out_path = os.path.join(out_dir, plt_file) plt.savefig(out_path, bbox_inches='tight') if out_file is not None: out_file.close()