<commit_before>"""
Classes representing cost functions.

Currently, these are primarily used to specify the objective function for
the SGD and BGD training algorithms.
"""

import functools
import logging
import warnings
from itertools import izip

import theano.tensor as T
from theano.compat.python2x import OrderedDict

from pylearn2.utils import safe_zip
from pylearn2.utils import safe_union
from pylearn2.space import CompositeSpace, NullSpace
from pylearn2.utils.data_specs import DataSpecsMapping


logger = logging.getLogger(__name__)

class Cost(object):
    """
    Represents an objective function to be minimized by some
    `TrainingAlgorithm`.

    Notes
    -----
    While functions may be represented just as theano graphs,
    this class allows us to add extra functionality. The
    `get_gradients` methods allows us to use a method other
    than `theano.tensor.grad` to compute the gradient of the
    cost function. This enables using approximate gradients
    of cost functions that are not differentiable or whose
    true gradient is computationally intractable. Additionally,
    the get_monitoring_channels method allows monitoring of
    quantities that are useful when training with a given
    objective function (such as termination criteria that are
    usually used with the function).
    """

    # TODO: remove this when it is no longer necessary, it should be
    # mostly phased out due to the new data_specs interface.
    # If True, the data argument to expr and get_gradients must be a
    # (X, Y) pair, and Y cannot be None.
    supervised = False

    def expr(self, model, data, ** kwargs):
        """
        Returns a theano expression for the cost function.

        Returns a symbolic expression for a cost function applied to the
        minibatch of data.
        Optionally, may return None. This represents that the cost function
        is intractable but may be optimized via the get_gradients method.

        Parameters
        ----------
        model : a pylearn2 Model instance
        data : a batch in cost.get_data_specs() form
        kwargs : dict
            Optional extra arguments. Not used by the base class.
        """
        raise NotImplementedError(str(type(self)) + " does not implement "
                                  "expr.")

    def get_gradients(self, model, data, ** kwargs):
        """
        Provides the gradients of the cost function with respect to the model
        parameters.

        These are not necessarily those obtained by theano.tensor.grad
        --you may wish to use approximate or even intentionally incorrect
        gradients in some cases.

        Parameters
        ----------
        model : a pylearn2 Model instance
        data : a batch in cost.get_data_specs() form
        kwargs : dict
            Optional extra arguments, not used by the base class.

        Returns
        -------
        gradients : OrderedDict
            a dictionary mapping from the model's parameters
            to their gradients
            The default implementation is to compute the gradients
            using T.grad applied to the value returned by expr.
            However, subclasses may return other values for the gradient.
            For example, an intractable cost may return a sampling-based
            approximation to its gradient.
        updates : OrderedDict
            a dictionary mapping shared variables to updates that must
            be applied to them each time these gradients are computed.
            This is to facilitate computation of sampling-based approximate
            gradients.
            The parameters should never appear in the updates dictionary.
            This would imply that computing their gradient changes
            their value, thus making the gradient value outdated.
        """

        try:
            cost = self.expr(model=model, data=data, **kwargs)
        except TypeError, e:
            # If anybody knows how to add type(self) to the exception message
            # but still preserve the stack trace, please do so
            # The current code does neither
            e.message += " while calling " + str(type(self)) + ".expr"
            logger.error(type(self))
            logger.error(e.message)
            raise e

        if cost is None:
            raise NotImplementedError(str(type(self)) +
                                      " represents an intractable cost and "
                                      "does not provide a gradient "
                                      "approximation scheme.")

        params = list(model.get_params())

        grads = T.grad(cost, params, disconnected_inputs='ignore')

        gradients = OrderedDict(izip(params, grads))

        updates = OrderedDict()

        return gradients, updates

    def get_monitoring_channels(self, model, data, **kwargs):
        """
        .. todo::

            WRITEME

        .. todo::

            how do you do prereqs in this setup? (I think PL changed
            it, not sure if there still is a way in this context)

        Returns a dictionary mapping channel names to expressions for
        channel values.

        Parameters
        ----------
        model : Model
            the model to use to compute the monitoring channels
        data : batch
            (a member of self.get_data_specs()[0])
            symbolic expressions for the monitoring data
        kwargs : dict
            used so that custom algorithms can use extra variables
            for monitoring.

        Returns
        -------
        rval : dict
            Maps channels names to expressions for channel values.
        """
        self.get_data_specs(model)[0].validate(data)
        return OrderedDict()

    def get_fixed_var_descr(self, model, data):
        """
        Subclasses should override this if they need variables held
        constant across multiple updates to a minibatch.

        TrainingAlgorithms that do multiple updates to a minibatch should
        respect this. See the FixedVarDescr class for details.

        Parameters
        ----------
        model : Model
        data : theano.gof.Variable or tuple
            A valid member of the Space used to train `model` with this
            cost.

        Returns
        -------
        fixed_var_descr : FixedVarDescr
            A description of how to hold the necessary variables constant
        """
        self.get_data_specs(model)[0].validate(data)
        fixed_var_descr = FixedVarDescr()
        return fixed_var_descr

    def get_data_specs(self, model):
        """
        Returns a specification of the Space the data should lie in and
        its source (what part of the dataset it should come from).

        Parameters
        ----------
        model : Model
            The model to train with this cost

        Returns
        -------
        data_specs : tuple
            The tuple should be of length two.
            The first element of the tuple should be a Space (possibly a
            CompositeSpace) describing how to format the data.
            The second element of the tuple describes the source of the
            data. It probably should be a string or nested tuple of strings.

        See Also
        --------
        Rather than implementing this method yourself, you probably want
        to just inherit from `DefaultDataSpecsMixin` or
        `NullDataSpecsMixin`.

        Notes
        -----
        .. todo

            figure out return format for sure. PL seems to have documented
            this method incorrectly.

        """
        raise NotImplementedError(str(type(self)) + " does not implement " +
                                  "get_data_specs.")

class SumOfCosts(Cost):
    """<commit_msg>change docstring to avoid triggering bug in Sphinx<commit_after>"""
Classes representing cost functions.

Currently, these are primarily used to specify the objective function for
the SGD and BGD training algorithms.
"""

import functools
import logging
import warnings
from itertools import izip

import theano.tensor as T
from theano.compat.python2x import OrderedDict

from pylearn2.utils import safe_zip
from pylearn2.utils import safe_union
from pylearn2.space import CompositeSpace, NullSpace
from pylearn2.utils.data_specs import DataSpecsMapping


logger = logging.getLogger(__name__)

class Cost(object):
    """
    Represents an objective function to be minimized by some
    `TrainingAlgorithm`.

    Notes
    -----
    While functions may be represented just as theano graphs,
    this class allows us to add extra functionality. The
    `get_gradients` methods allows us to use a method other
    than `theano.tensor.grad` to compute the gradient of the
    cost function. This enables using approximate gradients
    of cost functions that are not differentiable or whose
    true gradient is computationally intractable. Additionally,
    the get_monitoring_channels method allows monitoring of
    quantities that are useful when training with a given
    objective function (such as termination criteria that are
    usually used with the function).
    """

    # TODO: remove this when it is no longer necessary, it should be
    # mostly phased out due to the new data_specs interface.
    # If True, the data argument to expr and get_gradients must be a
    # (X, Y) pair, and Y cannot be None.
    supervised = False

    def expr(self, model, data, ** kwargs):
        """
        Returns a theano expression for the cost function.

        Returns a symbolic expression for a cost function applied to the
        minibatch of data.
        Optionally, may return None. This represents that the cost function
        is intractable but may be optimized via the get_gradients method.

        Parameters
        ----------
        model : a pylearn2 Model instance
        data : a batch in cost.get_data_specs() form
        kwargs : dict
            Optional extra arguments. Not used by the base class.
        """
        raise NotImplementedError(str(type(self)) + " does not implement "
                                  "expr.")

    def get_gradients(self, model, data, ** kwargs):
        """
        Provides the gradients of the cost function with respect to the model
        parameters.

        These are not necessarily those obtained by theano.tensor.grad
        --you may wish to use approximate or even intentionally incorrect
        gradients in some cases.

        Parameters
        ----------
        model : a pylearn2 Model instance
        data : a batch in cost.get_data_specs() form
        kwargs : dict
            Optional extra arguments, not used by the base class.

        Returns
        -------
        gradients : OrderedDict
            a dictionary mapping from the model's parameters
            to their gradients
            The default implementation is to compute the gradients
            using T.grad applied to the value returned by expr.
            However, subclasses may return other values for the gradient.
            For example, an intractable cost may return a sampling-based
            approximation to its gradient.
        updates : OrderedDict
            a dictionary mapping shared variables to updates that must
            be applied to them each time these gradients are computed.
            This is to facilitate computation of sampling-based approximate
            gradients.
            The parameters should never appear in the updates dictionary.
            This would imply that computing their gradient changes
            their value, thus making the gradient value outdated.
        """

        try:
            cost = self.expr(model=model, data=data, **kwargs)
        except TypeError, e:
            # If anybody knows how to add type(self) to the exception message
            # but still preserve the stack trace, please do so
            # The current code does neither
            e.message += " while calling " + str(type(self)) + ".expr"
            logger.error(type(self))
            logger.error(e.message)
            raise e

        if cost is None:
            raise NotImplementedError(str(type(self)) +
                                      " represents an intractable cost and "
                                      "does not provide a gradient "
                                      "approximation scheme.")

        params = list(model.get_params())

        grads = T.grad(cost, params, disconnected_inputs='ignore')

        gradients = OrderedDict(izip(params, grads))

        updates = OrderedDict()

        return gradients, updates

    def get_monitoring_channels(self, model, data, **kwargs):
        """
        .. todo::

            WRITEME

        .. todo::

            how do you do prereqs in this setup? (I think PL changed
            it, not sure if there still is a way in this context)

        Returns a dictionary mapping channel names to expressions for
        channel values.

        Parameters
        ----------
        model : Model
            the model to use to compute the monitoring channels
        data : batch
            (a member of self.get_data_specs()[0])
            symbolic expressions for the monitoring data
        kwargs : dict
            used so that custom algorithms can use extra variables
            for monitoring.

        Returns
        -------
        rval : dict
            Maps channels names to expressions for channel values.
        """
        self.get_data_specs(model)[0].validate(data)
        return OrderedDict()

    def get_fixed_var_descr(self, model, data):
        """
        Subclasses should override this if they need variables held
        constant across multiple updates to a minibatch.

        TrainingAlgorithms that do multiple updates to a minibatch should
        respect this. See the FixedVarDescr class for details.

        Parameters
        ----------
        model : Model
        data : theano.gof.Variable or tuple
            A valid member of the Space used to train `model` with this
            cost.

        Returns
        -------
        fixed_var_descr : FixedVarDescr
            A description of how to hold the necessary variables constant
        """
        self.get_data_specs(model)[0].validate(data)
        fixed_var_descr = FixedVarDescr()
        return fixed_var_descr

    def get_data_specs(self, model):
        """
        Returns a specification of the Space the data should lie in and
        its source (what part of the dataset it should come from).

        Parameters
        ----------
        model : Model
            The model to train with this cost

        Returns
        -------
        data_specs : tuple
            The tuple should be of length two.
            The first element of the tuple should be a Space (possibly a
            CompositeSpace) describing how to format the data.
            The second element of the tuple describes the source of the
            data. It probably should be a string or nested tuple of strings.

        See Also
        --------
        For many common cases, rather than implementing this method
        yourself, you probably want
        to just inherit from `DefaultDataSpecsMixin` or NullDataSpecsMixin.

        Notes
        -----
        .. todo

            figure out return format for sure. PL seems to have documented
            this method incorrectly.

        """
        raise NotImplementedError(str(type(self)) + " does not implement " +
                                  "get_data_specs.")

class SumOfCosts(Cost):
    """
{'fraction_non_alphanumeric': 0.05854187946884576, 'fraction_numerical': 0.0012768130745658835, 'mean_word_length': 1.9412316935786706, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1035419', 'n_tokens_mistral': 3686, 'n_tokens_neox': 3567, 'n_words': 1651}