machine-learning
Redes neuronales
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í:
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:
- 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.
- 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.
- 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:
Función sigmoidea
El sigmoide es una función de aplastamiento cuya salida está en el rango [0, 1]
.
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.
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.
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:
___________________________ ¿Dónde encaja? _____________________________
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.