def test_unpack(): assert unpack((1, 2)) == [1, 2] assert unpack([1, 2]) == [1, 2] assert unpack([1]) == 1 test = object() assert unpack(test) is test assert_raises(ValueError, unpack, [1, 2], True)
def do_test(with_serialization): data_stream = IterableDataset(range(10)).get_example_stream() main_loop = MainLoop(MockAlgorithm(), data_stream, extensions=[ WriteBatchExtension(), FinishAfter(after_n_batches=14) ]) main_loop.run() assert main_loop.log.status['iterations_done'] == 14 if with_serialization: main_loop = cPickle.loads(cPickle.dumps(main_loop)) finish_after = unpack([ ext for ext in main_loop.extensions if isinstance(ext, FinishAfter) ], singleton=True) finish_after.add_condition( ["after_batch"], predicate=lambda log: log.status['iterations_done'] == 27) main_loop.run() assert main_loop.log.status['iterations_done'] == 27 assert main_loop.log.status['epochs_done'] == 2 for i in range(27): assert main_loop.log[i + 1]['batch'] == {"data": i % 10}
def get_updates(self, learning_rate, grads, lr_scalers): """Wraps the respective method of the wrapped learning rule. Performs name-based input substitution for the monitored values. Currently very hacky: the inputs from the gradients are typically named `$ALGO[$SOURCE]` in PyLearn2, where `$ALGO` is the algorithm name and `$SOURCE` is a source name from the data specification. This convention is exploited to match them with the inputs of monitoring values, whose input names are expected to match source names. """ updates = self.learning_rule.get_updates(learning_rate, grads, lr_scalers) grad_inputs = ComputationGraph(list(grads.values())).dict_of_inputs() for value, accumulator in zip(self.values, self.accumulators): value_inputs = ComputationGraph(value).dict_of_inputs() replace_dict = dict() for name, input_ in value_inputs.items(): # See docstring to see how it works grad_input = grad_inputs[unpack([ n for n in grad_inputs if n.endswith('[{}]'.format(name)) ], singleton=True)] replace_dict[input_] = tensor.unbroadcast( grad_input, *range(grad_input.ndim)) updates[accumulator] = (accumulator + theano.clone(value, replace_dict)) self._callback_called = True updates.update(self.updates) return updates
def _compile_initial_state_and_context_computer(self): initial_states = VariableFilter( applications=[self.generator.initial_states], roles=[OUTPUT])(self.cg) #print("initial_states") #print initial_states initial_states2 = VariableFilter( bricks=[Encoder], roles=[OUTPUT])(self.cg) outputs = OrderedDict([(v.tag.name, v) for v in initial_states]) outputs[initial_states2[0].tag.name] = initial_states2[0] beam_size = unpack(VariableFilter( applications=[self.generator.initial_states], name='batch_size')(self.cg)) print self.inputs #print("outputs") #print outputs for name, context in equizip(self.context_names, self.contexts): outputs[name] = context outputs['beam_size'] = beam_size self.initial_state_and_context_computer = function( self.inputs, outputs, on_unused_input='ignore')
def do_test(with_serialization): data_stream = ContainerDataset(range(10)).get_default_stream() main_loop = MainLoop(None, data_stream, MockAlgorithm(), extensions=[FinishAfter(after_n_batches=14)]) main_loop.run() assert main_loop.log.status.iterations_done == 14 if with_serialization: string_io = BytesIO() dill.dump(main_loop, string_io, fmode=dill.CONTENTS_FMODE) string_io.seek(0) main_loop = dill.load(string_io) finish_after = unpack([ ext for ext in main_loop.extensions if isinstance(ext, FinishAfter) ], singleton=True) finish_after.add_condition( "after_batch", predicate=lambda log: log.status.iterations_done == 27) main_loop.run() assert main_loop.log.status.iterations_done == 27 assert main_loop.log.status.epochs_done == 2 for i in range(27): assert main_loop.log[i].batch == {"data": i % 10}
def get_updates(self, learning_rate, grads, lr_scalers): """Wraps the respective method of the wrapped learning rule. Performs name-based input substitution for the monitored values. Currently very hacky: the inputs from the gradients are typically named `$ALGO[$SOURCE]` in PyLearn2, where `$ALGO` is the algorithm name and `$SOURCE` is a source name from the data specification. This convention is exploited to match them with the inputs of monitoring values, whose input names are expected to match source names. """ updates = self.learning_rule.get_updates(learning_rate, grads, lr_scalers) grad_inputs = ComputationGraph(list(grads.values())).dict_of_inputs() for value, accumulator in zip(self.values, self.accumulators): value_inputs = ComputationGraph(value).dict_of_inputs() replace_dict = dict() for name, input_ in value_inputs.items(): # See docstring to see how it works grad_input = grad_inputs[unpack( [n for n in grad_inputs if n.endswith('[{}]'.format(name))], singleton=True)] replace_dict[input_] = tensor.unbroadcast( grad_input, *range(grad_input.ndim)) updates[accumulator] = ( accumulator + theano.clone(value, replace_dict)) self._callback_called = True updates.update(self.updates) return updates
def _compile_initial_state_and_context_computer(self): initial_states = VariableFilter( applications=[self.generator.initial_states], roles=[OUTPUT])(self.cg) outputs = OrderedDict([(v.tag.name, v) for v in initial_states]) beam_size = unpack(VariableFilter( applications=[self.generator.initial_states], name='batch_size')(self.cg)) for name, context in equizip(self.context_names, self.contexts): outputs[name] = context outputs['beam_size'] = beam_size self.initial_state_and_context_computer = function( self.inputs, outputs, on_unused_input='ignore')
def find_extension(self, name): """Find an extension with a given name. Parameters ---------- name : str The name of the extension looked for. Notes ----- Will crash if there no or several extension found. """ return unpack([extension for extension in self.extensions if extension.name == name], singleton=True)
def _compile_initial_state_and_context_computer(self): initial_states = VariableFilter( applications=[self.generator.initial_states], roles=[OUTPUT])(self.cg) outputs = OrderedDict([(v.tag.name, v) for v in initial_states]) beam_size = unpack(VariableFilter( applications=[self.generator.initial_states], name='batch_size')(self.cg)) for name, context in equizip(self.context_names, self.contexts): outputs[name] = context for name, embedding in equizip(self.topical_names, self.topical_embeddings): outputs[name] = embedding for name, context in equizip(self.topical_context_names, self.topical_contexts): outputs[name] = context for name, embedding in equizip(self.content_names, self.content_embeddings): outputs[name] = embedding outputs['beam_size'] = beam_size self.initial_state_and_context_computer = function( self.inputs, outputs, on_unused_input='ignore')
def do_test(with_serialization): data_stream = IterableDataset(range(10)).get_example_stream() main_loop = MainLoop( MockAlgorithm(), data_stream, extensions=[WriteBatchExtension(), FinishAfter(after_n_batches=14)]) main_loop.run() assert main_loop.log.status['iterations_done'] == 14 if with_serialization: main_loop = cPickle.loads(cPickle.dumps(main_loop)) finish_after = unpack( [ext for ext in main_loop.extensions if isinstance(ext, FinishAfter)], singleton=True) finish_after.add_condition( ["after_batch"], predicate=lambda log: log.status['iterations_done'] == 27) main_loop.run() assert main_loop.log.status['iterations_done'] == 27 assert main_loop.log.status['epochs_done'] == 2 for i in range(27): assert main_loop.log[i + 1]['batch'] == {"data": i % 10}
def __call__(self, *inputs, **kwargs): """Wraps an application method. This wrapper will provide some necessary pre- and post-processing of the Theano variables, such as tagging them with the brick that created them and naming them. These changes will apply to Theano variables given as positional arguments and keywords arguments. .. warning:: Properly set tags are important for correct functioning of the framework. Do not provide inputs to your apply method in a way different than passing them as positional or keyword arguments, e.g. as list or tuple elements. Notes ----- Application methods will allocate the brick parameters with a call :meth:`allocate` if they have not been allocated already. """ last = Application._last_brick_applied if last and last != self.brick and self.brick not in last.children: raise ValueError("The brick {} called an apply method of the" " brick {} without having it in the children" " list." .format(last, self.brick)) return_dict = kwargs.pop('return_dict', False) return_list = kwargs.pop('return_list', False) assert not return_list or not return_dict arg_names, varargs_name, _, _ = inspect.getargspec( self.application_method) arg_names = arg_names[1:] call = ApplicationCall(self.brick, self) if 'application_call' in arg_names: kwargs['application_call'] = call def copy_and_tag(variable, role, name): if Brick.print_shapes: variable = put_hook( variable, lambda x: logger.debug( "{}.{}.{}.shape = {}".format( self.brick.name, self.__name__, name, x.shape))) copy = variable.copy() copy.name = "{}_{}_{}".format(self.brick.name, self.__name__, name) copy.tag.application_call = call copy.tag.name = name copy.tag.role = role return copy if not self.brick.allocated: self.brick.allocate() if not self.brick.initialized and not self.brick.lazy: self.brick.initialize() inputs = list(inputs) for i, input_ in enumerate(inputs): name = (arg_names[i] if i < len(arg_names) else "{}_{}".format(varargs_name, i - len(arg_names))) if isinstance(input_, tensor.Variable): inputs[i] = copy_and_tag(input_, VariableRole.INPUT, name) for key, value in kwargs.items(): if isinstance(value, tensor.Variable): kwargs[key] = copy_and_tag(value, VariableRole.INPUT, key) Application._last_brick_applied = self.brick try: outputs = self.application_method(self.brick, *inputs, **kwargs) finally: Application._last_brick_applied = last # TODO allow user to return an OrderedDict outputs = pack(outputs) for i, output in enumerate(outputs): try: name = self.outputs[i] except: name = "output_{}".format(i) if isinstance(output, tensor.Variable): # TODO Tag with dimensions, axes, etc. for error-checking outputs[i] = copy_and_tag(outputs[i], VariableRole.OUTPUT, name) if return_list: return outputs if return_dict: return OrderedDict(zip(self.outputs, outputs)) return unpack(outputs)
def apply(self, bound_application, *args, **kwargs): as_dict = kwargs.pop('as_dict', False) as_list = kwargs.pop('as_list', False) if as_list and as_dict: raise ValueError brick = bound_application.brick # Find the names of the inputs to the application method args_names, varargs_name, _, _ = inspect.getargspec( self.application_function) args_names = args_names[1:] # Construct the ApplicationCall, used to store data in for this call call = ApplicationCall(bound_application) args = list(args) if 'application' in args_names: args.insert(args_names.index('application'), bound_application) if 'application_call' in args_names: args.insert(args_names.index('application_call'), call) # Allocate before applying, and optionally initialize if not brick.allocated: brick.allocate() # Annotate all the input variables which are Theano variables def copy_and_tag(variable, role, name): """Helper method to copy a variable and annotate it.""" copy = variable.copy() # Theano name copy.name = _variable_name(brick.name, self.name, name) add_annotation(copy, brick) add_annotation(copy, call) # Blocks name copy.tag.name = name add_role(copy, role) return copy for i, input_ in enumerate(args): if isinstance(input_, tensor.Variable): if i < len(args_names): name = args_names[i] else: name = "{}_{}".format(varargs_name, i - len(args_names)) args[i] = copy_and_tag(input_, INPUT, name) for name, input_ in kwargs.items(): if isinstance(input_, tensor.Variable): kwargs[name] = copy_and_tag(input_, INPUT, name) # Run the application method on the annotated variables last_brick = self.call_stack[-1] if self.call_stack else None if (last_brick and brick is not last_brick and brick not in last_brick.children): raise ValueError('Brick ' + str(self.call_stack[-1]) + ' tries ' 'to call brick ' + str(self.brick) + ' which ' 'is not in the list of its children.') self.call_stack.append(brick) try: outputs = self.application_function(brick, *args, **kwargs) outputs = pack(outputs) finally: self.call_stack.pop() # Rename and annotate output variables for i, output in enumerate(outputs): if isinstance(output, tensor.Variable): try: name = bound_application.outputs[i] except AttributeError: name = "output_{}".format(i) except IndexError: reraise_as(ValueError("Unexpected outputs")) # TODO Tag with dimensions, axes, etc. for error-checking outputs[i] = copy_and_tag(outputs[i], OUTPUT, name) # Return values if as_list: return outputs if as_dict: return OrderedDict(zip(bound_application.outputs, outputs)) return unpack(outputs)
def main(mode, save_path, num_batches, from_dump): if mode == "train": # Experiment configuration dimension = 100 readout_dimension = len(char2code) # Data processing pipeline data_stream = DataStreamMapping( mapping=lambda data: tuple(array.T for array in data), data_stream=PaddingDataStream( BatchDataStream( iteration_scheme=ConstantScheme(10), data_stream=DataStreamMapping( mapping=reverse_words, add_sources=("targets", ), data_stream=DataStreamFilter( predicate=lambda data: len(data[0]) <= 100, data_stream=OneBillionWord( "training", [99], char2code, level="character", preprocess=str.lower).get_default_stream()))))) # Build the model chars = tensor.lmatrix("features") chars_mask = tensor.matrix("features_mask") targets = tensor.lmatrix("targets") targets_mask = tensor.matrix("targets_mask") encoder = Bidirectional(GatedRecurrent(dim=dimension, activation=Tanh()), weights_init=Orthogonal()) encoder.initialize() fork = Fork([ name for name in encoder.prototype.apply.sequences if name != 'mask' ], weights_init=IsotropicGaussian(0.1), biases_init=Constant(0)) fork.input_dim = dimension fork.fork_dims = {name: dimension for name in fork.fork_names} fork.initialize() lookup = LookupTable(readout_dimension, dimension, weights_init=IsotropicGaussian(0.1)) lookup.initialize() transition = Transition(activation=Tanh(), dim=dimension, attended_dim=2 * dimension, name="transition") attention = SequenceContentAttention( state_names=transition.apply.states, match_dim=dimension, name="attention") readout = LinearReadout(readout_dim=readout_dimension, source_names=["states"], emitter=SoftmaxEmitter(name="emitter"), feedbacker=LookupFeedback( readout_dimension, dimension), name="readout") generator = SequenceGenerator(readout=readout, transition=transition, attention=attention, weights_init=IsotropicGaussian(0.1), biases_init=Constant(0), name="generator") generator.push_initialization_config() transition.weights_init = Orthogonal() generator.initialize() bricks = [encoder, fork, lookup, generator] # Give an idea of what's going on params = Selector(bricks).get_params() logger.info("Parameters:\n" + pprint.pformat([(key, value.get_value().shape) for key, value in params.items()], width=120)) # Build the cost computation graph batch_cost = generator.cost( targets, targets_mask, attended=encoder.apply(**dict_union(fork.apply( lookup.lookup(chars), return_dict=True), mask=chars_mask)), attended_mask=chars_mask).sum() batch_size = named_copy(chars.shape[1], "batch_size") cost = aggregation.mean(batch_cost, batch_size) cost.name = "sequence_log_likelihood" logger.info("Cost graph is built") # Fetch variables useful for debugging max_length = named_copy(chars.shape[0], "max_length") cost_per_character = named_copy( aggregation.mean(batch_cost, batch_size * max_length), "character_log_likelihood") cg = ComputationGraph(cost) energies = unpack(VariableFilter(application=readout.readout, name="output")(cg.variables), singleton=True) min_energy = named_copy(energies.min(), "min_energy") max_energy = named_copy(energies.max(), "max_energy") (activations, ) = VariableFilter( application=generator.transition.apply, name="states")(cg.variables) mean_activation = named_copy(activations.mean(), "mean_activation") # Define the training algorithm. algorithm = GradientDescent(cost=cost, step_rule=CompositeRule([ GradientClipping(10.0), SteepestDescent(0.01) ])) observables = [ cost, min_energy, max_energy, mean_activation, batch_size, max_length, cost_per_character, algorithm.total_step_norm, algorithm.total_gradient_norm ] for name, param in params.items(): observables.append(named_copy(param.norm(2), name + "_norm")) observables.append( named_copy(algorithm.gradients[param].norm(2), name + "_grad_norm")) main_loop = MainLoop( model=bricks, data_stream=data_stream, algorithm=algorithm, extensions=([LoadFromDump(from_dump)] if from_dump else []) + [ Timing(), TrainingDataMonitoring(observables, after_every_batch=True), TrainingDataMonitoring( observables, prefix="average", every_n_batches=10), FinishAfter(after_n_batches=num_batches).add_condition( "after_batch", lambda log: math.isnan( log.current_row.total_gradient_norm)), Plot(os.path.basename(save_path), [["average_" + cost.name], ["average_" + cost_per_character.name]], every_n_batches=10), SerializeMainLoop(save_path, every_n_batches=500, save_separately=["model", "log"]), Printing(every_n_batches=1) ]) main_loop.run() elif mode == "test": with open(save_path, "rb") as source: encoder, fork, lookup, generator = dill.load(source) logger.info("Model is loaded") chars = tensor.lmatrix("features") generated = generator.generate( n_steps=3 * chars.shape[0], batch_size=chars.shape[1], attended=encoder.apply(**dict_union( fork.apply(lookup.lookup(chars), return_dict=True))), attended_mask=tensor.ones(chars.shape)) sample_function = ComputationGraph(generated).get_theano_function() logging.info("Sampling function is compiled") while True: # Python 2-3 compatibility line = input("Enter a sentence\n") batch_size = int(input("Enter a number of samples\n")) encoded_input = [ char2code.get(char, char2code["<UNK>"]) for char in line.lower().strip() ] encoded_input = ([char2code['<S>']] + encoded_input + [char2code['</S>']]) print("Encoder input:", encoded_input) target = reverse_words((encoded_input, ))[0] print("Target: ", target) states, samples, glimpses, weights, costs = sample_function( numpy.repeat(numpy.array(encoded_input)[:, None], batch_size, axis=1)) messages = [] for i in range(samples.shape[1]): sample = list(samples[:, i]) try: true_length = sample.index(char2code['</S>']) + 1 except ValueError: true_length = len(sample) sample = sample[:true_length] cost = costs[:true_length, i].sum() message = "({})".format(cost) message += "".join(code2char[code] for code in sample) if sample == target: message += " CORRECT!" messages.append((cost, message)) messages.sort(key=lambda tuple_: -tuple_[0]) for _, message in messages: print(message)
def __call__(self, *inputs, **kwargs): """Wraps an application method. This wrapper will provide some necessary pre- and post-processing of the Theano variables, such as tagging them with the brick that created them and naming them. These changes will apply to Theano variables given as positional arguments and keywords arguments. .. warning:: Properly set tags are important for correct functioning of the framework. Do not provide inputs to your apply method in a way different than passing them as positional or keyword arguments, e.g. as list or tuple elements. Notes ----- Application methods will allocate the brick parameters with a call :meth:`allocate` if they have not been allocated already. """ last = Application._last_brick_applied if last and last != self.brick and self.brick not in last.children: raise ValueError("The brick {} called an apply method of the" " brick {} without having it in the children" " list.".format(last, self.brick)) return_dict = kwargs.pop('return_dict', False) return_list = kwargs.pop('return_list', False) assert not return_list or not return_dict arg_names, varargs_name, _, _ = inspect.getargspec( self.application_method) arg_names = arg_names[1:] call = ApplicationCall(self.brick, self) if 'application_call' in arg_names: kwargs['application_call'] = call def copy_and_tag(variable, role, name): if Brick.print_shapes: variable = put_hook( variable, lambda x: logger.debug("{}.{}.{}.shape = {}".format( self.brick.name, self.__name__, name, x.shape))) copy = variable.copy() copy.name = "{}_{}_{}".format(self.brick.name, self.__name__, name) copy.tag.application_call = call copy.tag.name = name copy.tag.role = role return copy if not self.brick.allocated: self.brick.allocate() if not self.brick.initialized and not self.brick.lazy: self.brick.initialize() inputs = list(inputs) for i, input_ in enumerate(inputs): name = (arg_names[i] if i < len(arg_names) else "{}_{}".format( varargs_name, i - len(arg_names))) if isinstance(input_, tensor.Variable): inputs[i] = copy_and_tag(input_, VariableRole.INPUT, name) for key, value in kwargs.items(): if isinstance(value, tensor.Variable): kwargs[key] = copy_and_tag(value, VariableRole.INPUT, key) Application._last_brick_applied = self.brick try: outputs = self.application_method(self.brick, *inputs, **kwargs) finally: Application._last_brick_applied = last # TODO allow user to return an OrderedDict outputs = pack(outputs) for i, output in enumerate(outputs): try: name = self.outputs[i] except: name = "output_{}".format(i) if isinstance(output, tensor.Variable): # TODO Tag with dimensions, axes, etc. for error-checking outputs[i] = copy_and_tag(outputs[i], VariableRole.OUTPUT, name) if return_list: return outputs if return_dict: return OrderedDict(zip(self.outputs, outputs)) return unpack(outputs)
def apply(self, bound_application, *args, **kwargs): as_dict = kwargs.pop('as_dict', False) as_list = kwargs.pop('as_list', False) call_id = kwargs.pop('call_id', None) if as_list and as_dict: raise ValueError brick = bound_application.brick # Find the names of the inputs to the application method args_names, varargs_name, _, _ = inspect.getargspec( self.application_function) args_names = args_names[1:] # Construct the ApplicationCall, used to store data in for this call call = ApplicationCall(bound_application) call.metadata['call_id'] = call_id args = list(args) if 'application' in args_names: args.insert(args_names.index('application'), bound_application) if 'application_call' in args_names: args.insert(args_names.index('application_call'), call) # Allocate before applying, and optionally initialize if not brick.allocated: brick.allocate() # Annotate all the input variables which are Theano variables for i, input_ in enumerate(args): if isinstance(input_, tensor.Variable): if i < len(args_names): name = args_names[i] else: name = "{}_{}".format(varargs_name, i - len(args_names)) args[i] = copy_and_tag(input_, brick, call, INPUT, self.name, name) for name, input_ in kwargs.items(): if isinstance(input_, tensor.Variable): kwargs[name] = copy_and_tag(input_, brick, call, INPUT, self.name, name) # Run the application method on the annotated variables last_brick = self.call_stack[-1] if self.call_stack else None if (last_brick and brick is not last_brick and brick not in last_brick.children): warnings.warn('Brick ' + str(self.call_stack[-1]) + ' tries ' 'to call brick ' + str(self.brick) + ' which ' 'is not in the list of its children. This could ' 'be caused because an @application decorator is ' 'missing.') self.call_stack.append(brick) try: outputs = self.application_function(brick, *args, **kwargs) outputs = pack(outputs) finally: self.call_stack.pop() # Rename and annotate output variables for i, output in enumerate(outputs): if isinstance(output, tensor.Variable): try: name = bound_application.outputs[i] except AttributeError: name = "output_{}".format(i) except IndexError: reraise_as(ValueError("Unexpected outputs")) # TODO Tag with dimensions, axes, etc. for error-checking outputs[i] = copy_and_tag(outputs[i], brick, call, OUTPUT, self.name, name) # Return values if as_list: return outputs if as_dict: return OrderedDict(zip(bound_application.outputs, outputs)) return unpack(outputs)
def find_extension(self, name): """Find an extension with a given name.""" return unpack([extension for extension in self.extensions if extension.name == name], singleton=True)
def apply(self, bound_application, *args, **kwargs): as_dict = kwargs.pop('as_dict', False) as_list = kwargs.pop('as_list', False) if as_list and as_dict: raise ValueError brick = bound_application.brick # Find the names of the inputs to the application method args_names, varargs_name, _, _ = inspect.getargspec( self.application_function) args_names = args_names[1:] # Construct the ApplicationCall, used to store data in for this call call = ApplicationCall(brick, bound_application) args = list(args) if 'application' in args_names: args.insert(args_names.index('application'), bound_application) if 'application_call' in args_names: args.insert(args_names.index('application_call'), call) # Allocate before applying, and optionally initialize if not brick.allocated: brick.allocate() if not brick.initialized and not brick.lazy: brick.initialize() # Annotate all the input variables which are Theano variables def copy_and_tag(variable, role, name): """Helper method to copy a variable and annotate it.""" copy = variable.copy() # Theano name copy.name = _variable_name(brick.name, self.name, name) add_annotation(copy, brick) add_annotation(copy, call) # Blocks name copy.tag.name = name add_role(copy, role) return copy for i, input_ in enumerate(args): if isinstance(input_, tensor.Variable): if i < len(args_names): name = args_names[i] else: name = "{}_{}".format(varargs_name, i - len(args_names)) args[i] = copy_and_tag(input_, INPUT, name) for name, input_ in kwargs.items(): if isinstance(input_, tensor.Variable): kwargs[name] = copy_and_tag(input_, INPUT, name) # Run the application method on the annotated variables if self.call_stack and brick is not self.call_stack[-1] and \ brick not in self.call_stack[-1].children: raise ValueError('Brick ' + str(self.call_stack[-1]) + ' tries ' 'to call brick ' + str(self.brick) + ' which ' 'is not in the list of its children.') self.call_stack.append(brick) try: outputs = self.application_function(brick, *args, **kwargs) outputs = pack(outputs) finally: self.call_stack.pop() # Rename and annotate output variables for i, output in enumerate(outputs): if isinstance(output, tensor.Variable): try: name = bound_application.outputs[i] except AttributeError: name = "output_{}".format(i) except IndexError: reraise_as(ValueError("Unexpected outputs")) # TODO Tag with dimensions, axes, etc. for error-checking outputs[i] = copy_and_tag(outputs[i], OUTPUT, name) # Return values if as_list: return outputs if as_dict: return OrderedDict(zip(bound_application.outputs, outputs)) return unpack(outputs)