scieee AI-readable full text Open interactive document viewer

Reconocimiento automático de caracteres manuscritos utilizando aprendizaje máquina

Gallo García, Roberto

Abstract

La Inteligencia Artificial y la búsqueda de la replicación de la consciencia y actividades humanas por parte de las máquinas como leer, escribir, cantar etc. ha sido desde hace siglos un sueño y un campo de investigación para la sociedad. En los últimos 60 años afortunadamente estos sueños se están volviendo realidad gracias al desarrollo exponencial de la tecnología para tareas de automatización de actividades realizadas hasta ahora por seres humanos. Una de las actividades con mayor recorrido y avances ha sido el reconocimiento óptico de formas y patrones, con aplicaciones en infinidad de campos y que permiten el proceso de información de nuestro entorno para luego combinarla con otras tareas de automatización y clasificación. Nos encontramos por lo tanto con un campo con una gran proyección a futuro y en el que las máquinas cada vez están más cerca de competir con capacidades humanas de lectura y abstracción. Dentro del campo del reconocimiento óptico, el reconocimiento de caracteres y en especial el de textos manuscritos ha sido uno de los mayores retos a afrontar debido a la diferencia en la que cada persona escribe un mismo carácter y las condiciones en las que cada texto se nos puede presentar. Estas dificultades son estudiadas y solventadas gracias al Reconocimiento Óptico de Caracteres (ROC). Recientemente gracias al estudio de las redes neuronales, a la creación de arquitecturas de procesamiento de la información cada vez más complejas y a la implementación de un hardware más potente en ordenadores comerciales se ha conseguido llevar todas estas cuestiones a un público más amplio y se ha conseguido mejorar la precisión de estos sistemas y minimizar los errores. Este proyecto expone el estudio de este campo desde las bases, partiendo de los retos y la tecnología a emplear y posteriormente realizando una comparativa de diferentes modelos, demostrando que el desarrollo de la Inteligencia Artificial ha pasado en solo unas décadas de un reducido grupo de personas a todas aquellas que con inquietud y tiempo contribuyen al avance de la sociedad.

Full text

Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica Reconocimiento automático de caracteres manuscritos utilizando aprendizaje máquina Dpto. de Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Autor: Roberto Gallo García Tutor: Francisco José Simois Tirado Sevilla,diciembre 2021 iii Trabajo Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica Reconocimiento automático de caracteres manuscritos utilizando aprendizaje máquina Autor: Roberto Gallo García Tutor: Francisco José Simois Tirado Profesor Contratado Doctor Dpto. de Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, septiembre 2022 v Trabajo Fin de Grado: Reconocimiento automático de caracteres manuscritos utilizando aprendizaje máquina Autor: Roberto Gallo García Tutor: Francisco José Simois Tirado El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2022 El Secretario del Tribunal vii A mi familia ix Agradecimientos Quisiera agradecer en primer lugar a todos aquellos que estuvieron conmigo al empezar la carrera y hoy en día siguen a mi lado viéndome dar estos últimos empujones. A mi familia, que estuvo ahí con cada bache y me ayudó a superarlo y a seguir adelante cuando ni yo mismo podía. A mis amigos del barrio, que siempre me dijeron que podía con todo lo que me echasen y a día de hoy aún me dan ánimos y están a mi lado. A mis amigos del instituto y de la Universidad, de los que aprendí que la distancia o los obstáculos en el camino no separan a las personas y gracias a las cuáles sigo persiguiendo con ilusión este proyecto. También me quisiera agradecer a mi pareja María el apoyo incondicional que me aportó en mis mejores y peores momentos y ha sido una de las mejores personas que me ha dado la carrera. Por último, me gustaría agradecer especialmente a todos aquellos profesores que han sido parte de mi vida; desde que empecé a educarme a mi tutor en este proyecto, Francisco, pues reúnen todas aquellas cualidades que he citado anteriormente y han sido fuente de inspiración y de constancia para mí. Termino así esta etapa en la que el aprendizaje y el descubrimiento han formado una parte especial de mi vida. Gracias de nuevo a todas las personas que han hecho que esto se pueda llevar a cabo. Roberto Gallo García Estudiante de ingeniería Sevilla, 2022 xvii ÍNDICE DE TABLAS Tabla 1. Matriz de verificación 23 Tabla 2. Muestra del coste del contrato dependiendo del tiempo de llamada y del número de estas 35 Tabla 3. Especificaciones de la computadora local. 43 Tabla 4. Especificaciones de la máquina virtual de Google Colab [26]. 43 Tabla 5. Comparativa de resultados con distintos optimizadores 62 Tabla 6. Comparativa de resultados con distintos tamaños de lote 63 Tabla 7. Comparativa con redes de diferente profundidad 64 Tabla 8. Resultados utilizando el Modelo 1 (CNN) en el set de pruebas de EMNIST Balanced 74 Tabla 9. Resultados utilizando el Modelo 2 (CNN+Dropout) en el set de pruebas de EMNIST Balanced 75 Tabla 10. Resultados utilizando el Modelo 3 (ResNet50) en el set de pruebas de EMNIST Balanced 76 Tabla 11. Resultados utilizando el Modelo 4 (CNN Letras + CNN Números) en el set de pruebas de EMNIST Balanced 77 Tabla 12. Comparativa entre los diferentes modelos en las fases de desarrollo del proyecto. 84 Tabla 13. Diagrama de Gantt 86 xix ÍNDICE DE FIGURAS Figura 1. Fragmento de la patente de la Máquina de Gismo. [4] 25 Figura 2. Análisis de las características y diferencias principales entre un cormorán y un zorro 29 Figura 3. Esquema de un perceptrón con tres entradas y una salida. 30 Figura 4. Esquema de red neuronal con tres entradas, tres capas ocultas y una salida 31 Figura 5. Representación gráfica de la salida en neuronas sigmoidales 32 Figura 6. Red compuesta por perceptrones 32 Figura 7. Ejemplos de corrección de pesos en una red con neuronas sigmoidales 33 Figura 8. Representación gráfica de cómo actúa el descenso del gradiente en una función de error 34 Figura 9. Esquema de la red neuronal propuesta 35 Figura 10. Implementación de la red neuronal 36 Figura 11. Compilación del modelo de ejemplo 37 Figura 12. Función de pérdida del modelo de red simple 37 Figura 13. Código y resultados obtenidos 38 Figura 14. Ejemplos de dos redes neuronales 39 Figura 15. Esquema de una neurona incluyendo la función de activación 40 Figura 16. Representación gráfica de la función de activación ReLU 40 Figura 17. Esquema del programa 42 Figura 18. Herramientas de desarrollo de código y librerías para la implementación de programas de Deep Learning [27] [28] [29] 43 Figura 19. Ejemplo de letras y números tomado de una de las bases de datos de EMNIST 45 Figura 20. Implementación de la normalización de los datos 45 Figura 21. Distribución de los diferentes caracteres y su número de muestras 46 Figura 22. Código de la normalización con la transposición realizada 46 Figura 23. Algunos de los caracteres del set de entrenamiento, etiquetados 47 Figura 24. Diagrama de la red neuronal diseñada 47 Figura 25. Resumen de la red neuronal 48 Figura 26. Ejemplos de predicciones con el set de pruebas 50 Figura 27. Ejemplos de predicciones con imágenes reales de una G 50 Figura 28. Muestras provenientes del Fashion MNIST Dataset de Zalando. [37] 52 Figura 29. Varios puntos de vista de una misma zapatilla deportiva (JD, s.f.) [38] 53 Figura 30. Partes de un gato 53 Figura 31. Esquema del proceso de convolución 54 Figura 32. Esquema del proceso de agrupación 54 Figura 33. Esquema LeNet de la CNN que utilizaremos 55 Figura 34. Resultados de la convolución de dos imágenes del carácter 8 pasadas por tres procesos de convolución y agrupación. 56 Figura 35. Resumen de la red diseñada. 57 Figura 36. Implementación del entrenamiento utilizando funciones de Keras [42] 58 Figura 37. Programación y resultado de aplicar ImageDataGenerator sobre imagen de letra “h” 60 Figura 38. Predicciones obtenidas para el primer modelo 63 Figura 39. Resumen del modelo con Dropout 65 Figura 40. Resultados de pérdidas sin aumento de datos e incluyendo Dropout. 65 Figura 41. Resultados de precisión sin aumento de datos e incluyendo Dropout. 66 Figura 42. Resultados de pérdidas con aumento de datos e incluyendo Dropout. 67 Figura 43. Resultados de precisión con aumento de datos e incluyendo Dropout. 67 Figura 44. Predicciones obtenidas para el segundo modelo con Dropout 68 Figura 45. Esquema de una conexión residual. 69 Figura 46. Diagrama y resumen de parámetros de nuestra red ResNet50 [46] 69 Figura 47. Resultados de pérdidas utilizando ResNet 70 Figura 48. Resultados de precisión utilizando ResNet 70 Figura 49. Predicciones obtenidas para el tercer modelo con ResNet 71 Figura 50. Resultados de pérdidas y precisión únicamente para las letras 71 Figura 51. Resultados de pérdidas y precisión únicamente para las letras 72 Figura 52. Comparativa del dato de entrada al programa vs dato de entrada a la Red Neuronal 73 Figura 53. Predicciones utilizando el Modelo 1 (CNN) 74 Figura 54. Predicciones utilizando el modelo 2 (CNN+Dropout) 75 Figura 55. Predicciones utilizando el modelo 3 (ResNet50) 76 Figura 56. Predicciones utilizando el modelo 4 (Dos modelos) 77 Figura 57. Letra “b” utilizando el modelo ResNet50. 77 Figura 58. Resultados utilizando CNN, indicando probabilidad de acierto 79 Figura 59. Resultados utilizando CNN+Dropout 79 Figura 60. Resultados utilizando ResNet50 79 Figura 61. Resultados con dos CNN, así como el carácter elegido y su probabilidad en cada caso. 80 Figura 62. Comparativa entre usar un mismo predictor para letras y números y seleccionar en cada zona de la imagen qué predictor usar 81 Figura 63. Relación entre el mapa de características de un fragmento de imagen y el fragmento del mapa de características de la imagen completa. 82 Figura 64. Nueva organización de los datos de entrenamiento y validación; con imágenes y etiquetas 83 21 1 INTRODUCCIÓN lo largo de los últimos 20 años, la tecnología de reconocimiento óptico de caracteres se ha usado para transcribir millones de libros, periódicos y artículos a texto que pueda ser encontrado fácilmente usando cualquier buscador en Internet. Para ello, se emplean herramientas capaces de procesar la imagen y luego extraer de las mismas el texto. Aún así, esta tarea solo puede realizarse de manera eficiente si la imagen se presenta de forma clara y el texto es lo suficientemente visible. La tarea de reconocimiento implica por lo tanto la localización del texto, la segmentación de este en caracteres y la correcta identificación de los mismos. Aunque algunos de los sistemas son capaces de identificar las palabras sin esa segmentación previa, requieren de un mayor coste computacional para obtener la misma precisión que los otros. Para ello, primero se ha de aplicar técnicas de procesamiento de la imagen como son la detección de contornos, la reducción de ruidos, erosión y dilatado de la imagen entre otras para conseguir que cada uno de los caracteres se presenten de la mejor forma. Eliminar sombras, aplicar filtros de color y binarizar la imagen son otros de los pasos posteriores que se han de seguir para normalizar la imagen. Posteriormente se ha de segmentar la imagen en cada uno de los elementos que lo conforman e introducirlos en nuestro programa ya entrenado para detectar el carácter ante el cual nos encontramos. Aunque son muchas las técnicas utilizadas para entrenar a nuestro sistema en el reconocimiento de caracteres, la más utilizada es aquella que se basa en Redes Neuronales, similares a las que conforman el cerebro humano y que a través de múltiples iteraciones con datos ya reconocidos poder crear las conexiones necesarias para poder predecir correctamente datos que no forman parte del entrenamiento. Esta tecnología la aplican hoy en día de forma satisfactoria en numerosas entidades bancarias, centros de marketing, académicos y no solo son capaces agilizar el procesamiento de información sino de mejorar la vida de las personas haciendo que un programa lea en voz alta ficheros de texto para personas con problemas de visión. Las aplicaciones de esta tecnología cada vez abarcan más ámbitos de nuestra sociedad y vida cotidiana. 1.1. Alcance del Proyecto 1.1.1. Objetivo El objetivo de este proyecto es estudiar y entrenar diferentes modelos de Redes Neuronales para probar su desempeño en el reconocimiento de caracteres tanto en datos de validación como en caracteres escritos a mano en diferentes condiciones. También se probará el programa detecte más de un carácter en la misma imagen y que puede hacerlo en tiempo real dada una entrada de vídeo Este trabajo podría tener numerosas aplicaciones aparte de las ya mencionadas, pasando de la detección de caracteres a palabras y luego usarlo por ejemplo para el reconocimiento de secciones de un carnet de identidad. A Introducción 22 1.1.2. Requisitos Según la norma ISO 21500 [1] se estructurará el proyecto en bloques diferenciados para realizar un seguimiento adecuado del mismo. De esta forma los requisitos pasaron a ser los siguientes: 1.1.2.1. Requisitos funcionales • F.1: El sistema leerá correctamente los datos de entrada para el entrenamiento, validación y pruebas. • F.2: El sistema procesará y mostrará los resultados de cada experimento de forma legible, y será capaz de contrastarlos con predicciones tomadas de los datos de validación • F.3: El sistema será capaz de procesar imágenes de caracteres individuales y predecirlos • F.4: El sistema detectará si hay más de un carácter en la imagen y realizará una predicción de cada uno, mostrando el resultado y el carácter en una caja delimitadora • F.5: El sistema será capaz de realizar predicciones en tiempo real para una entrada de vídeo dada. 1.1.2.2. Requisitos de prestaciones • P.5: El sistema contará con una entrada de vídeo para realizar las predicciones en tiempo real 1.1.3. Restricciones • R.5: La calidad de la cámara y las condiciones de iluminación de la estancia podrán no ser las óptimas para la correcta detección de los caracteres. 1.2. Planificación del proyecto A la hora de realizar cualquier proyecto es muy importante la organización y planificación de éste, marcando los objetivos y los pasos que debemos seguir desde el principio. La planificación de este proyecto se dividirá en varias etapas que coinciden con las del desarrollo del proyecto. La primera etapa consistirá en la búsqueda de información y estudio del estado del arte de forma que se parta de los conceptos básico en Inteligencia Artificial y cómo funciona el aprendizaje de una red neuronal. Visto los diferentes componentes de un sistema de aprendizaje automático se propondrá un ejemplo sencillo para entenderlos y desde esa base nos enfrentaremos al problema planteado en el proyecto. En la segunda etapa nos enfocamos en nuestro problema principal y analizamos el entorno de trabajo y los requisitos necesarios para un correcto entrenamiento. Una vez comprobado que se realiza la lectura de los sets elegidos para el entrenamiento, se realizarán distintos entrenamientos con distintas arquitecturas y se compararán los resultados, buscando predecir el comportamiento de cada modelo en un entorno real de trabajo. Una vez superada la etapa anterior, en una tercera etapa realizaremos las pruebas para caracteres de diferente tipografía de forma que se puedan contrastar las hipótesis del apartado anterior con los resultados obtenidos. Realizado este análisis introduciremos el problema de la detección de múltiples caracteres y cómo sería posible segmentar la imagen de forma eficaz. Realizaremos nuevamente pruebas con los distintos modelos y trasladaremos la solución propuesta para vídeos y comprobar que el sistema se comporta correctamente ante casos en tiempo real. Por último, se expondrán las conclusiones a las que se llegaron con la información recogida y los experimentos que se realizaron, así como propuestas de optimización y mejora. 23 1.3. Matriz de verificación • F.1: El sistema leerá correctamente los datos de entrada para el entrenamiento, validación y pruebas: o El sistema muestra correctamente datos de entrenamiento etiquetados (test 1.1) o El sistema muestra correctamente datos de validación etiquetados (test 1.2) o El sistema lee correctamente imagen y vídeo (test 1.3) • F.2: El sistema procesará y mostrará los resultados de cada experimento de forma legible, y será capaz de contrastarlos con predicciones tomadas de los datos de validación o Obtención de gráficas de precisión y de pérdida para cada uno de los modelos (test 2.1) o Obtención de los resultados de predicción para caracteres aleatorios del set de validación (test 2.2) • F.3: El sistema será capaz de procesar imágenes de caracteres individuales y predecirlos o Predicción de diferentes caracteres para los distintos modelos (test 3) • F.4: El sistema detectará si hay más de un carácter en la imagen y realizará una predicción de cada uno, mostrando el resultado y el carácter en una caja delimitadora o Predicción y delimitación para distintas imágenes de múltiples caracteres (test 4) • F.5: El sistema será capaz de realizar predicciones en tiempo real para una entrada de vídeo dada. o Pruebas con entrada de vídeo (webcam) donde aparezcan caracteres, y predicción y delimitación de los mismos (test 5) Requisito Nombre del requisito Verificación Nombre de prueba Estado I A D T F1 Lectura de datos X 1.1. Lectura de datos de prueba completado F1 Lectura de datos X 1.2. Lectura de datos de validación completado F1 Lectura de tados X 1.3. Leer imagen y vídeo completado F2 Procesamiento de resultados X 2.1. Obtención de gráficas de precisión y pérdidas completado F2 Procesamiento de resultados X 2.2. Obtención de resultados (set de validación) completado F3 Pruebas en imágenes X 3. Predicción para caracteres individuales completado F4 Caso de múltiples caracteres X 4. Predicción para múltiples caracteres completado F5 Predicción en tiempo real X 5. Predicción y delimitación de caracteres en TR completado Tabla 1. Matriz de verificación Estado del arte 24 2 ESTADO DEL ARTE l reconocimiento óptico de caracteres, usualmente abreviado como OCR por sus siglas en inglés (Optical Character Recognition) es la conversión por parte de una computadora de una imagen de texto, bien sea impreso o manuscrito, a texto entendible por el ordenador. En la actualidad sus usos abarcan infinidad de campos; como la lectura de pasaportes, notas bancarias, traducción automática de documentos o en cualquier otro sector en el que se usen archivos impresos. Es la forma más común en la que se digitaliza texto para luego ser almacenado, editado o usado en diversos procesos como transformar el texto en audio (text-to- speech) o gestión de bases de datos. [2] Un programa que implemente OCR deberá, por lo tanto, reconocer “patrones” que permitan distinguir los diferentes caracteres, aprendiendo a través de una red neuronal; y almacenar ese texto escrito en un fichero ASCII. Esto no es una tarea fácil y ha sido desarrollada recientemente gracias al gran desarrollo y éxito del Deep Learning, hasta conseguir modelos con una precisión y eficiencia del orden del 99% en el reconocimiento de dígitos manuscritos. Aunque en un principio pudiese parecer que el OCR es una consecuencia del desarrollo de la Inteligencia Artificial, la realidad es que el ser humano lleva intentando implementar esta tecnología desde finales de los años 20. La invención de este sistema se le atribuye a Gustav Tauschek, un inventor austriaco que en 1929 desarrolló un primitivo sistema de reconocimiento de caracteres para las máquinas de cálculo basadas en tarjetas perforadas. El dispositivo, bautizado como la Máquina de Tauscheck [3], era un aparato mecánico en el cual se colocaba un fotodetector y una serie de plantillas de forma que cuando la plantilla y el carácter estuviesen alineados, una luz era dirigida hacia el fotodetector, que almacenaba el valor del carácter reconocido. Este sistema, aunque limitado, puso en pie ya desde esa época la necesidad de trasladar texto impreso a un sistema de almacenaje y gestión de datos. A pesar de tener origen en esa época, no fue hasta 1953 cuando se empezaron a usar versiones comerciales de máquinas que convertían mensajes impresos en lenguajes para almacenarlos en un computador. Estos modelos utilizaban un análisis de la imagen en vez de una comparación pudiendo aceptar variaciones en la fuente, necesidad fundamental para reconocer caracteres manuscritos y base sobre la que funcionan los sistemas de reconocimiento de caracteres ópticos actuales. La máquina llamada Gismo [4] fue utilizada para automatizar datos de agencias, leer impresiones en tarjetas de crédito e incluso por la fuerza aérea de los Estados Unidos para transcribir mensajes escritos a máquina. E 25 Figura 1. Fragmento de la patente de la Máquina de Gismo. [4] Los sistemas OCR hasta ahora asumían que el texto seguía una estructura predeterminada y los caracteres se encontraban en posiciones fijas (como podría ser el caso de la matrícula de un coche). En la realidad las imágenes no siguen una plantilla y tendrán variaciones de tipografía, formas, iluminación y orientación por lo que estos sistemas seguían siendo extremadamente limitados. Los sistemas OCR actuales se consiguieron implementar a finales de los 90 combinando los métodos contemporáneos que se usaban con métodos de reconocimiento de patrones e inteligencia artificial. La mejora de estos sistemas también ha sido posible gracias al desarrollo no solo de los algoritmos sino también de la tecnología encargada de capturar y procesar la información: escáneres, cámaras, ordenadores… Todos estos avances han permitido el procesamiento de más datos tanto por la mayor capacidad de cómputo como por la mejor resolución de los sensores. [5] A pesar de que los resultados mejoran de forma exponencial con el paso de los años, todavía quedan multitud de retos por afrontar para el reconocimiento óptico de caracteres, como la gran variedad de situaciones de iluminación de los fotogramas, la situación y variedad de fuentes de los caracteres y sobre todo la diferencia de formas en las que varias personas pueden escribir un mismo carácter, lo que dificulta la clasificación con precisión de caracteres. Con objeto del estudio de estas técnicas de clasificación, así como su eficiencia y mostrar resultados empíricos de diferentes modelos de clasificadores, nace este Trabajo de Fin de Grado, donde se pretende en primer lugar dar unos esbozos y definir las bases que cimientan las redes de Deep Learning, estableciendo los conceptos y terminología que usaremos, el estado del arte de la Inteligencia Artificial tal y como hoy en día la conocemos y se explicará el diseño realizado desde cero del método de entrenamiento y predicción de diversos sistemas. Introducción a las Redes Neuronales 32 posible la función de coste. Para ello los perceptrones suponen un primer acercamiento al Machine Learning, pero al tener una salida en escalón en forma de ceros y unos provocan a la fuerza que la salida se modifique bruscamente al modificar ligeramente los parámetros de nuestra red. El modelo que buscamos debería cambiar ligeramente su salida si así lo hacen su peso o bias. Para ello es necesario hablar de las neuronas sigmoidales, cuya expresión genera este escalón más suavizado y que por primera vez introduce valores intermedios entre 0 y 1. 𝜙(𝑧)=1 1+𝑒−𝑧 ( 3 ) Figura 5. Representación gráfica de la salida en neuronas sigmoidales. Este cambio implica que si, por ejemplo, pretendemos entrenar una red para que actúe como un reconocedor de mascotas, suponiendo que una salida de 1 es un perro y de 0 es un gato, la red modificará sus parámetros en cuanto de alejada esté de la realidad. Figura 6. Si nuestra red está compuesta por perceptrones y se obtiene una predicción equivocada, el programa no sabrá cuánto ha de variar los parámetros de la red, ya que no sabemos a qué distancia nos encontramos de una predicción correcta. 33 Figura 7. Ejemplos de corrección de pesos en una red con neuronas sigmoidales. Para el segundo caso las neuronas corregirán menos sus valores, pues se encuentran más cerca de predecir el resultado “perro”, ya que obtiene un feedback de cuánto se está alejando de la predicción correcta. Introducción a las Redes Neuronales 34 Ya sabemos cuál es el criterio que utilizamos para modificar los valores de los parámetros. Pero ¿cómo sabe la red neuronal cuánto hay que modificar los pesos? Para ello se introduce el concepto de descenso del gradiente. Para visualizarlo mejor se ha representado una gráfica del error de nuestra red dependiendo de los parámetros introducidos. Los ejes x e y serían los parámetros de nuestra red y el eje z, el error. Buscamos acercarnos lo más posible a algún mínimo de la función partiendo desde un punto aleatorio y para ello nos aprovecharemos de la definición del gradiente, que apunta a la dirección en la que mayor es la pendiente y se lo restaremos al punto en el que estemos. Este proceso se realizará de forma continuada, descendiendo así por la función hasta encontrar el mínimo deseado. [17] Figura 8. Representación gráfica de cómo actúa el descenso del gradiente en una función de error Por último y para perfilar esta breve introducción a la estructura de una red neuronal, es necesario establecer los conceptos de capa de entrada, capas ocultas y capa de salida. La capa de entrada es la que recibe los valores dados por el usuario y los únicos que nosotros podemos controlar. Las capas intermedias contienen más neuronas con diferentes pesos y bias. Esto lo utilizaremos para ajustar de mejor forma, el comportamiento de la red. Cuantas más neuronas, más ajustes podrán realizarse para perfeccionar el comportamiento de la red, pero más complicada será de entrenar. Por último, tenemos la capa de salida que muestra los valores entre cero y uno obtenidos por sus neuronas y los resultados de nuestro sistema. Por lo general las redes suelen tomar una estructura feedforward o sin realimentación, no utilizando las salidas de capas más profundas para capas a la izquierda de estas. Crear un sistema con realimentación podría provocar bucles donde la entrada de una neurona acabe dependiendo de su salida, situación no aconsejable. Aunque no nos extenderemos en el estudio de las redes feedback, son objeto de interés al retratar de manera más fiel cómo funciona y aprende el cerebro humano y algunos modelos cuyas neuronas se activan y desactivan durante un breve periodo de tiempo han probado obtener una curva de aprendizaje mayor en menos tiempo que sus homólogos más simples. [17] 3.1 Diseño de las Redes Neuronales Explicados los componentes de las redes neuronales, comencemos creando y analizando nuestra primera red neuronal y con la que crearemos un primer modelo para predecir la salida dada una entrada. Como se comentó en el apartado anterior, las neuronas de las redes que usaremos se componen de pesos y bias, pero ¿Es necesario realizar un procedimiento complejo para su entrenamiento? ¿Es capaz nuestro programa de sacar una función válida únicamente a través de entradas y salidas? Pongamos como ejemplo un programa que introduciendo el número de minutos que hemos estado hablando por 35 teléfono nos indicase cuánto debemos pagar en nuestra factura. Este ejercicio que en un principio resulta fácil de diseñar con métodos de programación regular servirá para entender de forma práctica cómo se comporta una red neuronal. Nuestra función por calcular será la siguiente 𝐶(€)=0.12∗𝑡(min)+0.54∗𝑛+5 ( 4 ) Lo que quiere decir que el minuto cuesta 12 céntimos y el establecimiento de llamada, cincuenta y cuatro. Además, nuestra tarifa base es de 5 euros. Por lo tanto, algunos de nuestros costes asociados podrían ser: Tiempo (minutos) Número de llamadas Tarifa (euros) 200 10 34.40 145 12 24.02 300 20 51.80 32 1 9.38 0 0 5 100 60 49.4 1 1 5.66 Tabla 2. Muestra del coste del contrato dependiendo del tiempo de llamada y del número de estas Vamos a programar nuestra red neuronal para que tenga la siguiente forma: Figura 9. Esquema de la red neuronal propuesta Introducción a las Redes Neuronales 36 Nuestro programa internamente en cada iteración modificará los valores de los pesos y sesgos de forma que el error vaya siendo cada vez menor. De esta forma, se establecerán relaciones entre las neuronas que componen la red y sus parámetros. Toda red neuronal sigue el siguiente esquema para ser entrenada: 1. Se inicia la red con pesos y bias aleatorios 2. Calcular error de cada muestra y promediar el error de cada una de ellas. 3. Relacionar cada peso y sesgo con la importancia que han tenido a la hora de generar ese error. 4. Modificar los parámetros de forma proporcional (mayor error, mayor cambio) de forma que el error se reduzca 5. Repetir los pasos 2, 3 y 4 hasta conseguir que el error se reduzca lo máximo posible. La optimización realizada y el ajuste de pesos y bias se produce, como ya se comentó anteriormente, mediante el algoritmo de retropropagación (backpropagation) y la optimización por descenso de gradiente (gradient descent). Utilizando el lenguaje de programación Python definiremos cada una de las capas de la red neuronal y compilaremos el modelo completo Figura 10. Implementación de la red neuronal Lo siguiente será usar un optimizador para indicar la forma en la que se ajustan los parámetros. La función general para el cálculo de los pesos como explicamos anteriormente es la siguiente, aunque introduciendo un nuevo parámetro: 𝑤𝑡+1=𝑤𝑡−𝛿𝑓(𝑐𝑜𝑠𝑡𝑒) 𝛿𝑤 ∗𝑙𝑟 ( 5 ) Es decir, el peso nuevo sería el peso antiguo menos el gradiente de la función de coste para minimizar el error. El parámetro lr sería el llamado factor de entrenamiento o aprendizaje (learning rate). Los optimizadores son los encargados de calcular un parámetro adecuado para el mismo. Un valor de lr muy bajo comprometería a nuestra red modificándola de forma muy lenta o estancándose en un porcentaje de error. Por el contrario. Un factor de entrenamiento muy elevado provoca una inestabilidad permanente al realizar cambios de pesos muy bruscos. [18] Afortunadamente y gracias a los avances en este campo, tenemos a nuestra disposición una gran cantidad de optimizadores que pretenden buscar un mínimo local, siendo los principales [19] [20]: • Stochastic Gradient Descent (SGD) • Adaptative Gradient Algorithm (AdaGrad) • Adadelta • RMSprop (Root Mean Square Propagation) • Adam Las mejoras en los optimizadores más recientes surgen como combinación o a partir de algunos modelos anteriores, por lo que siempre se espera que los modernos sean superiores a los antiguos. Para nuestro ejemplo 37 utilizaremos el optimizador Adam. Por último, para evaluar el comportamiento de nuestra función indicaremos qué función de pérdida tomaremos como referencia, que mide la desviación entre predicciones y resultados reales. Unos valores pequeños indicaran que nuestra red neuronal funciona de manera eficiente y ajustando bien el modelo y lo contrario para unos valores altos. Que la función de pérdida vaya tomando cada vez valores más cercanos a cero significa que el ajuste de los pesos se está realizando de forma correcta. Para este ejemplo utilizaremos el error cuadrático medio. Figura 11. Compilación del modelo de ejemplo Para entrenar nuestro modelo utilizaremos a partir de ahora la función fit, que tiene como parámetros las entradas y salidas utilizadas para el entrenamiento y el número de repeticiones para entrenar a la red con los datos de entrenamiento. Una vez finalizada analicemos la función de pérdida: Figura 12. Función de pérdida del modelo de red simple Podemos observar que a las 100 iteraciones (épocas) el error ha conseguido descender lo suficiente para ajustarse con precisión al modelo esperado. Hagamos una prueba con unos valores que no aparezcan en la tabla proporcionada al programa. Introducción a las Redes Neuronales 38 Figura 13. Código y resultados obtenidos El resultado, aun contando con un muy pequeño error, es lo suficiente preciso para determinar el precio que pagaremos por nuestro consumo. El algoritmo partiendo de la nada ha conseguido encontrar unos valores para los pesos adecuados para que nuestra red neuronal se comporte como la ecuación de la que partimos. A la vista del éxito de las predicciones y sabiendo lo simple que ha resultado programar este sistema dotado de Inteligencia Artificial podemos tener la falsa idea de que nuestro programa será resultado de extrapolar lo visto hasta ahora con, quizás, una red más densa. El programa y la red presentada presentan entre otras cosas los siguientes inconvenientes: • La cantidad de datos es muy pequeña. Nosotros sólo hemos dotado a nuestra red de unos pocos valores, aunque suficientes para nuestro problema (recordemos que es posible hallar la ecuación de un plano; que sería la representación gráfica de la función dada, sabiendo únicamente 3 puntos). En el problema que queremos resolver necesitaremos muchísimos más datos ya que existen muchos tamaños, fuentes y sangrías diferentes para cada uno de los caracteres • Y el más importante de todos: nuestro problema tiene una solución lineal y en casos similares como el de ecuaciones de rectas, planos o hiperplanos podremos utilizar esta red o cualquier otra que la eficiencia será la misma. Al estar toda la red formada por neuronas que multiplican los valores de entrada por un peso y le suma un sesgo (operación lineal) para ser entrada de otras neuronas lineales el resultado por comparación también lo será. Por muchas capas que pongamos por lo tanto obtendremos el mismo resultado que si únicamente hubiéramos puesto una capa. Para un mismo problema lineal las redes de la figura 14 tendrán el mismo comportamiento y alcanzarán unos resultados idénticos, por lo que parece poco intuitivo usar estructuras complejas más difíciles de entrenar. 39 Figura 14. Ejemplos de dos redes neuronales. Los problemas del mundo real no siguen por lo general un comportamiento lineal, lo que una vez más vuelve a complicar nuestra tarea de lograr un reconocimiento de caracteres lo más preciso posible. En el siguiente apartado intentaremos modificar la red creada para que la combinación de todas salidas las sean ecuaciones no lineales. 3.2 Salir de la linealidad: Funciones de activación Ya hemos modelado nuestra primera red neuronal pero restringida y limitada por la linealidad. Para solucionar este problema se implementaron las funciones de activación: funciones a las que se le pasa la salida de una neurona antes de entrar a la siguiente y eliminan ese comportamiento lineal. Cada función de activación que definamos tendrá por lo tanto como entrada la suma ponderada de las entradas multiplicadas por los pesos y la suma de la Bias. Introducción a las Redes Neuronales 40 Figura 15. Esquema de una neurona incluyendo la función de activación Conceptualmente ya vimos dos funciones de activación en la introducción que se dio al mundo de las redes neuronales. La primera es la función de activación en escalón, que no es útil para los sistemas de Inteligencia artificial. Al tener derivada nula en casi la totalidad de su dominio no permite adecuar correctamente los sesgos, por lo que a partir de ahora lo que buscaremos en una función de activación es que sea diferenciable. Esta condición la cumplía, como podemos comprobar, la función de activación sigmoidal. No obstante, las funciones de este tipo introducen un problema en los modelos llamado “problema del desvanecimiento del gradiente” [19]. Al ir mejorando nuestra red neuronal observamos que nuestro gradiente es cada vez más pequeño por lo que irremediablemente nuestro programa al llegar a determinadas iteraciones dejará de mejorar significativamente y mantendrá un error residual. Aun así, estas funciones son útiles en la capa de salida de las redes neuronales y siguen siendo las más utilizadas en las capas de salida para problemas de clasificación binaria como el de perros y gatos. Una mejora de esta función sería la tangente hiperbólica, que cuenta con un mayor recorrido (de -1 a 1) y su derivada es mayor, lo que implica un aprendizaje más rápido. Como nuestro problema pretende clasificar caracteres en más de dos categorías, estas funciones no serán útiles y se ha optado por descartarlas para las capas ocultas. Actualmente la función de activación más utilizada en Machine Learning y la que aplicaremos más adelante en nuestro proyecto es la llamada ReLU (Rectified Linear Unit), que tiene la siguiente forma: 𝑓(𝑥)=max(0,𝑥) ( 6 ) Figura 16. Representación gráfica de la función de activación ReLU Esta función presenta dos ventajas fundamentales respeto a las anteriores. La primera es que el gradiente de la 41 función para valores positivos es constante y la función no está acotada, lo que permitirá acelerar el aprendizaje. Otro motivo por el que nos podría interesar elegir esta función es por su bajo coste computacional [21]. Al no depender de exponenciales u otras fórmulas matemáticas más complejas su velocidad de procesamiento es menor y permite obtener resultados similares a los de otras funciones en menos tiempo (hasta 6 veces más rápida que la tangente hiperbólica según el artículo Deep Sparse Rectified Neural Networks [22]). Por el contrario, el dominio negativo de la función hace que exista la posibilidad de que se generen “neuronas muertas” que únicamente dan como salida cero, entorpeciendo el aprendizaje. Actualmente, y aunque han surgido nuevas variantes de esta (Leaky ReLU, PReLU y GeLU entre otras), ReLU sigue siendo la función de activación más utilizada en las capas ocultas y será aquella que utilizaremos para implementar tanto nuestro propio modelo como otros desarrollados con base en esta función, como ResNet. Por último, resulta de interés para nuestro problema estudiar la función Softmax [20] Utilizada principalmente en la salida de las redes neuronales encargadas de tareas de clasificación. Esta función nos dará para cada una de nuestras salidas una probabilidad entre 0 y 1 y se quedará con la mayor. Calculando el exponente natural de cada una de las salidas y dividiéndolo entre el sumatorio de los resultados conseguimos que los números grandes tengan mucho mayor porcentaje que los pequeños. Será por lo tanto de gran utilidad incluir Softmax en nuestra red en cualquiera de las pruebas que hagamos de cara a obtener la fiabilidad con la que nuestra red detecta los caracteres y determinar si el valor detectado es útil o no. Para concluir con esta introducción, cabe destacar el campo de la Inteligencia Artificial es objeto de estudio e investigación constante, por lo que en los últimos años surgirían otras funciones que sin ser monotónicas han mejorado el rendimiento de ReLU, aunque a costa de introducir una mayor carga computacional. Algunas de las que se han incorporado al vasto catálogo de funciones de activación son Swish (2017) y Mish (2020) por enumerar algunas [23], siendo ya tarea del programador elegir la más adecuada para cada necesidad y capa. Habiendo explicado cada uno de los componentes de las redes neuronales y los parámetros necesarios para diseñarlas correctamente, finalmente comenzaremos con el problema que motivó la realización de este Trabajo de Fin de Grado y que pasaremos a explicar a continuación: partir de un archivo vacío y construir, obteniendo los resultados óptimos, una red neurona funcional que detecte mediante imágenes o vídeo letras de forma correcta. Creación de un clasificador de imágenes perceptrón multicapa 48 Figura 25. Resumen de la red neuronal. Tiene como entrada cada uno de los píxeles, dos capas ocultas y en la capa de salida aparecen cada uno de los 47 caracteres reconocibles. De esta red obtenemos los resultados expuestos en las figuras 26 y 27. Resulta de interés explicar y analizar las gráficas obtenidas, pues estas se utilizarán a partir de ahora en las pruebas que realicemos para valorar como de bien se comporta nuestra red con los datos de entrenamiento y de prueba: Figura 26. Gráfica de pérdidas del modelo 1 49 En la figura 26 se muestra la llamada “función de pérdida” o “función de coste” e indica cuánto varía el valor predicho con el valor real de la muestra a lo largo de las épocas (ciclos de propagación hacia detrás y hacia delante para ajustar los pesos de la red). Las pérdidas se calculan como el sumatorio de los errores para cada una de las muestras en los sets de entrenamiento y validación. Como ya se comentó en los apartados introductorios, esta función se utiliza para modificar los pesos de las neuronas y mejorar la red. Figura 27. Gráfica de precisión del modelo 1 En la figura 27 observamos la función que nos indica el porcentaje de precisión de la red neuronal a lo largo del entrenamiento. En los entrenamientos esta precisión se traduce en el porcentaje de aciertos de nuestro sistema. Dado una entrada se comparan de forma binaria si el resultado es igual o distinto al esperado y se realiza esta operación para cada una de las muestras de nuestros sets. Aunque no existe relación matemática entre las dos funciones antes vistas en la mayoría de los casos observaremos que cuando la gráfica de precisión crece la de pérdidas decrece, ya que una menor cantidad de pérdidas suelen implicar que los resultados del sistema serán mejores. Sin embargo, esto no siempre es así, sobre todo cuando se produce un sobreajuste en la red que hace que la precisión aumente, pero las pérdidas suban también. Creación de un clasificador de imágenes perceptrón multicapa 50 Figura 26. Ejemplos de predicciones con el set de pruebas Como podemos observar, esta red no produce un resultado del todo insatisfactorio en el entrenamiento, pero a la hora de predecir elementos nuevos nos encontraremos conque no obtenemos ese nivel de precisión. Esto se debe a que nuestra red lo único que ha hecho es relacionar la intensidad de cada píxel con la salida, que para imágenes muy parecidas en tamaño y forma a las del entrenamiento da buenos resultados, pero en el momento que cambiamos la posición o tamaño del número o letra la red nos empieza a dar problemas. Figura 27. Ejemplos de predicciones con imágenes reales de una G. 51 En la imagen de la izquierda el programa nos detecta un 6 lo cual es incorrecto, pero hasta cierto punto asumible dado los resultados de precisión de nuestro modelo. Sin embargo, en la segunda imagen la cual es resultado de un desplazamiento de la primera obtenemos un resultado completamente diferente. Este comportamiento no es deseable y nuestro objetivo será eliminarlo. Es a partir de este momento cuando las redes vistas hasta el momento no son suficientes para abarcar nuestro problema y fue tras un análisis de otros modelos que pretendían solucionar tareas de clasificación de imágenes que se introducen otro tipo de redes no vistas hasta ahora: Las Redes Neuronales Convolucionales. Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 52 5 CREACIÓN DE UN CLASIFICADOR DE IMÁGENES: REDES NEURONALES CONVOLUCIONALES. ste tipo de redes nos dan una aproximación distinta al problema de aprendizaje de nuestro programa. Hasta ahora los algoritmos ajustaban los pesos dadas unas entradas y salidas predeterminadas. Esta solución es ideal para entradas en forma de número o cadena de caracteres, siempre que estas definan características como podríamos pensar de un programa que calcula precios de viviendas basándose en entradas numéricas. Un píxel por sí solo no es una característica, pero un grupo de ellos formando un patrón determinado pueden serlo. En determinado datasets, como el creado por la web de venta de ropa Zalando [37], las prendas vienen en una disposición predeterminada e igual al resto de elementos con la misma etiqueta. Aunque gracias a esto podamos facilitar el entrenamiento, también limitamos el aprendizaje del sistema pues un objeto tridimensional puede encontrarse en distintos escenarios y orientaciones. Figura 28. Muestras provenientes del Fashion MNIST Dataset de Zalando. [37] E 53 Figura 29. Varios puntos de vista de una misma zapatilla deportiva (JD, s.f.) [38] Por la tanto llegamos a la conclusión de que para este tipo de problemas una red neuronal al uso no es ideal, como se ha visto anteriormente. Las redes neuronales convolucionales nos acercan más al razonamiento humano. Nuestro cerebro asocia a cada objeto patrones y formas. Si las características del elemento visualizado coinciden con algún concepto que tengamos le podremos asociar a ese objeto una salida, sin importar la posición, rotación o tamaño. Figura 30. Partes de un gato. Nuestro cerebro internamente al ver la imagen la segmenta en patrones reconocibles, y el conjunto de ellos es lo que le permite identificar al animal como un gato, aunque rotemos, volteemos o modifiquemos de alguna forma la imagen ¿Cómo puede nuestra red neuronal conseguir eso? Para ello es necesario presentar dos nuevas capas que se sitúan después de las capas de entrada. Estas son las capas de convolución y de agrupación, que extraerán las características de cada carácter de manera autónoma para luego introducir estas características en las capas Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 54 ocultas y trabajar como las redes que hemos visto hasta ahora. Las dos capas imitan el córtex visual de nuestro cerebro, que se componen de neuronas simples que detectan líneas o ejes en una región muy concreta y las complejas que agrupan la información de neuronas simples para crear formas más concretas y encadenándose entre sí es como formamos patrones reconocibles por nuestro cerebro [39]. En primer lugar, para la detección de ejes será necesario un proceso de convolución de la imagen en la capa del mismo nombre, concepto estudiado en el campo de Sistemas de Percepción y que implica multiplicar un píxel y sus vecinos por una matriz llamada núcleo o kernel y sumar los resultados, obteniendo un nuevo valor para cada píxel. Figura 31. Esquema del proceso de convolución En la red neuronal nosotros no especificamos los núcleos que se van a usar para la convolución de nuestra imagen, solo el número y el tamaño de estos, ya que al igual que vimos con los pesos, se ajustarán automáticamente. La segunda capa, la capa de agrupación, reduce el tamaño de la imagen con el objetivo de extraer las características principales y que las mismas no dependan de la posición. Figura 32. Esquema del proceso de agrupación Una vez obtenidas las imágenes resultantes, volveríamos a pasarlas por más capas de convolución y agrupación usando normalmente más núcleos al haber reducido el tamaño de nuestra imagen. Una forma de verlo gráficamente es la siguiente: 55 Figura 33. Esquema LeNet de la CNN que utilizaremos Tras colocar varias de estas capas habremos obtenido las características de entradas necesarias para nuestra red neuronal y podremos pasar al entrenamiento. Antes de continuar, veamos como nuestra red ha extraído las “características de la imagen” para su clasificación [40]. Entrada Primera convolución Segunda convolución Tercera convolución Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 56 Entrada Primera convolución Segunda convolución Tercera convolución Figura 34. Resultados de la convolución de dos imágenes del carácter 8 pasadas por tres procesos de convolución y agrupación. La última agrupación actuará como las características de cada imagen, teniendo caracteres iguales matrices muy parecidas 5.1 Modelos utilizados Utilizaremos varias redes neuronales para probar su eficacia y compararlas entre sí, en cada una de ellas mostraremos los cambios que se añaden, así como pruebas de predicción para los datos de la prueba. Distinguiremos cuatro modelos: 1. Red Convolucional simple, únicamente añadiendo las capas de convolución y agrupación 2. Red Convolucional con Dropout 3. Red con arquitectura ResNet 4. Dos redes diferentes para letras y para números 5.1.1 Red Convolucional. Nuestra red convolucional simple estará compuesta por un par de capas de convolución y agrupación, colocadas en serie, luego una capa oculta de 100 neuronas y finalmente una capa de salida con 47 neuronas, una para carácter que ha de ser reconocido. Las capas ocultas tienen la función de activación ReLu ya vista en apartados anteriores y la de salida la función Softmax. Para este primer modelo realizaremos pruebas con los optimizadores y parámetros de entrenamiento para comprobar la eficacia de cada uno. 57 Figura 35. Resumen de la red diseñada. Necesitamos definir para cada uno de los entrenamientos un tamaño de lote (o BS por sus siglas en inglés), el número de épocas y el número de muestras por lote (steps per epoch). El tamaño de lote es el número de muestras que se utilizarán para entrenar a la red, calcular el error cuadrático medio y variar los pesos de las neuronas. Esto significa que un número bajo de nuestro BS permite un ajuste más preciso y un menor coste computacional ya que nuestra memoria únicamente se carga con unas pocas muestras para el entrenamiento. Como desventaja obtenemos un comportamiento más ruidoso y el riesgo de estancarnos en un mínimo local utilizando el teorema del Descenso del Gradiente. Por el contrario, un valor más elevado resultaría en un entrenamiento algo más lento y un mayor coste computacional, pero obteniendo mayor confianza en los resultados [41]. El número de muestras por lote a su vez está relacionado tradicionalmente con el BS mediante la siguiente ecuación, ya que permite en cada época utilizar todos los datos disponibles para el entrenamiento. 𝑆𝑡𝑒𝑝𝑠 𝑝𝑒𝑟 𝑒𝑝𝑜𝑐ℎ=𝐿𝑜𝑛𝑔𝑖𝑡𝑢𝑑 𝑑𝑒 𝑙𝑜𝑠 𝑑𝑎𝑡𝑜𝑠 𝑑𝑒 𝑒𝑛𝑡𝑟𝑒𝑛𝑎𝑚𝑖𝑒𝑛𝑡𝑜 𝐵𝑎𝑡𝑐ℎ 𝑆𝑖𝑧𝑒 ( 7 ) El número de épocas lo ajustaremos en base a los resultados obtenidos, ya que implica tiempo de computación y puede no ir acompañado de una mejora notoria de la precisión. Utilizaremos en primer lugar unas 50 épocas. El tipo de función de pérdida que representaremos es la llamada Categorical Cross-Entropy, que se utiliza en los problemas de clasificación con más de dos clases. La salida de la función se calcula multiplicando la salida esperada por el logaritmo de la producida y sumando los resultados de cada una de las clases. Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 64 La red es capaz de reconocer la mayoría de los caracteres correctamente y con una precisión bastante elevada. En otras ocasiones se equivoca debido a la similitud con otros caracteres, aunque algo entendible debido al parecido entre algunos caracteres como la G y el 6, la I y la L… Tomar una red convolucional con más capas ocultas podría ayudar a mejorar la precisión de nuestro modelo. En este caso obtenemos los resultados siguientes para una red con un mayor número de capas. Número de capas Pérdidas Precisión 10 30 Tabla 7. Comparativa con redes de diferente profundidad Como podemos observar, una red neuronal más compleja no siempre implicará una mejora de los resultados y será muy importante comprobarlos gráficamente para quedarnos con los mejores modelos y evitar el overfitting. 5.1.2 Red Convolucional con Dropout Para la siguiente red lo que haremos será añadir una regularización mediante Dropout. El Dropout pretende mitigar el efecto de sobreajuste, “apagando” de forma aleatoria algunas neuronas de nuestra red durante el entrenamiento de forma que los valores de los pesos se distribuyan uniformemente y no tengan mayor impacto unas neuronas que otras ya que estaremos trabajando con redes neuronales diferentes, aunque parecidas las unas a las otras [41]. Lo configuraremos con un valor de 0.5, que significa que en cada época cada neurona tendrá un 50% de posibilidades de apagarse. Añadiendo este nuevo elemento a nuestro programa los resultados serán los siguientes: 65 Figura 39. Resumen del modelo con Dropout Figura 40. Resultados de pérdidas sin aumento de datos e incluyendo Dropout. Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 66 Figura 41. Resultados de precisión sin aumento de datos e incluyendo Dropout. Como se aprecia en las figuras 49 y 50, vemos que hemos eliminado el efecto de sobreajuste que había en el apartado anterior y los resultados en la validación están en torno al 89% para la precisión y en 0.34 para las pérdidas. Procedamos entonces a incluir el aumento de datos y ver si se observa alguna mejora. 67 Figura 42. Resultados de pérdidas con aumento de datos e incluyendo Dropout. Figura 43. Resultados de precisión con aumento de datos e incluyendo Dropout. Podemos notar que añadiendo el Dropout los resultados en la prueba mejoran ligeramente (comparar con figuras 42 y 43), pero sobre todo conseguimos reducir el ruido a de las gráficas haciendo este modelo más fiable. Más adelante se realizará una comparativa con caracteres escritos a mano. Otro de los resultados que podemos observar; con y sin aumento de datos, es que los resultados de validación Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 68 son mejores que los de entrenamiento, al menos durante un gran número de épocas. Esto en un principio no debería ser así porque el modelo intenta ajustarse cada vez más a los datos de entrenamiento, pero debido al dropout y a que las neuronas que se activan en cada iteración no son las mismas, al sistema le costará afinar los parámetros en base al entrenamiento a la vez que las pruebas de validación seguirán mejorando a un ritmo más elevado. Una solución para evitar este efecto sería reducir el dropout pero, con el fin de obtener la robustez necesaria para el modelo, se decidió mantener las gráficas de esta forma habiendo justificado su procedencia. Figura 44. Predicciones obtenidas para el segundo modelo con Dropout 5.1.3 ResNet En los apartados anteriores llegamos a la conclusión de que incluir más capas ocultas a nuestra red a pesar de hacerla en teoría más robusta y flexible para problemas más complejos pueden introducir el problema del desvanecimiento del gradiente, evitando que se modifiquen los pesos en las capas internas y por lo tanto más profundidad también implique un error mayor. Sin embargo, en el año 2015, el equipo de Microsoft Research, al mando del científico Kaiming He, presentaron en su artículo “Deep Residual Learning for Image Recognition” el concepto de las redes residuales [44]. Estas introducen una conexión atajo o residual entre capas antes de aplicar una función de activación. 69 Figura 45. Esquema de una conexión residual. Al tener esas conexiones lo que ocurrirá es que el gradiente atravesará tanto el camino común como el atajo, mejorando la forma en la que se modifican los pesos de las neuronas en capas más profundas [45]. Gracias a esto podremos tener modelos de cientos incluso miles de capas sin afectar al rendimiento de este y permitiendo operaciones más complejas. Para este caso utilizaremos el modelo ResNet50 que tiene la siguiente estructura, teniendo la conexión residual 3 capas ocultas y teniendo los atajos lugar en las capas de convolución, como se aprecia en la figura 51. Figura 46. Diagrama y resumen de parámetros de nuestra red ResNet50 [46] Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 70 Entrenando al modelo obtenemos los siguientes resultados: Figura 47. Resultados de pérdidas utilizando ResNet Figura 48. Resultados de precisión utilizando ResNet 71 Podemos ver que, aunque tengamos gran cantidad de capas su rendimiento sigue siendo muy bueno, aunque los modelos anteriores parecen ser mejores para los datos recibidos Figura 49. Predicciones obtenidas para el tercer modelo con ResNet 5.1.4 Entrenamiento con dos modelos Se planteó también un aproximamiento al problema de forma distinta, donde tendríamos dos predictores funcionando al mismo tiempo: uno para las letras y otro para los números, dado que las redes convolucionales obtenían una mayor precisión por separado que las vistas anteriormente. Emplearemos la red neuronal del primer ejemplo, pero duplicada. Figura 50. Resultados de pérdidas y precisión únicamente para las letras Creación de un clasificador de imágenes: Redes Neuronales Convolucionales. 72 Figura 51. Resultados de pérdidas y precisión únicamente para las letras En el caso práctico a cada imagen se le aplicarían los dos predictores y aquél que alcanzase un mayor porcentaje de seguridad sería el carácter reconocido. 73 6 RESULTADOS OBTENIDOS. 6.1 Resultados en imágenes Para comenzar el análisis primero es necesario ver el desempeño de la red en imágenes con un solo carácter en diferentes posiciones. Para que la imagen sea similar a la de los datos de entrada y con el objetivo de eliminar el ruido en la imagen, se convertirá la imagen a blanco y negro y se aplicará un filtro gaussiano, eliminando el ruido de la imagen. A continuación, binarizaremos la imagen y la reescalaremos a 28x28 píxeles, para que la red neuronal pueda recibirla como entrada [47]. Figura 52. Comparativa del dato de entrada al programa vs dato de entrada a la Red Neuronal Por último, lanzamos la predicción y la escribimos encima de la imagen original. Se realizarán pruebas para los 4 modelos con diferentes imágenes de caracteres con variaciones en tamaño y forma. Para ellos se utilizará OpenCV, una biblioteca especializada en la visión por computadora [48]. Resultados obtenidos. 80 Figura 61. Resultados con dos CNN, así como el carácter elegido y su probabilidad en cada caso. De los resultados obtenidos obtenemos las diferentes conclusiones. La primera de ellas es que, tal y como supusimos en las conclusiones anteriores, el hecho de recortar la entrada y que el carácter ocupe el máximo tamaño posible ha supuesto una mejora significativa en cuanto a la calidad de los resultados, pasando de un 55% de acierto hasta un 78% de media para estos ejemplos, acercándose más a los valores obtenidos en el entrenamiento. Observamos también que la red CNN+Dropout parece tener un comportamiento similar tanto en mayúsculas como en minúsculas, siendo el más robusto de los 4. Aun así, Los resultados son siempre mejores utilizando mayúsculas. Esto es porque los caracteres en mayúsculas son más reconocibles y tienden menos a confundirse entre sí. Es interesante por lo tanto destacar el paralelismo que existe entre el Deep Learning y la metodología humana, pues en muchos documentos también se pide rellenar los datos en mayúsculas para evitar confusiones entre letras. Nuevamente destacan los valores arrojados por el último programa. Si se hubiese usado únicamente el predictor de letras habríamos obtenido la mayor precisión de todos los modelos, por lo que la conclusión obtenida tras estos experimentos es que siempre que sea posible trataremos de indicarle a la red si la entrada son caracteres alfabéticos o numéricos. 81 Figura 62. Comparativa entre usar un mismo predictor para letras y números y seleccionar en cada zona de la imagen qué predictor usar 6.3 Estudio e implementación de los Resultados en Tiempo Real 6.3.1 YoloV5 Por último, se ha querido probar y analizar una arquitectura reciente para comparar sus resultados con aquellas ya vistas a lo largo de este proyecto y plantear pruebas en Tiempo Real para futuras líneas. Se ha optado por utilizar YoloV5 (lanzado en mayo de 2020) para este fin. YoloV5 es la quinta versión del algoritmo YOLO, acrónimo de You Only Look Once (solo miras una vez), que divide la imagen en celdas y de una sola pasada no solo realiza el trabajo de predicción sino además el de detección de objetos, pudiendo tener en una misma imagen más de un objeto y siendo capaz de encontrarlos y clasificarlos. La ventaja que supone este método respecto a los anteriores vistos es la velocidad ya que, según la Resultados obtenidos. 82 documentación proporcionada por su creador, Glenn Jocher, esta herramienta puede trabajar a una frecuencia de hasta 45 imágenes por segundo en una resolución HD, lo que nos da un comportamiento prácticamente en Tiempo Real [51]. A diferencia de las Redes Neuronales Convolucionales (CNN) que analizan toda la imagen de una sola vez para lanzar una predicción, Yolo detecta en primer lugar regiones de interés de la imagen para luego pasar esa región a la red. Se introduce por lo tanto el concepto de R-CNN (Regional CNN) y en ellos se demuestra que el mapa de características de una región de la imagen mantiene la posición en el mapa de características y es idéntico al de la región analizada por separado. Figura 63. Relación entre el mapa de características de un fragmento de imagen y el fragmento del mapa de características de la imagen completa. Para cada ventana de nuestra imagen tendremos el siguiente vector asociado: [𝑝𝑐,𝑐1,𝑐2,…,𝑥,𝑦,𝑤,ℎ] ( 13 ) 𝑝𝑐:𝑃𝑟𝑜𝑏𝑎𝑏𝑖𝑙𝑖𝑑𝑎𝑑 𝑑𝑒 𝑞𝑢𝑒 𝑒𝑥𝑖𝑠𝑡𝑎 𝑢𝑛 𝑜𝑏𝑗𝑒𝑡𝑜 𝑒𝑛 𝑒𝑠𝑎 𝑣𝑒𝑛𝑡𝑎𝑛𝑎 𝑐𝑛:𝐶𝑙𝑎𝑠𝑒 𝑎 𝑙𝑎 𝑞𝑢𝑒 𝑝𝑒𝑟𝑡𝑒𝑛𝑒𝑐𝑒 𝑒𝑙 𝑜𝑏𝑗𝑒𝑡𝑜 𝑎𝑠𝑜𝑐𝑖𝑎𝑑𝑜 𝑎 𝑒𝑠𝑎 𝑣𝑒𝑛𝑡𝑎𝑛𝑎 𝑥,𝑦:𝐶𝑜𝑜𝑟𝑑𝑒𝑛𝑎𝑑𝑎𝑠 𝑑𝑒𝑙 𝑐𝑒𝑛𝑡𝑟𝑜 𝑑𝑒 𝑙𝑎 𝑣𝑒𝑛𝑡𝑎𝑛𝑎 𝑤,ℎ: 𝐴𝑛𝑐ℎ𝑜 𝑦 𝑎𝑙𝑡𝑜 𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑜 𝑑𝑒 𝑙𝑎 𝑣𝑒𝑛𝑡𝑎𝑛𝑎 𝑟𝑒𝑠𝑝𝑒𝑐𝑡𝑜 𝑎 𝑙𝑎 𝑖𝑚𝑎𝑔𝑒𝑛 𝑐𝑜𝑚𝑝𝑙𝑒𝑡𝑎 La imagen dividida en celdas junto con el vector para cada una será la entrada en nuestra CNN para el entrenamiento. Podemos encontrarnos a la hora de predecir una imagen que el mismo objeto se detecte en múltiples ocasiones generando una gran cantidad de cajas delimitadoras. Para evitarlo la red realiza el siguiente procedimiento: 1. Mira el vector salida de cada ventana. 2. Elimina todas aquellas predicciones que cuenten con un pc menor a cierto umbral, por ejemplo 0,6. 3. Selecciona aquella con el pc máximo para cada clase. 4. Elimina las regiones que se solapen en exceso con la seleccionada. Habiendo explicado el procedimiento para la clasificación de objetos, a la hora de implementar el programa para el problema de detección de caracteres nos encontramos con un gran problema, y es que el dataset que estamos utilizando se encuentra en formato .csv y el programa de entrenamiento de YoloV5 para el cálculo de los pesos de la red exige que las imágenes se encuentren en formato .jpg y etiquetadas con su clase y posición y tamaño de los objetos. Para solventar este problema se decidió crear un programa auxiliar que leyese cada imagen, la 83 guardase en el ordenador en formato jpg y crease una única etiqueta con centro en el centro de la imagen y que abarcase el cien por cien de esta. Figura 64. Nueva organización de los datos de entrenamiento y validación; con imágenes y etiquetas Para este experimento al tener una gran cantidad de datos de entrenamiento en forma de imágenes transformábamos los programas anteriores que se podían entrenar con cierta facilidad en uno muy costoso computacionalmente y para el cuál el ordenador físico no estaba capacitado. Se entrenó por lo tanto utilizando el sistema virtual de Google Colab [52] y debido a las limitaciones del programa se entrenó únicamente por 10 épocas, obteniendo una precisión del 65% pero que iba en aumento. Los resultados en este caso son muy pobres, pero esto se debe en un primer lugar a la limitación temporal para el uso del programa y en segundo lugar debido al tamaño tan reducido de las imágenes. Se llega a la conclusión por lo tanto de que con un dataset más pequeño, pero con un mayor tamaño de imagen y etiquetado manualmente se conseguirían mejores resultados, quedando pendiente realizar pruebas en imágenes y vídeo. Conclusiones 84 7 CONCLUSIONES l objetivo de este proyecto ha sido el de realizar una aproximación al Deep Learning, concretamente al que conformado por redes neuronales y comprobar cómo funcionan algunos de los programas de OCR que se utilizan hoy en día como puede ser el de Google. Asimismo, se quería hacer una comparativa entre múltiples y modelos y comparar los resultados obtenidos en el entrenamiento con aquellos que se dan en la realidad. Tras realizar una investigación en profundidad sobre distintos tipos de Redes Neuronales se han encontrado distintas variaciones de CNNs que dan buenos resultados tanto con datos del Dataset EMNIST como con caracteres de distintas personas, confirmando la fiabilidad de los modelos. Además, las predicciones responden bien en problemas de tiempo real, logrando segmentar y localizar los caracteres. A modo de resumen, los resultados obtenidos para los diferentes modelos han sido los siguientes: Modelos CNN CNN+Dropout Resnet 2 Modelos CNN Entrenamiento 88.56% 88.94% 88.48% 94.05% / 99.72% Dataset de prueba 88.11% 88.21% 88.11% 96.20% (93.95%/99.54%) Caracteres Individuales 77.78% 55.56% 66.67% 55.56% Múltiples caracteres 80.77% 88.46% 76.92% 76.92% Tabla 12. Comparativa entre los diferentes modelos en las fases de desarrollo del proyecto. Podemos concluir que los resultados Los principales objetivos planteados inicialmente se han cumplido de manera satisfactoria, quedando pendientes ciertas mejoras y avances que podrían plantearse en futuras líneas de investigación sobre esta materia. E “Nobody phrases it this way, but I think that artificial intelligence is almost a humanities discipline. It’s really an attempt to understand human intelligence and human cognition.” - Sebastian Thrun- 85 7.1 Futuras mejoras Como ya se mencionó en las pruebas realizadas, ciertas mejoras y avances podrían hacerse siguiendo con la investigación y pruebas de más modelos utilizados en reconocimiento óptico de patrones y formas, ya que las mejoras en este campo muchas veces se realizan en cuestión de meses. Como mejoras a implementar se plantean las siguientes: • Continuar con las pruebas de los modelos propuestos, cambiando parámetros de entrenamiento para optimizar los valores de precisión y pérdidas, así como conseguir mejores resultados en menos épocas. Se propone, por ejemplo, realizar un bucle en el que se cambien ciertos parámetros como el número de capas ocultas de la red, los optimizadores o el tamaño de lote en el entrenamiento y analizar las gráficas obtenidas hasta quedarnos con la mejor. Es importante que los pesos iniciales en el entrenamiento sean los mismos en cada iteración para evitar la aleatoriedad de los experimentos. • Se ha trabajado con el conjunto de datos EMNIST por ser el más completo para el reconocimiento de caracteres escritos individuales, pero en muchos casos la forma de escribir un carácter es distinta en cada persona (sobre todo para caracteres como la s, la r o la m). Implementar un dataset propio donde se pudiesen incluir esas variaciones podría mejorar la precisión de los resultados, aunque requeriría un gran trabajo de recopilación y etiquetado previo de muestras. • Probar a realizar entrenamientos para detectar grupos de caracteres o de palabras, ya que muchas veces un único carácter no aporta suficiente. Se podría intentar implementar los modelos utilizando otros datasets y realizar pruebas para textos mecanografiados, ya que textos manuscritos requirían de una gran cantidad de datos y tiempo de entrenamiento para conseguir unos resultados aceptables. Algunos de los datasets empleados en estos casos son el Coco-Text Dataset y el Total-Text Dataset. Para su entrenamiento se utilizan principalmente Redes Neuronales completamente convolucionales (FCNN) como pueden ser la red EAST o la CRAFT [53] • Probar otros modelos para la detección de caracteres. Para nuestro proyecto se probaron 4 variantes para realizar la comparativa, pero existen infinitud de modelos que aproximan el problema de OCR de formas distintas. Los Transformers, RAMs y CRNNs son modelos que han demostrado ser muy fiables en la detección de texto manuscrito [53] • Cambio y mejora de las condiciones óptimas. Estudiar el comportamiento en otras circunstancias en las que no se puedan cumplir las mejores condiciones de luminosidad. Con la finalidad de adaptar el dispositivo al medio en vez de adaptar el medio al dispositivo. 7.2. Cronograma Como ya se explicó en el apartado de introducción y planificación del proyecto, el trabajo se ha compuesto de cuatro fases principales: recopilación de información y estudio del estado del arte, estudio del problema de detección de caracteres manuscritos e implementación de diferentes modelos, realización de pruebas para cada uno de los modelos en diferentes condiciones y elaboración de la documentación. El tiempo dedicado al desarrollo del proyecto ha sido de unas 400 horas efectivas de trabajo, unos dos meses y medio. La primera etapa de búsqueda de documentación y formación en Python e Inteligencia Artificial, pues se partió de conocimientos básicos al empezar el proyecto, abarcó las primeras dos semanas. A lo largo de estas se realizaron cursos acerca del Deep Learning a la vez que analizábamos código de problemas similares, como problemas de clasificación binaria o clasificación de números con una fuente determinada. La siguiente etapa, en la que realizamos la búsqueda y diseño de modelos compatibles para nuestro problema a la vez que se realizaban pequeñas pruebas acerca del funcionamiento de los modelos, ocupó la mayor parte del tiempo del desarrollo del proyecto, unas 150 horas. Esto se debe a la modificación que se estuvo realizando a Conclusiones 86 los modelos para lograr mejores resultados, a lo que añadíamos otra fase de búsqueda de información de cómo aplicarlos correctamente. Los tiempos de entrenamiento para cada uno de los modelos fueron de 25 minutos excepto el del YoloV5, que duró 3 horas. Se emplearon por lo tanto unas 10 horas únicamente en entrenar a los modelos y comprobar su eficacia. La tercera etapa supuso unas 100 horas de trabajo, y en esta etapa se buscó información acerca de la segmentación de caracteres en imágenes y se intentó optimizar el procesamiento de las imágenes y el vídeo de forma que se consiguiese entradas a la red neuronal muy parecida a las del entrenamiento para mejorar los resultados. Adicionalmente se realizaron las pruebas para cada modelo y se recopilaron los resultados obtenidos. Por último, la etapa de documentación, que engloba la realización de este documento, ha supuesto un total de dos semanas, aproximadamente. Pasamos en esta etapa a documentar las pruebas y los estudios realizados previamente sobre el estado del arte. En el siguiente cronograma puede apreciarse de manera visual la distribución del trabajo en las distintas semanas: Tabla 13. Diagrama de Gantt 7.3. Presupuesto El presupuesto que supondría que un ingeniero realizara este trabajo, tomando el sueldo de un titulado de grado superior que se expone en el BOE, en la resolución del 22 de febrero de 2018, de la Dirección General de Empleo [54], sería de unos 6300 €. Teniendo en cuenta que el salario total anual es de 25.289,02 €, el salario mensual sería de 2.099,09 € y el tiempo trabajado de 3 meses. En cuanto a los materiales empleados, sólo ha sido necesario un ordenador de sobremesa, cuyo valor a día de hoy es de algo menos de 1000 €. En caso de realizar avances en el proyecto y aumentar el número de experimentos o mejorar el equipo, habría que tener esto en cuenta de cara al presupuesto final. 87 Referencias 88 REFERENCIAS [1] STELLINGWERF, Rommert; ZANDHUIS, Anton, 2013. ISO 21500 Guidance on project management–A Pocket Guide. Van Haren. [2] IBM CORPORATION, 5 de enero de 2022. What Is Optical Character Recognition (OCR)? En: IBM [en línea]. Disponible en: https://www.ibm.com/cloud/blog/optical-character-recognition [3] BERCHMANS, Deepa; KUMAR, S. S. Optical character recognition: an overview and an insight. En 2014 International Conference on Control, Instrumentation, Communication and Computational Technologies (ICCICCT). IEEE, 2014. p. 1361-1365. SHEPARD, D. H. (1951). EEUU Patent No. US2663758A. En: History of Information. [4] AGARWAL, Rahul, 2021. Deep Learning Based OCR for Text in the Wild. En: Nanonets [en línea]. Disponible en: https://nanonets.com/blog/deep-learning-ocr/ [consulta: 26 de junio de 2021] [5] TURING, Alan M. 1 de octubre de 1950. Computing Machinery and intelligence. En: Mind, 59(236). pp. 433-460. [6] SERRANO, Alberto G. 2012. Inteligencia Artificial. Fundamentos, práctica y aplicaciones. San Fernando de Henares, Madrid: RC Libros. [7] BOBROW, Daniel G. 1964. Natural language input for a computer problem solving system. PhD. Massachusetts Institute of Technology. [8] NORVIG, Peter, 1992. Paradigms of Artificial Intelligence Programming. New York: Morgan Kaufmann Publishers, pp. 151–154. ISBN 1-55860-191-0. [9] HAENLEIN, Michael; KAPLAN, Andreas., 2019 A brief history of artificial intelligence: On the past, present, and future of artificial intelligence. California management review, vol. 61, no 4, pp. 5- 14. [10] BINI, Stefano A. 2018. Artificial Intelligence, Machine Learning, Deep Learning, and Cognitive Computing: What Do These Terms Mean and How Will They Impact Health Care? En: The Journal of arthroplasty, 33(8), pp. 2358-2361. [11] LUNA, Javier, 2018. Tipos de aprendizaje automático. En: SoldAi [en línea]. Disponible en: https://medium.com/soldai/tipos-de-aprendizaje-autom%C3%A1tico-6413e3c615e2 [12] ALPHASTAR TEAM, 2019. AlphaStar: Mastering the real-time strategy game StarCraft II. En: DeepMind [en línea]. Disponible en: https://www.deepmind.com/blog/alphastar-mastering-the-real- time-strategy-game-starcraft-ii [13] NIELSEN, Michael A. 2015. Neural networks and deep learning. San Francisco, CA, USA: Determination press. 89 [14] BECKER, Dan, s.f. Intro to Machine Learning. En: Kaggle [en línea]. Disponible en: https://www.kaggle.com/learn/intro-to-machine-learning [15] WOODFORD, Chris, 30 de junio de 2021. Neural Networks. En: ExplainThatStuff [en línea] Disponible en: https://www.explainthatstuff.com/introduction-to-neural-networks.html [consulta: 19 de julio de 2021] [16] FINLAY, Steven, 2020. Artificial Intelligence for Everyone. Lancaster University. [17] LLAMAS, Luis, 10 de febrero de 2019. Machine Learning con Tensorflow y Keras en Python. En: LuisLlamas.es [en línea]. Disponible en: https://www.luisllamas.es/machine-learning-con-tensorflow- y-keras-en-python/ [18] JOO, Gihun; PARK, Chihyun; IM, Hyeonseung, 2020. Performance evaluation of machine learning optimizers. Journal of IKEEE, vol. 24, no 3, p. 766-776. [19] VANI, S.; RAO, T. 2019. An Experimental Approach towards the Performance Assessment of Various Optimizers on Convolutional Neural Network. En: 3rd International Conference on Trends in Electronics and Informatics (ICOEI), (pp. 331-336). Tirunelveli, India. [20] RINGA TECH, 25 de mayo de 2022. Funciones de activación a detalle (Redes neuronales). En: Youtube [en línea]. Disponible en: https://www.youtube.com/watch?v=_0wdproot34&t=898s [21] BROWNLEE, Jason, 9 de junio de 2019. A Gentle Introduction to the Rectified Linear Unit (ReLU). En: Machine Learning Mastery [en línea]. Diponible en: https://machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural- networks/ [22] GLOROT, Xavier; BORDES, Antoine; BENGIO, Yoshua. junio de 2011. Deep sparse rectifier neural networks. En: Proceedings of the fourteenth international conference on artificial intelligence and statistics. JMLR Workshop and Conference Proceedings. pp. 315-323. [23] RASAMOELINA, Andrinandrasana David; ADJAILIA, Fouzia; SINČÁK, Peter, enero de 2020. A review of activation function for artificial neural network. En: 2020 IEEE 18th World Symposium on Applied Machine Intelligence and Informatics (SAMI). IEEE. pp. 281-286. [24] SEGURA MORALES, Santiago; DAVID MOGOLLON, Juan; CATALINA VILLAMIL, Angie, 24 de septiembre de 2020. Reconocimiento de caracteres de documentos de identidad utilizando Tesseract y Python en 4 pasos. En: Data Science AnalyticLab [en línea]. Disponible en: https://datascience-alw.medium.com/reconocimiento-de-caracteres-de-documentos-utilizando- tesseract-y-python-en-4-pasos-721d861ccf8c [25] AGARWAL, Rahul, 2021. Deep Learning Based OCR for Text in the Wild. En: Nanonets [en línea]. Disponible en: https://nanonets.com/blog/deep-learning-ocr/ [consulta: 26 de junio de 2021] [26] KAZEMNEJAD, Amirhossein, 2019. How to do Deep Learning research with absolutely no GPUs - Part 2. En: Amirhossein Kazemnejad's Blog [en línea]. Disponible en: https://kazemnejad.com/blog/how_to_do_deep_learning_research_with_absolutely_no_gpus_part_2/ [27] TENSORFLOW TEAM, 26 de Enero de 2022. Guardar y cargar modelos. En: TensorFlow [en línea]. Disponible en: https://www.tensorflow.org/tutorials/keras/save_and_load [consulta: 18 de mayo de 2022] [28] KERAS, s.f.. Keras datasets. En: keras.io [en línea]. Disponible en: https://keras.io/api/datasets/ [consulta: 27 de julio de 2021] Anexo B 96 42. layer = modelo.layers #Las capas convolucionales son la 0 y la 2 43. filters, biases = modelo.layers[0].get_weights() 44. print(layer[0].name, filters.shape) 45. 46. #Mostramos los filtros 47. fig1=plt.figure(figsize=(8, 12)) 48. columns = 8 49. rows = 8 50. n_filters = columns * rows 51. for i in range(0, n_filters): 52. f = filters[:, :, :, i] 53. fig1 =plt.subplot(rows, columns, i+1) 54. fig1.set_xticks([]) #Turn off axis 55. fig1.set_yticks([]) 56. plt.imshow(f[:, :, 0], cmap='gray') 57. plt.show() 58. 59. #Definimos un modelo mas corto para obtener como salida la convolucion 60. conv_layer_index = [0, 2, 4] 61. outputs = [modelo.layers[i].output for i in conv_layer_index] 62. model_short = Model(inputs=modelo.inputs, outputs=outputs) 63. print(model_short.summary()) 64. 65. #La entrada es 28x28, asi que ajustamos la imagen de entrada a este tamanio 66. img = load_img('extraccion_caracteristicas/imagen_1.jpeg', target_size=(28, 28)) #VGG user 224 as input 67. 68. # convert the image to an array 69. img = img_to_array(img) 70. img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 71. img = img.astype("float32") / 255.0 72. img = np.expand_dims(img, axis=0) 73. 74. # Generamos la salida de nuestra red 75. feature_output = model_short.predict(img) 76. 77. columns = 8 78. rows = 8 79. for ftr in feature_output: 80. fig=plt.figure(figsize=(12, 12)) 81. for i in range(1, columns*rows +1): 82. fig =plt.subplot(rows, columns, i) 83. fig.set_xticks([]) #Turn off axis 84. fig.set_yticks([]) 85. plt.imshow(ftr[0, :, :, i-1], cmap='gray') 86. plt.show() 97 ANEXO C Código correspondiente al entrenamiento de los modelos CNN: 1. #Importamos librerias necesarias 2. 3. import numpy as np 4. import tensorflow as tf 5. import torch 6. import pandas as pd 7. import matplotlib.pyplot as plt 8. import seaborn as sns 9. from keras.models import Sequential 10. from keras.layers import Dense, Conv2D, Flatten, MaxPooling2D, BatchNormalization, Activation, Dropout 11. from tensorflow.keras.optimizers import Adam 12. from tensorflow.keras.preprocessing.image import ImageDataGe nerator 13. from keras.layers.convolutional import Conv2D 14. from keras.layers.convolutional import MaxPooling2D 15. from keras import backend as K 16. from keras.utils import np_utils 17. from keras.utils import load_img, img_to_array 18. from sklearn.model_selection import train_test_split 19. import cv2 20. from imutils import build_montages 21. import random 22. from torch.utils.data.sampler import SubsetRandomSampler 23. import models 24. from datetime import datetime 25. 26. #Llamamos y almacenamos los Datasets de prueba 27. 28. print("ADQUIRIENDO DATASET...") 29. data_train= pd.read_csv('emnist-balanced-train.csv') #Datos de entrenamiento 30. data_test= pd.read_csv('emnist-balanced-test.csv') #Datos para el test 31. print("DATASET CARGADO") 32. 33. #Funcion para normalizar los datasets (Hacerlos de tipo float32 34. #y con valores entre 0 y 1) 35. 36. def Normalizar(data, label): 37. x = data.drop(label ,axis=1) 38. x=x.astype('float32') 39. x=x/255 40. y = data[label] 41. x= x.values.reshape(-1,28,28,1) Anexo C 98 42. x=np.transpose(x,[0, 2, 1, 3]) #Hay que transponer los datos porque si no el caracter no se escribe correctamente 43. return x,y 44. 45. #El entrenamiento y el test tienen etiquetas distintas 46. train_x, train_y= Normalizar (data_train,'45') 47. test_x, test_y = Normalizar(data_test, '41') 48. 49. #Imprimimos diagrama de distribución de nuestros datos de entrenamiento. En el eje x se representaran 50. #las variables y en el eje y, el numero de veces que aparece 51. sns.set(style="ticks", context="talk",font_scale = 1) 52. #plt.style.use("dark_background") 53. plt.figure(figsize = (30,16)) 54. ax = train_y.value_counts().sort_values(axis=0,ascending=False).plot( kind='bar', 55. grid = False, 56. fontsize=20, 57. color='grey') 58. plt.xticks(rotation=0) 59. for p in ax.patches: 60. height = p.get_height() 61. ax.text(p.get_x()+ p.get_width() / 2., height + 30, height, 62. ha = 'center', size = 15) 63. sns.despine() 64. 65. plt.savefig('figures/distribucion_dataset.png') 66. 67. #Lista con las diferentes etiquetas para su posterior traduccion 68. #y visualizacion 69. 70. clases=['0','1','2','3','4','5','6','7','8','9','A','B', 71. 'C','D','E','F','G','H','I','J','K','L','M','N', 72. 'O','P','Q','R','S','T','U','V','W','X','Y','Z', 73. 'a','b','d','e','f','g','h','n','q','r','t'] 74. 75. #Tomamos 9 muestras aleatorias del dataset de prueba 76. train_samples = random.sample(range(0, len(train_x)), 9) 77. #Mostramos los numeros que ha cogido por si es necesario contrastarlo con el dataset 78. print(train_samples) 79. 80. #Imprimimos las muestras 81. fig,axes = plt.subplots(4,4,figsize=(10,8)) 82. plt.suptitle('Set de entrenamiento') 83. for i in train_samples: 84. plt.subplot(3, 3, train_samples.index(i)+1) 85. plt.imshow(train_x[i], cmap='binary') 86. plt.title(f'Etiqueta: {clases[train_y[i]]}') 87. plt.axis('off') 88. plt.savefig('figures/ejemplos_dataset.png') 89. 90. 99 91. modelo_a_compilar=3 92. 93. 94. #Modelo secuencial normal 95. if modelo_a_compilar==1: 96. print('Vas a usar el MODELO 1: Red Neuronal Simple') 97. 98. modelo = tf.keras.Sequential([ 99. tf.keras.layers.Flatten(input_shape=(28,28,1)), 100. tf.keras.layers.Dense(units=25, activation='relu'), 101. tf.keras.layers.Dense(units=40, activation='relu'), 102. tf.keras.layers.Dense(47, activation='softmax') 103. ]) 104. 105. 106. elif modelo_a_compilar==2: 107. print('Vas a usar el MODELO 2: Red Neuronal Convolucional') 108. modelo = tf.keras.Sequential([ 109. tf.keras.layers.Conv2D(32, (3,3), input_shape=(28,28,1), activation='relu'), 110. tf.keras.layers.MaxPooling2D(2,2), #2,2 es el tamano de la matriz 111. 112. tf.keras.layers.Conv2D(64, (3,3), activation='relu'), 113. tf.keras.layers.MaxPooling2D(2,2), #2,2 es el tamano de la matriz 114. 115. tf.keras.layers.Flatten(), 116. tf.keras.layers.Dense(units=100, activation='relu'), 117. tf.keras.layers.Dense(47, activation='softmax') 118. ]) 119. 120. #Modelo con dropout 121. 122. elif modelo_a_compilar==3: 123. print('Vas a usar el MODELO 3: Red Neuronal Convolucional con Dropout') 124. modelo = tf.keras.models.Sequential([ 125. tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)), 126. tf.keras.layers.MaxPooling2D(2, 2), 127. 128. tf.keras.layers.Conv2D(64, (3,3), activation='relu'), 129. tf.keras.layers.MaxPooling2D(2,2), 130. 131. tf.keras.layers.Dropout(0.5), 132. tf.keras.layers.Flatten(), 133. tf.keras.layers.Dense(100, activation='relu'), 134. tf.keras.layers.Dense(47, activation="softmax") 135. ]) 136. Anexo C 100 137. elif modelo_a_compilar==4: 138. print('Vas a usar el MODELO 4: Prueba de Red Neuronal con numerosas capas ocultas') 139. 140. modelo = tf.keras.models.Sequential([ 141. tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)), 142. tf.keras.layers.MaxPooling2D(2, 2), 143. 144. tf.keras.layers.Conv2D(64, (3,3), activation='relu'), 145. tf.keras.layers.MaxPooling2D(2,2), 146. 147. #tf.keras.layers.Dropout(0.5), 148. tf.keras.layers.Flatten(), 149. tf.keras.layers.Dense(100, activation='relu'), 150. tf.keras.layers.Dense(100, activation='relu'), 151. tf.keras.layers.Dense(100, activation='relu'), 152. tf.keras.layers.Dense(100, activation='relu'), 153. tf.keras.layers.Dense(47, activation="softmax") 154. ]) 155. 156. #Compilar el modelo 157. modelo.compile( 158. optimizer='adam', 159. loss=tf.keras.losses.SparseCategoricalCrossentropy(), 160. metrics=['accuracy'] 161. ) 162. 163. modelo.summary() 164. print('Numero de capas de la Red Neuronal') 165. print(len(modelo.layers)) 166. 167. #Separamos test y entrenamiento 168. X_train, X_valid, Y_train, Y_valid = train_test_split(train_x, train_y, test_size=0.25, random_state=42) 169. 170. 171. print(train_y.shape) 172. print(test_y.shape) 173. print(train_x[1].shape) 174. # Realizamos el aumento de imagenes de prueba mediante distorsiones, desplazamientos y giros 175. aug = ImageDataGenerator(rotation_range=20, zoom_range=0.05, width_shift_range=0.1, 176. height_shift_range=0.1, shear_range=0.15, horizontal_flip=False, fill_mode="nearest") 177. 178. #Loads in image path 179. image_path='data/images/train/imagen_26.jpg' 180. img = load_img(image_path, target_size= (28,28)) 181. img_tensor = img_to_array(img) 182. img_tensor = np.expand_dims(img_tensor, axis=0) 183. #Uses ImageDataGenerator to flip the images 184. #Creates our batch of one image 185. pic = aug.flow(img_tensor, batch_size =1) 101 186. plt.figure(figsize=(10,8)) 187. #Plots our figures 188. for i in range(1,10): 189. plt.subplot(3, 3, i) 190. batch = pic.next() 191. image_ = batch[0].astype('uint8') 192. plt.imshow(image_) 193. plt.axis('off') 194. plt.show() 195. EPOCHS = 50 196. INIT_LR = 1e-1 197. BS = 128 198. print('COMENZANDO ENTRENAMIENTO...') 199. now = datetime.now().time() # time object 200. current_time = now.strftime("%H:%M:%S") 201. print("Hora de inicio =", current_time) 202. H = modelo.fit( 203. aug.flow(train_x, train_y, batch_size=BS), validation_data=( X_valid, Y_valid), steps_per_epoch=len(train_x) // BS, epochs=EPOCHS, 204. verbose=1) 205. '''H = modelo.fit( 206. train_x, train_y, batch_size=BS, validation_data=( X_valid, Y_valid), steps_per_epoch=len(train_x) // BS, epochs=EPOCHS, 207. verbose=1)''' 208. print('LISTO') 209. now = datetime.now().time() # time object 210. end_time = now.strftime("%H:%M:%S") 211. print("Hora de finalizacion =", end_time) 212. 213. 214. #Mostramos los resultados del entrenamiento vs test para la precisión 215. 216. plt.figure(figsize = (9,9)) 217. print(H.history.keys()) 218. plt.plot(H.history['accuracy']) 219. plt.plot(H.history['val_accuracy']) 220. plt.title('Precisión del modelo') 221. plt.ylabel('Precisión (pu)') 222. plt.xlabel('Número de épocas') 223. plt.legend(['Entrenamiento', 'Test'], loc='upper left') 224. plt.xticks(np.arange(0, 50, step=5)) # Set label locations. 225. plt.yticks(np.arange(0.54, 1, step=0.02)) # Set label locations. 226. plt.grid() 227. plt.savefig('figures/precision_modelo.png') 228. plt.show() 229. 230. 231. #Mostramos los resultados del entrenamiento vs test para las pérdidas 232. plt.figure(figsize = (9,9)) Anexo C 102 233. plt.plot(H.history['loss']) 234. plt.plot(H.history['val_loss']) 235. plt.title('Perdidas del modelo') 236. plt.ylabel('Pérdidas') 237. plt.xlabel('Número de épocas') 238. plt.legend(['Entrenamiento', 'Test'], loc='upper left') 239. plt.xticks(np.arange(0, 50, step=5)) # Set label locations. 240. plt.yticks(np.arange(0, 1.5, step=0.05)) # Set label locations. 241. plt.grid() 242. plt.savefig('figures/perdidas_del_modelo.png') 243. plt.show() 244. 245. test_samples = random.sample(range(0, len(test_x)), 9) 246. 247. print(test_samples) 248. 249. fig,axes = plt.subplots(4,4,figsize=(10,8)) 250. plt.suptitle('Prueba de predicción') 251. for i in test_samples: 252. #print(test_y) 253. #print(X_test.shape) 254. probs = modelo.predict(test_x[np.newaxis, i]) 255. print(probs) 256. prediction = probs.argmax(axis=1) 257. print(prediction) 258. label_predict = clases[prediction[0]] 259. plt.subplot(3, 3, test_samples.index(i)+1) 260. #imagen=train_x.loc[[i]].values.reshape(28,28) 261. #imagen=np.transpose(imagen) 262. #print(prediction[i]) 263. #print(clases[label_predict[i]]) 264. plt.imshow(test_x[i], cmap='binary') 265. plt.title(f'Era: {clases[test_y[i]]} predijo: {label_predict}') 266. plt.axis('off') 267. plt.savefig('figures/pruebas_prediccion_modelo.png') 268. 269. # save the model to disk 270. print() 271. if modelo_a_compilar==1: 272. 273. modelo.save('saved_model/my_modelSimple', save_format="h5") 274. elif modelo_a_compilar==2: 275. modelo.save('saved_model/my_modelConv', save_format="h5") 276. elif modelo_a_compilar==3: 277. modelo.save('saved_model/my_modelDrop', save_format="h5") 278. elif modelo_a_compilar==3: 279. modelo.save('saved_model/my_modelMal', save_format="h5") 103 ANEXO D Código perteneciente al Algoritmo de entrenamiento del ResNet50: 1. #2022 Roberto Gallo TFG 2. #Entrenamiento ResNet 3. #Importamos librerias necesarias 4. 5. import numpy as np 6. import tensorflow as tf 7. import torch 8. import pandas as pd 9. import matplotlib.pyplot as plt 10. import seaborn as sns 11. from keras.models import Sequential 12. from keras.layers import Dense 13. from keras.layers import Dropout 14. from keras.layers import Flatten 15. from tensorflow.keras.optimizers import Adam 16. from tensorflow.keras.preprocessing.image import ImageDataGe nerator 17. from keras.layers.convolutional import Conv2D 18. from keras.layers.convolutional import MaxPooling2D 19. from keras import backend as K 20. from keras.utils import np_utils 21. import cv2 22. from imutils import build_montages 23. import random 24. from torch.utils.data.sampler import SubsetRandomSampler 25. import models 26. from sklearn.preprocessing import LabelBinarizer 27. 28. #Llamamos y almacenamos los Datasets de prueba 29. print("ADQUIRIENDO DATASET...") 30. data_train= pd.read_csv('emnist-balanced-train.csv') 31. data_test= pd.read_csv('emnist-balanced-test.csv') 32. print("DATASET CARGADO") 33. 34. #Funcion para normalizar los datasets (Hacerlos de tipo float32 35. #y con valores entre 0 y 1) 36. 37. def Normalizar(data, label): 38. x = data.drop(label ,axis=1) 39. x=x.astype('float32') 40. x=x/255 41. y = data[label] 42. x= x.values.reshape(-1,28,28,1) 43. x=np.transpose(x,[0, 2, 1, 3]) 44. return x,y 45. 46. #El entrenamiento y el test tienen etiquetas distintas Anexo D 104 47. train_x, train_y= Normalizar (data_train,'45') 48. train_x = [cv2.resize(image, (32, 32)) for image in train_x] 49. train_x = np.expand_dims(train_x, axis=-1) 50. 51. test_x, test_y = Normalizar(data_test, '41') 52. test_x = [cv2.resize(image, (32, 32)) for image in test_x] 53. test_x = np.expand_dims(test_x, axis=-1) 54. 55. #Lista con las diferentes etiquetas para su posterior traduccion 56. #y visualizacion 57. 58. clases=['0','1','2','3','4','5','6','7','8','9','A','B', 59. 'C','D','E','F','G','H','I','J','K','L','M','N', 60. 'O','P','Q','R','S','T','U','V','W','X','Y','Z', 61. 'a','b','d','e','f','g','h','n','q','r','t'] 62. 63. #Con este bloque imprimimos y mostramos diferentes caracteres de 64. #nuestras pruebas 65. 66. train_samples = random.sample(range(0, len(train_x)), 9) 67. print(train_samples) 68. fig,axes = plt.subplots(4,4,figsize=(10,8)) 69. plt.suptitle('Training set') 70. for i in train_samples: 71. plt.subplot(3, 3, train_samples.index(i)+1) 72. plt.imshow(train_x[i], cmap='binary') 73. plt.title(f'Etiqueta: {clases[train_y[i]]}') 74. plt.axis('off') 75. 76. print('Vas a usar el MODELO ResNet50') 77. 78. EPOCHS = 50 79. INIT_LR = 1e-1 80. BS = 128 81. model = models.ResNet.build(32, 32, 1, 47, (3, 3, 3), (64, 64, 128, 256), reg=0.0005) 82. model.compile(loss="categorical_crossentropy", optimizer='adam', metrics=["accuracy"]) 83. 84. le = LabelBinarizer() 85. train_y = le.fit_transform(train_y) 86. ounts = train_y.sum(axis=0) 87. 88. test_y = le.fit_transform(test_y) 89. 90. classTotals = train_y.sum(axis=0) 91. classWeight = {} 92. model.summary() 93. print(len(model.layers)) 94. 95. #Separamos test y entrenamiento 105 96. X_train, X_valid, Y_train, Y_valid = train_test_split(train_x, train_y, test_size=0.25, random_state=42) 97. 98. 99. print(train_y.shape) 100. print(test_y.shape) 101. print(train_x[1].shape) 102. # Realizamos el aumento de imagenes de prueba mediante distorsiones, desplazamientos y giros 103. aug = ImageDataGenerator(rotation_range=20, zoom_range=0.05, width_shift_range=0.1, 104. height_shift_range=0.1, shear_range=0.15, horizontal_flip=False, fill_mode="nearest") 105. 106. #Loads in image path 107. image_path='data/images/train/imagen_26.jpg' 108. img = load_img(image_path, target_size= (28,28)) 109. img_tensor = img_to_array(img) 110. img_tensor = np.expand_dims(img_tensor, axis=0) 111. #Uses ImageDataGenerator to flip the images 112. #Creates our batch of one image 113. pic = aug.flow(img_tensor, batch_size =1) 114. plt.figure(figsize=(10,8)) 115. #Plots our figures 116. for i in range(1,10): 117. plt.subplot(3, 3, i) 118. batch = pic.next() 119. image_ = batch[0].astype('uint8') 120. plt.imshow(image_) 121. plt.axis('off') 122. plt.show() 123. 124. print("[INFO] compiling model...") 125. 126. EPOCHS = 50 127. INIT_LR = 1e-1 128. BS = 128 129. print("[INFO] training network...") 130. 131. H = model.fit( 132. aug.flow(X_train, Y_train, batch_size=BS), validation_data=( X_valid, Y_valid), steps_per_epoch=len(X_train) // BS, epochs=EPOCHS, 133. verbose=1) 134. 135. print('LISTO') 136. 137. #Mostramos los resultados del entrenamiento vs test para la precisión 138. 139. plt.figure(figsize = (9,9)) 140. print(H.history.keys()) 141. plt.plot(H.history['accuracy']) 142. plt.plot(H.history['val_accuracy']) Anexo F 112 ANEXO F Programa dedicado a la predicción de caracteres individuales (CNNs y ResNet): 1. #2022 Roberto Gallo TFG 2. #Prediccion usando los tres primeros modelos 3. #Importamos librerias necesarias 4. from tensorflow.keras.models import load_model 5. from imutils.contours import sort_contours 6. import numpy as np 7. import argparse 8. import imutils 9. import cv2 10. import matplotlib.pyplot as plt 11. 12. print("[INFO] loading handwriting OCR model...") 13. model = load_model('saved_model/model_resnet') #ruta del modelo guardado 14. caracteres=['imagenes/caracter_individual/0.jpeg', 'imagenes /caracter_individual/7.jpeg', 15. 'imagenes/caracter_individual/8.jpeg', 'imagenes/ caracter_individual/8_deformado.jpeg', 16. 'imagenes/caracter_individual/a.jpeg', 'imagenes/ caracter_individual/b_girada.jpeg', 17. 'imagenes/caracter_individual/i.jpeg', 'imagenes/ caracter_individual/l_mayuscula.jpeg', 18. 'imagenes/caracter_individual/l_mayuscula_desplaz ada.jpeg', 'imagenes/caracter_individual/l_minuscula.jpeg', 19. 'imagenes/caracter_individual/m.jpeg', 'imagenes/ caracter_individual/r.jpeg'] 20. clases=['0','1','2','3','4','5','6','7','8','9','A','B','C', 'D','E','F','G','H','I','J', 21. 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', 'Z','a','b','d','e','f','g','h','n','q','r','t'] 22. 23. 24. im = cv2.imread(caracteres[2]) 25. # calculate mean value from RGB channels and flatten to 1D array 26. vals = im.mean(axis=2).flatten() 27. # plot histogram with 255 bins 28. b, bins, patches = plt.hist(vals, 255) 29. plt.xlim([0,255]) 30. plt.show() 31. j=0 32. for i in caracteres: 33. 34. image = cv2.imread(i) 35. scale_percent = 50 36. width = int(image.shape[1] * scale_percent / 100) 37. height = int(image.shape[0] * scale_percent / 100) 38. dim = (width, height) 113 39. 40. # resize image 41. image_res = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 42. gray = cv2.cvtColor(image_res, cv2.COLOR_BGR2GRAY) 43. blurred = cv2.GaussianBlur(gray, (5, 5), 0) 44. edged = cv2.Canny(blurred, 10, 100) 45. 46. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, 47. cv2.CHAIN_APPROX_SIMPLE) 48. cnts = imutils.grab_contours(cnts) 49. 50. 51. #print(image.shape) 52. image = cv2.threshold(blurred, 0, 255, 53. cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] 54. 55. width = 28 #int(image.shape[1] * scale_percent / 100) 56. height = 28 #int(image.shape[0] * scale_percent / 100) 57. dim = (width, height) 58. cv2.imshow("cd", image) 59. image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 60. 61. 62. 63. cv2.waitKey(0) 64. image = image.astype("float32") / 255.0 65. 66. image= image.reshape(-1,28,28,1) 67. print(image.shape) 68. 69. image = np.array(image, dtype="float32") 70. #print(image) 71. preds = model.predict(image) 72. i = np.argmax(preds) 73. 74. prob = preds[0][i] 75. label = clases[i] 76. #print(prob) 77. #print(label) 78. 79. print("[INFO] {} - {:.2f}%".format(label, prob * 100)) 80. 81. image = cv2.imread(caracteres[j]) 82. j=j+1 83. 84. 85. cv2.putText(image_res, label, (50, 70), 86. cv2.FONT_HERSHEY_SIMPLEX, 3, (0, 0, 0), 2) 87. 88. 89. # show the image 90. cv2.imshow("Image", image_res) Anexo F 114 91. cv2.waitKey(0) 92. 93. 94. image = image.astype("float32") / 255.0 95. image = np.expand_dims(image, axis=0) 96. print(image.shape) 97. 98. # OCR the characters using our handwriting recognition model 99. preds = model.predict(image) 100. 101. clases=['0','1','2','3','4','5','6','7','8','9','A','B','C', 'D','E','F','G','H','I','J', 102. 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', 'Z','a','b','d','e','f','g','h','n','q','r','t'] 103. 104. i = np.argmax(preds) 105. print(preds) 106. print(i) 107. prob = preds[0][i] 108. label = clases[i] 109. print(prob) 110. print(label) 111. 112. print("[INFO] {} - {:.2f}%".format(label, prob * 100)) 113. 114. image = cv2.imread('imagenes/imagen_G_desplazado.jpeg') 115. 116. cv2.putText(image, label, (50, 50), 117. cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2) 118. 119. 120. # show the image 121. cv2.imshow("Image", image) 122. cv2.waitKey(0) 115 ANEXO G Código correspondiente a la predicción de caracteres individuales (2 Redes CNN para letras y números): 1. #2022 Roberto Gallo TFG 2. #Prediccion usando el cuarto modelo 3. #Importamos librerias necesarias 4. from tensorflow.keras.models import load_model 5. from imutils.contours import sort_contours 6. import numpy as np 7. import argparse 8. import imutils 9. import cv2 10. import matplotlib.pyplot as plt 11. print("[INFO] loading handwriting OCR model...") 12. model1 = load_model('saved_model/modelo_solo_letras') #ruta del modelo guardado 13. model2 = load_model('saved_model/modelo_solo_digitos') #ruta del modelo guardado 14. caracteres=['imagenes/caracter_individual/0.jpeg', 'imagenes /caracter_individual/7.jpeg', 15. 'imagenes/caracter_individual/8.jpeg', 'imagenes/ caracter_individual/8_deformado.jpeg', 16. 'imagenes/caracter_individual/a.jpeg', 'imagenes/ caracter_individual/b_girada.jpeg', 17. 'imagenes/caracter_individual/i.jpeg', 'imagenes/ caracter_individual/l_mayuscula.jpeg', 18. 'imagenes/caracter_individual/l_mayuscula_desplaz ada.jpeg', 'imagenes/caracter_individual/l_minuscula.jpeg', 19. 'imagenes/caracter_individual/m.jpeg', 'imagenes/ caracter_individual/r.jpeg'] 20. clases_letras=['0','A','B', 21. 'C','D','E','F','G','H','I','J','K','L','M','N', 22. 'O','P','Q','R','S','T','U','V','W','X','Y','Z'] 23. clases_digitos=['0','1','2','3','4','5','6','7','8','9'] 24. 25. im = cv2.imread(caracteres[2]) 26. # calculate mean value from RGB channels and flatten to 1D array 27. vals = im.mean(axis=2).flatten() 28. # plot histogram with 255 bins 29. b, bins, patches = plt.hist(vals, 255) 30. plt.xlim([0,255]) 31. plt.show() 32. 33. j=0 34. for i in caracteres: 35. 36. image = cv2.imread(i) Anexo G 116 37. scale_percent = 50 # 38. width = int(image.shape[1] * scale_percent / 100) 39. height = int(image.shape[0] * scale_percent / 100) 40. dim = (width, height) 41. 42. # resize image 43. image_res = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 44. gray = cv2.cvtColor(image_res, cv2.COLOR_BGR2GRAY) 45. blurred = cv2.GaussianBlur(gray, (5, 5), 0) 46. edged = cv2.Canny(blurred, 10, 100) 47. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, 48. cv2.CHAIN_APPROX_SIMPLE) 49. cnts = imutils.grab_contours(cnts) 50. 51. 52. image = cv2.threshold(gray, 120, 255, 53. cv2.THRESH_BINARY_INV)[1] 54. 55. width = 28 #int(image.shape[1] * scale_percent / 100) 56. height = 28 #int(image.shape[0] * scale_percent / 100) 57. dim = (width, height) 58. image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 59. 60. 61. cv2.imshow("cd", image) 62. cv2.waitKey(0) 63. image = image.astype("float32") / 255.0 64. image= image.reshape(-1,28,28,1) 65. print(image.shape) 66. image = np.array(image, dtype="float32") 67. preds_letra = model1.predict(image) 68. i = np.argmax(preds_letra) 69. #print(preds) 70. #print(i) 71. prob_letra = preds_letra[0][i] 72. label_letra = clases_letras[i] 73. #------------- 74. preds_digito = model2.predict(image) 75. i = np.argmax(preds_digito) 76. prob_digito= preds_digito[0][i] 77. label_digito = clases_digitos[i] 78. 79. if prob_letra>prob_digito: 80. label=label_letra 81. prob=prob_letra 82. else: 83. label=label_digito 84. prob=prob_digito 85. #print(prob) 86. #print(label) 87. 88. print("[INFO] {} - {:.2f}%".format(label_letra, prob_letra * 100)) 117 89. print("[INFO] {} - {:.2f}%".format(label_digito, prob_digito * 100)) 90. 91. image = cv2.imread(caracteres[j]) 92. j=j+1 93. 94. 95. cv2.putText(image_res, label, (50, 70), 96. cv2.FONT_HERSHEY_SIMPLEX, 3, (0, 0, 0), 2) 97. 98. 99. 100. cv2.imshow("Image", image_res) 101. cv2.waitKey(0) Anexo H 118 ANEXO H Programa diseñado para predicción de varios caracteres (CNNs y ResNet) 1. #2022 Roberto Gallo TFG 2. #Prediccion de varios caracteres usando los tres primeros modelos 3. #Importamos librerias necesarias 4. from tensorflow.keras.models import load_model 5. from imutils.contours import sort_contours 6. import numpy as np 7. import argparse 8. import imutils 9. import cv2 10. print("[INFO] loading handwriting OCR model...") 11. model = load_model('saved_model/model_resnet') #ruta del modelo guardado 12. image = cv2.imread('imagenes/letras_numeros.jpeg') 13. 14. scale_percent = 50 # percent of original size 15. width = int(image.shape[1] * scale_percent / 100) 16. height = int(image.shape[0] * scale_percent / 100) 17. dim = (width, height) 18. 19. # resize image 20. image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 21. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 22. blurred = cv2.GaussianBlur(gray, (5, 5), 0) 23. edged = cv2.Canny(blurred, 60, 100) 24. cv2.imshow("Image", edged) 25. cv2.waitKey(0) 26. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, 27. cv2.CHAIN_APPROX_SIMPLE) 28. cnts = imutils.grab_contours(cnts) 29. cnts = sort_contours(cnts, method="left-to-right")[0] 30. #Crear cortina blanca 31. temp = np.ones(edged.shape,np.uint8)*255 32. # Dibuje el contorno: la temperatura es la cortina blanca, los contornos son el contorno, -1 es la imagen completa, luego el color, el grosor 33. cv2.drawContours(temp,cnts,-1,(0,255,0),3) 34. cv2.imshow("contours",temp) 35. cv2.waitKey(0) 36. 37. #Inicializar la lista de contornos y caracteres asociados 38. 39. chars = [] 40. 41. # loop 42. for c in cnts: 43. (x, y, w, h) = cv2.boundingRect(c) 119 44. 45. #Nos aseguramos que los contornos no son demasiados grandes ni demasiado pequenios 46. if (h >=(image.shape[0])/30) and (h<20*w) and (w<10*h): 47. roi = gray[y:y + h, x:x + w] 48. thresh = cv2.threshold(roi, 0, 255, 49. cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] 50. (tH, tW) = thresh.shape 51. 52. if tW > tH: 53. thresh = imutils.resize(thresh, width=32) 54. 55. else: 56. thresh = imutils.resize(thresh, height=32) 57. 58. #Determinamos cuanto hay redimensionar la imagen 59. (tH, tW) = thresh.shape 60. dX = int(max(0, 32 - tW) / 2.0) 61. dY = int(max(0, 32 - tH) / 2.0) 62. 63. # Forzamos una dimension de 28x28 64. padded = cv2.copyMakeBorder(thresh, top=dY, bottom=dY, 65. left=dX, right=dX, borderType=cv2.BORDER_CONSTANT, 66. value=(0, 0, 0)) 67. padded = cv2.resize(padded, (32, 32)) 68. 69. #Preparamos la imagen para su clasificacion 70. padded = padded.astype("float32") / 255.0 71. padded = np.expand_dims(padded, axis=-1) 72. print(padded.shape) 73. 74. #Actualizamos lista 75. chars.append((padded, (x, y, w, h))) 76. 77. #Lanzamos prediccion 78. preds = model.predict(chars) 79. clases=['0','1','2','3','4','5','6','7','8','9','A','B','C', 'D','E','F','G','H','I','J', 80. 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', 'Z','a','b','d','e','f','g','h','n','q','r','t'] 81. 82. for (pred, (x, y, w, h)) in zip(preds, boxes): 83. i = np.argmax(pred) 84. prob = pred[i] 85. label = clases[i] 86. #Dibujamos los cuadros delimitadores 87. print("[INFO] {} - {:.2f}%".format(label, prob * 100)) 88. if (prob*100)>40: 89. cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2) 90. cv2.putText(image, label, (x - 10, y - 10), 91. cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2) 92. Anexo H 120 93. 94. # Mostramos la imagen 95. cv2.imshow("Image", image) 96. cv2.waitKey(0) 97. 98. #Prueba sobre un dataset completo 99. print("ADQUIRIENDO DATASET...") 100. data_test= pd.read_csv('emnist-balanced-test.csv') 101. print("DATASET CARGADO") 102. 103. def Normalizar(data, label): 104. x = data.drop(label ,axis=1) 105. x=x.astype('float32') 106. x=x/255 107. y = data[label] 108. x= x.values.reshape(-1,28,28,1) 109. x=np.transpose(x,[0, 2, 1, 3]) #Hay que transponer los datos porque si no el caracter no se escribe correctamente 110. return x,y 111. 112. test_x, test_y= Normalizar (data_test,'41') 113. test_x = [cv2.resize(image, (32, 32)) for image in test_x] 114. test_x = np.expand_dims(test_x, axis=-1) 115. correctas=0 116. incorrectas=0 117. prueba=0 118. for i in range(len(test_x)): 119. #print(test_y) 120. #print(X_test.shape) 121. probs = model.predict(test_x[np.newaxis, i], verbose=False) 122. #print(probs) 123. prediction = probs.argmax(axis=1) 124. #print(prediction) 125. #label_predict = clases[prediction[0]] 126. #plt.subplot(3, 3, test_samples.index(i)+1) 127. #imagen=train_x.loc[[i]].values.reshape(28,28) 128. #imagen=np.transpose(imagen) 129. #print(prediction[i]) 130. #print(clases[label_predict[i]]) 131. #plt.imshow(test_x[i], cmap='binary') 132. if test_y[i]==prediction[0]: 133. correctas=correctas+1 134. else: 135. incorrectas=incorrectas+1 136. prueba=prueba+1 137. print(prueba) 138. print("Datos correctos:") 139. print(correctas) 140. print("Datos incorrectos:") 141. print(incorrectas) 142. print("Total:") 143. print(correctas+incorrectas) 121 ANEXO I Código correspondiente a la predicción de varios caracteres (2 Redes CNN) 1. #2022 Roberto Gallo TFG 2. #Prediccion de varios caracteres usando el cuarto modelo 3. #Importamos librerias necesarias 4. from tensorflow.keras.models import load_model 5. from imutils.contours import sort_contours 6. import numpy as np 7. import argparse 8. import imutils 9. import cv2 10. print("[INFO] loading handwriting OCR model...") 11. model_letras = load_model('saved_model/modelo_solo_letras') #ruta del modelo guardado 12. #model_digitos = load_model('saved_model/modelo_solo_digitos') #ruta del modelo guardado 13. model_digitos = load_model('saved_model/modelo_solo_digitos') #ruta del modelo guardado 14. image = cv2.imread('imagenes/letras_numeros.jpeg') 15. 16. scale_percent = 50 17. width = int(image.shape[1] * scale_percent / 100) 18. height = int(image.shape[0] * scale_percent / 100) 19. dim = (width, height) 20. 21. image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) 22. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 23. blurred = cv2.GaussianBlur(gray, (5, 5), 0) 24. edged = cv2.Canny(blurred, 60, 100) 25. cv2.imshow("Image", edged) 26. cv2.waitKey(0) 27. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, 28. cv2.CHAIN_APPROX_SIMPLE) 29. cnts = imutils.grab_contours(cnts) 30. cnts = sort_contours(cnts, method="left-to-right")[0] 31. #Crear cortina blanca 32. temp = np.ones(edged.shape,np.uint8)*255 33. # Dibuje el contorno: la temperatura es la cortina blanca, los contornos son el contorno, -1 es la imagen completa, luego el color, el grosor 34. cv2.drawContours(temp,cnts,-1,(0,255,0),3) 35. 36. cv2.imshow("contours",temp) 37. cv2.waitKey(0)