예제 #1
0
    def __init__(self, **kwargs):
        super(HostState, self).__init__(**kwargs)

        self.results_dir = self._register('results_dir', 'results/', True)
        self.tmp_dir = self._register('tmp_dir', 'tmp/')
        self.export_dir = self._register('export_dir', 'export/', True)

        # ensure the user don't shoot himself in the foot
        if self.results_dir == self.tmp_dir:
            fatal('Result dir and tmp dir must be different')

        # create directory if needed
        tf_utils.create_directory(self.results_dir)
        tf_utils.create_directory(self.tmp_dir, remove_existing=True)
        tf_utils.create_directory(self.export_dir)

        # init _HOST
        config._Host = Host()
        status = config._Host.get_status()
        tf_version = status['software']['tensorflow']
        if tf_version:
            major, minor, rev = tf_version.split('.')
            if major == '1':
                if int(minor) >= 13:
                    print('ok')
                else:
                    fatal(
                        "Keras Tuner only work with TensorFlow version >= 1.13\
                      current version: %s - please upgrade" % tf_version)
        else:
            warning('Could not determine TensorFlow version.')
예제 #2
0
    def add(self, metric):
        """ Add a metric to the collection

        Args:
            metric (Metric or str): Metric object or metric name
        """
        # our own metric object -> direct add
        if isinstance(metric, Metric):
            # our own metric, do nothing
            metric_name = metric.name
        else:
            if isinstance(metric, str):
                # metric by name
                metric_name = metric
            else:
                # keras metric
                metric_name = metric.name

            metric_name = self._replace_alias(metric_name)
            # canonalize metric name (val_metric vs metric)
            no_val_name = metric_name.replace('val_', '')
            if no_val_name in _METRIC_DIRECTION:
                direction = _METRIC_DIRECTION[no_val_name]
            else:
                fatal('Unknown metric %s' % metric_name)

            # create a metric object
            metric = Metric(metric_name, direction)

        if metric_name in self._objects:
            fatal('Duplicate metric:%s' % metric_name)
        self._objects[metric_name] = metric
        self._last_insert_idx = metric_name
예제 #3
0
    def set_objective(self, name):
        "Mark a metric as the tuning objective"
        name = self._replace_alias(name)
        print(name)
        if name not in self._objects:
            found = False

            # accuracy special case which seems to be only a
            # Windows -TF 1.13+ issue
            if 'accuracy' in name:
                # is it a validation metric
                val = False
                if 'val' in name:
                    val = True

                for mm in self.get_metric_names():
                    # none val_* accuracy
                    if not val and 'accuracy' in mm and 'val' not in mm:
                        name = mm
                        found = True

                    # val_* accuracy
                    if val and 'accuracy' in mm and 'val' in mm:
                        name = mm
                        found = True

            if not found:
                metrics = ", ".join(list(self._objects.keys()))
                fatal("can't find objective: %s in metric list:%s" % (name,
                                                                      metrics))
        if self._objective_name:
            fatal("Objective already set to %s" % self._objective_name)
        self._objective_name = name
        self._objects[name].is_objective = True
        return name
예제 #4
0
    def __init__(self, name, direction):
        """ Initialize a metric object

        Args:
            name (str): metric name
            direction (str): metric direction. One of {'min', 'max'}

        Attributes:
            history (list): metric epoch history
            is_objective (bool): is this metric the main tuning objectived.
            Defaults to False.
            start_time (float): when the metric was created
            wall_time (list): time it took to reach a given epoch from start
            time. Data recorded as float which are delta from start_time.

        """
        self.name = name
        if direction not in ['min', 'max']:
            fatal('invalid direction. must be in {min, max}')
        self.direction = direction
        self.history = []
        self.is_objective = False

        self.start_time = time()
        self.wall_time = []
예제 #5
0
def parse_args():
    "Parse cmdline options"
    parser = argparse.ArgumentParser(description='display tuning results')

    parser.add_argument('--input_dir',
                        '-i',
                        type=str,
                        default='results/',
                        help='Directory containing tuner results')

    parser.add_argument('--project',
                        '-p',
                        type=str,
                        default='default',
                        help='Which project to display result for')

    parser.add_argument('--architecture',
                        '-a',
                        type=str,
                        default=None,
                        help='Restrict results to a given architecture')

    parser.add_argument('--num_models',
                        '-n',
                        type=int,
                        default=10,
                        help='Num models to display')

    parser.add_argument('--metric',
                        '-m',
                        type=str,
                        default=None,
                        help='Metrics to sort by - if None\
                                            use objective')

    parser.add_argument('--display_hyper_parameters',
                        '--hyper',
                        type=bool,
                        default=True,
                        help='Display hyperparameters values')

    parser.add_argument('--use_colors',
                        '-c',
                        type=bool,
                        default=True,
                        help='Use terminal colors.')

    args = parser.parse_args()

    if not os.path.exists(args.input_dir):
        fatal("[Error] Invalid Input directory %s" % args.input_dir,
              raise_exception=False)
        parser.print_help()
        quit()

    return args
예제 #6
0
def results_summary(input_dir='results/',
                    project='default',
                    architecture=None,
                    sort_metric=None,
                    display_hyper_parameters=True,
                    num_models=10,
                    use_colors=True):
    """
    Collect kerastuner results and output a summary
    """

    ic = InstanceStatesCollection()
    ic.load_from_dir(input_dir, project=project, verbose=0)
    if sort_metric:
        instance_states = ic.sort_by_metric(sort_metric)
    else:
        # by default sort by objective
        instance_states = ic.sort_by_objective()
        sort_metric = ic.get_last().objective

    other_metrics = ic.get_last().agg_metrics.get_metric_names()
    other_metrics.remove(sort_metric)  # removing the main metric

    sort_metric_values = []
    other_metrics_values = defaultdict(list)
    hyper_parameters = defaultdict(list)
    hyper_parameters_group = defaultdict(set)
    for instance in instance_states[:num_models]:
        val = instance.agg_metrics.get(sort_metric).get_best_value()
        sort_metric_values.append(round(val, 4))

        # other metrics
        for metric in other_metrics:
            val = instance.agg_metrics.get(metric).get_best_value()
            other_metrics_values[metric].append(round(val, 4))

        # hyper-parameters
        for k, v in instance.hyper_parameters.items():
            hyper_parameters[k].append(v)
            hyper_parameters_group[v['group']].add(k)

    if not len(sort_metric_values):
        fatal("No models found - wrong dir (-i) or project (-p)?")

    num_models = min(len(sort_metric_values), num_models)
    section("Result summary")
    subsection("Metrics")
    display_metrics(sort_metric, sort_metric_values, other_metrics,
                    other_metrics_values, num_models, use_colors)

    if display_hyper_parameters:
        subsection("Hyper Parameters")
        display_hparams(hyper_parameters, hyper_parameters_group, num_models,
                        use_colors)
    def Linear(self, name, start, stop, num_buckets, precision=0,
               group='default'):
        """Return a random value from a range which is linearly divided.
        Args:
            name (str): name of the parameter
            start (int/float): lower bound of the range
            stop (int/float): upper bound of the range
            num_buckets (int): into how many buckets to divided the range in
            precision (int): For float range. Round the result rounded to the
                            nth decimal if needed. 0 means not rounded
        Returns:
            an element of the range
        """
        if not isinstance(start, float) and not isinstance(start, int):
            fatal("start must be a float or an int")
        if not isinstance(stop, float) and not isinstance(stop, int):
            fatal("stop must be a float or an int")
        if not isinstance(num_buckets, int):
            fatal("num_bucket must be an integer")
        if stop <= start:
            fatal("start value:%s larger than stop value:%s" % (start, stop))

        my_range = np.linspace(start, stop, num_buckets)
        self._record_hyperparameters(name, 'Linear', num_buckets, start, stop,
                                     group, my_range)
        return start
    def Range(self, name, start, stop, increment=1, group='default'):
        """Return a random value from a range.
        Args:
            name (str): name of the parameter
            start (int/float): lower bound of the range
            stop (int/float): upper bound of the range
            increment (int/float): incremental step
        Returns:
            an element of the range
        """
        if not isinstance(start, int) or not isinstance(stop, int):
            fatal("start, stop must be integers")
        if not isinstance(increment, int):
            fatal("increment must be an integer")
        if stop <= start:
            fatal("start value:%s larger than stop value:%s" % (start, stop))

        rsize = stop - start
        if rsize < increment:
            fatal("increment: %s greater than range size:%s" % (increment,
                                                                rsize))

        my_range = list(range(start, stop, increment))
        self._record_hyperparameters(name, 'Range', len(my_range),
                                     start, stop, group, my_range)
        return start
    def Choice(self, name, selection, group="default"):
        """Return a random value from an explicit list of choice.
        Args:
            name (str): name of the parameter
            selection (list): list of explicit choices
            group (str): Optional logical group name this parameter belongs to
        Returns:
            an element of the list provided
        """
        if not isinstance(selection, list):
            fatal("list if choice must be a list []")

        self._record_hyperparameters(name, 'Choice', len(selection),
                                     selection[0], selection[-1], group,
                                     selection)
        return selection[0]
예제 #10
0
    def _register(self, name, default_value, to_report=False):
        """
        Register a user value and check its value type match what is expected

        Args:
            name (str): Arg name
            default_value: the default value if not supplied by the user
            to_report (bool, optional): Defaults to False. Report as key param?

        Returns:
            value to use
        """

        value = self.kwargs.get(name, default_value)
        if not isinstance(value, type(default_value)):
            fatal('Invalid type for %s -- expected:%s, got:%s' %
                  (name, type(default_value), type(value)))
        self.user_parameters.append(name)
        if to_report:
            self.to_report.append(name)
        return value
예제 #11
0
    def _record_hyperparameters(self, name, htype, space_size, start, stop,
                                group, values):
        """
        Record a given hyperparameter

        Args:
            name (str): name of the hyperparameter
            htype (str): type of hyperparameter
            space_size (int): number of values the param can take
            start: lower bound
            stop: upper bound
            values (list): list of potential values. Truncated to 100
        """

        key = self._get_key(name, group)

        # check if we have a duplicate
        if key in self._hyperparameters_config:
            fatal("%s hyperparameter is declared twice" % key)

        # making sure values are serializable
        serializable_values = []
        for v in values[:self.max_reported_values]:
            if isinstance(v, np.integer):
                serializable_values.append(int(v))
            if isinstance(v, np.float):
                serializable_values.append(float(v))
            else:
                serializable_values.append(v)

        self._hyperparameters_config[key] = {
            "name": name,
            "group": group,
            "type": htype,
            "space_size": space_size,
            "start": start,
            "stop": stop,
            "values": serializable_values
        }
예제 #12
0
    def _record_hyperparameter(self, name, value, group):
        """ Record hyperparameter value
        Args:
            name (str): name of the hyperparameter
            value: value of the hyperparameter
            group (str): which logical group this parameters belongs to
        """

        hparam = {"name": name, "value": value, "group": group}
        key = self._get_key(name, group)

        # new hyper-parameter - makes reporting unstable
        if key not in self._hyperparameters_config:
            self.dynamic_hyperparameters = True
            if self.fatal_on_dynamic_hyperparmeter:
                fatal('Parameter %s is dynamic - this is incompatible with\
                      tuning algorithm' % key)
            else:
                warning('Parameter %s is dynamic - this will make reporitng\
                        innacurate. Consider making hyperparameters\
                        non-conditional' % key)

        self._hyperparameters[key] = hparam
예제 #13
0
    def __init__(self, model_fn, objective, name, distributions, **kwargs):
        """ Tuner abstract class

        Args:
            model_fn (function): Function that return a Keras model
            name (str): name of the tuner
            objective (str): Which objective the tuner optimize for
            distributions (Distributions): distributions object

        Notes:
            All meta data and varialbles are stored into self.state
            defined in ../states/tunerstate.py
        """

        # hypertuner state init
        self.state = TunerState(name, objective, **kwargs)
        self.stats = self.state.stats  # shorthand access
        self.cloudservice = CloudService()

        # check model function
        if not model_fn:
            fatal("Model function can't be empty")
        try:
            mdl = model_fn()
        except:
            traceback.print_exc()
            fatal("Invalid model function")

        if not isinstance(mdl, Model):
            t = "tensorflow.keras.models.Model"
            fatal("Invalid model function: Doesn't return a %s object" % t)

        # function is valid - recording it
        self.model_fn = model_fn

        # Initializing distributions
        hparams = config._DISTRIBUTIONS.get_hyperparameters_config()
        if len(hparams) == 0:
            warning("No hyperparameters used in model function. Are you sure?")

        # set global distribution object to the one requested by tuner
        # !MUST be after _eval_model_fn()
        config._DISTRIBUTIONS = distributions(hparams)

        # instances management
        self.max_fail_streak = 5  # how many failure before giving up
        self.instance_states = InstanceStatesCollection()

        # previous models
        print("Loading from %s" % self.state.host.results_dir)
        count = self.instance_states.load_from_dir(self.state.host.results_dir,
                                                   self.state.project,
                                                   self.state.architecture)
        self.stats.instance_states_previously_trained = count
        info("Tuner initialized")