Buscar..


Comenzando: Una ANN simple con Python

El código que figura a continuación intenta clasificar los dígitos escritos a mano a partir del conjunto de datos MNIST. Los dígitos se ven así:

MNIST

El código preprocesará estos dígitos, convirtiendo cada imagen en una matriz 2D de 0s y 1s, y luego utilizará estos datos para entrenar una red neuronal con una precisión de hasta el 97% (50 épocas).

"""
Deep Neural Net 

(Name: Classic Feedforward)

"""

import numpy as np
import pickle, json
import sklearn.datasets
import random
import time
import os


def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

def relU(z):
    return np.maximum(z, 0, z)
    
def relU_prime(z):
    return z * (z <= 0)
    
def tanh(z):
    return np.tanh(z)

def tanh_prime(z):
    return 1 - (tanh(z) ** 2)

def transform_target(y):
    t = np.zeros((10, 1))
    t[int(y)] = 1.0
    return t

"""--------------------------------------------------------------------------------"""

class NeuralNet:

    def __init__(self, layers, learning_rate=0.05, reg_lambda=0.01):
        self.num_layers = len(layers)
        self.layers = layers
        self.biases = [np.zeros((y, 1)) for y in layers[1:]]
        self.weights = [np.random.normal(loc=0.0, scale=0.1, size=(y, x)) for x, y in zip(layers[:-1], layers[1:])]
        self.learning_rate = learning_rate
        self.reg_lambda = reg_lambda
        self.nonlinearity = relU
        self.nonlinearity_prime = relU_prime

    def __feedforward(self, x):
        """ Returns softmax probabilities for the output layer """
        for w, b in zip(self.weights, self.biases):
            x = self.nonlinearity(np.dot(w, np.reshape(x, (len(x), 1))) + b)

        return np.exp(x) / np.sum(np.exp(x))

    def __backpropagation(self, x, y):
        """
        :param x: input
        :param y: target
        """
        weight_gradients = [np.zeros(w.shape) for w in self.weights]
        bias_gradients = [np.zeros(b.shape) for b in self.biases]

        # forward pass
        activation = x
        hidden_activations = [np.reshape(x, (len(x), 1))]
        z_list = []

        for w, b in zip(self.weights, self.biases):    
            z = np.dot(w, np.reshape(activation, (len(activation), 1))) + b
            z_list.append(z)
            activation = self.nonlinearity(z)
            hidden_activations.append(activation)

        t = hidden_activations[-1] 
        hidden_activations[-1] = np.exp(t) / np.sum(np.exp(t))

        # backward pass
        delta = (hidden_activations[-1] - y) * (z_list[-1] > 0)
        weight_gradients[-1] = np.dot(delta, hidden_activations[-2].T)
        bias_gradients[-1] = delta

        for l in range(2, self.num_layers):
            z = z_list[-l]
            delta = np.dot(self.weights[-l + 1].T, delta) * (z > 0)
            weight_gradients[-l] = np.dot(delta, hidden_activations[-l - 1].T)
            bias_gradients[-l] = delta

        return (weight_gradients, bias_gradients)

    def __update_params(self, weight_gradients, bias_gradients):
        for i in xrange(len(self.weights)):
            self.weights[i] += -self.learning_rate * weight_gradients[i]
            self.biases[i] += -self.learning_rate * bias_gradients[i]

    def train(self, training_data, validation_data=None, epochs=10):
        bias_gradients = None
        for i in xrange(epochs):
            random.shuffle(training_data)
            inputs = [data[0] for data in training_data]
            targets = [data[1] for data in training_data]

            for j in xrange(len(inputs)):
                (weight_gradients, bias_gradients) = self.__backpropagation(inputs[j], targets[j])
                self.__update_params(weight_gradients, bias_gradients)

            if validation_data: 
                random.shuffle(validation_data)
                inputs = [data[0] for data in validation_data]
                targets = [data[1] for data in validation_data]

                for j in xrange(len(inputs)):
                    (weight_gradients, bias_gradients) = self.__backpropagation(inputs[j], targets[j])
                    self.__update_params(weight_gradients, bias_gradients)

            print("{} epoch(s) done".format(i + 1))

        print("Training done.")

    def test(self, test_data):
        test_results = [(np.argmax(self.__feedforward(x[0])), np.argmax(x[1])) for x in test_data]
        return float(sum([int(x == y) for (x, y) in test_results])) / len(test_data) * 100

    def dump(self, file):
        pickle.dump(self, open(file, "wb"))

"""--------------------------------------------------------------------------------"""

if __name__ == "__main__":
    total = 5000
    training = int(total * 0.7)
    val = int(total * 0.15)
    test = int(total * 0.15)

    mnist = sklearn.datasets.fetch_mldata('MNIST original', data_home='./data')

    data = zip(mnist.data, mnist.target)
    random.shuffle(data)
    data = data[:total]
    data = [(x[0].astype(bool).astype(int), transform_target(x[1])) for x in data]

    train_data = data[:training]
    val_data = data[training:training+val]
    test_data = data[training+val:]

    print "Data fetched"

    NN = NeuralNet([784, 32, 10]) # defining an ANN with 1 input layer (size 784 = size of the image flattened), 1 hidden layer (size 32), and 1 output layer (size 10, unit at index i will predict the probability of the image being digit i, where 0 <= i <= 9)  

    NN.train(train_data, val_data, epochs=5)

    print "Network trained"

    print "Accuracy:", str(NN.test(test_data)) + "%"

Este es un ejemplo de código autocontenido, y puede ejecutarse sin más modificaciones. Asegúrate de que tienes numpy y scikit learn para tu versión de python.

Backpropagation - El corazón de las redes neuronales

El objetivo de la propagación hacia atrás es optimizar los pesos para que la red neuronal pueda aprender cómo asignar correctamente las entradas arbitrarias a las salidas.

Cada capa tiene su propio conjunto de pesos, y estos pesos deben ajustarse para poder predecir con precisión la salida correcta dada la entrada.

Una visión general de alto nivel de la propagación hacia atrás es la siguiente:

  1. Pase hacia adelante: la entrada se transforma en alguna salida. En cada capa, la activación se calcula con un producto de punto entre la entrada y los pesos, seguido de la suma de la resultante con el sesgo. Finalmente, este valor se pasa a través de una función de activación, para obtener la activación de esa capa que se convertirá en la entrada a la siguiente capa.
  2. En la última capa, la salida se compara con la etiqueta real correspondiente a esa entrada, y se calcula el error. Por lo general, es el error cuadrático medio.
  3. Paso hacia atrás: el error calculado en el paso 2 se propaga de nuevo a las capas internas y los pesos de todas las capas se ajustan para dar cuenta de este error.

1. Inicialización de pesas

A continuación se muestra un ejemplo simplificado de inicialización de pesos:

layers = [784, 64, 10] 
weights = np.array([(np.random.randn(y, x) * np.sqrt(2.0 / (x + y))) for x, y in zip(layers[:-1], layers[1:])])
biases = np.array([np.zeros((y, 1)) for y in layers[1:]])
  • La capa 1 oculta tiene un peso de dimensión [64, 784] y sesgo de dimensión 64.

  • La capa de salida tiene un peso de dimensión [10, 64] y un sesgo de dimensión

Es posible que se pregunte qué sucede al inicializar los pesos en el código anterior. Esto se llama inicialización de Xavier, y es un paso mejor que la inicialización aleatoria de sus matrices de peso. Sí, la inicialización sí importa. Según su inicialización, es posible que pueda encontrar mejores mínimos locales durante el descenso del gradiente (la propagación hacia atrás es una versión glorificada del descenso del gradiente).

2. Pase hacia adelante

activation = x
hidden_activations = [np.reshape(x, (len(x), 1))]
z_list = []

for w, b in zip(self.weights, self.biases):    
    z = np.dot(w, np.reshape(activation, (len(activation), 1))) + b
    z_list.append(z)
    activation = relu(z)
    hidden_activations.append(activation)

t = hidden_activations[-1] 
hidden_activations[-1] = np.exp(t) / np.sum(np.exp(t))

Este código realiza la transformación descrita anteriormente. hidden_activations[-1] contiene probabilidades de softmax: predicciones de todas las clases, cuya suma es 1. Si estamos pronosticando dígitos, la salida será un vector de probabilidades de dimensión 10, cuya suma es 1.

3. Pase hacia atrás

weight_gradients = [np.zeros(w.shape) for w in self.weights]
bias_gradients = [np.zeros(b.shape) for b in self.biases]

delta = (hidden_activations[-1] - y) * (z_list[-1] > 0) # relu derivative
weight_gradients[-1] = np.dot(delta, hidden_activations[-2].T)
bias_gradients[-1] = delta

for l in range(2, self.num_layers):
    z = z_list[-l]
    delta = np.dot(self.weights[-l + 1].T, delta) * (z > 0) # relu derivative
    weight_gradients[-l] = np.dot(delta, hidden_activations[-l - 1].T)
    bias_gradients[-l] = delta

Las 2 primeras lineas inicializan los gradientes. Estos gradientes se calculan y se utilizarán para actualizar las ponderaciones y los sesgos más adelante.

Las siguientes 3 líneas calculan el error restando la predicción del objetivo. El error se propaga de nuevo a las capas internas.

Ahora, sigue cuidadosamente el funcionamiento del bucle. Las líneas 2 y 3 transforman el error de la layer[i] a la layer[i - 1] . Traza las formas de las matrices que se multiplican para entender.

4. Pesos / Actualización de Parámetros

for i in xrange(len(self.weights)):
    self.weights[i] += -self.learning_rate * weight_gradients[i]
    self.biases[i] += -self.learning_rate * bias_gradients[i] 

self.learning_rate especifica la velocidad a la que la red aprende. No quieres que aprenda demasiado rápido, porque puede que no converja. Se favorece un suave descenso para encontrar un buen mínimo. Generalmente, las tasas entre 0.01 y 0.1 se consideran buenas.

Funciones de activacion

Las funciones de activación también conocidas como función de transferencia se utilizan para asignar los nodos de entrada a los nodos de salida de cierta manera.

Se utilizan para impartir no linealidad a la salida de una capa de red neuronal.

Algunas funciones de uso común y sus curvas se dan a continuación: Funciones de activacion


Función sigmoidea

El sigmoide es una función de aplastamiento cuya salida está en el rango [0, 1] .

introduzca la descripción de la imagen aquí

El código para implementar sigmoid junto con su derivado con numpy se muestra a continuación:

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

Función tangente hiperbólica (tanh)

La diferencia básica entre las funciones tanh y sigmoide es que tanh está centrado en 0, aplastando las entradas en el rango [-1, 1] y es más eficiente para calcular.

introduzca la descripción de la imagen aquí

Puede usar fácilmente las funciones np.tanh o math.tanh para calcular la activación de una capa oculta.

Función ReLU

Una unidad lineal rectificada hace simplemente max(0,x) . Es una de las opciones más comunes para las funciones de activación de las unidades de red neuronal.

introduzca la descripción de la imagen aquí

Las ReLU abordan el problema de la degradación de la degradación de las unidades tangentes sigmoideas / hiperbólicas, lo que permite una propagación eficiente de las degradaciones en redes profundas.

El nombre ReLU proviene del documento de Nair y Hinton, Unidades lineales rectificadas que mejoran las máquinas de Boltzmann restringidas .

Tiene algunas variaciones, por ejemplo, ReLU (LReLU) con fugas y Unidades lineales exponenciales (ELU).

El código para implementar ReLU de vainilla junto con su derivado con numpy se muestra a continuación:

def relU(z):
    return z * (z > 0)

def relU_prime(z):
    return z > 0

Función softmax

La regresión de Softmax (o regresión logística multinomial) es una generalización de la regresión logística al caso en el que queremos manejar múltiples clases. Es particularmente útil para redes neuronales donde queremos aplicar una clasificación no binaria. En este caso, la regresión logística simple no es suficiente. Necesitaríamos una distribución de probabilidad en todas las etiquetas, que es lo que nos brinda softmax.

Softmax se calcula con la siguiente fórmula:

Fórmula

___________________________ ¿Dónde encaja? _____________________________

Softmax Para normalizar un vector aplicándole la función numpy con numpy , use:

np.exp(x) / np.sum(np.exp(x))

Donde x es la activación de la capa final de la ANN.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow