class Block(object): """ The building block of which the agent is composed Blocks are arranged hierarchically within the agent. The agent begins with only one block, and creates additional blocks in a tower arrangement as lower ones mature. The block's input channels (cables) are organized into clusters (bundles) whose activities are passed up to the next block in the hierarchy. Each block performs the same two functions, 1) a step_up where cable activities are converted to bundle activities and passed up the tower and 2) a step_down where bundle activity goals are passed back down and converted into cable activity goals. Internally, a block contains a number of cogs that work in parallel to convert cable activities into bundle activities and back again. """ def __init__(self, min_cables, name='anonymous', level=0): #def __init__(self, max_cables=1400, max_cogs=280, # max_cables_per_cog=10, max_bundles_per_cog=5, # name='anonymous', level=0): #def __init__(self, max_cables=250, max_cogs=50, # max_cables_per_cog=10, max_bundles_per_cog=5, # name='anonymous', level=0): """ Initialize the level, defining the dimensions of its cogs """ self.max_cables = int(2 ** np.ceil(np.log2(min_cables))) self.max_cables_per_cog = 8 self.max_bundles_per_cog = 4 self.max_cogs = self.max_cables / self.max_bundles_per_cog self.max_bundles = self.max_cogs * self.max_bundles_per_cog self.name = name self.level = level ziptie_name = ''.join(('ziptie_', self.name)) self.ziptie = ZipTie(self.max_cables, self.max_cogs, max_cables_per_bundle=self.max_cables_per_cog, mean_exponent=-2, joining_threshold=0.05, name=ziptie_name) self.cogs = [] # TODO: only create cogs as needed for cog_index in range(self.max_cogs): self.cogs.append(Cog(self.max_cables_per_cog, self.max_bundles_per_cog, max_chains_per_bundle=self.max_cables_per_cog, name='cog'+str(cog_index), level=self.level)) self.cable_activities = np.zeros((self.max_cables, 1)) self.ACTIVITY_DECAY_RATE = .5 # real, 0 < x < 1 # Constants for adaptively rescaling the cable activities self.max_vals = np.zeros((self.max_cables, 1)) self.min_vals = np.zeros((self.max_cables, 1)) self.RANGE_DECAY_RATE = 10 ** -5 def step_up(self, new_cable_activities, reward): """ Find bundle_activities that result from new_cable_activities """ new_cable_activities = tools.pad(new_cable_activities, (self.max_cables, 1)) ''' # Condition the new_cable_activities to fall between 0 and 1 self.min_vals = np.minimum(new_cable_activities, self.min_vals) self.max_vals = np.maximum(new_cable_activities, self.max_vals) spread = self.max_vals - self.min_vals new_cable_activities = ((new_cable_activities - self.min_vals) / (self.max_vals - self.min_vals + tools.EPSILON)) self.min_vals += spread * self.RANGE_DECAY_RATE self.max_vals -= spread * self.RANGE_DECAY_RATE ''' # Update cable_activities, incorporating sensing dynamics self.cable_activities = tools.bounded_sum([ new_cable_activities, self.cable_activities * (1. - self.ACTIVITY_DECAY_RATE)]) # debug #print self.name, 'ca', self.cable_activities.shape #print self.cable_activities.ravel() # Update the map from self.cable_activities to cogs self.ziptie.update(self.cable_activities) # Process the upward pass of each of the cogs in the block self.bundle_activities = np.zeros((0, 1)) for cog_index in range(len(self.cogs)): # Pick out the cog's cable_activities, process them, # and assign the results to block's bundle_activities cog_cable_activities = self.cable_activities[ self.ziptie.get_projection(cog_index).ravel().astype(bool)] enough_cables = (self.ziptie.cable_fraction_in_bundle(cog_index) > 0.7) cog_bundle_activities = self.cogs[cog_index].step_up( cog_cable_activities, reward, enough_cables) self.bundle_activities = np.concatenate((self.bundle_activities, cog_bundle_activities)) return self.bundle_activities def step_down(self, bundle_activity_goals): """ Find cable_activity_goals, given a set of bundle_activity_goals """ bundle_activity_goals = tools.pad(bundle_activity_goals, (self.max_bundles, 1)) instant_cable_activity_goals = np.zeros((self.max_cables, 1)) #self.cable_activity_goals = np.zeros((self.max_cables, 1)) #self.reaction = np.zeros((self.max_cables, 1)) self.surprise = np.zeros((self.max_cables, 1)) # Process the downward pass of each of the cogs in the level cog_index = 0 for cog in self.cogs: # Gather the goal inputs for each cog cog_bundle_activity_goals = bundle_activity_goals[ cog_index * self.max_bundles_per_cog: cog_index + 1 * self.max_bundles_per_cog,:] # Update the downward outputs for the level instant_cable_activity_goals_by_cog = cog.step_down( cog_bundle_activity_goals) cog_cable_indices = self.ziptie.get_projection( cog_index).ravel().astype(bool) #instant_cable_activity_goals[cog_cable_indices] = np.maximum( # tools.pad(instant_cable_activity_goals_by_cog, # (cog_cable_indices[0].size, 0)), # instant_cable_activity_goals[cog_cable_indices]) instant_cable_activity_goals[cog_cable_indices] = np.maximum( instant_cable_activity_goals_by_cog, instant_cable_activity_goals[cog_cable_indices]) #self.cable_activity_goals[cog_cable_indices] = np.maximum( # tools.pad(cog.goal_output, (cog_cable_indices[0].size, 0)), # self.cable_activity_goals[cog_cable_indices]) #self.reaction[cog_cable_indices] = np.maximum( # tools.pad(cog.reaction, (cog_cable_indices[0].size, 0)), # self.reaction[cog_cable_indices]) self.surprise[cog_cable_indices] = np.maximum( cog.surprise, self.surprise[cog_cable_indices]) cog_index += 1 return instant_cable_activity_goals def get_projection(self, bundle_index): """ Represent one of the bundles in terms of its cables """ # Find which cog it belongs to and which output it corresponds to cog_index = int(bundle_index / self.max_bundles_per_cog) cog_bundle_index = bundle_index - cog_index * self.max_bundles_per_cog # Find the projection to the cog's own cables cog_cable_indices = self.ziptie.get_projection( cog_index).ravel().astype(bool) num_cables_in_cog = np.sum(cog_cable_indices) cog_projection = self.cogs[cog_index].get_projection(cog_bundle_index) # Then re-sort them to the block's cables projection = np.zeros((self.max_cables, 2)) projection[cog_cable_indices,:] = cog_projection[:num_cables_in_cog,:] return projection def bundles_created(self): total = 0. for cog in self.cogs: # Check whether all cogs have created all their bundles total += cog.num_bundles() if np.random.random_sample() < 0.01: print total, 'bundles in', self.name, ', max of', self.max_bundles return total def visualize(self): """ Show what's going on inside the level """ self.ziptie.visualize() #for cog in self.cogs: # cog.visualize() return
class Cog(object): """ The basic units of which blocks are composed Cogs are named for their similarity to clockwork cogwheels. They are simple and do the same task over and over, but by virtue of how they are connected to their fellows, they collectively bring about interesting behavior. Input channels are similar to cables in that they carry activity signals that vary over time. Each cog contains two important parts, a daisychain and a ziptie. The daisychain is an object that builds cables into short sequences, and the ziptie is an object that takes the resulting chains and performs clustering on them, creating bundles. During upward processing, cable activities are used to train the daisychain and ziptie, making new bundles, maturing existing bundles, and calculating the activity in bundle. During downward processing, the daisychain and ziptie use the bundle activity goals from the next level higher to create goals for the cables. """ def __init__(self, max_cables, max_bundles, max_chains_per_bundle=None, name='anonymous', level=0): """ Initialize the cogs with a pre-determined maximum size """ self.name = name self.max_cables = max_cables self.max_bundles = max_bundles if max_chains_per_bundle is None: max_chains_per_bundle = int(max_cables ** 2 / max_bundles) self.daisychain = DaisyChain(max_cables, name=name) if max_bundles > 0: self.ziptie = ZipTie(max_cables **2, max_bundles, max_cables_per_bundle=max_chains_per_bundle, name=name) def step_up(self, cable_activities, reward, enough_cables): # TODO: fix this so that cogs can gracefully handle more cables # or else never be assigned them in the first place if cable_activities.size > self.max_cables: cable_activities = cable_activities[:self.max_cables, :] print '----- Number of max cables exceeded in', self.name, \ ' -----' """ cable_activities percolate upward through daisychain and ziptie """ chain_activities = self.daisychain.update(cable_activities, reward) self.reaction= self.daisychain.get_cable_activity_reactions() self.surprise = self.daisychain.get_surprise() if enough_cables is True: bundle_activities = self.ziptie.update(chain_activities) else: bundle_activities = np.zeros((0,1)) bundle_activities = tools.pad(bundle_activities, (self.max_bundles, 0)) return bundle_activities def step_down(self, bundle_activity_goals): """ bundle_activity_goals percolate downward """ chain_activity_goals = self.ziptie.get_cable_deliberation_vote( bundle_activity_goals) instant_cable_activity_goals = self.daisychain.deliberate( chain_activity_goals) self.cable_activity_goals =self.daisychain.get_cable_deliberation_vote() return instant_cable_activity_goals def get_projection(self, bundle_index): """ Project a bundle down through the ziptie and daisychain """ chain_projection = self.ziptie.get_projection(bundle_index) cable_projection = self.daisychain.get_projection(chain_projection) return cable_projection def fraction_filled(self): """ How full is the set of cables for this cog? """ return float(self.daisychain.num_cables) / float(self.max_cables) def num_bundles(self): """ How many bundles have been created in this cog? """ return self.ziptie.num_bundles def visualize(self): """ Show the internal state of the daisychain and ziptie """ self.daisychain.visualize() if self.max_bundles > 0: self.ziptie.visualize() return