El método __update_parameters

Codifiquemos entonces este método. Tal y como se ha comentado debemos inicializar a cero las variables que contendrán el gradiente acumulado para pesos y bias, variables que hemos dicho que llamaríamos total_gradient_weightstotal_gradient_bias:

total_gradient_weights = [np.zeros(w.shape) for w in self.weights]
total_gradient_bias = [np.zeros(b.shape) for b in self.biases]

Recordemos que los atributos weightsbiases contienen sendas listas formadas por los pesos y biases de cada capa (sin incluir la capa de entrada). Los acumulados de los gradientes deberán tener las mismas dimensiones.

A continuación, deberemos extraer cada muestra del mini-batch y obtener el gradiente de la función de error para los pesos y los bias, para lo que debemos suponer la existencia de un método en nuestra clase con este objetivo. Llamemos a este método __backpropagation:

delta_gradient_weights, delta_gradient_bias = self.__backpropagation(x, y)

En la expresión anterior estamos suponiendo que las variables x e y contienen una muestra del mini-batch ("x" contiene las características predictivas e "y" la variable objetivo)

Tras esto deberemos actualizar las variables que contienen el gradiente acumulado con los gradientes devueltos por nuestro método __backpropagation:

total_gradient_weights = [gw + dgw for gw, dgw in zip(total_gradient_weights, delta_gradient_weights)]
total_gradient_bias = [gb + dgb for gb, dgb in zip(total_gradient_bias, delta_gradient_bias)]

En el código anterior estamos "recorriendo" todas las capas añadiendo al gradiente acumulado de pesos y de bias los gradientes parciales obtenidos.

Este proceso deberá repetirse para cada muestra del mini-batch. Una vez hayamos terminado deberemos actualizar los parámetros restando el gradiente acumulado tras multiplicarlo por la tasa de aprendizaje:

self.weights = [w - gw * self.learning_rate for w, gw in zip(self.weights, total_gradient_weights)]
self.biases = [b - gb * self.learning_rate for b, gb in zip(self.biases, total_gradient_bias)]

En el código anterior estamos recorriendo -una vez más usando una list comprehension- los pesos y bias de todas las capas restando el resultado comentado.

El código completo de este método queda, por lo tanto, de la siguiente forma:

def __update_parameters(self, mini_batch):
    """ Actualiza los parámetros de la red aplicando descenso de gradiente a un
    mini-batch """
    total_gradient_weights = [np.zeros(w.shape) for w in self.weights]
    total_gradient_bias = [np.zeros(b.shape) for b in self.biases]
    for x, y in mini_batch:
        delta_gradient_weights, delta_gradient_bias = self.__backpropagation(x, y)
        total_gradient_weights = [gw + dgw for gw, dgw \
                        in zip(total_gradient_weights, delta_gradient_weights)]
        total_gradient_bias = [gb + dgb for gb, dgb \
                        in zip(total_gradient_bias, delta_gradient_bias)]
    self.weights = [w - gw * self.learning_rate for w, gw \
                    in zip(self.weights, total_gradient_weights)]
    self.biases = [b - gb * self.learning_rate for b, gb \
                    in zip(self.biases, total_gradient_bias)]