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