Exemple #1
0
class NeuralLayer(object):
    """
    Clase básica para modelar una capa de neuronas que compone una red neuronal.
    Contiene sus "neuronas" representadas por pesos sinápticos **w** y **b**,
    además de una función de activación asociada para dichos pesos.

    Una correcta inicialización de los pesos sinápticos está muy ligada a la función de activación elegida:

    * Por defecto, los pesos sinápticos se inicializan con una distribución uniforme \
        con media *0* y varianza :math:`\\tfrac{2.0}{\sqrt{n_{in}}}`, \
        lo cual da buenos resultados especialmente usando ReLUs.
    * Para la función *Tanh* se muestrea sobre una distribución uniforme \
        en el rango :math:`\pm \sqrt{\\frac{6}{n_{in}+n_{out}}}`.
    * Para la *Sigmoid* en el rango :math:`\pm 4.0 \sqrt{\\frac{6}{n_{in}+n_{out}}}`.

    :param n_in: int, dimensión de la entrada.
    :param n_out: int, dimensión de la salida.
    :param activation: string, key de alguna función de activación soportada en :mod:`~learninspy.core.activations`.
    :param distributed: si es True, indica que se utilicen arreglos distribuidos para **w** y **b**.
    :param w: :class:`.LocalNeurons`, matriz de pesos sinápticos. Si es *None*, se crea por defecto.
    :param b: :class:`.LocalNeurons`, vector de pesos bias. Si es *None*, se crea por defecto.
    :param rng: si es *None*, se crea un generador de números aleatorios
     mediante una instancia **numpy.random.RandomState**.

    .. note:: el parámetro *distributed* no tiene efecto, ya que el uso de arreglos distribuidos
       se deja para un próximo release.

    >>> n_in, n_out = (10, 5)
    >>> layer = NeuralLayer(n_in, n_out, activation='Tanh')
    >>> x = np.random.rand(n_in)
    >>> out = layer.output(x)
    >>> len(out)
    5

    """

    def __init__(self, n_in, n_out, activation='ReLU', distributed=False, w=None, b=None, rng=None):
        self.n_out = n_out
        self.n_in = n_in
        self.activation = act.fun_activation[activation]
        self.activation_d = act.fun_activation_d[activation]
        distributed = False  # TODO completar esta funcionalidad

        if rng is None:
            rng = np.random.RandomState(123)
        self.rng = rng
        self.rnd_state = self.rng.get_state()

        self.shape_w = n_out, n_in
        self.shape_b = n_out, 1

        # Recomendaciones de http://cs231n.github.io/neural-networks-2/ y http://deeplearning.net/tutorial/mlp.html#mlp
        # TODO: ver si conviene dejar acá la inicializ de pesos, o en core.neurons (en términos de legibilidad)
        if w is None:
            if activation is "Tanh":
                w = np.asarray(
                    self.rng.uniform(
                        low=-np.sqrt(6.0 / (n_in + n_out)),
                        high=+np.sqrt(6.0 / (n_in + n_out)),
                        size=self.shape_w),
                    dtype=np.dtype(float)
                )
            elif activation is "Sigmoid":
                w = np.asarray(
                    self.rng.uniform(
                        low=-np.sqrt(6.0 / (n_in + n_out))*4.0,
                        high=+np.sqrt(6.0 / (n_in + n_out))*4.0,
                        size=self.shape_w),
                    dtype=np.dtype(float)
                )
            else:
                w = self.rng.randn(*self.shape_w) * np.sqrt(2.0/n_in)

        if b is None:
            b = np.zeros(self.shape_b, dtype=np.dtype(float))

        # TODO weights_T era p/ poder hacer operaciones distribuidas, pero se deja como TBC la class DistributedNeurons
        assert distributed is False, logger.error("DistributedNeurons will be implemented soon ...")
        self.weights = LocalNeurons(w, self.shape_w)
        #self.weights_T = LocalNeurons(w.transpose(), self.shape_w[::-1])
        self.bias = LocalNeurons(b, self.shape_b)

    def __div__(self, other):
        self.weights /= other
        self.bias /= other
        return self

    def __mul__(self, other):
        self.weights *= other
        self.bias *= other
        return self

    def l1(self):
        """
        Norma **L1** sobre la matriz **w** de pesos sinápticos,
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.l1`.

        Por lo tanto, se retorna el resultado de aplicar la norma y el gradiente de la misma.

        :return: tuple de float, :class:`~learninspy.core.neurons.LocalNeurons`
        """
        return self.weights.l1()

    def l2(self):
        """
        Norma **L2** sobre la matriz **w** de pesos sinápticos,
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.l2`.

        Por lo tanto, se retorna el resultado de aplicar la norma y el gradiente de la misma.

        :return: tuple de float, :class:`~learninspy.core.neurons.LocalNeurons`
        """
        return self.weights.l2()

    def output(self, x, grad=False):
        """
        Salida de la capa neuronal. Se toma una entrada :math:`x \in \Re^{n_{in}}`, se pondera con los
        pesos sinápticos **W** y el bias **b**, y luego se aplica la función de activación **f** para retornar como
        resultado:

        :math:`a = f(Wx + b), \quad a' = f'(Wx + b)`

        :param x: **numpy.ndarray**, vector de entrada
        :param grad: Si es *True*, se retorna además el gradiente de la salida.
        :return: **numpy.ndarray**, o tupla de ellos si *grad* es True.
        """
        wx = self.weights.mul_array(x)
        z = wx.sum_array(self.bias)
        a = z.activation(self.activation)
        if grad is True:
            d_a = z.activation(self.activation_d)
            a = (a, d_a)
        return a

    # Basado en http://cs231n.github.io/neural-networks-2/
    def dropoutput(self, x, p, grad=True):
        """
        Salida de la capa neuronal, luego de aplicar la regularización de los pesos sinápticos por Dropout
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.dropout`.

        :param x: numpy.ndarray, vector de entrada
        :param p: float, tal que :math:`0<p<1`
        :param grad: Si es *True*, se retorna además el gradiente de la salida.
        :return: numpy.ndarray, (o tuple de  numpy.ndarray, numpy.ndarray si *grad* es *True*),
         numpy.ndarray correspondiente a la máscara binaria utilizada en el DropOut.

        .. note:: En las predicciones de la red no se debe efectuar Dropout.
        """
        self.rng.set_state(self.rnd_state)  # Para que sea reproducible
        out = self.output(x, grad)
        if grad is True:
            a, d_a = out
            a, mask = a.dropout(p, self.rng.randint(500))  # randint es para generar un nuevo seed (reproducible)
            out = a, d_a
        else:
            out, mask = out.dropout(p, self.rng.randint(500))  # TODO: en que caso no se necesitaria el grad?
        return out, mask  # Devuelvo ademas la mascara utilizada, para el backprop

    def update(self, step_w, step_b):  # Actualiza sumando los argumentos w y b a los respectivos pesos
        """
        Se actualizan los arreglos **w** y **b** sumando respectivamente los incrementos
        dados por los parámetros recibidos.

        :param step_w: :class:`.LocalNeurons`
        :param step_b: :class:`.LocalNeurons`
        """
        self.weights += step_w
#        self.weights_T += step_w.transpose()
        self.bias += step_b
        return

    def get_weights(self):
        """
        Se devuelve la matriz de pesos sinápticos **w**.

        :return: numpy.ndarray.
        """
        return self.weights

    def get_bias(self):
        """
        Se devuelve el vector de bias **b**.

        :return: numpy.ndarray.
        """
        return self.bias
Exemple #2
0
class NeuralLayer(object):
    """
    Clase básica para modelar una capa de neuronas que compone una red neuronal.
    Contiene sus "neuronas" representadas por pesos sinápticos **w** y **b**,
    además de una función de activación asociada para dichos pesos.

    Una correcta inicialización de los pesos sinápticos está muy ligada a la función de activación elegida:

    * Por defecto, los pesos sinápticos se inicializan con una distribución uniforme \
        con media *0* y varianza :math:`\\tfrac{2.0}{\sqrt{n_{in}}}`, \
        lo cual da buenos resultados especialmente usando ReLUs.
    * Para la función *Tanh* se muestrea sobre una distribución uniforme \
        en el rango :math:`\pm \sqrt{\\frac{6}{n_{in}+n_{out}}}`.
    * Para la *Sigmoid* en el rango :math:`\pm 4.0 \sqrt{\\frac{6}{n_{in}+n_{out}}}`.

    :param n_in: int, dimensión de la entrada.
    :param n_out: int, dimensión de la salida.
    :param activation: string, key de alguna función de activación soportada en :mod:`~learninspy.core.activations`.
    :param distributed: si es True, indica que se utilicen arreglos distribuidos para **w** y **b**.
    :param w: :class:`.LocalNeurons`, matriz de pesos sinápticos. Si es *None*, se crea por defecto.
    :param b: :class:`.LocalNeurons`, vector de pesos bias. Si es *None*, se crea por defecto.
    :param rng: si es *None*, se crea un generador de números aleatorios
     mediante una instancia **numpy.random.RandomState**.

    .. note:: el parámetro *distributed* no tiene efecto, ya que el uso de arreglos distribuidos
       se deja para un próximo release.

    >>> n_in, n_out = (10, 5)
    >>> layer = NeuralLayer(n_in, n_out, activation='Tanh')
    >>> x = np.random.rand(n_in)
    >>> out = layer.output(x)
    >>> len(out)
    5

    """

    def __init__(self, n_in, n_out, activation='ReLU', distributed=False, w=None, b=None, rng=None):
        self.n_out = n_out
        self.n_in = n_in
        self.activation = act.fun_activation[activation]
        self.activation_d = act.fun_activation_d[activation]
        distributed = False  # TODO completar esta funcionalidad

        if rng is None:
            rng = np.random.RandomState(123)
        self.rng = rng
        self.rnd_state = self.rng.get_state()

        self.shape_w = n_out, n_in
        self.shape_b = n_out, 1

        # Recomendaciones de http://cs231n.github.io/neural-networks-2/ y http://deeplearning.net/tutorial/mlp.html#mlp
        # TODO: ver si conviene dejar acá la inicializ de pesos, o en core.neurons (en términos de legibilidad)
        if w is None:
            if activation is "Tanh":
                w = np.asarray(
                    self.rng.uniform(
                        low=-np.sqrt(6.0 / (n_in + n_out)),
                        high=+np.sqrt(6.0 / (n_in + n_out)),
                        size=self.shape_w),
                    dtype=np.dtype(float)
                )
            elif activation is "Sigmoid":
                w = np.asarray(
                    self.rng.uniform(
                        low=-np.sqrt(6.0 / (n_in + n_out))*4.0,
                        high=+np.sqrt(6.0 / (n_in + n_out))*4.0,
                        size=self.shape_w),
                    dtype=np.dtype(float)
                )
            else:
                w = self.rng.randn(*self.shape_w) * np.sqrt(2.0/n_in)

        if b is None:
            b = np.zeros(self.shape_b, dtype=np.dtype(float))

        # TODO weights_T era p/ poder hacer operaciones distribuidas, pero se deja como TBC la class DistributedNeurons
        assert distributed is False, logger.error("DistributedNeurons will be implemented soon ...")
        self.weights = LocalNeurons(w, self.shape_w)
        #self.weights_T = LocalNeurons(w.transpose(), self.shape_w[::-1])
        self.bias = LocalNeurons(b, self.shape_b)

    def __div__(self, other):
        self.weights /= other
        self.bias /= other
        return self

    def __mul__(self, other):
        self.weights *= other
        self.bias *= other
        return self

    def l1(self):
        """
        Norma **L1** sobre la matriz **w** de pesos sinápticos,
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.l1`.

        Por lo tanto, se retorna el resultado de aplicar la norma y el gradiente de la misma.

        :return: tuple de float, :class:`~learninspy.core.neurons.LocalNeurons`
        """
        return self.weights.l1()

    def l2(self):
        """
        Norma **L2** sobre la matriz **w** de pesos sinápticos,
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.l2`.

        Por lo tanto, se retorna el resultado de aplicar la norma y el gradiente de la misma.

        :return: tuple de float, :class:`~learninspy.core.neurons.LocalNeurons`
        """
        return self.weights.l2()

    def output(self, x, grad=False):
        """
        Salida de la capa neuronal. Se toma una entrada :math:`x \in \Re^{n_{in}}`, se pondera con los
        pesos sinápticos **W** y el bias **b**, y luego se aplica la función de activación **f** para retornar como
        resultado:

        :math:`a = f(Wx + b), \quad a' = f'(Wx + b)`

        :param x: **numpy.ndarray**, vector de entrada
        :param grad: Si es *True*, se retorna además el gradiente de la salida.
        :return: **numpy.ndarray**, o tupla de ellos si *grad* es True.
        """
        wx = self.weights.mul_array(x)
        z = wx.sum_array(self.bias)
        a = z.activation(self.activation)
        if grad is True:
            d_a = z.activation(self.activation_d)
            a = (a, d_a)
        return a

    # Basado en http://cs231n.github.io/neural-networks-2/
    def dropoutput(self, x, p, grad=True):
        """
        Salida de la capa neuronal, luego de aplicar la regularización de los pesos sinápticos por Dropout
        utilizando la funcion :func:`~learninspy.core.neurons.LocalNeurons.dropout`.

        :param x: numpy.ndarray, vector de entrada
        :param p: float, tal que :math:`0<p<1`
        :param grad: Si es *True*, se retorna además el gradiente de la salida.
        :return: numpy.ndarray, (o tuple de  numpy.ndarray, numpy.ndarray si *grad* es *True*),
         numpy.ndarray correspondiente a la máscara binaria utilizada en el DropOut.

        .. note:: En las predicciones de la red no se debe efectuar Dropout.
        """
        self.rng.set_state(self.rnd_state)  # Para que sea reproducible
        out = self.output(x, grad)
        if grad is True:
            a, d_a = out
            a, mask = a.dropout(p, self.rng.randint(500))  # randint es para generar un nuevo seed (reproducible)
            out = a, d_a
        else:
            out, mask = out.dropout(p, self.rng.randint(500))  # TODO: en que caso no se necesitaria el grad?
        return out, mask  # Devuelvo ademas la mascara utilizada, para el backprop

    def update(self, step_w, step_b):  # Actualiza sumando los argumentos w y b a los respectivos pesos
        """
        Se actualizan los arreglos **w** y **b** sumando respectivamente los incrementos
        dados por los parámetros recibidos.

        :param step_w: :class:`.LocalNeurons`
        :param step_b: :class:`.LocalNeurons`
        """
        self.weights += step_w
#        self.weights_T += step_w.transpose()
        self.bias += step_b
        return

    def get_weights(self):
        """
        Se devuelve la matriz de pesos sinápticos **w**.

        :return: numpy.ndarray.
        """
        return self.weights

    def get_bias(self):
        """
        Se devuelve el vector de bias **b**.

        :return: numpy.ndarray.
        """
        return self.bias