Abril de 2019

Volumen 34, número 4

[Inteligencia artificial]

¿Cómo aprenden las redes neuronales?

Por Frank La La | Abril de 2019

Frank La VigneEn mi columna anterior (“A Closer Look at Neural Networks” [Aproximación a las redes neuronales], msdn.com/magazine/mt833269), exploré la estructura básica de las redes neuronales y creé una desde cero con Python. Después de revisar las estructuras básicas comunes de todas las redes neuronales, creé un marco de muestra para calcular las sumas ponderadas y los valores de salida. Las neuronas en sí son sencillas y realizan funciones matemáticas básicas para normalizar su resultado entre 1 y 0, o entre -1 y 1. Sin embargo, aumentan su eficacia cuando se conectan entre sí. Las neuronas se organizan en capas en una red neuronal y cada neurona pasa valores a la siguiente capa. Los valores de entrada se organizan en cascada hacia adelante en la red y afectan a la salida en un proceso denominado propagación hacia delante.

Pero, ¿cómo aprenden las redes neuronales, exactamente? ¿Cuál es el proceso y qué ocurre dentro de una red neuronal cuando aprende? En mi columna anterior, me centré en la propagación hacia adelante de valores. Para escenarios de aprendizaje supervisado, las redes neuronales pueden aprovechar un proceso denominado propagación hacia atrás.

Propagación hacia atrás, pérdida y épocas

Recuerde que cada neurona de una red neuronal toma los valores de entrada multiplicados por una ponderación para representar la fortaleza de esa conexión. La propagación hacia atrás detecta las ponderaciones correctas que se deben aplicar a los nodos de una red neuronal mediante la comparación de las salidas actuales de la red con los resultados correctos o deseados. La diferencia entre el resultado deseado y el resultado actual se calcula mediante la función de pérdida o costo. En otras palabras, la función de pérdida nos indica el grado de precisión que tiene nuestra red neuronal al realizar predicciones para una entrada determinada.

La fórmula para calcular la pérdida está representada en la figura 1. No deje que las matemáticas le intimiden: solo se trata de sumar los cuadrados de todas las diferencias. Inicialmente, las ponderaciones y sesgos se suelen establecer en valores aleatorios que, a menudo, producen un valor alto de pérdida cuando se empieza a entrenar una red neuronal.

La función de costo o pérdida
Figura 1 La función de costo o pérdida

A continuación, el algoritmo ajusta cada ponderación para minimizar la diferencia entre el valor calculado y el valor correcto. El término "propagación hacia atrás" procede del hecho de que el algoritmo retrocede y ajusta las ponderaciones y los sesgos después de calcular una respuesta. Cuanto menor sea la pérdida para una red, más precisa será. A continuación, se puede cuantificar el proceso de aprendizaje como la reducción del resultado de la función de pérdida. Cada ciclo de corrección de propagación hacia atrás y hacia adelante para reducir la pérdida se denomina época. En resumen, la propagación hacia atrás consiste en determinar las mejores ponderaciones y sesgos de entrada para obtener un resultado más preciso o "minimizar la pérdida". Si piensa que esto consume muchos recursos de proceso, está en lo cierto. De hecho, la capacidad de proceso era insuficiente hasta hace relativamente poco para que este proceso resultara práctico para el uso general.

Descenso de gradiente, velocidad de aprendizaje y descenso de gradiente estocástico

¿Cómo se ajustan las ponderaciones en cada época? ¿Se pueden ajustar de forma aleatoria o existe un proceso? En este punto es donde muchos principiantes empiezan a confundirse, ya que hay muchos términos desconocidos implicados, como descenso de gradiente y velocidad de aprendizaje. Sin embargo, cuando se explica bien, no es tan complicado. La función de pérdida reduce toda la complejidad de una red neuronal hasta un único número que indica si la respuesta de la red neuronal está muy lejos de la respuesta deseada. Pensar en el resultado de la red neuronal como número único nos permite pensar en su rendimiento en términos sencillos. El objetivo es encontrar la serie de ponderaciones que devuelve el valor de pérdida inferior o mínimo.

Al representarlo en un gráfico, como en la figura 2, se observa que la función de pérdida tiene su propia curva y gradientes que se pueden usar como guía para ajustar las ponderaciones. La pendiente de la curva de la función de pérdida sirve de guía y señala el valor mínimo. El objetivo es encontrar el mínimo de toda la curva, que representa las entradas donde la red neuronal es más precisa.

Gráfico de la función de pérdida con una curva sencilla
Figura 2 Gráfico de la función de pérdida con una curva sencilla

En la figura 2, al sumar más a las ponderaciones, se alcanza un punto bajo y, a continuación, empieza a subir de nuevo. La pendiente de la línea muestra la dirección hacia el punto inferior de la curva, que representa la pérdida más baja. Cuando la pendiente es negativa, se suma una cantidad a las ponderaciones. Cuando la pendiente es positiva, se resta una cantidad de las ponderaciones. La cantidad específica que se suma o se resta a las ponderaciones se conoce como velocidad de aprendizaje. Determinar una velocidad de aprendizaje ideal es tanto un arte como una ciencia. Si es demasiado grande, el algoritmo podría no acertar el mínimo. Si es demasiado baja, el aprendizaje tardará demasiado. Este proceso se denomina descenso de gradiente. Los lectores que están más familiarizados con las complicaciones del cálculo verán este proceso como lo que es: la determinación de la derivada de la función de pérdida.

Sin embargo, pocas veces es el gráfico de una función de pérdida tan sencillo como el de la figura 2. En la práctica, hay muchos altibajos. Entonces, el desafío consiste en encontrar el punto más bajo de los puntos bajos (el mínimo global) y en no dejarse engañar por los puntos bajos cercanos (mínimo local). El mejor enfoque en esta situación es elegir un punto de la curva de forma aleatoria y, a continuación, continuar con el proceso de descenso de gradiente descrito anteriormente; de ahí el término "Descenso de gradiente estocástico". Para obtener una excelente explicación de los conceptos matemáticos de este proceso, vea el vídeo de YouTube "Gradient Descent, How Neural Networks Learn | Deep Learning, Chapter 2” (Descenso de gradiente, Cómo funcionan las redes neuronales | Aprendizaje profundo, Capítulo 2") en youtu.be/IHZwWFHWa-w.

En su mayor parte, este nivel de arquitectura de red neuronal se ha abstraído en gran medida mediante bibliotecas como Keras y TensorFlow. Al igual que en cualquier labor de ingeniería de software, conocer los aspectos básicos siempre ayuda a la hora de enfrentarse a desafíos en el campo.

Poner en práctica la teoría

En la columna anterior, creé una red neuronal desde cero para procesar los dígitos MNIST. El código base resultante para arrancar el problema era ideal para ilustrar el trabajo interno de las arquitecturas de las redes neuronales, pero sacarlo adelante no resultaba práctico. Ahora, existen muchos marcos y bibliotecas que realizan la misma tarea con menos código.

Para empezar, abra un nuevo cuaderno de Jupyter, escriba lo siguiente en una celda en blanco y ejecútelo para importar todas las bibliotecas necesarias:

import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import to_categorical
import matplotlib.pyplot as plt

Tenga en cuenta que la salida de esta celda indica que Keras usa un back-end de TensorFlow. Dado que el ejemplo de red neuronal MNIST es tan común, Keras lo incluye como parte de su API e incluso divide los datos en un conjunto de aprendizaje y un conjunto de pruebas. Escriba el código siguiente en una celda nueva y ejecútelo para descargar los datos y leerlos en las variables adecuadas:

# import the data
from keras.datasets import mnist
# read the data
(X_train, y_train), (X_test, y_test) = mnist.load_data()

Una vez que el resultado indique que se han descargado los archivos, use el código siguiente para examinar brevemente el conjunto de datos de aprendizaje y de pruebas:

print(X_train.shape)
print(X_test.shape)

El resultado debe indicar que el conjunto de datos x_train tiene 60 000 elementos y el conjunto de datos x_test, 10 000. Ambos constan de una matriz de píxeles de 28 x 28. Para ver una imagen concreta de los datos MNIST, use MatPlotLib para representar una imagen con el código siguiente:

plt.imshow(X_train[10])

El resultado debe ser similar a un "3" trazado a mano. Para ver el contenido del conjunto de datos de prueba, escriba el código siguiente:

plt.imshow(X_test[10])

El resultado muestra un cero. No dude en experimentar cambiando el número de índice y el conjunto de datos para explorar los conjuntos de datos de imagen.

Dar forma a los datos

Como sucede con cualquier proyecto de IA o ciencia de datos, los datos de entrada se deben ajustar a las necesidades de los algoritmos. Los datos de imagen se deben acoplar en un vector unidimensional. Como cada imagen tiene 28 x 28 píxeles, el vector unidimensional será 1 por (28 x 28) o 1 por 784. Escriba el código siguiente en una celda nueva y ejecútelo (tenga en cuenta que esto no producirá ningún texto de salida):

num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')

Los valores en píxeles pueden oscilar entre 0 y 255. Para poder utilizarlos, deberá normalizarlos en valores de entre cero y uno. Para hacerlo, use el código siguiente:

X_train = X_train / 255
X_test = X_test / 255

A continuación, escriba el código siguiente para echar un vistazo al aspecto actual de los datos:

X_train[0]

El resultado revela una matriz de 784 valores entre cero y uno.

La tarea de utilizar varias imágenes de dígitos escritos a mano y determinar qué número representan es la clasificación. Antes de generar el modelo, debe dividir las variables de destino en categorías. En este caso, sabe que hay 10, pero puede usar la función to_categorical de Keras para determinarlo automáticamente. Escriba el código siguiente y ejecútelo (el resultado debe mostrar 10):

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
num_classes = y_test.shape[1]
print(num_classes)

Compilar, entrenar y probar la red neuronal

Ahora que los datos ya tienen forma y están preparados, ha llegado el momento de compilar las redes neuronales mediante Keras. Escriba el código siguiente para crear una función que cree una red neuronal secuencial con tres niveles con una capa de entrada de un valor de neuronas num_pixels (o 784):

def classification_model():
  model = Sequential()
  model.add(Dense(num_pixels, activation='relu', input_shape=(num_pixels,)))
  model.add(Dense(100, activation='relu'))
  model.add(Dense(num_classes, activation='softmax'))
  model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
  return model

Compare este código con el código de los métodos desde cero de mi última columna. Puede que observe que se hace referencia a términos nuevos, como "relu" o "softmax" en las funciones de activación. Hasta ahora, solo he explorado la función de activación Sigmoid, pero hay varios tipos de funciones de activación. Por ahora, recuerde que todas las funciones de activación comprimen un valor de entrada al generar un valor entre 0 y 1 o -1 y 1.

Con toda la infraestructura implementada, ha llegado el momento de compilar, entrenar y puntuar el modelo. Escriba el código siguiente en una celda nueva y ejecútelo:

model = classification_model()
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, verbose=2)
scores = model.evaluate(X_test, y_test, verbose=0)

A medida que se ejecute la red neuronal, observe que el valor de pérdida baja con cada iteración. En consecuencia, la precisión también mejora. Además, anote el tiempo que se tarda en ejecutar cada época. Una vez completado, escriba el código siguiente para ver la precisión y los porcentajes de error:

print('Model Accuracy: {} \n Error: {}'.format(scores[1], 1 - scores[1]))

El resultado revela una precisión superior al 98 % y un error del 1,97 %.

Persistencia del modelo

Ahora que el modelo se ha entrenado con un alto grado de precisión, puede guardarlo para un uso futuro a fin de evitar tener que volver a entrenarlo. Afortunadamente, Keras facilita esta tarea. Escriba el código siguiente en una nueva celda y ejecútelo:

model.save('MNIST_classification_model.h5')

Esto crea un archivo binario de, aproximadamente, 8 KB de tamaño que contiene los valores óptimos de ponderaciones y sesgos. Cargar el modelo también resulta fácil con Keras, como se muestra a continuación:

from keras.models import load_model
pretrained_model = load_model('MNIST_classification_model.h5')

Este archivo h5 contiene el modelo y se puede implementar junto con el código para volver a dar forma y preparar los datos de imagen de entrada. En otras palabras, el largo proceso de entrenar un modelo solo debe realizarse una vez. Para hacer referencia a un modelo predefinido, no se necesita el proceso de aprendizaje que consume tantos recursos de proceso y, en el sistema de producción final, la red neuronal se puede implementar rápidamente.

Resumen

Las redes neuronales pueden solucionar los problemas que han frustrado los algoritmos tradicionales durante décadas. Como hemos visto, su sencilla estructura oculta su auténtica complejidad. Las redes neuronales funcionan propagando entradas, ponderaciones y sesgos hacia adelante. Sin embargo, es en el proceso inverso de propagación hacia atrás donde la red aprende realmente al determinar los cambios exactos que se deben aplicar a las ponderaciones y sesgos para producir un resultado exacto.

Desde el punto de vista de la máquina, el aprendizaje consiste en minimizar la diferencia entre el resultado real y el correcto. Este proceso es tedioso y consume muchos recursos de proceso, como pone de manifiesto el tiempo que tarda en ejecutarse una época. Afortunadamente, este aprendizaje solo se debe realizar una vez y no cada vez que se necesita el modelo. Además, exploré mediante Keras cómo elaborar esta red neuronal. Aunque es posible escribir el código necesario para elaborar redes neuronales desde cero, es mucho más sencillo usar bibliotecas existentes, como Keras, que se encargan de los pequeños detalles por usted.


Frank La Vigne trabaja en Microsoft como profesional de soluciones de tecnología de inteligencia artificial, donde ayuda a las empresas a llegar más lejos aprovechando al máximo sus datos con inteligencia artificial y análisis. También hospeda en colaboración el podcast DataDriven. Escribe publicaciones con regularidad en FranksWorld.com y puede verlo en su canal de YouTube, "Frank’s World TV" (FranksWorld.TV).

Gracias a los siguientes expertos técnicos por revisar este artículo: Andy Leonard