Clase NNClassifier v5

Si actualizamos nuestro código, la definición de la clase queda del siguiente modo:

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 __backpropagation(self, x, y):
        """ Devuelve una tupla representando el gradiente para la función de coste
        correspondiente a una muestra. Los valores de estas listas son arrays NumPy con
        la misma estructura que self.weights y self.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]
        return total_gradient_weights, total_gradient_bias
    
    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)]
    
    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)
            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

Obsérvese cómo el método __backpropagation está devolviendo dos listas formadas por arrays NumPy con las mismas dimensiones que los arrays de pesos y bias y, por ahora, rellenas con ceros.