def calculate_min_activity(columns, active, distances, inhibition_area, activity, min_activity_threshold): """ Calculates the minActivity matrix from the paper (p. 4). :param columns: the 4-dimensional array of HTM columns: a 4-dimensional array of shape *shape* that represents the HTM columns and their synapses; each element columns[a, b, c, d] contains the permanence value of the synapse connecting the column with coordinates (a, b) to input with coordinates (c, d). :param active: array of shape (columns.shape[0], columns.shape[0]); each element active[a, b] is True if the column [a, b] is active this iteration and False otherwise. :param distances: a 4-dimensional array of the same shape as *columns*; each element distances[a, b, c, d] contains the euclidean distance from (a, b) to (c, d). :param inhibition_area: the BSP's inhibitionArea parameter (p. 3, 4). :param activity: the BSP's activity matrix (p. 4). This parameter is modified and returned. It is a dictionary with tuples (y, x) of HTM column coordinates as keys and deque (a queue implementation) instances as values. The queue for key [a, b] stores a 1 each iteration the the column [a, b] is active during the last 1000 iterations. :param min_activity_threshold: the BSP's minActivityThreshold parameter (p. 4). :return: the BSP's minActivity matrix (p. 4); an array min_activity of shape (columns.shape[0], columns.shape[1]), where each min_activity[a b] represents the calculated minActivity for the column [a, b]: minActivity([a, b]) = max(activity[u, v]) * minActivityThreshold where the column [u, v] is a neighbour of [y, x] (within inhibitionArea) """ # Initialize the min_activity array. min_activity = np.zeros(shape=columns.shape[:2]) # For each active column [y, x], ... for y, x, _ in iter_columns(columns, active_matrix=active): c = (y, x) max_activity = 0 # get the neighbours of [y, x] ... neighbours = iter_neighbours(columns, y, x, distances, inhibition_area) # and for each neighbour [u, v] of [y, x], ... for u, v, _ in neighbours: n = (u, v) # calculate the how many times [u, v] was active during the # last 1000 iterations, ... activity_count = activity[n].sum() # and choose the maximum count among all the neighbours of # [y, x]. if activity_count > max_activity: max_activity = activity_count # Finally, scale the maximum activity count among the neighbours of # [y, x] by min_activity_threshold. min_activity[c] = max_activity * min_activity_threshold return min_activity
def update_inhibition_area(columns, connect_threshold): """ Implements the updateInhibitionArea function from the paper (p. 4). :param columns: the 4-dimensional array of HTM columns: a 4-dimensional array of shape *shape* that represents the HTM columns and their synapses; each element columns[a, b, c, d] contains the permanence value of the synapse connecting the column with coordinates (a, b) to input with coordinates (c, d). :param connect_threshold: threshold over which a potential synapse is considered *connected*. All potential synapses start with a permanence value within 0.1 of this parameter. :return: the inhibition area for the algorithm, calculated as the mean size of the receptive fields of all columns, where a column's receptive field is calculated as \pi*d^2/16, with: d = max([a, b], x) - min([a, b], x) + max([a, b], y) - min([a, b], y) + 2 where max and min return the the maximum and minimum x and y values of all connected synapses belonging to column [a, b]. """ # Initialize the inhibition area accumulator. inhibition_area = 0 # Get the shape of the synapse's matrix shape = columns.shape[2:] # Generate matrix of synapse's coordinates. For instane, for a synapse's # matrix of shape (5, 5), coord_matrix would be: # [[0, 1, 2, 3, 4], # [1, 2, 3, 4, 5], # [2, 3, 4, 5, 6], # [3, 4, 5, 6, 7], # [4, 5, 6, 7, 8]] coord_matrix = np.array([i + j for i in range(shape[0]) for j in range(shape[1])], dtype=np.float) coord_matrix = coord_matrix.reshape(shape) # For each column ... for _, _, syn_matrix in iter_columns(columns): # create matrices to calculate the synapse's min and max coordinates min_matrix, max_matrix = create_min_max_matrices(coord_matrix, syn_matrix, connect_threshold) # calculate the synapse's min and max coordinates min_y, min_x, max_y, max_x = calculate_min_max_y_x(min_matrix, max_matrix) # set d = max([a, b], y) - min([a, b], y) # + max([a, b], x) - min([a, b], x) # + 2, ... d = max_y - min_y + max_x - min_x + 2 # calculate the receptive field based on d, ... receptive_radius = d / 4 receptive_field = np.pi * receptive_radius ** 2 # add the receptive field of the column [y, x] to the accumulator, ... inhibition_area += receptive_field # and finally calculate the average of all receptive fields. inhibition_area /= columns.shape[0] * columns.shape[1] return inhibition_area
def calculate_overlap(input_vector, columns, min_overlap, connect_threshold, boost): """ Implements the calculateOverlap function from the paper (p. 3). :param input_vector: a single input_vector from the images set. :param columns: the 4-dimensional array of HTM columns: a 4-dimensional array of shape *shape* that represents the HTM columns and their synapses; each element columns[a, b, c, d] contains the permanence value of the synapse connecting the column with coordinates (a, b) to input with coordinates (c, d). :param min_overlap: the BSP's minOverlap parameter (p. 3). Type: float. :param connect_threshold: the BSP's connectThreshold parameter (p. 3). Type: float. :param boost: the BSP's boost matrix (pp. 3, 4). It is a matrix of shape (columns.shape[0], columns.shape[1]) :param overlap_sum: the BSP's overlapSum matrix (p. 4). This parameter is modified and returned. It is a dictionary with tuples (y, x) of HTM column coordinates as keys and deque (a queue implementation) instances as values. The queue for key [a, b] stores a 1 each time the overlap of the column [a, b] was above the minOverlap threshold during the last 1000 iterations. :return: a tuple (overlap, overlap_sum). *overlap* is an array of shape (columns.shape[0], columns.shape[0]); each element overlap[a, b] contains the overlap of the column [a, b] with the input_vector. *overlap_sum* is the parameter of the same name; the queue in overlap_sum[a, b] will have a 1 pushed into it if the overlap for the column [a, b] was above the min_overlap threshold this iteration. """ # Initialize the overlap array. overlap = np.zeros(columns.shape[:2]) # for each column ... for y, x, syn_matrix in iter_columns(columns): # @UnusedVariable c = (y, x) # calculate the overlap as the sum of pixel's values in the # input_vector assigned to *connected* synapses and, ... # (numexpr.evaluate optimizes and carries out the operations defined in # its string argument, it is used here because numpy has some # problems when comparing NaNs with numbers) active_synapses = ne.evaluate('syn_matrix >= connect_threshold') overlap[c] = input_vector[active_synapses].sum() # if the overlap is not enough, ... if overlap[c] < min_overlap: # reset it, but, ... overlap[c] = 0 # if the overlap is enough, ... else: # then boost it. overlap[c] *= boost[c] return overlap
def learn_synapse_connections(columns, active, input_vector, p_inc, p_dec, activity, min_activity, boost, b_inc, p_mult, connect_threshold, distances, b_max): """ Calculates the minActivity matrix from the paper (p. 6). :param columns: the 4-dimensional array of HTM columns: a 4-dimensional array of shape *shape* that represents the HTM columns and their synapses; each element columns[a, b, c, d] contains the permanence value of the synapse connecting the column with coordinates (a, b) to input with coordinates (c, d). :param active: array of shape (columns.shape[0], columns.shape[0]); each element active[a, b] is True if the column [a, b] is active this iteration and False otherwise. :param input_vector: the input to be learned. A 2-dimensional array of shape (columns.shape[0], columns.shape[1]). :param p_inc: the BSP'perm pInc parameter (p. 4). A float that indicates the amount by which a synapse'perm permanence must be incremented. :param p_dec: the BSP'perm pDec parameter (p. 4). A float that indicates the amount by which a synapse'perm permanence must be decremented. :param activity: the BSP'perm activity matrix (p. 4). This parameter is modified and returned. It is a dictionary with tuples (y, x) of HTM column coordinates as keys and deque (a queue implementation) instances as values. The queue for key [a, b] stores a 1 each iteration the the column [a, b] is active during the last 1000 iterations. :param overlap_sum: the BSP'perm overlapSum matrix (p. 4). This parameter is modified and returned. It is a dictionary with tuples (y, x) of HTM column coordinates as keys and deque (a queue implementation) instances as values. The queue for key [a, b] stores a 1 each time the overlap of the column [a, b] was above the minOverlap threshold during the last 1000 iterations. :param min_activity: the BSP'perm minActivity matrix (p. 4); an array min_activity of shape (columns.shape[0], columns.shape[1]), where each min_activity[a b] represents the calculated minActivity for the column [a, b]. :param boost: the BSP'perm boost matrix (pp. 3, 4). It is a matrix of shape (columns.shape[0], columns.shape[1]) :param b_inc: the BSP'perm bInc parameter (p. 4). A float that indicates the amount by which a column'perm boost must be incremented. :param p_mult: the BSP'perm pMult parameter (p. 4). A float that indicates the amount by which a synapse'perm permanence must be multiplied. :param connect_threshold: threshold over which a potential synapse is considered *connected*. All potential synapses start with a permanence value within 0.1 of this parameter. :param distances: a 4-dimensional array of the same shape as *columns*; each element distances[a, b, c, d] contains the euclidean distance from (a, b) to (c, d). :param b_max: boost threshold (p. 6). :return: a tuple (columns, synapse_modified). *columns* is the parameter of the same name, modified according to the BSP learning algorithm. *synapse_modified* is a boolean indicating whether any synapses were modified in the course of learning. """ # Assume no synapse will be modified. synapse_modified = False mean_input = np.nanmean(input_vector) # Store which synapses are connecter and which aren't. pre_col_matrix = ne.evaluate('columns >= connect_threshold') # For each active column [y, x] ... for _, _, syn_matrix in iter_columns(columns, active_matrix=active): # for each potential synapse [u, v] of [y, x] with permanence perm, # (NOTE: by definition, perm = syn_matrix[u, v]) for u, v, perm in iter_synapses(syn_matrix, only_potential=True): s = (u, v) if (input_vector[s] > mean_input and perm >= connect_threshold): syn_matrix[s] = min(perm + p_inc, 1) elif (input_vector[s] > mean_input and perm < connect_threshold): syn_matrix[s] = min(perm + p_inc, connect_threshold - p_inc) else: syn_matrix[s] = max(perm - p_dec, 0) # For each column [y, x] ... for y, x, syn_matrix in iter_columns(columns): c = (y, x) # if the activity of [y, x] over the last 1000 iterations was too low, if activity[c].sum() < min_activity[c]: # increment the boost by b_inc (in the paper this is done inside # the if clause in lines 14-15 of algorithm 3 in page 6), ... boost[c] += b_inc # if its boost is too high, ... if boost[c] > b_max: # define a function to filter all synapses with a permanence # value below the threshold, ... def filter_permanences(s): u, v, perm = s if perm < connect_threshold: return (perm, distances[c][u, v]) else: return -np.infty # reset the boost for column [y, x], ... boost[c] = 1 # select the disconnected synapse with the highest permanence, # choosing the nearest synapse in case of tie, ... max_syn = max(iter_synapses(syn_matrix), key=filter_permanences) max_s = max_syn[:2] # and set the selected synapse's permanence value to p_inc # above the threshold. syn_matrix[max_s] = connect_threshold + p_inc # Set synapse_modified to True if any synapse change from connected to # disconnected or vice-versa by this algorithm. if (pre_col_matrix != ne.evaluate('columns >= connect_threshold')).any(): synapse_modified = True # Return the columns array, with its synapses modified. return columns, synapse_modified
def inhibit_columns(columns, distances, inhibition_area, overlap, activity, desired_activity): """ Implements the inhibitColums function from the paper (pp. 3, 4) :param columns: the 4-dimensional array of HTM columns: a 4-dimensional array of shape *shape* that represents the HTM columns and their synapses; each element columns[a, b, c, d] contains the permanence value of the synapse connecting the column with coordinates (a, b) to input with coordinates (c, d). :param distances: a 4-dimensional array of the same shape as *columns*; each element distances[a, b, c, d] contains the euclidean distance from (a, b) to (c, d). :param inhibition_area: the BSP's inhibitionArea parameter (p. 3, 4). :param overlap: an array of shape (columns.shape[0], columns.shape[0]); each element overlap[a, b] contains the overlap of the column [a, b] with the image. :param activity: the BSP's activity matrix (p. 4). This parameter is modified and returned. It is a dictionary with tuples (y, x) of HTM column coordinates as keys and deque (a queue implementation) instances as values. The queue for key [a, b] stores a 1 each iteration the the column [a, b] is active during the last 1000 iterations. :param desired_activity: the BSP's desiredActivity parameter (p. 4). :return: a tuple (active, activity). *active* is an array of shape (columns.shape[0], columns.shape[0]); each element active[a, b] is True if the column [a, b] is active this iteration and False otherwise. *activity* is the parameter of the same name; the queue in activity[a, b] will have a 1 pushed into it if the the column [a, b] was active this iteration. """ # Initialize the active array filling it with False. active = np.zeros(shape=columns.shape[:2], dtype=np.bool) # For each column [y, x]... for y, x, _ in iter_columns(columns): # if [y, x] reacted to the input at all, ... # (NOTE: This test is not present in the ASP paper, but it is present # in the original HTM paper. However it does make sense that a column # should be active only if some synapses were exited by the input.) if overlap[y, x] > 0: # initialize the activity counter, ... activity_sum = 0 # obtain the list of neighbours of the column [y, x], ... neighbours = iter_neighbours(columns, y, x, distances, inhibition_area) # for each neighbour [u, v] of [y, x] ... for u, v, _ in neighbours: # if the neighbour's overlap is over this column's overlap, ... if overlap[u, v] > overlap[y, x]: # count it towards the activity, ... activity_sum += 1 # and if the neighbours of [y, x] are not too active, ... if activity_sum < desired_activity: # set [y, x] as active, ... active[y, x] = True # and then update the activity array, indicating that # the column [y, x] was active in this iteration. activity[y, x].append(1) else: # and then update the activity array, indicating that # the column [y, x] was inactive in this iteration. activity[y, x].append(0) else: # update the activity array, indicating that # the column [y, x] was inactive in this iteration. activity[y, x].append(0) return active, activity