Clase NNClassifier v3

Si llevamos las funciones anteriores a nuestra clase, el resultado es el siguiente:

import numpy as np

def sigmoid(a: np.ndarray):
    """ Aplica la función sigmoide al array a elemento por elemento """
    return 1 / (1 + np.exp(-a))

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 fit(self, X, y):
        """ Entrenamiento de la red neuronal"""
        pass
    
    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

Podemos probar nuestro código instanciando la clase:

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

...y realizando la predicción para, por ejemplo, las dos primeras muestras del dataset de validación:

model.predict(x_test.iloc[:2])

array([8., 6.])

Según nuestra red, dichas imágenes contienen la representación de un "8" y de un "6" cuando, en realidad, contienen un "3" y un "6":

y_test.iloc[:2]

3048     3
19563    6
Name: 0, dtype: int64

...pero recordemos que nuestra red ha sido inicializada con parámetros aleatorios. De hecho podemos invocar el método getOutput directamente pasando como argumento la primera muestra (convertida en array NumPy y redimensionada adecuadamente) para ver los 10 valores devueltos por la capa de salida:

model.getOutput(x_test.iloc[:1].values.reshape(-1, 1))

array([[0.30523477],
       [0.57950046],
       [0.28522335],
       [0.46637227],
       [0.77167217],
       [0.02410213],
       [0.65351747],
       [0.01062654],
       [0.77868403],
       [0.04864604]]).