class TrainingDataMonitoring(SimpleExtension, MonitoringExtension):
    """Monitors values of Theano variables on training batches.

    Use this extension to monitor a quantity on every training batch
    cheaply. It integrates with the training algorithm in order to avoid
    recomputing same things several times. For instance, if you are
    training a network and you want to log the norm of the gradient on
    every batch, the backpropagation will only be done once.  By
    controlling the frequency with which the :meth:`do` method is called,
    you can aggregate the monitored variables, e.g. only log the gradient
    norm average over an epoch.

    Parameters
    ----------
    variables : list of :class:`~tensor.TensorVariable`
        The variables to monitor. The variable names are used as record
        names in the logs.

    Notes
    -----
    All the monitored variables are evaluated _before_ the parameter
    update.

    Requires the training algorithm to be an instance of
    :class:`.DifferentiableCostMinimizer`.

    """
    def __init__(self, variables, **kwargs):
        kwargs.setdefault("before_training", True)
        super(TrainingDataMonitoring, self).__init__(**kwargs)
        self._buffer = AggregationBuffer(variables, use_take_last=True)
        self._last_time_called = -1

    def do(self, callback_name, *args):
        """Initializes the buffer or commits the values to the log.

        What this method does depends on from what callback it is called.
        When called within `before_training`, it initializes the
        aggregation buffer and instructs the training algorithm what
        additional computations should be carried at each step by adding
        corresponding updates to it. In all other cases it writes
        aggregated values of the monitored variables to the log.

        """
        if callback_name == 'before_training':
            if not isinstance(self.main_loop.algorithm,
                              DifferentiableCostMinimizer):
                raise ValueError
            self.main_loop.algorithm.add_updates(
                self._buffer.accumulation_updates)
            self._buffer.initialize_aggregators()
        else:
            if (self.main_loop.status['iterations_done'] ==
                    self._last_time_called):
                raise Exception("TrainingDataMonitoring.do should be invoked"
                                " no more than once per iteration")
            self._last_time_called = self.main_loop.status['iterations_done']
            self.add_records(self.main_loop.log,
                             self._buffer.get_aggregated_values().items())
            self._buffer.initialize_aggregators()
示例#2
0
class TrainingDataMonitoring(SimpleExtension, MonitoringExtension):
    """Monitors values of Theano variables on training batches.

    Use this extension to monitor a quantity on every training batch
    cheaply. It integrates with the training algorithm in order to avoid
    recomputing same things several times. For instance, if you are
    training a network and you want to log the norm of the gradient on
    every batch, the backpropagation will only be done once.  By
    controlling the frequency with which the :meth:`do` method is called,
    you can aggregate the monitored variables, e.g. only log the gradient
    norm average over an epoch.

    Parameters
    ----------
    variables : list of :class:`~tensor.TensorVariable` or
                  :class:`~blocks.monitoring.aggregation.MonitoredQuantity`
        The variables or non-Theano quantities to monitor.
        The variable names are used as record names in the logs.

    Notes
    -----
    All the monitored variables are evaluated _before_ the parameter
    update.

    Requires the training algorithm to be an instance of
    :class:`.DifferentiableCostMinimizer`.

    """
    def __init__(self, variables, **kwargs):
        kwargs.setdefault("before_training", True)
        super(TrainingDataMonitoring, self).__init__(**kwargs)
        self.add_condition(['after_batch'], arguments=('just_aggregate',))

        self._non_variables = []
        self._variables = []
        for variable_or_not in variables:
            if isinstance(variable_or_not, theano.Variable):
                self._variables.append(variable_or_not)
            elif isinstance(variable_or_not, MonitoredQuantity):
                self._non_variables.append(variable_or_not)
            else:
                raise ValueError("can not monitor {}".format(variable_or_not))

        self._non_variables = MonitoredQuantityBuffer(self._non_variables)
        self._required_for_non_variables = AggregationBuffer(
            [take_last(v) for v in self._non_variables.requires])
        self._variables = AggregationBuffer(
            self._variables, use_take_last=True)
        self._last_time_called = -1

    def do(self, callback_name, *args):
        """Initializes the buffer or commits the values to the log.

        What this method does depends on from what callback it is called
        and with which arguments.  When called within `before_training`, it
        initializes the aggregation buffer and instructs the training
        algorithm what additional computations should be carried at each
        step by adding corresponding updates to it. In most_other cases it
        writes aggregated values of the monitored variables to the log. An
        exception is when an argument `just_aggregate` is given: in this
        cases it updates the values of monitored non-Theano quantities, but
        does not write anything to the log.

        """
        data, args = self.parse_args(callback_name, args)
        if callback_name == 'before_training':
            if not isinstance(self.main_loop.algorithm,
                              DifferentiableCostMinimizer):
                raise ValueError
            self.main_loop.algorithm.add_updates(
                self._variables.accumulation_updates)
            self.main_loop.algorithm.add_updates(
                self._required_for_non_variables.accumulation_updates)
            self._variables.initialize_aggregators()
            self._required_for_non_variables.initialize_aggregators()
            self._non_variables.initialize_quantities()
        else:
            # When called first time at any iterations, update
            # monitored non-Theano quantities
            if (self.main_loop.status['iterations_done'] >
                    self._last_time_called):
                self._non_variables.aggregate_quantities(
                    list(self._required_for_non_variables
                         .get_aggregated_values().values()))
                self._required_for_non_variables.initialize_aggregators()
                self._last_time_called = (
                    self.main_loop.status['iterations_done'])
            # If only called to update non-Theano quantities,
            # do just that
            if args == ('just_aggregate',):
                return
            # Otherwise, also output current values of from the accumulators
            # to the log.
            self.add_records(
                self.main_loop.log,
                self._variables.get_aggregated_values().items())
            self._variables.initialize_aggregators()
            self.add_records(
                self.main_loop.log,
                self._non_variables.get_aggregated_values().items())
            self._non_variables.initialize_quantities()
示例#3
0
class TrainingDataMonitoring(SimpleExtension, MonitoringExtension):
    """Monitors values of Theano variables on training batches.

    Use this extension to monitor a quantity on every training batch
    cheaply. It integrates with the training algorithm in order to avoid
    recomputing same things several times. For instance, if you are
    training a network and you want to log the norm of the gradient on
    every batch, the backpropagation will only be done once.  By
    controlling the frequency with which the :meth:`do` method is called,
    you can aggregate the monitored variables, e.g. only log the gradient
    norm average over an epoch.

    Parameters
    ----------
    variables : list of :class:`~tensor.TensorVariable` or
                  :class:`~blocks.monitoring.aggregation.MonitoredQuantity`
        The variables or non-Theano quantities to monitor.
        The variable names are used as record names in the logs.

    Notes
    -----
    All the monitored variables are evaluated _before_ the parameter
    update.

    Requires the training algorithm to be an instance of
    :class:`.DifferentiableCostMinimizer`.

    """
    def __init__(self, variables, **kwargs):
        kwargs.setdefault("before_training", True)
        super(TrainingDataMonitoring, self).__init__(**kwargs)
        self.add_condition(['after_batch'], arguments=('just_aggregate', ))

        self._non_variables = []
        self._variables = []
        for variable_or_not in variables:
            if isinstance(variable_or_not, theano.Variable):
                self._variables.append(variable_or_not)
            elif isinstance(variable_or_not, MonitoredQuantity):
                self._non_variables.append(variable_or_not)
            else:
                raise ValueError("can not monitor {}".format(variable_or_not))

        self._non_variables = MonitoredQuantityBuffer(self._non_variables)
        self._required_for_non_variables = AggregationBuffer(
            [take_last(v) for v in self._non_variables.requires])
        self._variables = AggregationBuffer(self._variables,
                                            use_take_last=True)
        self._last_time_called = -1

    def do(self, callback_name, *args):
        """Initializes the buffer or commits the values to the log.

        What this method does depends on from what callback it is called
        and with which arguments.  When called within `before_training`, it
        initializes the aggregation buffer and instructs the training
        algorithm what additional computations should be carried at each
        step by adding corresponding updates to it. In most_other cases it
        writes aggregated values of the monitored variables to the log. An
        exception is when an argument `just_aggregate` is given: in this
        cases it updates the values of monitored non-Theano quantities, but
        does not write anything to the log.

        """
        data, args = self.parse_args(callback_name, args)
        if callback_name == 'before_training':
            if not isinstance(self.main_loop.algorithm,
                              DifferentiableCostMinimizer):
                raise ValueError
            self.main_loop.algorithm.add_updates(
                self._variables.accumulation_updates)
            self.main_loop.algorithm.add_updates(
                self._required_for_non_variables.accumulation_updates)
            self._variables.initialize_aggregators()
            self._required_for_non_variables.initialize_aggregators()
            self._non_variables.initialize_quantities()
        else:
            # When called first time at any iterations, update
            # monitored non-Theano quantities
            if (self.main_loop.status['iterations_done'] >
                    self._last_time_called):
                self._non_variables.aggregate_quantities(
                    list(self._required_for_non_variables.
                         get_aggregated_values().values()))
                self._required_for_non_variables.initialize_aggregators()
                self._last_time_called = (
                    self.main_loop.status['iterations_done'])
            # If only called to update non-Theano quantities,
            # do just that
            if args == ('just_aggregate', ):
                return
            # Otherwise, also output current values of from the accumulators
            # to the log.
            self.add_records(self.main_loop.log,
                             self._variables.get_aggregated_values().items())
            self._variables.initialize_aggregators()
            self.add_records(
                self.main_loop.log,
                self._non_variables.get_aggregated_values().items())
            self._non_variables.initialize_quantities()
示例#4
0
class SaveImages(SimpleExtension):
    def __init__(self,
                 picsources=None,
                 pattern=None,
                 title=None,
                 data=None,
                 graph=None,
                 graph_len=None,
                 unit_order=None,
                 **kwargs):
        kwargs.setdefault("before_training", True)
        self.picsources = picsources
        self.count = 0
        if pattern is None:
            pattern = 'pics/syn/%s_%04d.jpg'
        self.pattern = pattern
        self.unit_order = unit_order
        self.title = title
        self.data = data
        self.graph = graph
        self.graph_len = graph_len
        self.graph_data = None
        # Now create an AggregationBuffer for theano variables to monitor
        self.variables = AggregationBuffer(data, use_take_last=True)
        super(SaveImages, self).__init__(**kwargs)

    def do(self, callback_name, *args):
        self.parse_args(callback_name, args)
        if (callback_name == 'before_training'):
            self.main_loop.algorithm.add_updates(
                self.variables.accumulation_updates)
            self.variables.initialize_aggregators()
        else:
            title = self.title
            if self.data:
                values = dict(
                    (k, float(v))
                    for k, v in self.variables.get_aggregated_values().items())
                values['i'] = self.count
                title = title.format(**values)
            graph = None
            if self.graph:
                if self.graph_data is None:
                    self.graph_data = numpy.array(())
                self.graph_data = numpy.append(self.graph_data,
                                               values[self.graph])
                graph = self.graph_data / self.graph_data.max()
            filename = self.pattern % ('composite', self.count)
            self.count += 1
            picdata = [ps.get_picdata() for ps in self.picsources]
            self.save_composite_image(title=title,
                                      graph=graph,
                                      graph_len=self.graph_len,
                                      picdata=picdata,
                                      unit_order=self.unit_order,
                                      filename=filename,
                                      aspect_ratio=16.0 / 9.0)
            self.variables.initialize_aggregators()

    def save_composite_image(self,
                             title=None,
                             graph=None,
                             graph_len=None,
                             picdata=None,
                             filename=None,
                             aspect_ratio=None,
                             unit_order=None):
        if filename is None:
            pattern = 'synpic.jpg'
        unit_count = 0
        layer_count = 0
        if graph is not None:
            unit_count += 4  # TODO: make configurable
        if title is not None:
            unit_count += 1
        merged = OrderedDict([(k, [d[k] for d in picdata])
                              for k in picdata[0].keys()])
        unit_width = 0
        for name, d in merged.items():
            for dat in d:
                if len(dat.shape) != 4:
                    raise NotImplementedError('%s has %s dimensions' %
                                              (name, dat.shape))
                unit_count += dat.shape[0]
                unit_width = max(unit_width, dat.shape[1])
            layer_count += 1
        unit_width += 1
        column_height, column_count = plan_grid(unit_count + layer_count,
                                                aspect_ratio, dat.shape[-2:],
                                                (1, unit_width))
        filmstrip = Filmstrip(image_shape=dat.shape[-2:],
                              grid_shape=(column_height,
                                          column_count * unit_width))
        pos = 0
        if graph is not None:
            col, row = divmod(pos, column_height)
            filmstrip.plot_graph((row, col * unit_width + 1),
                                 (4, unit_width - 1), graph, graph_len)
            pos += 4
        if title is not None:
            col, row = divmod(pos, column_height)
            filmstrip.set_text((row, col * unit_width + unit_width // 2),
                               title)
            pos += 1
        for layername, d in merged.items():
            units = d[0].shape[0]
            col, row = divmod(pos, column_height)
            filmstrip.set_text((row, col * unit_width + unit_width // 2),
                               layername)
            pos += 1
            if unit_order:
                ordering = unit_order[layername]
            else:
                ordering = range(units)
            scales = [dat.std() * 5 for dat in d]
            for unit in ordering:
                for dat, scale in zip(d, scales):
                    col, row = divmod(pos, column_height)
                    filmstrip.set_text((row, col * unit_width), "%d:" % unit)
                    im = dat[unit, :, :, :]
                    # imin = im.min()
                    # imax = im.max()
                    # scale = (imax - imin) * 0.7
                    im = im / (scale + 1e-9) + 0.5
                    for label in range(im.shape[0]):
                        filmstrip.set_image(
                            (row, label + 1 + col * unit_width),
                            im[label, :, :])
                    pos += 1
        filmstrip.save(filename)
示例#5
0
class myTrainingDataMonitoring(SimpleExtension, MonitoringExtension):
    def __init__(self, variables, saveEveryXIteration, **kwargs):
        self.saveEveryXIteration = saveEveryXIteration
        kwargs.setdefault("before_training", True)
        super(myTrainingDataMonitoring, self).__init__(**kwargs)
        self.add_condition(['after_batch'], arguments=('just_aggregate', ))

        self._non_variables = []
        self._variables = []
        for variable_or_not in variables:
            if isinstance(variable_or_not, theano.Variable):
                self._variables.append(variable_or_not)
            elif isinstance(variable_or_not, MonitoredQuantity):
                self._non_variables.append(variable_or_not)
            else:
                raise ValueError("can not monitor {}".format(variable_or_not))

        self._non_variables = MonitoredQuantityBuffer(self._non_variables)
        self._required_for_non_variables = AggregationBuffer(
            [take_last(v) for v in self._non_variables.requires])
        self._variables = AggregationBuffer(self._variables,
                                            use_take_last=True)
        self._last_time_called = -1

    def do(self, callback_name, *args):
        data, args = self.parse_args(callback_name, args)
        if callback_name == 'before_training':
            if not isinstance(self.main_loop.algorithm,
                              DifferentiableCostMinimizer):
                raise ValueError
            self.main_loop.algorithm.add_updates(
                self._variables.accumulation_updates)
            self.main_loop.algorithm.add_updates(
                self._required_for_non_variables.accumulation_updates)
            self._variables.initialize_aggregators()
            self._required_for_non_variables.initialize_aggregators()
            self._non_variables.initialize_quantities()
        else:
            log = self.main_loop.log
            iteration = log.status['iterations_done']
            if iteration % self.saveEveryXIteration == 0:
                # When called first time at any iterations, update
                # monitored non-Theano quantities
                if (self.main_loop.status['iterations_done'] >
                        self._last_time_called):
                    self._non_variables.aggregate_quantities(
                        list(self._required_for_non_variables.
                             get_aggregated_values().values()))
                    self._required_for_non_variables.initialize_aggregators()
                    self._last_time_called = (
                        self.main_loop.status['iterations_done'])
                # If only called to update non-Theano quantities,
                # do just that
                if args == ('just_aggregate', ):
                    return
                # Otherwise, also output current values of from the accumulators
                # to the log.
                self.add_records(
                    self.main_loop.log,
                    self._variables.get_aggregated_values().items())
                self._variables.initialize_aggregators()
                self.add_records(
                    self.main_loop.log,
                    self._non_variables.get_aggregated_values().items())
                self._non_variables.initialize_quantities()
class DatasetEvaluator(object):

    """A DatasetEvaluator evaluates many Theano variables or other quantities.
    The DatasetEvaluator provides a do-it-all method, :meth:`evaluate`,
    which computes values of ``variables`` on a dataset.
    Alternatively, methods :meth:`initialize_aggregators`,
    :meth:`process_batch`, :meth:`get_aggregated_values` can be used with a
    custom loop over data.
    The values computed on subsets of the given dataset are aggregated
    using the :class:`AggregationScheme`s provided in the
    `aggregation_scheme` tags. If no tag is given, the value is **averaged
    over minibatches**. However, care is taken to ensure that variables
    which do not depend on data are not unnecessarily recomputed.
    Parameters
    ----------
    variables : list of :class:`~tensor.TensorVariable` and
        :class:`MonitoredQuantity`
        The variable names are used as record names in the logs. Hence, all
        the names must be different.
        Each variable can be tagged with an :class:`AggregationScheme` that
        specifies how the value can be computed for a data set by
        aggregating minibatches.
    updates : list of tuples or :class:`~collections.OrderedDict` or None
        :class:`~tensor.TensorSharedVariable` updates to be performed
        during evaluation. This parameter is only for Theano variables.
        Be careful not to update any model parameters as this is not
        intended to alter your model in any meaningfullway. A typical
        use case of this option arises when the theano function used
        for evaluation contains a call to:function:`~theano.scan` which
        might have returned shared variable updates.
    """

    def __init__(self, variables, mini_batch_size, state_updates,
                 dataset, updates=None):
        theano_variables = []
        monitored_quantities = []
        for variable in variables:
            if isinstance(variable, MonitoredQuantity):
                monitored_quantities.append(variable)
            else:
                theano_variables.append(variable)
        self.theano_variables = theano_variables
        self.monitored_quantities = monitored_quantities
        variable_names = [v.name for v in variables]
        if len(set(variable_names)) < len(variables):
            raise ValueError("variables should have different names")
        self.theano_buffer = AggregationBuffer(theano_variables)
        self.monitored_quantities_buffer = MonitoredQuantityBuffer(
            monitored_quantities)
        self.dataset = dataset
        self.updates = updates
        self.mini_batch_size = mini_batch_size
        self._compile(state_updates)

    def _compile(self, state_updates):
        """Compiles Theano functions.
        .. todo::
            The current compilation method does not account for updates
            attached to `ComputationGraph` elements. Compiling should
            be out-sourced to `ComputationGraph` to deal with it.
        """
        inputs = []
        outputs = []
        updates = None

        givens, f_updates = carry_hidden_state(state_updates,
                                               self.mini_batch_size,
                                               reset=not(has_indices(self.dataset)))

        if self.theano_buffer.accumulation_updates:
            updates = OrderedDict()
            updates.update(self.theano_buffer.accumulation_updates)
            if self.updates:
                updates.update(self.updates)
            inputs += self.theano_buffer.inputs
        inputs += self.monitored_quantities_buffer.inputs
        outputs = self.monitored_quantities_buffer.requires

        if inputs != []:
            self.unique_inputs = list(set(inputs))
            updates.update(f_updates)
            self._accumulate_fun = theano.function(self.unique_inputs,
                                                   outputs,
                                                   givens=givens,
                                                   updates=updates)
        else:
            self._accumulate_fun = None

    def initialize_aggregators(self):
        self.theano_buffer.initialize_aggregators()
        self.monitored_quantities_buffer.initialize()

    def process_batch(self, batch):
        try:
            input_names = [v.name for v in self.unique_inputs]
            batch = dict_subset(batch, input_names)
        except KeyError:
            reraise_as(
                "Not all data sources required for monitoring were"
                " provided. The list of required data sources:"
                " {}.".format(input_names))
        if self._accumulate_fun is not None:
            numerical_values = self._accumulate_fun(**batch)
            self.monitored_quantities_buffer.accumulate_quantities(
                numerical_values)

    def get_aggregated_values(self):
        values = self.theano_buffer.get_aggregated_values()
        values.update(
            self.monitored_quantities_buffer.get_aggregated_values())
        return values

    def evaluate(self, data_stream):
        """Compute the variables over a data stream.
        Parameters
        ----------
        data_stream : instance of :class:`.DataStream`
            The data stream. Only the first epoch of data is used.
        Returns
        -------
        A mapping from record names to the values computed on the provided
        dataset.
        """
        self.initialize_aggregators()
        if self._accumulate_fun is not None:
            for batch in data_stream.get_epoch_iterator(as_dict=True):
                self.process_batch(batch)
        else:
            logger.debug(
                'Only data independent variables were given,'
                'will not iterate the over data!')

        return self.get_aggregated_values()
示例#7
0
class SaveImages(SimpleExtension):
    def __init__(self, picsources=None, pattern=None,
            title=None, data=None, graph=None, graph_len=None,
            unit_order=None, **kwargs):
        kwargs.setdefault("before_training", True)
        self.picsources = picsources
        self.count = 0
        if pattern is None:
            pattern = 'pics/syn/%s_%04d.jpg'
        self.pattern = pattern
        self.unit_order = unit_order
        self.title = title
        self.data = data
        self.graph = graph
        self.graph_len = graph_len
        self.graph_data = None
        # Now create an AggregationBuffer for theano variables to monitor
        self.variables = AggregationBuffer(data, use_take_last=True)
        super(SaveImages, self).__init__(**kwargs)

    def do(self, callback_name, *args):
        self.parse_args(callback_name, args)
        if (callback_name == 'before_training'):
            self.main_loop.algorithm.add_updates(
                    self.variables.accumulation_updates)
            self.variables.initialize_aggregators()
        else:
            title = self.title
            if self.data:
                values = dict((k, float(v))
                    for k, v in self.variables.get_aggregated_values().items())
                values['i'] = self.count
                title = title.format(**values)
            graph = None
            if self.graph:
                if self.graph_data is None:
                    self.graph_data = numpy.array(())
                self.graph_data = numpy.append(
                        self.graph_data, values[self.graph])
                graph = self.graph_data / self.graph_data.max()
            filename = self.pattern % ('composite', self.count)
            self.count += 1
            picdata = [ps.get_picdata() for ps in self.picsources]
            self.save_composite_image(
                    title=title, graph=graph, graph_len=self.graph_len,
                    picdata=picdata, unit_order=self.unit_order,
                    filename=filename, aspect_ratio=16.0/9.0)
            self.variables.initialize_aggregators()

    def save_composite_image(self,
                title=None, graph=None, graph_len=None, picdata=None,
                filename=None, aspect_ratio=None, unit_order=None):
        if filename is None:
            pattern = 'synpic.jpg'
        unit_count = 0
        layer_count = 0
        if graph is not None:
            unit_count += 4 # TODO: make configurable
        if title is not None:
            unit_count += 1
        merged = OrderedDict([
            (k, [d[k] for d in picdata]) for k in picdata[0].keys()])
        unit_width = 0
        for name, d in merged.items():
            for dat in d:
                if len(dat.shape) != 4:
                    raise NotImplementedError('%s has %s dimensions' % (
                        name, dat.shape))
                unit_count += dat.shape[0]
                unit_width = max(unit_width, dat.shape[1])
            layer_count += 1
        unit_width += 1
        column_height, column_count = plan_grid(unit_count + layer_count,
                aspect_ratio, dat.shape[-2:], (1, unit_width))
        filmstrip = Filmstrip(image_shape=dat.shape[-2:],
            grid_shape=(column_height, column_count * unit_width))
        pos = 0
        if graph is not None:
            col, row = divmod(pos, column_height)
            filmstrip.plot_graph((row, col * unit_width + 1),
                (4, unit_width - 1),
                graph, graph_len)
            pos += 4
        if title is not None:
            col, row = divmod(pos, column_height)
            filmstrip.set_text((row, col * unit_width + unit_width // 2),
                    title)
            pos += 1
        for layername, d in merged.items():
            units = d[0].shape[0]
            col, row = divmod(pos, column_height)
            filmstrip.set_text((row, col * unit_width + unit_width // 2),
                    layername)
            pos += 1
            if unit_order:
                ordering = unit_order[layername]
            else:
                ordering = range(units)
            scales = [dat.std() * 5 for dat in d]
            for unit in ordering:
                for dat, scale in zip(d, scales):
                    col, row = divmod(pos, column_height)
                    filmstrip.set_text((row, col * unit_width), "%d:" % unit)
                    im = dat[unit, :, :, :]
                    # imin = im.min()
                    # imax = im.max()
                    # scale = (imax - imin) * 0.7
                    im = im / (scale + 1e-9) + 0.5
                    for label in range(im.shape[0]):
                        filmstrip.set_image((row, label + 1 +
                            col * unit_width), im[label, :, :])
                    pos += 1
        filmstrip.save(filename)
示例#8
0
class DatasetEvaluator(object):
    """A DatasetEvaluator evaluates many Theano variables or other quantities.
    The DatasetEvaluator provides a do-it-all method, :meth:`evaluate`,
    which computes values of ``variables`` on a dataset.
    Alternatively, methods :meth:`initialize_aggregators`,
    :meth:`process_batch`, :meth:`get_aggregated_values` can be used with a
    custom loop over data.
    The values computed on subsets of the given dataset are aggregated
    using the :class:`AggregationScheme`s provided in the
    `aggregation_scheme` tags. If no tag is given, the value is **averaged
    over minibatches**. However, care is taken to ensure that variables
    which do not depend on data are not unnecessarily recomputed.
    Parameters
    ----------
    variables : list of :class:`~tensor.TensorVariable` and
        :class:`MonitoredQuantity`
        The variable names are used as record names in the logs. Hence, all
        the names must be different.
        Each variable can be tagged with an :class:`AggregationScheme` that
        specifies how the value can be computed for a data set by
        aggregating minibatches.
    updates : list of tuples or :class:`~collections.OrderedDict` or None
        :class:`~tensor.TensorSharedVariable` updates to be performed
        during evaluation. This parameter is only for Theano variables.
        Be careful not to update any model parameters as this is not
        intended to alter your model in any meaningfullway. A typical
        use case of this option arises when the theano function used
        for evaluation contains a call to:function:`~theano.scan` which
        might have returned shared variable updates.
    """
    def __init__(self,
                 variables,
                 mini_batch_size,
                 state_updates,
                 updates=None):
        theano_variables = []
        monitored_quantities = []
        for variable in variables:
            if isinstance(variable, MonitoredQuantity):
                monitored_quantities.append(variable)
            else:
                theano_variables.append(variable)
        self.theano_variables = theano_variables
        self.monitored_quantities = monitored_quantities
        variable_names = [v.name for v in variables]
        if len(set(variable_names)) < len(variables):
            raise ValueError("variables should have different names")
        self.theano_buffer = AggregationBuffer(theano_variables)
        self.monitored_quantities_buffer = MonitoredQuantityBuffer(
            monitored_quantities)
        self.updates = updates
        self.mini_batch_size = mini_batch_size
        self._compile(state_updates)

    def _compile(self, state_updates):
        """Compiles Theano functions.
        .. todo::
            The current compilation method does not account for updates
            attached to `ComputationGraph` elements. Compiling should
            be out-sourced to `ComputationGraph` to deal with it.
        """
        inputs = []
        outputs = []
        updates = None

        state_vars = [
            theano.shared(
                numpy.zeros((self.mini_batch_size, v.shape[1].eval()),
                            dtype=numpy.float32), v.name + '-gen')
            for v, _ in state_updates
        ]
        givens = [(v, x) for (v, _), x in zip(state_updates, state_vars)]
        f_updates = [(x, upd)
                     for x, (_, upd) in zip(state_vars, state_updates)]

        if self.theano_buffer.accumulation_updates:
            updates = OrderedDict()
            updates.update(self.theano_buffer.accumulation_updates)
            if self.updates:
                updates.update(self.updates)
            inputs += self.theano_buffer.inputs
        inputs += self.monitored_quantities_buffer.inputs
        outputs = self.monitored_quantities_buffer.requires

        if inputs != []:
            self.unique_inputs = list(set(inputs))
            updates.update(f_updates)
            self._accumulate_fun = theano.function(self.unique_inputs,
                                                   outputs,
                                                   givens=givens,
                                                   updates=updates)
        else:
            self._accumulate_fun = None

    def initialize_aggregators(self):
        self.theano_buffer.initialize_aggregators()
        self.monitored_quantities_buffer.initialize()

    def process_batch(self, batch):
        try:
            input_names = [v.name for v in self.unique_inputs]
            batch = dict_subset(batch, input_names)
        except KeyError:
            reraise_as("Not all data sources required for monitoring were"
                       " provided. The list of required data sources:"
                       " {}.".format(input_names))
        if self._accumulate_fun is not None:
            numerical_values = self._accumulate_fun(**batch)
            self.monitored_quantities_buffer.accumulate_quantities(
                numerical_values)

    def get_aggregated_values(self):
        values = self.theano_buffer.get_aggregated_values()
        values.update(self.monitored_quantities_buffer.get_aggregated_values())
        return values

    def evaluate(self, data_stream):
        """Compute the variables over a data stream.
        Parameters
        ----------
        data_stream : instance of :class:`.DataStream`
            The data stream. Only the first epoch of data is used.
        Returns
        -------
        A mapping from record names to the values computed on the provided
        dataset.
        """
        self.initialize_aggregators()
        if self._accumulate_fun is not None:
            for batch in data_stream.get_epoch_iterator(as_dict=True):
                self.process_batch(batch)
        else:
            logger.debug('Only data independent variables were given,'
                         'will not iterate the over data!')

        return self.get_aggregated_values()