def __init__(
        self,
        debug=False,
        n_inputs=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs=self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(n_cables=self.n_inputs, debug=self.debug)

        # n_features: int
        #     The total number of features that have been colllected so far.
        #     This includes the cable candidate pools from each ziptie.
        self.n_features_by_level = [0, 0]

        # mapping: 2D array of ints
        #     The transformation from candidates (List or arrays of values)
        #     to the feature pool.
        #     If there is a one at [row_i, col_j] then
        #     candidate row_i maps to feature index col_j .
        #         feature_pool = np.matmul(candidates, self.mapping)
        self.mapping = np.zeros((0, 0), dtype=np.int)
class Featurizer(object):
    """
    Convert inputs to bundles and learn new bundles.
    Inputs are transformed into bundles, sets of inputs
    that tend to co-occur.
    """
    def __init__(
        self,
        debug=False,
        n_inputs=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs=self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(n_cables=self.n_inputs, debug=self.debug)

        # n_features: int
        #     The total number of features that have been colllected so far.
        #     This includes the cable candidate pools from each ziptie.
        self.n_features_by_level = [0, 0]

        # mapping: 2D array of ints
        #     The transformation from candidates (List or arrays of values)
        #     to the feature pool.
        #     If there is a one at [row_i, col_j] then
        #     candidate row_i maps to feature index col_j .
        #         feature_pool = np.matmul(candidates, self.mapping)
        self.mapping = np.zeros((0, 0), dtype=np.int)

    def featurize(self, new_candidates):
        """
        Learn bundles and calculate bundle activities.

        Parameters
        ----------
        new_candidates : array of floats
            The candidates collected by the brain for the current time step.

        Returns
        -------
        feature_pool: array of floats
        """
        self.ziptie_0_cable_pool = new_candidates
        cable_activities = self.filter.update_activities(
            candidate_activities=self.ziptie_0_cable_pool)

        # Incrementally update the bundles in the ziptie.
        self.ziptie.create_new_bundles()
        self.ziptie.grow_bundles()

        # Run the inputs through the ziptie to find bundle activities
        # and to learn how to bundle them.
        ziptie_1_cable_pool = self.ziptie.update_bundles(cable_activities)

        self.activities = [
            self.ziptie_0_cable_pool,
            ziptie_1_cable_pool,
        ]

        self.feature_pool = self.map_to_feature_pool(self.activities)

        return self.feature_pool

    def defeaturize(self, feature_pool):
        """
        Take a set of feature activities and represent them in candidates.
        """
        ziptie_0_cable_pool, ziptie_1_cable_pool = (
            self.map_from_feature_pool(feature_pool))
        # TODO: iterate over multiple zipties
        ziptie_0_cables = self.ziptie.project_bundle_activities(
            ziptie_1_cable_pool)
        ziptie_0_cable_pool_upstream = self.filter.project_activities(
            ziptie_0_cables)
        n_candidates_0 = ziptie_0_cable_pool_upstream.size
        ziptie_0_cable_pool = np.maximum(ziptie_0_cable_pool[:n_candidates_0],
                                         ziptie_0_cable_pool_upstream)
        return ziptie_0_cable_pool

    def calculate_fitness(self, feature_fitness):
        """
        Find the predictive fitness of each of cables in each ziptie.

        Parameters
        ----------
        candidate_fitness: array of floats
        """
        # TODO: Handle a hierarchy of zipties and input filters
        # Include upstream fitness
        all_input_fitness = self.map_from_feature_pool(feature_fitness)

        cable_fitness = self.ziptie.project_bundle_activities(
            all_input_fitness[1])
        pool_fitness = self.filter.update_fitness(cable_fitness)

        return pool_fitness

    def update_inputs(self):
        """
        Give each input filter a chance to update their inputs and propagate
        any resets that result up through the zipties and to the model.

        Returns
        -------
        resets: array of ints
            The feature candidate indices that are being reset.
        """
        filter_resets = self.filter.update_inputs()
        bundle_resets = self.ziptie.update_inputs(filter_resets)
        # Leave an empty list of resets for lowest level input.
        # They are always all passed in as feature candidates.
        # They never get reset or swapped out. The model's input filter
        # deals with them.
        # As other levels are created, append their bundle resets as well.
        all_resets = [[], bundle_resets]

        resets = []
        for i_level, level_resets in enumerate(all_resets):
            i_start = np.sum(np.array(self.n_features_by_level[:i_level]))
            for i_reset in level_resets:
                resets.append(np.where(self.mapping[i_start + i_reset, :])[0])

        return resets

    def map_from_feature_pool(self, feature_values):
        """
        For an array corresponding to feature candidates from the model,
        generate and order those values corresponding to ziptie candidates.

        Parameters
        ----------
        feature_values: array of floats

        Returns
        -------
        candidate_values: list of array of floats
        """
        candidate_values = []
        i_last = 0
        for n_feat in self.n_features_by_level:
            candidate_values.append(feature_values[i_last:i_last + n_feat])
            i_last += n_feat
        return candidate_values

    def map_to_feature_pool(self, candidate_values):
        """
        Map the candidates over all levels to the appropriate
        feature candidates.

        Parameters
        ----------
        candidate_values: list of arrays of floats

        Returns
        -------
        feature_values: array of floats
        """
        self.grow_map(candidate_values)
        all_candidate_values = []
        for level_candidate_values in candidate_values:
            all_candidate_values += list(level_candidate_values)
        feature_values = np.matmul(np.array(all_candidate_values),
                                   self.mapping)
        return feature_values

    def grow_map(self, candidate_values):
        """
        Check whether we need to add more candidates to the feature pool.

        New candidates will come in appended to the end of their
        respective input pools. However, feature indices need to
        stay consistent throughout the life of each feature.
        these new candidates need to be given indices at the
        end of the currently used set of feature pool indices.

        Parameters
        ----------
        candidate_values: list of arrays of floats
        """
        # Check whether the number of candidates has expanded
        # at any level and adapt.
        n_candidates_by_level = [values.size for values in candidate_values]
        total_n_candidates = np.sum(np.array(n_candidates_by_level))
        total_n_features = np.sum(np.array(self.n_features_by_level))

        if (total_n_features < total_n_candidates):
            # Create a larger map
            new_mapping = []
            i_last_old = 0  # Track last candidate handled.
            j_last_new = total_n_features  # Track last feature assigned.

            for i_level in range(len(self.n_features_by_level)):
                n_cand = n_candidates_by_level[i_level]
                n_feat = self.n_features_by_level[i_level]
                delta = n_cand - n_feat

                level_old_map = np.zeros((n_feat, total_n_candidates))
                level_old_map[:, :total_n_features] = self.mapping[
                    i_last_old:i_last_old + n_feat, :]
                new_mapping.append(level_old_map)

                if delta > 0:
                    level_new_map = np.zeros((delta, total_n_candidates))
                    level_new_map[:, j_last_new:j_last_new +
                                  delta] = np.eye(delta)
                    new_mapping.append(level_new_map)

                    j_last_new += delta
                    self.n_features_by_level[i_level] += delta

                i_last_old += n_feat
            self.mapping = np.concatenate(new_mapping)
        return
Example #3
0
class Featurizer(object):
    """
    Convert inputs to bundles and learn new bundles.

    Inputs are transformed into bundles, sets of inputs that tend to co-occur.
    """
    def __init__(self, brain, max_num_inputs, max_num_features=None):
        """
        Configure the featurizer.

        Parameters
        ---------
        max_num_inputs : int
            See Featurizer.max_num_inputs.
        max_num_features : int
            See Featurizer.max_num_features.
        """
        # debug : boolean
        #     Print out extra information about the featurizer's operation.
        self.debug = False

        # name : string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon : float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # max_num_inputs : int
        # max_num_bundles : int
        # max_num_features : int
        #     The maximum numbers of inputs, bundles and features
        #     that this level can accept.
        #     max_num_features = max_num_inputs + max_num_bundles
        self.max_num_inputs = max_num_inputs
        if max_num_features is None:
            # Choose the total number of bundles (created features) allowed,
            # in terms of the number of inputs allowed.
            self.max_num_bundles = 3 * self.max_num_inputs
            self.max_num_features = self.max_num_inputs + self.max_num_bundles
        else:
            self.max_num_features = max_num_features
            self.max_num_bundles = self.max_num_features - self.max_num_inputs

        # Normalization constants.
        # input_max : array of floats
        #     The maximum of each input's activity.
        #     Start with the assumption that each input has a
        #     maximum value of zero. Then adapt up from there
        #     based on the incoming observations.
        # input_max_decay_time, input_max_grow_time : float
        #     The time constant over which maximum estimates are
        #     decreased/increased. Growing time being lower allows the
        #     estimate to be weighted toward the maximum value.
        self.input_max = np.zeros(self.max_num_inputs)
        self.input_max_grow_time = 1e2
        self.input_max_decay_time = self.input_max_grow_time * 1e2

        # input_activities,
        # bundle_activities,
        # feature_activities : array of floats
        #     inputs, bundles and features are characterized by their
        #     activity--their level of activation at each time step.
        #     Activity for each input or bundle or feature
        #     can vary between zero and one.
        self.input_activities = np.zeros(self.max_num_inputs)
        self.bundle_activities = np.zeros(self.max_num_bundles)
        self.feature_activities = np.zeros(self.max_num_features)

        # live_features : array of floats
        #     A binary array tracking which of the features have ever
        #     been active.
        self.live_features = np.zeros(self.max_num_features)

        # TODO:
        #     Move ziptie creation into step, along with clustering.

        # ziptie : Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(self.max_num_inputs,
                             num_bundles=self.max_num_bundles,
                             debug=self.debug)


    def featurize(self, new_inputs):
        """
        Learn bundles and calculate bundle activities.

        Parameters
        ----------
        new_inputs : array of floats
            The inputs collected by the brain for the current time step.
        """
        # Start by normalizing all the inputs.
        self.input_activities = self.update_inputs(new_inputs)

        # Run the inputs through the ziptie to find bundle activities
        # and to learn how to bundle them.
        bundle_activities = self.ziptie.featurize(self.input_activities)[1]
        # The element activities are the combination of the residual
        # input activities and the bundle activities.
        self.feature_activities = np.concatenate((self.input_activities,
                                                  bundle_activities))

        # Track features that are active.
        self.live_features[np.where(
            self.feature_activities > self.epsilon)] = 1.

        # Incrementally update the bundles in the ziptie.
        self.ziptie.learn(self.input_activities)

        return self.feature_activities, np.where(self.live_features > 0.)[0]


    def defeaturize(self, feature_activities):
        """
        Take a set of feature activities and represent them in inputs.
        """
        input_activities = feature_activities[:self.max_num_inputs]
        # Project each ziptie down to inputs.
        bundle_activities = feature_activities[self.max_num_inputs:]
        # TODO: iterate over multiple zipties
        ziptie_input_activities = self.ziptie.project_bundle_activities(
            bundle_activities)
        input_activities = np.maximum(
            input_activities, ziptie_input_activities)
        return input_activities


    def update_inputs(self, inputs):
        """
        Normalize and update inputs.

        Normalize activities so that they are predictably distrbuted.
        Use a running estimate of the maximum of each cable activity.
        Scale it so that the max would fall at 1.

        Normalization has several benefits.
        1. It makes for fewer constraints on worlds and sensors.
           It allows any sensor can return any range of values.
        2. Gradual changes in sensors and the world can be adapted to.
        3. It makes the bundle creation heuristic more robust and
           predictable. The approximate distribution of cable
           activities is known and can be designed for.

        After normalization, update each input activity with either
        a) the normalized value of its corresponding input or
        b) the decayed value, carried over from the previous time step,
        whichever is greater.

        Parameters
        ----------
        inputs : array of floats
            The current and previous activity of the inputs.

        Returns
        -------
        None
            self.input_activities is modified to include
            the normalized values of each of the inputs.
        """
        # TODO: numpy-ify this

        if inputs.size > self.max_num_inputs:
            print("Featurizer.update_inputs:")
            print("    Attempting to update out of range input activities.")

        # This is written to be easily compilable by numba, however,
        # it hasn't proven to be at all significant in profiling, so it
        # has stayed in slow-loop python for now.
        stop_index = min(inputs.size, self.max_num_inputs)
        # Input index
        j = 0
        for i in range(stop_index):
            val = inputs[j]

            # Decay the maximum value.
            self.input_max[i] += ((val - self.input_max[i]) /
                                  self.input_max_decay_time)

            # Eventually move this pre-processing to brain.py?
            # Grow the maximum value, when appropriate.
            if val > self.input_max[i]:
                self.input_max[i] += ((val - self.input_max[i]) /
                                      self.input_max_grow_time)

            # Scale the input by the maximum.
            val = val / (self.input_max[i] + self.epsilon)
            # Ensure that 0 <= val <= 1.
            val = max(0., val)
            val = min(1., val)

            self.input_activities[i] = val
            j += 1
        return self.input_activities


    def visualize(self, brain, world=None):
        """
        Show the current state of the featurizer.
        """
        viz.visualize(self, brain, world)
Example #4
0
    def __init__(self, brain, max_num_inputs, max_num_features=None):
        """
        Configure the featurizer.

        Parameters
        ---------
        max_num_inputs : int
            See Featurizer.max_num_inputs.
        max_num_features : int
            See Featurizer.max_num_features.
        """
        # debug : boolean
        #     Print out extra information about the featurizer's operation.
        self.debug = False

        # name : string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon : float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # max_num_inputs : int
        # max_num_bundles : int
        # max_num_features : int
        #     The maximum numbers of inputs, bundles and features
        #     that this level can accept.
        #     max_num_features = max_num_inputs + max_num_bundles
        self.max_num_inputs = max_num_inputs
        if max_num_features is None:
            # Choose the total number of bundles (created features) allowed,
            # in terms of the number of inputs allowed.
            self.max_num_bundles = 3 * self.max_num_inputs
            self.max_num_features = self.max_num_inputs + self.max_num_bundles
        else:
            self.max_num_features = max_num_features
            self.max_num_bundles = self.max_num_features - self.max_num_inputs

        # Normalization constants.
        # input_max : array of floats
        #     The maximum of each input's activity.
        #     Start with the assumption that each input has a
        #     maximum value of zero. Then adapt up from there
        #     based on the incoming observations.
        # input_max_decay_time, input_max_grow_time : float
        #     The time constant over which maximum estimates are
        #     decreased/increased. Growing time being lower allows the
        #     estimate to be weighted toward the maximum value.
        self.input_max = np.zeros(self.max_num_inputs)
        self.input_max_grow_time = 1e2
        self.input_max_decay_time = self.input_max_grow_time * 1e2

        # input_activities,
        # bundle_activities,
        # feature_activities : array of floats
        #     inputs, bundles and features are characterized by their
        #     activity--their level of activation at each time step.
        #     Activity for each input or bundle or feature
        #     can vary between zero and one.
        self.input_activities = np.zeros(self.max_num_inputs)
        self.bundle_activities = np.zeros(self.max_num_bundles)
        self.feature_activities = np.zeros(self.max_num_features)

        # live_features : array of floats
        #     A binary array tracking which of the features have ever
        #     been active.
        self.live_features = np.zeros(self.max_num_features)

        # TODO:
        #     Move ziptie creation into step, along with clustering.

        # ziptie : Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(self.max_num_inputs,
                             num_bundles=self.max_num_bundles,
                             debug=self.debug)
Example #5
0
class Featurizer(object):
    """
    Convert inputs to bundles and learn new bundles.
    Inputs are transformed into bundles, sets of inputs
    that tend to co-occur.
    """
    def __init__(
        self,
        debug=False,
        n_inputs=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs=self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(
            n_cables=self.n_inputs,
            debug=self.debug)

        # n_features: int
        #     The total number of features that have been colllected so far.
        #     This includes the cable candidate pools from each ziptie.
        self.n_features_by_level = [0, 0]

        # mapping: 2D array of ints
        #     The transformation from candidates (List or arrays of values)
        #     to the feature pool.
        #     If there is a one at [row_i, col_j] then
        #     candidate row_i maps to feature index col_j .
        #         feature_pool = np.matmul(candidates, self.mapping)
        self.mapping = np.zeros((0, 0), dtype=np.int)

    def featurize(self, new_candidates):
        """
        Learn bundles and calculate bundle activities.

        Parameters
        ----------
        new_candidates : array of floats
            The candidates collected by the brain for the current time step.

        Returns
        -------
        feature_pool: array of floats
        """
        self.ziptie_0_cable_pool = new_candidates
        cable_activities = self.filter.update_activities(
            candidate_activities=self.ziptie_0_cable_pool)

        # Incrementally update the bundles in the ziptie.
        self.ziptie.create_new_bundles()
        self.ziptie.grow_bundles()

        # Run the inputs through the ziptie to find bundle activities
        # and to learn how to bundle them.
        ziptie_1_cable_pool = self.ziptie.update_bundles(cable_activities)

        self.activities = [
            self.ziptie_0_cable_pool,
            ziptie_1_cable_pool,
        ]

        self.feature_pool = self.map_to_feature_pool(self.activities)

        return self.feature_pool

    def defeaturize(self, feature_pool):
        """
        Take a set of feature activities and represent them in candidates.
        """
        ziptie_0_cable_pool, ziptie_1_cable_pool = (
            self.map_from_feature_pool(feature_pool))
        # TODO: iterate over multiple zipties
        ziptie_0_cables = self.ziptie.project_bundle_activities(
            ziptie_1_cable_pool)
        ziptie_0_cable_pool_upstream = self.filter.project_activities(
            ziptie_0_cables)
        n_candidates_0 = ziptie_0_cable_pool_upstream.size
        ziptie_0_cable_pool = np.maximum(
            ziptie_0_cable_pool[:n_candidates_0],
            ziptie_0_cable_pool_upstream)
        return ziptie_0_cable_pool

    def calculate_fitness(self, feature_fitness):
        """
        Find the predictive fitness of each of cables in each ziptie.

        Parameters
        ----------
        candidate_fitness: array of floats
        """
        # TODO: Handle a hierarchy of zipties and input filters
        # Include upstream fitness
        all_input_fitness = self.map_from_feature_pool(feature_fitness)

        cable_fitness = self.ziptie.project_bundle_activities(
            all_input_fitness[1])
        pool_fitness = self.filter.update_fitness(cable_fitness)

        return pool_fitness

    def update_inputs(self):
        """
        Give each input filter a chance to update their inputs and propagate
        any resets that result up through the zipties and to the model.

        Returns
        -------
        resets: array of ints
            The feature candidate indices that are being reset.
        """
        filter_resets = self.filter.update_inputs()
        bundle_resets = self.ziptie.update_inputs(filter_resets)
        # Leave an empty list of resets for lowest level input.
        # They are always all passed in as feature candidates.
        # They never get reset or swapped out. The model's input filter
        # deals with them.
        # As other levels are created, append their bundle resets as well.
        all_resets = [[], bundle_resets]

        resets = []
        for i_level, level_resets in enumerate(all_resets):
            i_start = np.sum(np.array(self.n_features_by_level[:i_level]))
            for i_reset in level_resets:
                resets.append(np.where(
                    self.mapping[i_start + i_reset, :])[0])

        return resets

    def map_from_feature_pool(self, feature_values):
        """
        For an array corresponding to feature candidates from the model,
        generate and order those values corresponding to ziptie candidates.

        Parameters
        ----------
        feature_values: array of floats

        Returns
        -------
        candidate_values: list of array of floats
        """
        candidate_values = []
        i_last = 0
        for n_feat in self.n_features_by_level:
            candidate_values.append(
                feature_values[i_last: i_last + n_feat])
            i_last += n_feat
        return candidate_values

    def map_to_feature_pool(self, candidate_values):
        """
        Map the candidates over all levels to the appropriate
        feature candidates.

        Parameters
        ----------
        candidate_values: list of arrays of floats

        Returns
        -------
        feature_values: array of floats
        """
        self.grow_map(candidate_values)
        all_candidate_values = []
        for level_candidate_values in candidate_values:
            all_candidate_values += list(level_candidate_values)
        feature_values = np.matmul(
            np.array(all_candidate_values), self.mapping)
        return feature_values

    def grow_map(self, candidate_values):
        """
        Check whether we need to add more candidates to the feature pool.

        New candidates will come in appended to the end of their
        respective input pools. However, feature indices need to
        stay consistent throughout the life of each feature.
        these new candidates need to be given indices at the
        end of the currently used set of feature pool indices.

        Parameters
        ----------
        candidate_values: list of arrays of floats
        """
        # Check whether the number of candidates has expanded
        # at any level and adapt.
        n_candidates_by_level = [
            values.size for values in candidate_values]
        total_n_candidates = np.sum(np.array(n_candidates_by_level))
        total_n_features = np.sum(np.array(self.n_features_by_level))

        if (total_n_features < total_n_candidates):
            # Create a larger map
            new_mapping = []
            i_last_old = 0  # Track last candidate handled.
            j_last_new = total_n_features  # Track last feature assigned.

            for i_level in range(len(self.n_features_by_level)):
                n_cand = n_candidates_by_level[i_level]
                n_feat = self.n_features_by_level[i_level]
                delta = n_cand - n_feat

                level_old_map = np.zeros((n_feat, total_n_candidates))
                level_old_map[:, :total_n_features] = self.mapping[
                    i_last_old:i_last_old + n_feat, :]
                new_mapping.append(level_old_map)

                if delta > 0:
                    level_new_map = np.zeros((delta, total_n_candidates))
                    level_new_map[
                        :,
                        j_last_new: j_last_new + delta
                    ] = np.eye(delta)
                    new_mapping.append(level_new_map)

                    j_last_new += delta
                    self.n_features_by_level[i_level] += delta

                i_last_old += n_feat
            self.mapping = np.concatenate(new_mapping)
        return
Example #6
0
    def __init__(
        self,
        debug=False,
        n_inputs=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs=self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(
            n_cables=self.n_inputs,
            debug=self.debug)

        # n_features: int
        #     The total number of features that have been colllected so far.
        #     This includes the cable candidate pools from each ziptie.
        self.n_features_by_level = [0, 0]

        # mapping: 2D array of ints
        #     The transformation from candidates (List or arrays of values)
        #     to the feature pool.
        #     If there is a one at [row_i, col_j] then
        #     candidate row_i maps to feature index col_j .
        #         feature_pool = np.matmul(candidates, self.mapping)
        self.mapping = np.zeros((0, 0), dtype=np.int)
Example #7
0
    def __init__(
            self,
            n_inputs,
            # max_n_features=None,
            threshold=None,
            verbose=False,
            ):
        """
        Configure the featurizer.

        Parameters
        ---------
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        threshold : float
            See Ziptie.nucleation_threshold
        """
        # verbose : boolean
        #     Print out extra information about the featurizer's operation.
        self.verbose = verbose

        # name : string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon : float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs : int
        # n_bundles : int
        # max_n_features : int
        #     The maximum numbers of inputs, bundles and features
        #     that this level can accept.
        #     max_n_features = n_inputs + n_bundles
        self.n_inputs = n_inputs
        # self.max_n_features = self.n_inputs
        self.n_bundles = 4 * self.n_inputs
        # if max_n_features is None:
        #     # Choose the total number of bundles (created features) allowed,
        #     # in terms of the number of inputs allowed.
        #     self.n_bundles = 3 * self.n_inputs
        #     self.max_n_features = self.n_inputs + self.n_bundles
        # else:
        #     self.max_n_features = max_n_features
        #     self.n_bundles = self.max_n_features - self.n_inputs

        # Normalization constants.
        # input_max : array of floats
        #     The maximum of each input's activity.
        #     Start with the assumption that each input has a
        #     maximum value of zero. Then adapt up from there
        #     based on the incoming observations.
        # input_max_decay_time, input_max_grow_time : float
        #     The time constant over which maximum estimates are
        #     decreased/increased. Growing time being lower allows the
        #     estimate to be weighted toward the maximum value.
        #self.input_max = np.zeros(self.n_inputs)
        #self.input_max_grow_time = 1e2
        #self.input_max_decay_time = self.input_max_grow_time * 1e2

        # input_activities,
        # bundle_activities,
        # feature_activities : array of floats
        #     inputs, bundles and features are characterized by their
        #     activity--their level of activation at each time step.
        #     Activity for each input or bundle or feature
        #     can vary between zero and one.
        #self.input_activities = np.zeros(self.n_inputs)
        #self.bundle_activities = np.zeros(self.n_bundles)
        #self.feature_activities = np.zeros(self.max_n_features)

        # live_features : array of floats
        #     A binary array tracking which of the features have ever
        #     been active.
        # self.live_features = np.zeros(self.max_n_features)

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs_final = self.n_inputs,
            verbose=self.verbose,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(
            n_cables=self.n_inputs,
            n_bundles=self.n_bundles,
            threshold=threshold,
            verbose=self.verbose)
Example #8
0
    def __init__(
        self,
        debug=False,
        n_inputs=None,
        threshold=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        threshold : float
            See Ziptie.nucleation_threshold
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs = self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(
            n_cables=self.n_inputs,
            threshold=threshold,
            debug=self.debug)

        # mapping_to_features: list of lists
        #     Tracks which feature candidate index corresponds to each
        #     ziptie input. The first level list corresponds to
        #         0: inputs to ziptie level 0
        #         1: inputs to ziptie level 1
        #               (also bundles from ziptie level 0)
        #         2: inputs to ziptie level 2
        #               (also bundles from ziptie level 1)
        #         ...
        #     The second level list index of the feature candidate that
        #     each input maps to.
        self.mapping_to_features = [[]]
        # mapping_from_features: list of 2-tuples,
        #     The inverse of mapping_to_features. Tracks which input
        #    corresponds which each feature candidate.
        #     Each item is of the format
        #         (level, input index)
        self.mapping_from_features = []
Example #9
0
class Featurizer(object):
    """
    Convert inputs to bundles and learn new bundles.
    Inputs are transformed into bundles, sets of inputs
    that tend to co-occur.
    """
    def __init__(
        self,
        debug=False,
        n_inputs=None,
        threshold=None,
    ):
        """
        Configure the featurizer.

        Parameters
        ---------
        debug: boolean
        n_inputs : int
            The number of inputs (cables) that each Ziptie will be
            equipped to handle.
        threshold : float
            See Ziptie.nucleation_threshold
        """
        self.debug = debug

        # name: string
        #     A label for this object.
        self.name = 'featurizer'

        # epsilon: float
        #     A constant small theshold used to test for significant
        #     non-zero-ness.
        self.epsilon = 1e-8

        # n_inputs: int
        #     The maximum numbers of inputs and bundles
        #     that this level can accept.
        self.n_inputs = n_inputs

        # TODO:
        # Move input filter and ziptie creation into step,
        # along with clustering.

        # filter: InputFilter
        #     Reduce the possibly large number of inputs to the number
        #     of cables that the Ziptie can handle. Each Ziptie will
        #     have its own InputFilter.
        self.filter = InputFilter(
            n_inputs = self.n_inputs,
            name='ziptie_0',
            debug=self.debug,
        )
        # ziptie: Ziptie
        #     The ziptie is an instance of the Ziptie algorithm class,
        #     an incremental method for bundling inputs. Check out
        #     ziptie.py for a complete description. Zipties note which
        #     inputs tend to be co-active and creates bundles of them.
        #     This feature creation mechanism results in l0-sparse
        #     features, which sparsity helps keep Becca fast.
        self.ziptie = Ziptie(
            n_cables=self.n_inputs,
            threshold=threshold,
            debug=self.debug)

        # mapping_to_features: list of lists
        #     Tracks which feature candidate index corresponds to each
        #     ziptie input. The first level list corresponds to
        #         0: inputs to ziptie level 0
        #         1: inputs to ziptie level 1
        #               (also bundles from ziptie level 0)
        #         2: inputs to ziptie level 2
        #               (also bundles from ziptie level 1)
        #         ...
        #     The second level list index of the feature candidate that
        #     each input maps to.
        self.mapping_to_features = [[]]
        # mapping_from_features: list of 2-tuples,
        #     The inverse of mapping_to_features. Tracks which input
        #    corresponds which each feature candidate.
        #     Each item is of the format
        #         (level, input index)
        self.mapping_from_features = []
        # If mapping_from_features[i] = (j, k)
        # then mapping_to_features[j, k] = i

    def calculate_fitness(self, feature_fitness):
        """
        Find the predictive fitness of each of cables in each ziptie.

        Parameters
        ----------
        candidate_fitness: array of floats
        """
        # TODO: Handle a hierarchy of zipties and input filters
        # Include upstream fitness
        all_input_fitness = self.map_from_feature_pool(feature_fitness)

        cable_fitness = self.ziptie.project_bundle_activities(
            all_input_fitness[1])
        input_fitness = np.maximum(cable_fitness, all_input_fitness[0])
        self.filter.update_fitness(input_fitness)

    def update_inputs(self):
        """
        Give each input filter a chance to update their inputs and propagate
        any resets that result up through the zipties and to the model.

        Returns
        -------
        resets: array of ints
            The feature candidate indices that are being reset. 
        """
        filter_resets = self.filter.update_inputs()
        bundle_resets = self.ziptie.update_inputs(filter_resets)
        # Leave an empty list of resets for lowest level input.
        # They are always all passed in as feature candidates.
        # They never get reset or swapped out. The model's input filter
        # deals with them.
        # As other levels are created, append their bundle resets as well.
        all_resets = [[], bundle_resets]

        resets = []
        for i_level, level_resets in enumerate(all_resets):
            for i_reset in level_resets:
                resets.append(self.mapping_to_features[i_level][i_reset])

        return resets

    def map_from_feature_pool(self, feature_values):
        """
        For an array corresponding to feature candidates from the model,
        generate and order those values corresponding to ziptie candidates.

        Parameters
        ----------
        feature_values: array of floats

        Returns
        -------
        candidate_values: list of array of floats
        """
        candidate_values = [np.zeros(self.n_inputs)]
        candidate_values.append(np.zeros(self.ziptie.n_bundles))
         
        for i_feature, (i_level, i_candidate) in enumerate(
                self.mapping_from_features):
            # print('cv', candidate_values,
            #       'il',   i_level,
            #        'ic',  i_candidate,
            #       'fv',   feature_values,
            #       'if',   i_feature,
            #         )
            candidate_values[i_level][i_candidate] = feature_values[i_feature]
        return candidate_values

    def map_to_feature_pool(self, candidate_values):
        """
        Map the candidates over all levels to the appropriate feature candidates.

        Parameters
        ----------
        candidate_values: list of arrays of floats

        Returns
        -------
        feature_values: array of floats
        """
        # Check whether the number of candidates has expanded at any level
        # and adapt.
        for _ in range(len(self.mapping_to_features), len(candidate_values)):
            self.mapping_to_features.append([])
        for i_level, ziptie_cable_pool in enumerate(candidate_values):
            # Map any unmapped candidates to features.
            for i_new_candidate in range(len(self.mapping_to_features[i_level]),
                                     ziptie_cable_pool.size):
                self.mapping_to_features[i_level].append(
                    len(self.mapping_from_features))
                self.mapping_from_features.append((i_level, i_new_candidate))

        feature_values = np.zeros(len(self.mapping_from_features))
        for i_level, level_mapping in enumerate(self.mapping_to_features):
            for i_candidate, i_feature in enumerate(level_mapping):
                feature_values[i_feature] = candidate_values[i_level][i_candidate]

        return feature_values

    def featurize(self, new_candidates):
        """
        Learn bundles and calculate bundle activities.

        Parameters
        ----------
        new_candidates : array of floats
            The candidates collected by the brain for the current time step.

        Returns
        -------
        feature_pool: array of floats
        """
        self.ziptie_0_cable_pool = new_candidates
        cable_activities = self.filter.update_activities(
            candidate_activities=self.ziptie_0_cable_pool)

        # Incrementally update the bundles in the ziptie.
        self.ziptie.create_new_bundles()
        self.ziptie.grow_bundles()

        # Run the inputs through the ziptie to find bundle activities
        # and to learn how to bundle them.
        ziptie_1_cable_pool = self.ziptie.update_bundles(cable_activities)

        all_ziptie_cable_pool = [
            self.ziptie_0_cable_pool,
            ziptie_1_cable_pool,
        ]

        self.feature_pool = self.map_to_feature_pool(all_ziptie_cable_pool)

        return self.feature_pool

    def defeaturize(self, feature_pool):
        """
        Take a set of feature activities and represent them in candidates.
        """
        ziptie_0_cable_pool, ziptie_1_cable_pool = (
            self.map_from_feature_pool(feature_pool))
        # TODO: iterate over multiple zipties
        ziptie_0_cables = self.ziptie.project_bundle_activities(
            ziptie_1_cable_pool)
        ziptie_0_cable_pool_upstream = self.filter.project_activities(
            ziptie_0_cables)
        n_candidates_0 = ziptie_0_cable_pool_upstream.size
        ziptie_0_cable_pool = np.maximum(
            ziptie_0_cable_pool[:n_candidates_0],
            ziptie_0_cable_pool_upstream)
        return ziptie_0_cable_pool

    def visualize(self, brain, world=None):
        """
        Show the current state of the featurizer.
        """
        viz.visualize(self, brain, world)