Пример #1
0
    def _construct_single(cls, recipe, name, args, kwargs) -> Any:
        try:
            if args is not None and not isinstance(args, list): args = [args]

            if args is not None:
                args = [ cls._construct_or_return(a) for a in args ]

            if kwargs is not None:
                kwargs = { k:cls._construct_or_return(v) for k,v in kwargs.items() }

            if args is not None and kwargs is not None:
                return cls.retrieve(name)(*args, **kwargs)

            elif args is not None and kwargs is None:
                try:
                    return cls.retrieve(name)(*args)
                except TypeError as e:
                    return cls.retrieve(name)(args)

            elif args is None and kwargs is not None:
                return cls.retrieve(name)(**kwargs)
            else:
                return cls.retrieve(name)()

        except KeyError:
            raise CobaException(f"Unknown recipe {str(recipe)}")

        except Exception as e:
            raise CobaException(f"Unable to create recipe {str(recipe)}")
Пример #2
0
    def _load_file_configs(cls) -> Dict[str, Any]:
        config = {}

        for search_path in cls.search_paths:

            potential_coba_config = search_path / ".coba"

            if potential_coba_config.exists(
            ) and potential_coba_config.read_text().strip() != "":
                try:
                    file_config = json.loads(potential_coba_config.read_text())

                    if not isinstance(file_config, dict):
                        raise CobaException(
                            f"Expecting a JSON object (i.e., {{}}).")

                    cls._resolve_and_expand_paths(file_config,
                                                  str(search_path))

                    config.update(file_config)

                except Exception as e:
                    raise CobaException(
                        f"{str(e).strip('.')} in {potential_coba_config}.")

        return config
Пример #3
0
    def encodes(self, values: Sequence[Any]) -> Sequence[Tuple[int, ...]]:

        if self._onehots is None:
            raise CobaException(
                "This encoder must be fit before it can be used.")

        try:
            return list(map(self._onehots.__getitem__, values))
        except KeyError as e:
            raise CobaException(
                f"We were unable to find {e} in {list(self._onehots.keys())}")
Пример #4
0
    def encode(self, value: Any) -> Tuple[int, ...]:

        if self._onehots is None:
            raise CobaException(
                "This encoder must be fit before it can be used.")

        try:
            return self._onehots[value]
        except KeyError as e:
            raise CobaException(
                f"We were unable to find {e} in {list(self._onehots.keys())}")
Пример #5
0
    def encode(self, value: Any) -> int:

        if self._levels is None:
            raise CobaException(
                "This encoder must be fit before it can be used.")

        try:
            return self._levels[value]
        except KeyError as e:
            raise CobaException(
                f"We were unable to find {e} in {self._levels.keys()}"
            ) from None
Пример #6
0
    def predict(self, context: Context,
                actions: Sequence[Action]) -> Tuple[Probs, Info]:

        if not self._adf and not self._actions:
            self._actions = actions

        if not self._vw.is_initialized:  #this should only be true for not adf with no actions given
            self._n_actions = len(actions)
            args = self._args.replace('--cb_explore', '').replace('--cb', '')
            args = f"--cb_explore {len(actions)} " if self._explore else f"--cb {len(actions)} " + args
            args = args.strip()
            self._vw.init_learner(args, 4)

        if not self._adf and actions != self._actions:
            raise CobaException(
                "Actions are only allowed to change between predictions when using `adf`."
            )

        if not self._adf and len(actions) != self._n_actions:
            raise CobaException(
                "The number of actions doesn't match the `--cb` action count given in args."
            )

        info = (actions if self._adf else self._actions)

        context = {'x': self._flat(context)}
        adfs = None if not self._adf else [{
            'a': self._flat(action)
        } for action in actions]

        if self._adf and self._explore:
            probs = self._vw.predict(
                self._vw.make_examples(context, adfs, None))

        if self._adf and not self._explore:
            losses = self._vw.predict(
                self._vw.make_examples(context, adfs, None))
            min_loss = min(losses)
            min_bools = [s == min_loss for s in losses]
            min_count = sum(min_bools)
            probs = [
                int(min_indicator) / min_count for min_indicator in min_bools
            ]

        if not self._adf and self._explore:
            probs = self._vw.predict(self._vw.make_example(context, None))

        if not self._adf and not self._explore:
            index = self._vw.predict(self._vw.make_example(context, None))
            probs = [int(i == index) for i in range(1, len(actions) + 1)]

        return probs, info
Пример #7
0
    def construct(cls, recipe:Any) -> Any:
        name   = ""
        args   = None
        kwargs = None
        method = "singular"

        if not cls.is_valid_recipe(recipe):
            raise CobaException(f"Invalid recipe {str(recipe)}")

        if isinstance(recipe, str):
            name = recipe
    
        if isinstance(recipe, dict):
            mutable_recipe = dict(recipe)

            method = mutable_recipe.pop("method", "singular")
            name   = mutable_recipe.pop("name"  , ""        )
            args   = mutable_recipe.pop("args"  , None      )
            kwargs = mutable_recipe.pop("kwargs", None      )

            if len(mutable_recipe) == 1:
                name, implicit_args = list(mutable_recipe.items())[0]

                if isinstance(implicit_args, dict) and not cls.is_known_recipe(implicit_args):
                    kwargs = implicit_args
                else:
                    args = implicit_args

        if method == "singular":
            return cls._construct_single(recipe, name, args, kwargs)
        else:
            if not isinstance(kwargs, list): kwargs = repeat(kwargs)
            if not isinstance(args  , list):   args = repeat(args)
            return [ cls._construct_single(recipe, name, a, k) for a,k in zip(args, kwargs) ]
Пример #8
0
    def from_prebuilt(name: str) -> 'Environments':
        """Instantiate Environments from a pre-built definition made for diagnostics and comparisons across projects."""

        repo_url = "https://github.com/mrucker/coba_prebuilds/blob/main"
        definition_url = f"{repo_url}/{name}/index.json?raw=True"

        definition_rsp = HttpSource(definition_url).read()

        if definition_rsp.status_code == 404:
            root_dir_text = HttpSource(
                "https://api.github.com/repos/mrucker/coba_prebuilds/contents/"
            ).read().content.decode('utf-8')
            root_dir_json = JsonDecode().filter(root_dir_text)
            known_names = [
                obj['name'] for obj in root_dir_json
                if obj['name'] != "README.md"
            ]
            raise CobaException(
                f"The given prebuilt name, {name}, couldn't be found. Known names are: {known_names}"
            )

        definition_txt = definition_rsp.content.decode('utf-8')
        definition_txt = definition_txt.replace('"./', f'"{repo_url}/{name}/')
        definition_txt = definition_txt.replace('.json"', '.json?raw=True"')

        return Environments.from_file(ListSource([definition_txt]))
Пример #9
0
    def from_file(filename: str) -> 'Result':
        """Create a Result from a transaction file."""

        if not Path(filename).exists(): 
            raise CobaException("We were unable to find the given Result file.")

        return TransactionIO(filename).read()
Пример #10
0
    def filter(
        self, interactions: Iterable[SimulatedInteraction]
    ) -> Iterable[SimulatedInteraction]:

        rng = CobaRandom(self._seed)

        for interaction in interactions:

            if isinstance(interaction, LoggedInteraction):
                raise CobaException(
                    "We do not currently support adding noise to a LoggedInteraction."
                )

            noisy_context = self._noises(interaction.context, rng,
                                         self._context_noise)
            noisy_actions = [
                self._noises(a, rng, self._action_noise)
                for a in interaction.actions
            ]

            noisy_kwargs = {}

            if 'rewards' in interaction.kwargs and self._reward_noise:
                noisy_kwargs['rewards'] = self._noises(
                    interaction.kwargs['rewards'], rng, self._reward_noise)

            yield SimulatedInteraction(noisy_context, noisy_actions,
                                       **noisy_kwargs)
Пример #11
0
        def _construct(item: Any) -> Sequence[Any]:
            result = None

            if isinstance(item, str) and item in variables:
                result = variables[item]

            if isinstance(item, str) and item not in variables:
                result = CobaRegistry.construct(item)

            if isinstance(item, dict):
                result = CobaRegistry.construct(item)

            if isinstance(item, list):
                pieces = list(map(_construct, item))

                if hasattr(pieces[0][0], 'read'):
                    result = [
                        Pipes.join(s, *f) for s in pieces[0]
                        for f in product(*pieces[1:])
                    ]
                else:
                    result = sum(pieces, [])

            if result is None:
                raise CobaException(
                    f"We were unable to construct {item} in the given environment definition file."
                )

            return result if isinstance(
                result, collections.abc.Sequence) else [result]
Пример #12
0
    def test_filter_coba_exception(self):

        decorator = ExceptLog()
        exception = CobaException("Test Exception")

        log = decorator.filter(exception)

        self.assertEqual(log, "Test Exception")
Пример #13
0
    def filter(self,
               interactions: Iterable[Interaction]) -> Iterable[Interaction]:

        iter_interactions = iter(interactions)
        train_interactions = list(islice(iter_interactions, self._using))
        test_interactions = chain.from_iterable(
            [train_interactions, iter_interactions])

        stats: Dict[Hashable, float] = defaultdict(int)
        features: Dict[Hashable, List[Number]] = defaultdict(list)

        for interaction in train_interactions:
            for name, value in self._context_as_name_values(
                    interaction.context):
                if isinstance(value, Number) and not isnan(value):
                    features[name].append(value)

        for feat_name, feat_numeric_values in features.items():

            if self._stat == "mean":
                stats[feat_name] = mean(feat_numeric_values)

            if self._stat == "median":
                stats[feat_name] = median(feat_numeric_values)

            if self._stat == "mode":
                stats[feat_name] = mode(feat_numeric_values)

        for interaction in test_interactions:

            kv_imputed_context = {}

            for name, value in self._context_as_name_values(
                    interaction.context):
                kv_imputed_context[name] = stats[name] if isinstance(
                    value, Number) and isnan(value) else value

            if interaction.context is None:
                final_context = None
            elif isinstance(interaction.context, dict):
                final_context = kv_imputed_context
            elif isinstance(interaction.context, tuple):
                final_context = tuple(kv_imputed_context[k]
                                      for k, _ in self._context_as_name_values(
                                          interaction.context))
            else:
                final_context = kv_imputed_context[1]

            if isinstance(interaction, SimulatedInteraction):
                yield SimulatedInteraction(final_context, interaction.actions,
                                           **interaction.kwargs)
            elif isinstance(interaction, LoggedInteraction):
                yield LoggedInteraction(final_context, interaction.action,
                                        **interaction.kwargs)
            else:  #pragma: no cover
                raise CobaException(
                    "Unknown interactions were given to the Impute filter.")
Пример #14
0
 def __reduce__(self) -> tuple:
     try:
         pickle.dumps(self._args)
     except Exception:
         message = (
             "We were unable to pickle the Noise filter. This is likely due to using lambda functions for noise generation. "
             "To work around this we recommend you first define your lambda functions as a named function and then pass the "
             "named function to Noise.")
         raise CobaException(message)
     else:
         return (Noise, self._args)
Пример #15
0
    def _acquire_write_lock(self, key: _K):

        if self._has_read_lock(key) or self._has_write_lock(key):
            raise CobaException("The concurrent cacher was asked to enter a race condition.")

        self.write_waits += 1
        while not self._acquired_write_lock(key):
            with self._cond:
                self._cond.wait(1)
        self.write_waits -= 1
        self._write_locks[(current_thread().ident,key)] += 1
Пример #16
0
    def join(
        *pipes: Union[Source, Filter,
                      Sink]) -> Union[Source, Filter, Sink, Line]:
        """Join a sequence of pipes into a single pipe.

        Args:
            pipes: a sequence of pipes.

        Returns:
            A single pipe that is a composition of the given pipes. The type of pipe returned
            is determined by the sequence given. A sequence of Filters will return a Filter. A
            sequence that begins with a Source and is followed by Filters will return a Source.
            A sequence that starts with Filters and ends with a Sink will return a Sink. A 
            sequence that begins with a Source and ends with a Sink will return a completed pipe.
        """
        if len(pipes) == 0:
            raise CobaException("No pipes were passed to join.")

        if len(pipes) == 1 and any(
                hasattr(pipes[0], attr)
                for attr in ['read', 'filter', 'write']):
            return pipes[0]

        first = pipes[0] if not isinstance(pipes[0],
                                           Foreach) else pipes[0]._pipe
        last = pipes[-1] if not isinstance(pipes[-1],
                                           Foreach) else pipes[-1]._pipe

        if hasattr(first, 'read') and hasattr(last, 'write'):
            return Pipes.Line(*pipes)

        if hasattr(first, 'read') and hasattr(last, 'filter'):
            return SourceFilters(*pipes)

        if hasattr(first, 'filter') and hasattr(last, 'filter'):
            return FiltersFilter(*pipes)

        if hasattr(first, 'filter') and hasattr(last, 'write'):
            return FiltersSink(*pipes)

        raise CobaException("An unknown pipe was passed to join.")
Пример #17
0
    def read(self) -> Iterable[Tuple[Any, Any]]:
        """Read and parse the openml source."""
        try:
            dataset_description = self._get_dataset_description(self._data_id)

            if dataset_description['status'] == 'deactivated':
                raise CobaException(
                    f"Openml {self._data_id} has been deactivated. This is often due to flags on the data."
                )

            feature_descriptions = self._get_feature_descriptions(
                self._data_id)
            task_descriptions = self._get_task_descriptions(self._data_id)

            is_ignore = lambda r: (r['is_ignore'] == 'true' or r[
                'is_row_identifier'] == 'true' or r['data_type'] not in
                                   ['numeric', 'nominal'])

            ignore = [
                self._name_cleaning(f['name']) for f in feature_descriptions
                if is_ignore(f)
            ]
            target = self._name_cleaning(
                self._get_target_for_problem_type(task_descriptions))

            if target in ignore: ignore.pop(ignore.index(target))

            def row_has_missing_values(row):
                row_values = row._values.values() if isinstance(
                    row, SparseWithMeta) else row._values
                return "?" in row_values or "" in row_values

            source = ListSource(
                self._get_dataset_lines(dataset_description["file_id"], None))
            reader = ArffReader(cat_as_str=self._cat_as_str)
            drop = Drop(drop_cols=ignore, drop_row=row_has_missing_values)
            structure = Structure([None, target])

            return Pipes.join(source, reader, drop, structure).read()

        except KeyboardInterrupt:
            #we don't want to clear the cache in the case of a KeyboardInterrupt
            raise

        except CobaException:
            #we don't want to clear the cache if it is an error we know about (the original raise should clear if needed)
            raise

        except Exception:
            #if something unexpected went wrong clear the cache just in case it was corrupted somehow
            self._clear_cache()
            raise
Пример #18
0
    def _get_target_for_problem_type(self, tasks: Sequence[Dict[str, Any]]):

        task_type_id = 1 if self._problem_type == "C" else 2

        for task in tasks:
            if task["task_type_id"] == task_type_id:
                for input in task['input']:
                    if input['name'] == 'target_feature':
                        return input['value']  # just take the first one

        raise CobaException(
            f"Openml {self._data_id} does not appear to be a {self._problem_type} dataset"
        )
Пример #19
0
    def _encoders(self, encodings: Sequence[str], is_dense: bool) -> Encoder:
        numeric_types = ('numeric', 'integer', 'real')
        string_types = ("string", "date", "relational")
        r_comma = None
        identity = lambda x: None if x == "?" else x.strip()

        for encoding in encodings:

            if self._skip_encoding:
                yield identity
            elif encoding in numeric_types:
                yield lambda x: None if x == "?" else float(x)
            elif encoding.startswith(string_types):
                yield identity
            elif encoding.startswith('{'):
                r_comma = r_comma or re.compile("(,)")
                categories = list(self._pattern_split(encoding[1:-1], r_comma))

                if not is_dense:
                    #there is a bug in ARFF where the first class value in an ARFF class can will dropped from the
                    #actual data because it is encoded as 0. Therefore, our ARFF reader automatically adds a 0 value
                    #to all sparse categorical one-hot encoders to protect against this.
                    categories = ["0"] + categories

                def encoder(
                        x: str,
                        cats=categories,
                        get=OneHotEncoder(categories)._onehots.__getitem__):

                    x = x.strip()

                    if x == "?":
                        return None

                    if x not in cats and x[0] in self._quotes and x[0] == x[
                            -1] and len(x) > 1:
                        x = x[1:-1]

                    if x not in cats:
                        raise CobaException(
                            "We were unable to find one of the categorical values in the arff data."
                        )

                    return x if self._cat_as_str else get(x)

                yield encoder
            else:
                raise CobaException(
                    f"An unrecognized encoding was found in the arff attributes: {encoding}."
                )
Пример #20
0
    def __init__(self, url: str) -> None:
        """Instantiate a UrlSource.
        
        Args:
            url: The url to a resource. Can be either a web request or a local path.
        """
        self._url = url

        if url.startswith("http://") or url.startswith("https://"):
            self._source = HttpSource(url, mode='lines')
        elif url.startswith("file://"):
            self._source = DiskSource(url[7:])
        elif "://" not in url:
            self._source = DiskSource(url)
        else:
            raise CobaException(
                "Unrecognized scheme, supported schemes are: http, https or file."
            )
Пример #21
0
 def __reduce__(self) -> Tuple[object, ...]:
     if self._n_interactions is not None and self._n_interactions < 5000:
         #This is an interesting idea but maybe too wink-wink nudge-nudge in practice. It causes a weird flow
         #in the logs that look like bugs. It also causes unexpected lags because IO is happening at strange
         #places and in a manner that can cause thread locks.
         return (LambdaSimulation.Spoof, (list(self.read()), self.params,
                                          str(self), type(self).__name__))
     else:
         message = (
             "It is not possible to pickle a LambdaSimulation due to its use of lambda methods in the constructor. "
             "This error occured because an experiment containing a LambdaSimulation tried to execute on multiple processes. "
             "If this is neccesary there are three options to get around this limitation: (1) run your experiment "
             "on a single process rather than multiple, (2) re-design your LambdaSimulation as a class that inherits "
             "from LambdaSimulation and implements __reduce__ (see coba.environments.simulations.LinearSyntheticSimulation "
             "for an example), or (3) specify a finite number for n_interactions in the LambdaSimulation constructor (this "
             "allows us to create the interactions in memory ahead of time and convert to a MemorySimulation when pickling)."
         )
         raise CobaException(message)
Пример #22
0
    def __init__(self, transaction_log: Optional[str] = None) -> None:

        if not transaction_log or not Path(transaction_log).exists():
            version = None
        else:
            version = JsonDecode().filter(next(DiskSource(transaction_log).read()))[1]

        if version == 3:
            self._transactionIO = TransactionIO_V3(transaction_log)

        elif version == 4:
            self._transactionIO = TransactionIO_V4(transaction_log)

        elif version is None:
            self._transactionIO = TransactionIO_V4(transaction_log)

        else:
            raise CobaException("We were unable to determine the appropriate Transaction reader for the file.")
Пример #23
0
                def encoder(
                        x: str,
                        cats=categories,
                        get=OneHotEncoder(categories)._onehots.__getitem__):

                    x = x.strip()

                    if x == "?":
                        return None

                    if x not in cats and x[0] in self._quotes and x[0] == x[
                            -1] and len(x) > 1:
                        x = x[1:-1]

                    if x not in cats:
                        raise CobaException(
                            "We were unable to find one of the categorical values in the arff data."
                        )

                    return x if self._cat_as_str else get(x)
Пример #24
0
    def learn(self, context: Context, action: Action, reward: float,
              probability: float, info: Info) -> None:

        if not self._vw.is_initialized:
            raise CobaException(
                "When using `cb  without `adf` predict must be called before learn to initialize the vw learner"
            )

        actions = info
        labels = self._labels(actions, action, reward, probability)
        label = labels[actions.index(action)]

        context = {'x': self._flat(context)}
        adfs = None if not self._adf else [{
            'a': self._flat(action)
        } for action in actions]

        if self._adf:
            self._vw.learn(self._vw.make_examples(context, adfs, labels))
        else:
            self._vw.learn(self._vw.make_example(context, label))
Пример #25
0
    def __init__(self, 
        learners: Sequence[Learner], 
        eta     : float = 0.075,
        T       : float = math.inf, 
        mode    : Literal["importance","rejection","off-policy"] ="importance", 
        seed    : int = 1) -> None:
        """Instantiate a CorralLearner.

        Args:
            learners: The collection of base learners.
            eta: The learning rate. This controls how quickly Corral picks a best base_learner. 
            T: The number of interactions expected during the learning process. A small T will cause
                the learning rate to shrink towards 0 quickly while a large value for T will cause the
                learning rate to shrink towards 0 slowly. A value of inf means that the learning rate
                will remain constant.
            mode: Determines the method with which feedback is provided to the base learners. The 
                original paper used importance sampling. We also support `off-policy` and `rejection`.
            seed: A seed for a random number generation in ordre to get repeatable results.
        """
        if mode not in ["importance", "off-policy", "rejection"]:
            raise CobaException("The provided `mode` for CorralLearner was unrecognized.")

        self._base_learners = [ SafeLearner(learner) for learner in learners]

        M = len(self._base_learners)

        self._T     = T
        self._gamma = 1/T
        self._beta  = 1/math.exp(1/math.log(T))

        self._eta_init = eta
        self._etas     = [ eta ] * M
        self._rhos     = [ float(2*M) ] * M
        self._ps       = [ 1/M ] * M
        self._p_bars   = [ 1/M ] * M

        self._mode = mode

        self._random_pick   = CobaRandom(seed)
        self._random_reject = CobaRandom(CobaRandom(seed).randint(0,10000))
Пример #26
0
    def init_learner(self, args: str, label_type: int) -> 'VowpalMediator':
        """Create a VW learner from a command line arg string.
        
        Args:
            args: The command line arg string to use for VW learner creation.
            label_type: The label type this VW learner will take.
                - 1 : `simple`__
                - 2 : `multiclass`__
                - 3 : `cost sensitive`__
                - 4 : `contextual bandit`__
                - 5 : max (deprecated)
                - 6 : `conditional contextual bandit`__
                - 7 : `slates`__
                - 8 : `continuous actions`__

        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Input-format#simple
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Input-format#multiclass
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Input-format#cost-sensitive
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Input-format#contextual-bandit
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Conditional-Contextual-Bandit#vw-text-format
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Slates#text-format
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/CATS,-CATS-pdf-for-Continuous-Actions#vw-text-format
        """
        from vowpalwabbit import pyvw, __version__

        if self._vw is not None:
            raise CobaException(
                "We cannot initilaize a VW learner twice in a single mediator."
            )

        self._version = __version__
        self._vw = pyvw.Workspace(args) if __version__[0] == '9' else pyvw.vw(
            args)
        self._label_type = pyvw.LabelType(
            label_type) if __version__[0] == '9' else label_type
        self._example_type = pyvw.Example if __version__[
            0] == '9' else pyvw.example

        return self
Пример #27
0
    def __init__(
            self,
            args:
        str = "--cb_explore_adf --epsilon 0.05 --interactions ax --interactions axx --ignore_linear x --random_seed 1 --quiet",
            vw: VowpalMediator = None) -> None:
        """Instantiate a VowpalArgsLearner.

        Args:
            args: Command line arguments to instantiate a Vowpal Wabbit contextual bandit learner. For 
                examples and documentation on how to instantiate VW learners from command line arguments 
                see `here`__. We require that either cb, cb_adf, cb_explore, or cb_explore_adf is used. 
                When we format examples for VW context features are placed in the 'x' namespace and action 
                features, when relevant, are placed in the 'a' namespace.
            vw: A mediator able to communicate with VW. This should not need to ever be changed. 
        __ https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Contextual-Bandit-algorithms
        """

        if "--cb" not in args:
            raise CobaException(
                "VowpalArgsLearner was instantiated without a cb flag. One of the cb flags must be defined."
            )

        self._args = args
        self._explore = "--cb_explore" in args
        self._adf = "--cb_adf" in args or "--cb_explore_adf" in args

        self._n_actions = None
        self._actions = None

        try:
            self._n_actions = int(
                re.match("--cb.*?\s+(\d*)\s*-?.*$", args).group(1))
        except:
            pass

        self._vw = vw or VowpalMediator()

        if self._adf or self._n_actions is not None:
            self._vw.init_learner(args, 4)
Пример #28
0
    def _parse_sparse_data(
        self, lines: Iterable[str], headers: Sequence[str],
        encoders: Sequence[Encoder]
    ) -> Iterable[Union[MutableSequence, MutableMapping]]:

        headers_dict = dict(zip(headers, count()))
        defaults_dict = {
            k: "0"
            for k in range(len(encoders)) if encoders[k]("0") != 0
        }
        encoders_dict = dict(zip(count(), encoders))

        for i, line in enumerate(lines):

            keys_and_vals = re.split('\s*,\s*|\s+', line.strip("} {"))

            keys = list(map(int, keys_and_vals[0::2]))
            vals = keys_and_vals[1::2]

            if max(keys) >= len(headers) or min(keys) < 0:
                raise CobaException(
                    f"We were unable to parse line {i} in a way that matched the expected attributes."
                )

            final = {**defaults_dict, **dict(zip(keys, vals))}

            final_headers = headers_dict if self._header_indexing else {}
            final_encoders = encoders_dict if self._lazy_encoding else {}
            final_items = final if self._lazy_encoding else {
                k: encoders[k](v)
                for k, v in final.items()
            }

            if not self._lazy_encoding and not self._header_indexing:
                yield final_items
            else:
                yield SparseWithMeta(final_items, final_headers,
                                     final_encoders)
Пример #29
0
    def learn(self, context: Context, action: Action, reward: float, probability: float, info: Info) -> None:

        import numpy as np

        if isinstance(action, dict) or isinstance(context, dict):
            raise CobaException("Sparse data cannot be handled by this algorithm.")

        if not context:
            self._X_encoder = InteractionsEncoder(list(set(filter(None,[ f.replace('x','') for f in self._X]))))

        context = list(Flatten().filter([list(context)]))[0] if context else []
        features: np.ndarray = np.array([1]+self._X_encoder.encode(x=context,a=action)).T

        if(self._A_inv is None):
            self._theta = np.zeros((features.shape[0]))
            self._A_inv = np.identity(features.shape[0])

        r = self._theta @ features
        w = self._A_inv @ features
        v = features    @ w

        self._A_inv = self._A_inv - np.outer(w,w)/(1+v)
        self._theta = self._theta + (reward-r)/(1+v) * w
Пример #30
0
    def predict(self, context: Context, actions: Sequence[Action]) -> Probs:

        import numpy as np #type: ignore

        if isinstance(actions[0], dict) or isinstance(context, dict):
            raise CobaException("Sparse data cannot be handled by this algorithm.")

        if not context:
            self._X_encoder = InteractionsEncoder(list(set(filter(None,[ f.replace('x','') for f in self._X]))))
 
        context = list(Flatten().filter([list(context)]))[0] if context else []
        features: np.ndarray = np.array([[1]+self._X_encoder.encode(x=context,a=action) for action in actions]).T

        if(self._A_inv is None):
            self._theta = np.zeros(features.shape[0])
            self._A_inv = np.identity(features.shape[0])

        point_estimate = self._theta @ features
        point_bounds   = np.diagonal(features.T @ self._A_inv @ features)

        action_values = point_estimate + self._alpha*np.sqrt(point_bounds)
        max_indexes   = np.where(action_values == np.amax(action_values))[0]

        return [ int(ind in max_indexes)/len(max_indexes) for ind in range(len(actions))]