-LeNet5
Primera red convolucional
El objeto de este artículo es construir y ejecutar el modelo de red neuronal LeNet5, especializada en el reconocimiento de dígitos manuscritos del 0 al 9, utilizando para ello las bibliotecas de Keras y TensorFlow de Python.
LeNet5 es una de las redes convolucionales más fáciles de construir, y por ello, la programación de su modelo es considerado como el «Hola mundo» del Aprendizaje Profundo.
Para construir la red tomaremos como referencia el artículo original de Yann LeCun del año 1998, cuyo contenido ya fue analizado en nuestro artículo dedicado a las redes convolucionales.
Programas
Elaboraremos dos programas en Python: el primero de ellos construirá, entrenará y evaluará el modelo y tras ello lo guardará en disco. El segundo programa cargará el modelo ya guardado y realizará predicciones con el mismo.
El programa se articula internamente en siete secciones diferentes:
- Importación de las bibliotecas de Python necesarias para la tarea.
- Carga del banco de imágenes.
- Construcción de la arquitectura de red.
- Determinación de la función de pérdida, y el modo de optimización (modificación de parámetros entrenables) del modelo.
- Entrenamiento del modelo.
- Comprobación de la eficiencia del modelo ya entrenado.
- Almacenamiento en disco.

[1] -Importación de bibliotecas
Para nuestro primer programa, nos bastará con importar las bibliotecas de TensorFlow y Keras, así como los objetos correspondientes al «modelo secuencial» (más abajo se expondrá este concepto) y las diferentes capas que componen LeNet-5 (convolucional, densa, etc…). Ello se hace mediante las líneas indicadas a la derecha
[2] -Carga del banco de imágenes
La segunda sección del programa tiene por misión cargar el banco de imágenes usado para entrenar y testar la red.
Estructura de los datos
La base de datos a emplear es MNIST, que está compuesta de miles de gráficos en cada uno de los cuales se traza un dígito manuscrito del 0 al 9. De dichos gráficos, 60.000 son imágenes de entrenamiento y otras 10.000 imágenes de evaluación. Cada imagen es un cuadro con dimensiones de 28×28 píxeles. Los píxeles representan gamas de grises que oscilan entre 0 (negro) y 255 (blanco). Estas imágenes vienen acompañadas de una etiqueta (un entero del 0 al 9) que señala de qué dígito se trata.
En Keras y TensorFlow las bases de datos se descargan de la red o se extraen de un archivo local. Y tras ello, se organizan en matrices n-dimensionales denominadas tensores. En nuestro programa, crearemos tres tensores que almacenarán las imágenes de entrenamiento (entrena_x), validación (val_x), y comprobación (comp_x). Los tensores tienen tres dimensiones: ancho, alto e índice de la imagen. El rango de las dos primeras se extiende de 0 a 27. El de la tercera, de 0 a 59.999 (datos de entrenamiento) o de 0 a 4.999 (datos de validación y comprobación). Los píxeles representan gamas de grises. Oscilan entre 0 (negro) y 255 (blanco).
Las etiquetas se almacenarán a su vez en tres tensores de una dimensión, llamados entrena_y, val_y y comp_y. La única dimensión señala el índice de la imagen de que se trate. El contenido de cada elemento es un entero del 0 al 9, como ya hemos señalado.

Carga de la base de datos
La instrucción load_data() descarga por entero la base de datos MNIST desde el repositorio remoto de Keras y la almacena, bajo la forma de cuatro tensores, en la memoria de la GPU.
Como el lector puede fácilmente comprobar, dos de los cuatro tensores generados por la instrucción son de entrenamiento (entrena_x y entrena_y) y los otros dos de comprobación (test_x y test_y).
Reestructuración de los datos
Los datos de comprobación serán a su vez divididos en dos: con las primeras 5.000 imágenes y etiquetas se construirán los dos tensores de validación (val_x, val_y), usados para comprobar cómo evoluciona el modelo durante el entrenamiento. Con las últimas 5.000 imágenes y etiquetas construiremos los dos tensores de comprobación (comp_x, comp_y) o evaluación del modelo al final, una vez este haya sido entrenado por completo.
[3] -Construcción del modelo
Para Keras, una red neuronal es un modelo. Y un perceptrón multicapas orientado hacia adelante es un modelo secuencial, que está compuesto de capas que se van agregando sucesivamente, desde la entrada hasta la salida. Tanto el modelo como las capas son considerados como objetos en el lenguaje Python, que se crean a partir de clases.
Procedemos a crear el modelo, que denominamos lenet5, mediante la ejecución del método Sequential().
Tras ello, vamos agregando una por una las diferentes capas de la arquitectura, tal y como están descritas en el célebre artículo de Yann LeCun y sus compañeros del año 1998. Las capas son objetos pertenecientes a las clases Conv2D, MaxPooling2, Flatten y Dense, dependiendo del tipo de capa de que se trate. Cada vez que se crea una de ellas, se debe agregar al modelo mediante el método add:
Capas convolucionales
Como vemos, dos capas convolucionales con kernel 5×5 se intercalan con otras dos de max pooling, de dimensiones 2×2. La primera capa convolucional, consta de 6 mapas (aquí llamados filters o filtros) y está dotada de padding same (no se produce, pues, disminución de resolución). La segunda capa convolucional consta de 16 filtros, y está está dotada de padding valid, que provoca una disminución de resolución en la capa siguiente.
Capas densas y pooling
La transición de la sección convolucional a las capas densamente conectadas se realiza por una capa Flatten, que transforma las matrices de la capa de pooling en una array unidimensional, destinado a alimentar a la siguiente capa densa. Las tres últimas capas, densamente conectadas con la anterior, están dotadas de 120, 84 y 10 neuronas respectivamente.
La función de activación de todas las neuronas es la tangente hiperbólica, salvo las de la última capa de salida, que tienen una función softmax, tal y como es usual en las redes clasificadoras.
'
same'
, activation='
tanh'
))'
same'
, activation='
tanh'
))'
tanh'
))'
tanh'
))
'
softmax'
))
[4] -Entrenamiento de la red
Tras la construcción de la arquitectura de red, pasamos a la compilación del modelo. Tenemos que determinar cuál es la función de pérdida, y qué sistema de optimización (es decir, de modificación de pesos sinápticos y de sesgos) va a ser utilizado durante los entrenamientos.
Parámetros de aprendizaje
Como es usual en las redes clasificadoras, la función de pérdida es la entropía cruzada, que compara el resultado ofrecido por la capa de salida de la red con un vector de verdad subyacente (ground truth) de tipo 1-de-N (o one-hot), en el que todos los valores se ponen a cero salvo el correspondiente a la solución correcta (es decir, una expresión de tipo [0,0,0,1,0,0,0,0,0,0]).
Dado que las etiquetas de MNIST no consisten en vectores one-hot sino en un número entero del cero al nueve, es necesario utilizar una variante de la entropía categórica denominada entropía categoría dispersa (sparse categorical crossentropy), específica para etiquetas numéricas de clases, y que transforma números enteros del 0 al 9 en vectores one-hot.
Entrenamiento de la red
Todo está listo, pues, para entrenar al modelo. Ello se realiza mediante una simple línea de código. El método fit() recibe cuatro argumentos: El tensor de imágenes de entrenamiento (entrena_x), el tensor de etiquetas de entrenamiento (entrena_y), el número de épocas (epochs), en este caso cinco, y los datos de validación (val_x y val_y). Soi se tiene habilitada un GPU, todos los datos de entrenamiento, así como el código a ejecutar, son enviados a dicha GPU, que es donde tienen lugar los procesos de aprendizaje. LeNet-5 tiene unos 60.000 parámetros, que tras ser calculados por la tarjeta gráfica serán enviados al chipset y almacenados en la memoria RAM.
Tal y como se puede leer en la última línea, la precisión final (accuracy) de nuestro modelo supera el 98% con los datos de entrenamiento, y casi un 97% con los datos de validación.
'
adam'
, loss='
sparse_categorical_crossentropy'
, metrics=['
accuracy'
]) Epoch 1/5
1875/1875 [==============================] – 54s 29ms/step – loss: 0.1732 – accuracy: 0.9461 – val_loss: 0.1029 – val_accuracy: 0.9656
Epoch 2/5
1875/1875 [==============================] – 54s 29ms/step – loss: 0.0780 – accuracy: 0.9759 – val_loss: 0.1053 – val_accuracy: 0.9648
Epoch 3/5
1875/1875 [==============================] – 54s 29ms/step – loss: 0.0573 – accuracy: 0.9812 – val_loss: 0.0998 – val_accuracy: 0.9672
Epoch 4/5
1875/1875 [==============================] – 54s 29ms/step – loss: 0.0468 – accuracy: 0.9852 – val_loss: 0.0870 – val_accuracy: 0.9702
Epoch 5/5
1875/1875 [==============================] – 54s 29ms/step – loss: 0.0465 – accuracy: 0.9854 – val_loss: 0.1045 – val_accuracy: 0.9666
[5] -Evaluación de la red
La evaluación del modelo se realiza invocando el método evaluate(), que recibe como argumento el tensor de imágenes de comprobación y el tensor de las etiquetas de dichas imágenes.
El sistema retorna entonces el valor de la función de pérdida y el porcentaje de acierto (accuracy) de la red.
La precisión es de más del 98%.
[6] -Almacenamiento del modelo
Si cerramos definitivamente tanto la sesión en Keras como el compilador de Python, los datos relativos a la arquitectura de la red y los parámetros entrenables de esta última se perderán. Para prevenirlo, hemos de guardar y exportar estos datos por medio del método save() donde ruta* es la ruta del directorio donde se guardará el modelo (recordemos que en Python es necesario hacer uso de la doble barra \\ para marcar la ruta).
lenet5.save('
C:/Users/Modelos/lenet5'
)
-Segundo programa: recarga y uso del modelo
El segundo programa se encargará de cargar el modelo que previamente hemos guardado en disco y realizar predicciones con él a partir de imágenes aleatorias extraídas de MNIST.
Se compone de las siguientes secciones:
-Importación de las bibliotecas necesarias.
-Carga de la base de datos
-Carga del modelo.
-Selección de una muestra de la base de datos, e impresión de su imagen.
-Realización de la predicción respecto de dicha imagen.
[1] -Importación de bibliotecas
[2] -Carga de la base de datos y del modelo
Bajamos de nuevo la base de datos MNIST, utilizando las mismas líneas de código que en el primer programa, y cargamos el modelo desde el disco duro mediante el método load_model():
'
C:/Users/Modelos/lenet5'
)[3] -Impresión de una imagen de muestra
'
/n'
+ '
Imprimimos la imagen de la muestra '
+ str(muestra) + '
:'
)[4] - Realización de una predicción
'
La imagen corresponde al '
+ str(np.argmax(prediccion)))- Conclusión
En este tutorial hemos construido una red neuronal relativamente simple, LeNet-5, utilizando para ello, y por razones pedagógicas, la menor cantidad de código posible. Animamos a nuestros lectores a estudiar en profundidad el lenguaje Python, y particularmente sus bibliotecas Keras y TensorFlow, con ayuda de las cuales podrá implementar la mayor parte de las ideas expuestas en esta serie.
Nuestro siguiente tutorial mostrará cómo podemos cargar otras bases de datos e incluso hacer uso de imágenes propias para entrenar nuestras redes.
Primer Programa
¡Copia y pega el código de abajo en un cuaderno online de Colab y observa cómo entrena la red!
lenet5.add(Conv2D(input_shape=(28,28,1), filters=6, kernel_size=(5,5), strides=1, padding='
same'
, activation='
tanh'
))'
same'
, activation='
tanh'
))'
tanh'
))'
tanh'
))'
softmax'
))
'
adam'
, loss='
sparse_categorical_crossentropy'
, metrics=['
accuracy'
])
'
C:/Users/Modelos/lenet5'
) Segundo Programa
Copia y pega el código de abajo en un cuaderno online de Colab y observa cómo entrena la red.
'
C:/Users/Modelos/lenet5'
)
'
/n'
+ '
Imprimimos la imagen de la muestra '
+ str(muestra) + '
:'
)
'
La imagen corresponde al '
+ str(np.argmax(prediccion)))