Ya comentamos que, tal y como está ahora el código, los "batches" son los mismos en cada epoch, lo que podría dificultar el aprendizaje.
Para permitir que la red pueda ser desordenada vamos a añadir un nuevo parámetro a nuestra clase, shuffle, que determinará si el dataset se va a desordenar o no al comienzo de cada epoch. El constructor de la clase será ahora el siguiente:
def __init__(self, sizes, learning_rate = 0.01, batch_size = 16, epochs = 10, shuffle = True):
""" Constructor de la red neuronal """
self.num_layers = len(sizes) # Número total de capas de la red
self.sizes = sizes # Lista conteniendo el número de neuronas por capa
self.learning_rate = learning_rate # Tasa de aprendizaje
self.batch_size = batch_size # Tamaño del batch
self.epochs = epochs # Número de epochs durante los que entrenar la red
self.weights = [np.random.randn(x, y) for (x, y) in zip(sizes[1:], sizes[:-1])]
self.biases = [np.random.randn(n, 1) for n in sizes[1:]]
self.shuffle = shuffle # Si toma el valor True, se desordenará el dataset
En el método .fit(), por otro lado, es donde vamos a desordenar físicamente el dataset, para lo que haremos uso de la función np.random.shuffle de NumPy:
def fit(self, X: pd.core.frame.DataFrame, y: pd.core.series.Series):
""" Entrenamiento de la red neuronal"""
x_train = [x.values.reshape(-1, 1) for (i, x) in X.iterrows()]
y_train = [to_categorical(n, self.sizes[-1]) for n in y]
training_data = [(x, y) for (x, y) in zip(x_train, y_train)]
n = len(training_data)
for epoch in range(self.epochs):
if self.shuffle:
np.random.shuffle(training_data)
mini_batches = [training_data[start:start + self.batch_size]
for start in range(0, n, self.batch_size)]
for mini_batch in mini_batches:
self.__update_parameters(mini_batch)
print("Epoch {} complete".format(epoch))
Como vemos, esta función -que desordena la estructura in-place- solo se va ejecutar cuando el parámetro shuffle tome el valor True (valor que hemos programado como valor por defecto).