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_weights y total_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 weights y biases 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)]