(self, controller_output, memory_state)
return memory_output, memory_state, extra_visualization_info
One step of memory. Performs all the read and write operations and returns some useful tensors for visualization
        
        :param controller_output: used to create the interface vector which parametrizes memory operations
        :param memory_state: list of a bunch of useful memory tensors from previous step which are necessary for 
            the calculation of next step
        :return: output of the memory, all the updated memory states and a bunch of of other tensors for visualization
One step of memory. Performs all the read and write operations and returns some useful tensors for visualization
        
        :param controller_output: used to create the interface vector which parametrizes memory operations
        :param memory_state: list of a bunch of useful memory tensors from previous step which are necessary for 
            the calculation of next step
        :return: output of the memory, all the updated memory states and a bunch of of other tensors for visualization
One step of memory . Performs all the read and write operations and returns some useful tensors for visualization : param controller_output : used to create the interface vector which parametrizes memory operations : param memory_state : list of a bunch of useful memory tensors from previous step which are necessary for the calculation of next step : return : output of the memory all the updated memory states and a bunch of of other tensors for visualization
def step(self, controller_output, memory_state):
        """
        One step of memory. Performs all the read and write operations and returns some useful tensors for visualization
        
        :param controller_output: used to create the interface vector which parametrizes memory operations
        :param memory_state: list of a bunch of useful memory tensors from previous step which are necessary for 
            the calculation of next step
        :return: output of the memory, all the updated memory states and a bunch of of other tensors for visualization
        """

        read_weightings, write_weighting, usage_vector, precedence_weighting, m, link_matrix, read_vectors = \
            memory_state

        interface_vector = controller_output @ self.interface_weights  # shape [batch_size, interf_vector_size]

        interf = self.split_interface(interface_vector)

        interf["write_key"] = tf.expand_dims(interf["write_key"], dim=1)
        interf["r_read_keys"] = tf.reshape(interf["r_read_keys"], [-1, self.num_read_heads, self.word_size])

        # memory retention is of shape [batch_size, memory_size]
        memory_retention = tf.reduce_prod(1 - tf.einsum("br,bnr->bnr", interf["r_free_gates"], read_weightings), 2)

        # calculating the usage vector, which has some sort of short-term memory
        usage_vector = (usage_vector + write_weighting - usage_vector * write_weighting) * memory_retention
        allocation_weighting = self.calculate_allocation_weighting(usage_vector)

        # calculating write content weighting with memory from previous time step, shape [batch_size, memory_size, 1]
        # it represents a normalized probability distribution over the memory locations
        write_content_weighting = Memory.content_based_addressing(m, interf["write_key"], interf["write_strength"])

        # write weighting, shape [batch_size, memory_size], represents a normalized probability distribution over the
        # memory locations. Its sum is <= 1
        write_weighting = interf["write_gate"] * (interf["allocation_gate"] * allocation_weighting
                                                  + tf.einsum("bnr,bi->bn",
                                                              write_content_weighting,
                                                              (1 - interf["allocation_gate"])))

        m = m * (1 - tf.einsum("bn,bw->bnw", write_weighting, interf["erase_vector"])) + \
            tf.einsum("bn,bw->bnw", write_weighting, interf["write_vector"])

        link_matrix = self.update_link_matrix(link_matrix, precedence_weighting, write_weighting)
        precedence_weighting = tf.einsum("bn,b->bn",
                                         precedence_weighting,
                                         (1 - tf.reduce_sum(write_weighting, axis=1))) + write_weighting

        forwardw = tf.einsum("bmn,bnr->bmr", link_matrix, read_weightings)
        backwardw = tf.einsum("bnm,bnr->bmr", link_matrix, read_weightings)

        # calculating rcw with updated memory, shape [batch_size, memory_size, num_read_heads]
        # it represents a normalized probability distribution over the memory locations, for each read head
        read_content_weighting = Memory.content_based_addressing(m, interf["r_read_keys"],
                                                                 interf["r_read_strengths"])

        read_weightings = Memory.calculate_read_weightings(interf["r_read_modes"],
                                                           backwardw,
                                                           read_content_weighting,
                                                           forwardw)

        read_vectors = tf.einsum("bnw,bnr->brw", m, read_weightings)

        memory_output = tf.einsum("brw,rwo->bo", read_vectors, self.output_weights)

        extra_visualization_info = [interf["r_read_modes"], interf["write_gate"], interf["allocation_gate"],
                                    interf["write_strength"], interf["r_read_strengths"], interf["erase_vector"],
                                    interf["write_vector"], forwardw, backwardw, interf["r_free_gates"],
                                    interf["r_read_keys"]]

        memory_state = [read_weightings,
                        write_weighting,
                        usage_vector,
                        precedence_weighting,
                        m,
                        link_matrix,
                        read_vectors]
        return memory_output, memory_state, extra_visualization_info
def step ( self , controller_output , memory_state ) : read_weightings , write_weighting , usage_vector , precedence_weighting , m , link_matrix , read_vectors = memory_state interface_vector = controller_output @ self . interface_weights # shape [batch_size, interf_vector_size] interf = self . split_interface ( interface_vector ) interf [ "write_key" ] = tf . expand_dims ( interf [ "write_key" ] , dim = 1 ) interf [ "r_read_keys" ] = tf . reshape ( interf [ "r_read_keys" ] , [ - 1 , self . num_read_heads , self . word_size ] ) # memory retention is of shape [batch_size, memory_size] memory_retention = tf . reduce_prod ( 1 - tf . einsum ( "br,bnr->bnr" , interf [ "r_free_gates" ] , read_weightings ) , 2 ) # calculating the usage vector, which has some sort of short-term memory usage_vector = ( usage_vector + write_weighting - usage_vector * write_weighting ) * memory_retention allocation_weighting = self . calculate_allocation_weighting ( usage_vector ) # calculating write content weighting with memory from previous time step, shape [batch_size, memory_size, 1] # it represents a normalized probability distribution over the memory locations write_content_weighting = Memory . content_based_addressing ( m , interf [ "write_key" ] , interf [ "write_strength" ] ) # write weighting, shape [batch_size, memory_size], represents a normalized probability distribution over the # memory locations. Its sum is <= 1 write_weighting = interf [ "write_gate" ] * ( interf [ "allocation_gate" ] * allocation_weighting + tf . einsum ( "bnr,bi->bn" , write_content_weighting , ( 1 - interf [ "allocation_gate" ] ) ) ) m = m * ( 1 - tf . einsum ( "bn,bw->bnw" , write_weighting , interf [ "erase_vector" ] ) ) + tf . einsum ( "bn,bw->bnw" , write_weighting , interf [ "write_vector" ] ) link_matrix = self . update_link_matrix ( link_matrix , precedence_weighting , write_weighting ) precedence_weighting = tf . einsum ( "bn,b->bn" , precedence_weighting , ( 1 - tf . reduce_sum ( write_weighting , axis = 1 ) ) ) + write_weighting forwardw = tf . einsum ( "bmn,bnr->bmr" , link_matrix , read_weightings ) backwardw = tf . einsum ( "bnm,bnr->bmr" , link_matrix , read_weightings ) # calculating rcw with updated memory, shape [batch_size, memory_size, num_read_heads] # it represents a normalized probability distribution over the memory locations, for each read head read_content_weighting = Memory . content_based_addressing ( m , interf [ "r_read_keys" ] , interf [ "r_read_strengths" ] ) read_weightings = Memory . calculate_read_weightings ( interf [ "r_read_modes" ] , backwardw , read_content_weighting , forwardw ) read_vectors = tf . einsum ( "bnw,bnr->brw" , m , read_weightings ) memory_output = tf . einsum ( "brw,rwo->bo" , read_vectors , self . output_weights ) extra_visualization_info = [ interf [ "r_read_modes" ] , interf [ "write_gate" ] , interf [ "allocation_gate" ] , interf [ "write_strength" ] , interf [ "r_read_strengths" ] , interf [ "erase_vector" ] , interf [ "write_vector" ] , forwardw , backwardw , interf [ "r_free_gates" ] , interf [ "r_read_keys" ] ] memory_state = [ read_weightings , write_weighting , usage_vector , precedence_weighting , m , link_matrix , read_vectors ] return memory_output , memory_state , extra_visualization_info