Construido e implementado desde cero: hacia la IA

Estás leyendo la publicación: Construido e implementado desde cero: hacia la IA

Publicado originalmente en Hacia la IA.

Este artículo tiene como objetivo proporcionar una descripción general del perceptrón multicapa, cubriendo áreas clave desde el punto de vista matemático, visual y programático.

Se ha adoptado un enfoque para permitir al lector capturar la intuición detrás de un perceptrón multicapa utilizando un método paso a paso del tamaño de un bocado.

La lectura previa relacionada con la multiplicación de matrices, el álgebra lineal (transformación lineal), la simplificación de expresiones algebraicas y el cálculo (derivadas, derivadas parciales y regla de la cadena) ayudarán al lector a aprovechar al máximo este artículo. Además, la experiencia con Python ayudará al lector a comprender la aplicación de la arquitectura de perceptrón multicapa que se analiza en este artículo.

Arquitectura modelo:

Crearemos un perceptrón multicapa (MLP), que es una red neuronal de avance. En este modelo, las entradas se multiplican por pesos, se suman y se pasan a través de una función de activación no lineal que activa cada entrada. Los datos activados de la capa oculta se envían luego a la capa de salida que proporciona la predicción. A continuación se proporciona una descripción general de la arquitectura del modelo:

Capa de entrada: Usaremos 2 entradas (), relacionadas con cada característica en cada ejemplo de entrenamiento.

Capa oculta: incluiremos una capa entre la capa de entrada y la capa de salida, que consta de 3 neuronas ().

Capa de salida: Usaremos una capa para la predicción () que consta de una neurona.

La figura 1 visualiza la arquitectura MLP que implementaremos.

X : entidad de entrada en la capa de entrada

z : transformación lineal a la capa oculta

h: función de activación en la capa oculta

Z h: transformación lineal a la capa oculta

pag : predicción en la capa de salida

Ejemplos de entrenamiento:

Querremos entrenar nuestra red neuronal MLP para que pueda aprender patrones en los datos. Para simplificar, utilizaremos puertas lógicas XOR para entrenar los datos. Una puerta lógica XOR produce una salida verdadera () si el número de entradas verdaderas es impar.

La Tabla 1 proporciona una descripción general de los datos de la puerta lógica XOR que usaremos para entrenar la red.

Como se muestra a continuación, la cantidad de ejemplos de capacitación es 4. Para cada ejemplo de capacitación, se utilizarán dos características de entrada (). Cada ejemplo de entrenamiento tiene una salida correspondiente. La salida es la etiqueta de verdad de campo que se comparará con la predicción de salida de MLP () para evaluar el rendimiento del modelo.

Nota importante: este artículo se centra en la formación de un MLP. Después del entrenamiento, los MLP se evalúan en datos no vistos, denominados datos de prueba. Esto evalúa qué tan bien generaliza el MLP. La evaluación del MLP en datos de prueba está fuera del alcance de este artículo.

Construcción del perceptrón multicapa

Paso 1: Características de Entrada (Matriz de Diseño) y Ground Truth y Labels

Primero almacenaremos las características de entrada () para cada uno de los 4 ejemplos de entrenamiento en una matriz conocida como matriz de diseño. También almacenaremos las etiquetas de verdad del terreno correspondientes en un vector de columna.

La Fig. 2 proporciona una descripción general de la matriz de diseño y el vector de columna.

Paso 2: Propagación hacia adelante

Ahora que hemos definido el modelo y los datos de entrenamiento, podemos reenviar las entradas para llegar a la salida. Para ello, tendremos que hacer lo siguiente:

wwh

Nota: para simplificar, no incluiremos el sesgo en las transformaciones lineales utilizadas en este artículo. El sesgo (b) es la constante aditiva en funciones lineales wx+b, utilizada para compensar el resultado y desplazar la función de activación.

A continuación se proporcionan más detalles:

Parámetros del modelo:

Los coeficientes de peso son los parámetros de la red. Los inicializaremos a partir de una distribución gaussiana aleatoria, que tiene una media de 0 y una varianza de 1. Al hacer esto, habrá una mayor posibilidad de dibujar pesos cercanos a la media, lo que dará como resultado valores de peso más estables que se utilizan para inicializar el red. Estos pesos serán actualizados posteriormente por la red para optimizar su capacidad de predicción (más sobre esto a continuación).

El número total de parámetros (pesos) utilizados en esta red es 9. Esto se puede calcular multiplicando el número de neuronas en la capa de entrada por el número de neuronas en la capa oculta (2 x 3 = 6) y el número de neuronas en la capa oculta por el número de neuronas en la capa de salida (3 x 1 = 3).

Matriz de Peso (Entrada a Capa Oculta): Como tenemos 4 ejemplos de entrenamiento, primero crearemos una matriz de peso para almacenar los coeficientes de peso desde la capa de entrada hasta la capa oculta (6 pesos).

La Fig. 3 proporciona una descripción general de la matriz de pesos.

Transformación Lineal (a la Capa Oculta): Realizaremos una transformación lineal en cada característica de entrada ) pasada a la capa oculta. Esto significa que el modelo puede aprender relaciones en los datos. Para hacer esto, realizaremos la multiplicación de matrices en cada característica de entrada y el coeficiente de peso asociado. La matriz de diseño tiene dimensiones de y la matriz de peso es (como se muestra arriba). La multiplicación de estas dos matrices da como resultado una nueva matriz Z, donde cada fila representa cada uno de los 4 ejemplos de entrenamiento y cada columna representa un nodo z. Esto se ha ilustrado en la Fig. 4 y la Fig. 5:

🔥 Recomendado:  Series temporales univariadas con LSTM apilado, BiLSTM y NeuralProphet: hacia la IA

La transformación lineal para el ejemplo de entrenamiento 1 se ha visualizado en la figura 6, lo que demuestra la transformación lineal de las dos características de entrada a través de la suma ponderada en cada nodo z.

Función de Activación (Función Sigmoide):

Nuestro siguiente paso es incluir una función de activación no lineal que ajustará el nivel de activación de cada neurona en la capa oculta y la capa de salida. Para ello, utilizaremos la función sigmoidea. Toma un valor () y lo aplasta entre 0 y 1. Por ejemplo, si le pasamos el valor -10 a la función sigmoidea, devolverá un valor cercano a cero. Cuanto más cerca de 0, menos activada está la neurona, y cuanto más cerca de 1, más activada está. La idea es que cuanto más positiva es la neurona, más activa es, inspirada en el cerebro humano, ¡llevándonos más allá de la linealidad!

La representación matemática y la intuición visual de la función sigmoidea se proporcionan a continuación:

Función de activación (en la capa oculta): Ahora que hemos definido la función de activación, podemos usarla en la capa oculta. Para hacer esto, aplicamos la función sigmoidea a cada elemento en la matriz Z para cada ejemplo de entrenamiento, como se detalla en la Fig.9:

La función de activación en la capa oculta para el ejemplo de entrenamiento 1 se ha visualizado en la Fig. 10:

Predicción de la capa de salida: Ahora realizaremos una transformación lineal y aplicaremos la función sigmoidea de la capa oculta a la capa de salida, lo que dará como resultado la salida prevista (consulte la Fig. 11 a continuación). Mediante el uso de la función sigmoidea, las predicciones de nuestro modelo están limitadas entre 0 y 1. Estos valores límite pueden verse como las probabilidades predichas del modelo.

La transformación lineal y la predicción en la capa de salida para el ejemplo de entrenamiento 1 se han visualizado a continuación:

Función de pérdida y costo: función de pérdida/costo de entropía cruzada binaria

Las funciones y nos muestran la diferencia entre las etiquetas de verdad del terreno y las predicciones asociadas. En particular, la función muestra la diferencia para un ejemplo de entrenamiento, mientras que la función muestra la diferencia promedio en todos los ejemplos de entrenamiento.

Usaremos la función Loss para los propósitos de explicación e intuición a continuación:

Pérdida de entropía cruzada binaria: Si recuerda, las predicciones de nuestro modelo están limitadas entre 0 y 1. Usando la pérdida de entropía cruzada binaria, podemos comparar cada una de las predicciones del modelo con las etiquetas de verdad del terreno asociadas ().

La pérdida de entropía cruzada binaria se ha representado matemáticamente en la Fig. 13.

Usando el logaritmo negativo, la pérdida de entropía cruzada binaria penaliza en gran medida las predicciones incorrectas cuanto más se alejan de la etiqueta de verdad básica. Esto ha sido ilustrado intuitivamente en la Fig. 14 y la Fig. 15.

Si = 0, cuanto más cerca de 1 resulte en un rápido aumento de . Alternativamente, cuanto más se acerca a 0, más cerca está de 0.

Si = 1, cuanto más se acerca a 0, se produce un rápido aumento de . Alternativamente, cuanto más se acerca a 1, más se acerca a 0.

Función de costo de entropía cruzada binaria: podemos tomar el promedio entre las etiquetas de verdad y las predicciones sobre todos los ejemplos de entrenamiento para optimizar el rendimiento general del modelo.

Para evaluar el rendimiento general del modelo, utilizaremos la función de costo de entropía cruzada binaria, que se ha descrito matemáticamente de la siguiente manera:

Propagación directa (resumen de capa de entrada a capa de salida):

La figura 17 reúne la propagación hacia adelante que hemos cubierto anteriormente, desde la capa de entrada hasta la capa de salida, para un ejemplo de entrenamiento.

Paso 3. Propagación hacia atrás:

Nuestro modelo ha producido una serie de predicciones, pero es posible que no sean las mejores predicciones que el modelo puede lograr. Podemos intentar optimizar el modelo y mejorar sus predicciones ajustando sus pesos un poco. Antes de hacer esto, cubramos algunos conceptos relacionados a un alto nivel:

f(x) = x²

zxyxy

x yzxy

f(g(x)). g(x) f(x)f(x) sen(x²)f(x) = sen(x)g(x) = x²

Retropropagación MLP:

Nuestra red neuronal se puede describir como una composición de múltiples funciones: donde está la entrada en la capa de entrada, es la transformación lineal de, es la función de activación sigmoidea en la capa oculta, es la transformación lineal de y es la predicción de la función sigmoidea del modelo en la capa de salida.

Nuestra función de pérdida de entropía cruzada binaria toma la predicción de la red neuronal como entrada, junto con la etiqueta de verdad básica ().

La derivada parcial es particularmente importante para las redes neuronales. Esto se debe a que se calcula la derivada parcial de la función con respecto a cada peso y luego se modifica una pequeña cantidad para mejorar las predicciones del modelo. Actualizar los pesos de esta manera significa que podemos apuntar a encontrar el mínimo de la función, lo que a su vez nos permite encontrar la predicción óptima (más sobre esto más adelante).

🔥 Recomendado:  Cómo eliminar mensajes de Snapchat & Borrar chats

Para actualizar los pesos, haremos retropropagación, usando la regla de la cadena para encontrar la derivada parcial de cada peso con respecto a la Pérdida.

Usando la red neuronal que hemos construido hasta ahora, demostraremos la derivada parcial y la regla de la cadena en acción para un ejemplo de entrenamiento y dos pesos ():

La derivada parcial de la Pérdida con respecto a wh1:

Querremos empujar un poco para evaluar su impacto en el . Para hacer esto, primero necesitamos encontrar la derivada parcial de con respecto a .

No podemos acceder directamente a la derivada parcial de con respecto a . Para superar esto, necesitamos movernos río abajo usando la regla de la cadena, comenzando con la derivada parcial de con respecto a (ver Fig. 20 y 21 para más detalles).

El siguiente esquema demuestra cómo las derivadas parciales anteriores se relacionan entre sí:

  • representa la sensibilidad de la función a los cambios en .
  • 1/1 representa la sensibilidad de la función a los cambios en .
  • ∂/∂ representa la sensibilidad de la función a los cambios en .

Tomando las derivadas parciales anteriores y multiplicándolas (usando la regla de la cadena), podemos encontrar la derivada parcial de con respecto a

La derivada parcial de la Pérdida con respecto a w11:

A continuación, querremos empujar un poco para evaluar su impacto en la Pérdida. De manera similar, no podemos acceder directamente a la derivada parcial de con respecto a . Por lo tanto, necesitamos movernos río abajo usando la regla de la cadena, comenzando con la derivada parcial de la Pérdida con respecto a (ver Fig. 23 y 24 para más detalles).

El siguiente esquema demuestra cómo las derivadas parciales anteriores se relacionan entre sí:

  • representa la sensibilidad de la función a los cambios en .
  • representa la sensibilidad de la función a los cambios en .
  • ∂/∂ representa la sensibilidad de la función a los cambios en .
  • ∂/∂ representa la sensibilidad de la función a los cambios en .
  • ∂z/∂representa la sensibilidad de la función a los cambios en .

La derivada parcial de la Pérdida con respecto a w11 se puede representar como:

La derivada parcial de con respecto a los pesos restantes del modelo deberá calcularse utilizando los procedimientos mencionados anteriormente.

Optimización: descenso de gradiente y tasa de aprendizaje

Ahora que hemos calculado las derivadas parciales de con respecto a cada ponderación, podemos ajustar estas ponderaciones en una pequeña cantidad en un intento de mejorar las predicciones de nuestro modelo.

Descenso de gradiente: Usando el descenso de gradiente, podemos encontrar la predicción óptima, encontrando el mínimo de la función. En esencia, estamos tratando de encontrar los mejores pesos que produzcan el resultado de función más bajo.

Tasa de aprendizaje: Si ajustamos cada ponderación en una pequeña cantidad, podemos evaluar cómo cambiará esto el rendimiento de predicción del modelo. La tasa de aprendizaje es un hiperparámetro ajustable que usamos para hacer esto.

A continuación se proporcionan ejemplos intuitivos de descenso de gradiente, utilizando una tasa de aprendizaje grande y una tasa de aprendizaje pequeña. Como se muestra en la Fig. 26, cuanto mayor sea la tasa de aprendizaje, más rápido se entrenará la red, pero es posible que nunca alcance el mínimo de la función (0). En la Fig. 27, cuanto menor sea la tasa de aprendizaje, más tiempo llevará entrenar la red, pero es más probable que se acerque al mínimo de la función.

Actualización de los pesos utilizando el algoritmo de descenso de gradiente:

Usamos el algoritmo de descenso de gradiente para actualizar los pesos del modelo durante cada iteración de entrenamiento.

A continuación se proporciona un ejemplo intuitivo de actualización de un peso para una iteración. En este ejemplo, multiplicamos la derivada parcial de con respecto al peso por la tasa de aprendizaje (). Reducimos el peso ( por este valor. Esto produce el nuevo peso utilizado en la siguiente iteración de la red neuronal.

Para nuestra red neuronal, utilizaremos 100 000 iteraciones y una tasa de aprendizaje de 0,1 (1e-1). Esto significa que nuestros pesos se inicializarán y luego se actualizarán 99 999 veces, con cada iteración utilizando una tasa de aprendizaje de 0,1. Cada iteración incluye los 4 ejemplos de entrenamiento. Como se mencionó anteriormente, podemos ajustar estos hiperparámetros para optimizar las predicciones del modelo.

En la Fig. 29 se proporciona un diagrama de flujo que demuestra el proceso de optimización. Como se muestra, los pesos se actualizan iterativamente. Para cada iteración, la función muestra la diferencia promedio en todos los ejemplos de entrenamiento hasta que se producen las predicciones del modelo final (en nuestro caso, en la iteración 100 000).

Nota importante: como se mencionó al principio de este artículo, solo hemos capacitado al MLP. En realidad, una vez finalizado el entrenamiento, los pesos de un modelo se congelarían. Luego, el modelo se evaluaría en datos de prueba no vistos para evaluar qué tan bien se generaliza.

Resumen:

Hemos explorado el MLP desde una perspectiva matemática y visual, cubriendo la propagación directa, la propagación inversa y la optimización. En la Fig. 30 se muestra una descripción general de los pasos clave relacionados con el entrenamiento del modelo:

🔥 Recomendado:  Los 24 mejores sitios web para vender cosas localmente (guía 2023)

Para los propósitos de este artículo y el problema simple que intenta abordar, hemos utilizado una función de activación específica, un algoritmo de optimización y una función de Pérdida/Costo. En otros escenarios, donde los datos son mucho más grandes y complejos, estas opciones pueden no ser las más adecuadas. Aunque no es exhaustivo, he enumerado funciones de activación adicionales, algoritmos de optimización y funciones de pérdida/costo a continuación, que insto al lector a explorar y comparar con lo que se ha utilizado en este artículo.

Funciones de activación:

  • Unidad lineal rectificada (ReLU)
  • Unidad lineal rectificada con fugas (LReLU)
  • Función Tanh

Algoritmos de optimización:

  • Descenso de gradiente estocástico
  • Descenso de gradiente de mini lotes
  • Estimación adaptativa del momento Adam)

Funciones de pérdida/costo:

  • Error cuadrático medio (MSE)
  • Error absoluto medio (MAE)
  • Pérdida de entropía cruzada categórica

El código :

El siguiente código implementa el MLP basado en los datos de entrenamiento XOR discutidos anteriormente:

#importar bibliotecas requeridas importar numpy como np desde matplotlib importar pyplot como plt

clase MLP():

“””
Esta es la clase MLP utilizada para retroalimentar y propagar la red a través de un número definido
de iteraciones y producir predicciones. Después de la iteración, las predicciones se evalúan utilizando
Función de costo de entropía cruzada binaria.
“””

imprimir(‘Ejecutando…’)

def __init__(self, design_matrix, Y, iteraciones=100000, lr=1e-1, input_layer = 2, hidden_layer = 3,output_layer =1):
self.design_matrix = design_matrix #atributo de matriz de diseño
self.iterations = iteraciones #atributo iteraciones
self.lr = lr #atributo de tasa de aprendizaje
self.input_layer = input_layer #atributo de la capa de entrada
self.hidden_layer = hidden_layer #atributo de capa oculta
self.output_layer = output_layer #atributo de la capa de salida
self.weight_matrix_1 = np.random.randn(self.input_layer, self.hidden_layer) #atributo de peso que se conecta a la capa oculta
self.weight_matrix_2 = np.random.randn(self.hidden_layer, self.output_layer)#peso atributo que se conecta a la capa de salida
costo propio = [] #atributo de lista de costos
self.p_hats = [] #atributo de lista de predicciones

def sigmoid(self, x): # función sigmoide utilizada en la capa oculta y la capa de salida
devuelve 1 / (1 + np.exp(-x))

def sigmoid_derivative(self, x): # derivado sigmoide utilizado para retropropagación
devuelve self.sigmoid(x) * (1 – self.sigmoid(x))

def forward_propagation(self):#define función para alimentar la red
z = np.dot(self.design_matrix, self.weight_matrix_1) #transformación lineal a la capa oculta
activación_func = self.sigmoid(z)#función de activación de capa oculta
zh = np.dot(activation_func, self.weight_matrix_2)#transformación lineal a la capa de salida
p_hat = self.sigmoid(zh)#predicción de capa de salida
volver z, activación_func, zh, p_sombrero

def BCECost(self, y, p_hat): # función de costo de entropía cruzada binaria
bce_cost = -(np.sum(y * np.log(p_sombrero) + (1 – y) * np.log(1 – p_sombrero))) / len(y)
volver bce_cost

def backword_prop(self, z_1, activación_func, z_2, p_hat): #backpropagation
del_2_1 = p_sombrero – Y
derivado_parcial_2 = np.dot(activation_func.T, del_2_1) #∂loss/∂p *∂p/∂zh * ∂zh/∂wh
del_1_1 = del_2_1
del_1_2 = np.multiply(del_1_1, self.peso_matriz_2.T)
del_1_3 = np.multiply(del_1_2, self.sigmoid_derivative(z_1))
derivado_parcial_1 = np.dot(self.design_matrix.T, del_1_3) #∂loss/∂p * ∂p/∂zh * ∂zh/∂h * ∂h/∂z * ∂z/∂w
devuelve derivación_parcial_2, derivación_parcial_1

def entrenar(auto):#entrenar la red
for i in range(self.iterations): #loop basado en el número de iteraciones
z_1, activación_función, z_2, p_sombrero = self.forward_propagation()# feedforward
derivado_parcial_2, derivado_parcial_1 = self.backword_prop(z_1, activación_func, z_2, p_hat)#backpropgate
self.weight_matrix_1 = self.weight_matrix_1 – self.lr * Partial_deriv_1#actualizar pesos que se conectan a la capa oculta (descenso de gradiente)
self.weight_matrix_2 = self.weight_matrix_2 – self.lr * Partial_deriv_2#actualizar pesos que se conectan a la capa de salida (descenso de gradiente)
self.cost.append(self.BCECost(Y, p_hat))#store BCE cost in list
self.p_hats.append(p_hat)#store predicciones en la lista

imprimir(‘Entrenamiento completo’)
imprimir(‘————————–‘)

# Prepare los datos de la puerta lógica XOR: cree una matriz para cada característica x del ejemplo de entrenamiento y una matriz para cada etiqueta y correspondiente.
X = np.matriz([[1, 0], [0, 1], [0, 0], [1, 1]]) #características de entrada (matriz de diseño 4 x 2)
Y = np.matriz([[1], [1], [0], [0]])#verdad fundamental y etiquetas (4×1)

mlp = MLP(X,Y)#Pasar datos al modelo (matriz de diseño y etiqueta y)
mlp.train() #Entrenar al modelo

#trazar la función de costo
plt.grid()
plt.plot(rango(mlp.iteraciones),mlp.coste)
plt.xlabel(‘Iteraciones’)
plt.ylabel(‘Coste’)
plt.title(‘Función de Costo BCE’)

#Imprimir predicciones, número de iteraciones y las etiquetas de verdad del terreno.
print(f’\n Las predicciones de MLP para cada ejemplo de entrenamiento, basadas en iteraciones {mlp.iterations} son:\n\n{np.round(mlp.p_hats[-1],2)}’)
imprimir(‘\n—————————————————————————-‘)
print(f’\n Las etiquetas de verdad básica Y son:\n\n{Y}’)

Como se muestra arriba, la función Costo del modelo se ha reducido a medida que han aumentado las iteraciones de entrenamiento. La función Costo está cerca de 0, lo que significa que nuestras predicciones están cerca de nuestras etiquetas de verdad básica.

Ejecute el código usted mismo e intente ajustar la tasa de aprendizaje y una serie de iteraciones de entrenamiento para ver qué sucede.

Publicado a través de Hacia la IA