Clase NNClassifier v4

El código de nuestra clase (sin incluir la importación de la librería NumPy ni la definición de las funciones sigmoid ni to_categorical) queda, por lo tanto, de la siguiente forma:

class NNClassifier(object):
    
    def __init__(self, sizes, learning_rate = 0.01, batch_size = 16, epochs = 10):
        """ 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:]]
    
    def __update_parameters(self, mini_batch, learning_rate):
        """ Actualiza los parámetros de la red (pesos y bias) aplicando descenso de gradiente 
        a cada mini-batch. Un mini-batch es una lista de tuplas (x, y) """
    pass
    
    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):
            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, self.learning_rate)
            print("Epoch {} complete".format(epoch))
    
    def getOutput(self, x: np.ndarray):
        """ Obtención de la predicción para una muestra """
        for b, w in zip(self.biases, self.weights):
            x = sigmoid(np.dot(w, x) + b)
        return x
    
    def predict(self, X: pd.core.frame.DataFrame):
        """ Obtención de la predicción para las muestras contenidas en un DataFrame """
        predictions = np.zeros(shape = len(X))
        X.reset_index(inplace = True, drop = True)
        for i, sample in X.iterrows():
            prediction = model.getOutput(sample.values.reshape(-1, 1))
            predictions[i] = np.argmax(prediction)
        return predictions

Vemos que se ha añadido un mensaje de "Epoch X complete" en el método .fit() para indicar que se ha completado cada ciclo de entrenamiento.

También se ha definido el método privado __update_parameters() que recibirá cada mini-batch y actualizará los parámetros de la red.

Si instanciamos nuestra clase:

model = NNClassifier(
    sizes = [784, 16, 10], 
    learning_rate = 0.01,
    batch_size = 32,
    epochs = 10
)

...y "entrenamos" el algoritmo, el resultado es:

model.fit(x_train, y_train)

Epoch 0 complete
Epoch 1 complete
Epoch 2 complete
Epoch 3 complete
Epoch 4 complete
Epoch 5 complete
Epoch 6 complete
Epoch 7 complete
Epoch 8 complete
Epoch 9 complete

("entrenamos" el algoritmo -entre comillas- pues ya sabemos que todavía no estamos modificando los parámetros aleatorios asignados inicialmente).