Reconocimiento de imágenes con redes convolucionales en C
Abstract
En el siguiente proyecto se explica cómo realizar una red neuronal artificial, en concreto, una red convolucional, para el reconocimiento de símbolos. Se trata de una red supervisada, la cual se entrena a partir de un conjunto muy amplio de símbolos proporcionados por el dataset MNIST. Una vez la red haya sido entrenada, esta será capaz de clasificar un símbolo (ya sea del propio MNIST o de uno realizado por nosotros mismos) con una alta fiabilidad, del orden del 90%.
Full text
i Equation Chapter 1 Section 1 Trabajo de Fin de Grado Grado de Ingeniería Electrónica, Róbotica y Mecatrónica Reconocimiento de imágenes con Redes Convolucionales en C Autor: Álvaro Casas Martínez Tutor: Alejandro José del Real Torres Dep. de Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2017
ii
iii Trabajo de Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica Reconocimiento de imágenes con Redes Convolucionales en C Autor: Álvaro Casas Martínez Tutor: Alejandro José del Real Torres Profesor titular Dep. de Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2017
iv
v Proyecto Fin de Carrera: Reconocimiento de imágenes con Redes Convolucionales en C Autor: Álvaro Casas Martínez Tutor: Alejandro del Real Torres El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de:
vi Sevilla, 2017 El Secretario del Tribunal
vii A mi familia A mi tutor A mis profesores A mis compañeros
viii
ix Agradecimientos El presente trabajo de fin de grado fue realizado bajo la supervisión del doctor Alejandro José del Real Torres, a quien me gustaría expresar mi más profundo agradecimiento por hacer posible el desarrollo de este estudio. Además de agradecer su paciencia, tiempo y dedicación. A mis padres Felicia y Diego, por apoyarme en los momentos más difíciles durante estos cuatro años de carrera, y por acompañarme en los buenos momentos a pesar de la distancia. A mi hermana Mirian, por guiarme por el camino correcto y ayudarme a tomar decisiones importantes. A mis compañeros y amigos, por haber vivido momentos muy felices con ellos y por el apoyo que he recibido de ellos. Por último a mis profesores, por enseñarme sus conocimientos e invertir tiempo en que haya un futuro lleno de ingenieros competentes. Álvaro Casas Martínez Sevilla, 2017
xvi ÍNDICE DE TABLAS Tabla 5-1: Comparación al aplicar diferentes filtros a una imagen. 30
xvii
xviii ÍNDICE DE FIGURAS Figura 1-1: Diagrama esquemático de dos neuronas biológicas. 2 Figura 2-1: Gráfica en Matlab de precios reales y predichos. 7 Figura 3-1: Esquema de una red neuronal simple. 10 Figura 3-2: Esquema de una red neuronal multicapa. 11 Figura 3-3: Esquema de una red neuronal multicapa con dos capas ocultas. 12 Figura 3-4: Esquema del cálculo de la salida de una red. 13 Figura 3-5: Esquema de la retropropagación en una red. 14 Figura 3-6: Esquema de aprendizaje de una red. 14 Figura 3-7: Ejemplo de una red. 17 Figura 3-8: Funcionamiento de una neurona. 18 Figura 3-9: Ejemplo de propagación en una red. 19 Figura 3-10: Ejemplo de propagación en una red en las capas ocultas. 19 Figura 3-11: Ejemplo de propagación a la salida. 20 Figura 3-12: Cálculo del error a la salida de la red. 20 Figura 3-13: Ejemplo de retropropagación (I). 20 Figura 3-14: Ejemplo de retropropagación (II). 21 Figura 3-15: Ejemplo de retropropagación (III) 23 Figura 4-1: Gráfica en la que se representa el problema de overfitting. 25 Figura 5-1: Esquema de una red convolucional. 27 Figura 5-2: Representación de una imagen como una matriz. 28 Figura 5-3: Convolución. 29 Figura 5-4: Convolución con varios filtros. 31 Figura 5-5: Ejemplo de max pool aplicado a una matrix. 32 Figura 5-6: Ejemplo de pooling a varias feature maps. 32 Figura 5-7: Esquema de la red convolucional y todas sus capas. 33 Figura 5-8: Ejemplo de una capa fully connected. 35 Figura 5-9: Esquema de las partes de una red convolucional. 35 Figura 6-1: Ordenador empleado para la realización del proyecto. 38 Figura 7-1: Instalación de los programas y librerías (I). 40 Figura 7-2: Instalación de los programas y librerías (II). 41 Figura 7-3: Instalación de los programas y librerías (III). 41 Figura 7-4: Instalación de los programas y librerías (IV). 42 Figura 7-5: Instalación de los programas y librerías (V). 42 Figura 7-6: Instalación de los programas y librerías (VI). 43
xix Figura 7-7: Instalación de los programas y librerías (VII). 43 Figura 9-1: Resultado tras la ejecución del programa. 47
xx
xxi Notación a Número real a Vector A Matriz ℜ𝑛 Conjuntos de números reales de dimensión n e Número de Euler 𝑥𝑗 Elemento j-ésimo del vector 𝒙� 𝑥(𝑖) Columna i-ésima de la matriz 𝑿 𝑨𝑇 ⨀ 𝑓� ′ (𝑧�) ∇𝜃 � 𝜎�(z) 𝑃�(𝑦� = 1|𝒙�) ∂J(θ) ∂θ𝑖� � Matriz 𝑨� traspuesta Producto de Hadamard Derivada de la función 𝑓�(𝑧�) Gradiente con respecto a 𝜽 Función sigmoidea Probabilidad de que 𝑦��valga�1�sabiendo�𝒙� Derivada parcial de la función 𝐽�(𝜽�) con respecto al i-ésimo elemento del vector 𝜽�
1 INTRODUCCIÓN lo largo de la historia las redes neuronales han sido un importante campo de investigación para su posible uso en varias ramas de la ciencia. El objetivo de su estudio era, tratar de crear modelos articiales de redes neuronales que solucionen problemas difíciles de resolver mediante técnicas algorítmicas convencionales. La visión de una moderna red neuronal comienza en 1940 con los trabajos de Warren McCulloch y Walter Pitts quienes mostraron que las redes neuronales artificiales se podían estudiar con funciones matemáticas. Posteriormente, Donald Hebb propuso un mecanismo de aprendizaje en las neuronas biólogicas. Seguidamente aparece la red perceptrón y el aprendizaje asociado demostrando la capacidad para llevar a cabo el reconocimiento de patrones. La continuación del avance de las redes neuronales comienza con el uso de la mecánica estadística para explicar el funcionamiento de la red recurrente y el desarrollo del algoritmo backpropagation. Hoy en día se sigue investigando en redes neuroanles ya que aunque no aporten soluciones a todos los problemas son herramientas esenciales para determinados problemas. 1.1 Inspiración badada en la biología Las redes neuronales artificiales están relacionados con sus homólogos biológicos. A continuación, se describen las características de la función cerebral que han inspirado el desarrollo de las redes neuronales artificiales. El cerebro se compone de un gran número de neuronas interconectadas entre sí. Para el propósito de una red artificial, las neuronas tienen tres componentes principales: las dendritas, el cuerpo celular y el axón. Las dendritas son receptores que llevan señales eléctricas al cuerpo de la célula. El cuerpo de la célula sintetiza las señales entrantes. El axón es una fibra larga que transporta la señal desde el cuerpo celular a otras neuronas. El punto de contacto entre un axón y una dendrita de diferentes células se llama sinapsis. En la siguiente figura se muestra el diagrama esquemático simplificado de dos neuronas biológicas. A El esfuerzo es solo esfuerzo cuando comienza a doler. - José Ortega y Gassett -
2 Figura 1-1: Diagrama esquemático de dos neuronas biológicas. Las principales similitudes entre las redes biológicas y artificiales. En primer lugar, los bloques de construcción de ambas redes son simples dispositivos computacionales que son interconectados. En segundo lugar, las conexiones entre las neuronas determinan la función de la red. Las redes neuronales artificiales comparten con las biológicas una estructura de computación en paralelo que aumenta la velocidad de resolución de un problema. 1.2 Aplicaciones Las redes neuronales tienen un gran número de aplicaciones reales en la industria. De hecho ya han sido aplicadas en muchos dispositivos electrónicos comerciales, debido a que éstas, muestran mejores resultados en el reconocimiento de patrones. Algunos campos en los que pueden trabajar las redes neuronales son: • Reconocimiento de Caracteres: Esta idea tomó mucho auge y fue tomada inicialmente en las palm que fueron muy populares hace algunos años, aún esta aplicación se emplea en algunos tablets y teléfonos celulares táctiles. De hecho, el reconocimiento de caracteres, permite reconocer texto escrito manualmente. • Compresión de imágenes: Las redes neuronales pueden recibir y procesar grandes cantidades de información a la vez, siendo esto útil en la compresión de imágenes, solo basta hacer una observación con el crecimiento del internet y del diseño de imágenes y de animaciones, cada vez más pesadas. • Mercado de Valores: El negocio del día a día de la bolsa bursátil es muy complicado, muchos factores pesan en si una acción propuesta subirá o bajará. Desde las RNA se puede examinar una gran cantidad de información de forma rápida y organizar todo de manera que se pueda hacer un adecuado estudio de proyección y predecir el valor de las acciones. • Procesamiento de alimentos: En este caso se ha implementado el uso de una nariz electrónica que reemplaza a los seres humanos y llevar un adecuado control en la inspección, clasificación de los productos, por ejemplo en las pescaderías industriales, en el control del nivel de acidez de la mayonesa, el seguimiento de la maduración del queso, control de sabores, filtrado de señales etc. • Medicina: una de las áreas que más ha ganado la atención es en la detección de afecciones cardiopulmonares, es decir compara muchos modelos distintos para identificar similitud en patrones y síntomas de la enfermedad. Estos sistemas ayudan a los médicos con el diagnóstico por el análisis de los síntomas reportados y las resonancias magnéticas y rayos x. También se han usado para dispositivos analizadores de habla para ayudar a
3 personas con sordera profunda, monitorización de cirugías, predicción de reacciones adversas a un medicamento, entendimiento de causas de ataques epilépticos. • Milicia: Las redes neuronales juegan un papel importante en el campo de batalla, especialmente en aviones de combate y tanques que son equipados con cámaras digitales de alta resolución que funciona conectado a un computador que continuamente explora el exterior de posibles amenazas. De igual manera se pueden emplear para clasificar señales de radar, creación de armas inteligentes. • Diagnóstico de máquinas: A nivel industrial cuando una de estas máquinas presenta fallos automáticamente las apaga cuando esto ocurre. • Análisis de Firmas: Las redes neuronales pueden ser empleadas para la comparación de firmas generadas, (Por ejemplo en los bancos) Esta ha sido una de las primeras aplicaciones implementada a gran escala en USA y también han sido los primeros en usar un chip neuronal. • Monitoreo de Aviones: controlan el estado de los motores de las aeronaves. Revisan los niveles de vibración de sonido y alertas tempranas de problemas en el motor. • Biología: Mayor entendimiento del funcionamiento del cerebro, obtención de modelos de la retina. • A nivel Administrativo: Identificaciones de candidatos para posiciones específicas, optimización de plazas y horarios en líneas de vuelo, minería de datos. 1.3 Actualidad de las redes neuronales Las redes neuronales alcanzan cada vez mayor auge, teniendo multitud de aplicaciones en campos diversos y dando soluciones sencillas a problemas cuya resolución resulta complicada cuando se emplean máquinas algorítmicas. Aún así, el futuro de las redes neuronales no está todavía claro y será en los próximos años cuando se determine su evolución. Sin embargo y aunque suena a ciencia ficción, las redes neuronales podrán en el futuro permitir: • Robots que pueden ver, sentir, oler y percibir el mundo que los rodea (De hecho ya existe algo llamado Computación afectiva). • Predicción de valores e implementación en vehículos para la auto - conducción. • Composición de música y documentos escritos (obras literarias). • La comprensión de la información en el Genoma Humano. • Autodiagnóstico médico por medio de redes neuronales. 1.4 Conclusiones El mundo de la informática tiene mucho que ganar con las redes neuronales. Su capacidad para aprender por ejemplo, hace que sean extremadamente flexibles y adaptables a un sin número de aplicaciones, convirtiéndolas en una herramienta potente. Cabe destacar que no se hace necesario elaborar un algoritmo para realizar una tarea específica, es decir, no es necesario conocer los mecanismos internos del problema. Las redes neuronales también son adecuadas para los sistemas en tiempo real, por las características de su arquitectura en paralelo, permitiendo así ejecutar cálculos a una gran velocidad y a sus tiempos de respuesta. Las redes neuronales contribuyen también a otras áreas de investigación tales como la neurología y la psicología que se utilizan regularmente para modelar partes de los organismos vivos y para investigar igualmente los mecanismos del cerebro. Por último, hay científicos que afirman que la conciencia podrá ser modelada en un futuro no lejano y que esta será una de las propiedades de las redes neuronales.
4
11 Figura 3-2: Esquema de una red neuronal multicapa. En esta figura, se han representado las entradas con círculos al igual que las neuronas. Los círculos con el valor “+1” se conocen como bias y corresponden al término de intercepción. La capa que se encuentra a la izquierda (Layer 𝐿1) se llama capa de entrada, y la de la derecha (Layer 𝐿3) es la capa de salida (en este ejemplo la capa de salida solo tiene un nodo pero podrían ser más). La capa del medio se denomina capa oculta. Sin contar las bias, se puede decir que en este ejemplo la red neuronal tiene tres unidades de entrada, tres unidades ocultas y una unidad de salida. Para denotar el número de capas de la red se usa 𝑛𝑙, en este ejemplo 𝑛𝑙=3. También se etiqueta la capa l-ésima como 𝐿𝑙, en este ejemplo 𝐿1 es la capa de entrada y 𝐿𝑛𝑙 es la capa de salida. La red neuronal tiene un conjunto de parámetros (𝑾,𝑏)=(𝑾(1),𝑏(1),𝑾(2),𝑏(2)), donde 𝑊𝑖𝑗(1) denota el parámetro (o peso) asociado a la conexión entre la unidad 𝑗 en la capa 𝑙 y la unidad 𝑖 en la capa 𝑙+1. De la misma forma,�𝑏𝑖(1) es la bias asociada a la unidad 𝑖 en la capa 𝑙+1. Por lo tanto, en el ejemplo, 𝑾(1) pertenece a ℜ3𝑥3, y 𝑾(2) pertenece a ℜ1𝑥3. Se usa 𝑎𝑖(𝑙) para denotar la activación (el valor de salida una vez evaluada la función de activación) de una unidad 𝑖 en la capa 𝑙. Para 𝑙=1, también se usa 𝑎𝑖(1)=𝑥𝑖 para denotar la entrada i-ésima. Dado un conjunto de parámetros 𝑾,𝑏 la red define una ℎ𝑊,𝑏(𝒙) que tiene como salida un número real. El cálculo que la red neuronal representa viene dado por las siguientes expresiones: 𝑎1(2)=𝑓(𝑊11 (1)𝑥1+𝑊12 (1)𝑥2+𝑊13 (1)𝑥3+𝑏1(1)) 𝑎2(2)=𝑓(𝑊21 (1)𝑥1+𝑊22 (1)𝑥2+𝑊23 (1)𝑥3+𝑏2(1)) 𝑎3(2)=𝑓(𝑊31 (1)𝑥1+𝑊32 (1)𝑥2+𝑊33 (1)𝑥3+𝑏3(1)) ℎ𝑊,𝑏=𝑎1(3)=𝑓(𝑊11 (2)𝑎1(2)+𝑊12 (2)𝑎2(2)+𝑊13 (2)𝑎3(2)+𝑏1(2)) ( 3-2 ) También se denota 𝑧𝑖(𝑙) a la suma total (incluyendo los pesos) de las entradas para cada unidad 𝑖 en la capa 𝑙, incluyendo las bias. Por ejemplo, 𝑧𝑖(2)=∑𝑊𝑖𝑗(1)𝑥𝑗+𝑏𝑖(1) 𝑛 𝑗=1 , así que 𝑎1(𝑙)=𝑓(𝑧𝑖(𝑙)). Se puede llegar a una notación más compacta si se aplica la función 𝑓(.) tal que así 𝑓([𝑧1,𝑧2,𝑧3])= [𝑓(𝑧1),𝑓(𝑧2),𝑓(𝑧3)] Con esta notación las ecuaciones anteriores quedarían como: 𝒛(2)=𝑾(1)𝒙+𝒃(1) ( 3-3 )
12 𝒂(2)=𝑓(𝒛(2)) 𝒛(3)=𝑾(2)𝒂(2)+𝒃(2) ℎ𝑊,𝑏(𝒙)=𝒂(3)=𝑓(𝒛(3)) A este paso se le conoce como forward propagation. De una manera más general para calcular la activación de la capa 𝑙+1: 𝒛(𝑙+1)=𝑾(𝑙)𝒂(𝑙)+𝒃(𝑙) 𝒂(𝑙+1)=𝑓(𝒛(𝑙+1)) ( 3-4 ) A lo largo de la explicación se ha profundizado en el modelo de la red anterior pero hay otras posibles arquitecturas diferentes para una red neuronal, por ejemplo, redes con múltiples capas ocultas. La elección más común es una red con 𝑛𝑙 capas donde la capa 1 es la de entrada, la capa 𝑛𝑙 es la capa de salida, y cada capa l está conectada a la capa 𝑙+1a través de las diferentes unidades que la componen. En esta configuración, para calcular la salida de la red se puede calcular de froma sucesiva todas las activaciones en la capa 𝐿2, después en la capa 𝐿3 y así sucesivamente hasta la capa 𝐿𝑛𝑙, usando las ecuaciones anteriores que describen el forward propagation. Este es un ejemplo de una red neurnal feedforward, ya que el gráfico de conecividad no tiene ciclos ni bucles. Las redes neuronales también pueden tener varias unidades de salida. En la siguiente imagen se representa una red con dos capas ocultas 𝐿2 y 𝐿3, y dos unidades de salida en la capa 𝐿4: Figura 3-3: Esquema de una red neuronal multicapa con dos capas ocultas. Para entrenar esta red se necesitan los ejemplos de entrenamiento (𝒙(𝑖),𝑦(𝑖)) donde 𝑦(𝑖) pertenece a ℜ2. Este tipo de redes son útiles si hay más de una salida. Por ejemplo, una aplicación puede ser un diagnóstico médico, donde el vector 𝒙 debería contener las caraterísticas de un paciente y el vector 𝒚 el tipo de enfermedad que puede tener. 3.2 Entrenamiento de una red: Algoritmo de retropropagación Backpropagation es uno de muchos métodos para entrenar una red neuronal. Es un esquema de entrenamiento supervisado, que significa, aprende de datos de entrenamiento (hay un supervisor para guiar su aprendizaje). De una forma simple, backpropagation es como aprender de los errores. El supervisor corrige la red neuronal cuando comete errores. El objetivo del aprendizaje es asignar los correctos pesos para que al presentar a la red una nueva entrada esta
13 sea capaz de clasificarla con un buen porcentaje de predicción. 3.2.1 Idea intuitiva Para explicar este algoritmo se presenta el siguiente ejemplo de red neuronal (ver figura 3-4). La red tiene dos nodos en la capa de entrada (sin contar la bias). También tiene una capa oculta con dos nodos. La salida de la capa tiene dos nodos también, el nodo superior emite la probabilidad de “pass”, mientras que el nodo inferior genera la probabilidad de “fail”. En las tareas de clasificación se usa generalmente una función Softmax como función de activación en la capa de salida para asegurar que las salidas son probabilidades y su suma es 1 de tal forma que: 𝑃𝑟𝑜𝑏𝑎𝑏𝑖𝑙𝑖𝑑𝑎𝑑𝑝𝑎𝑠𝑠+𝑃𝑟𝑜𝑏𝑎𝑏𝑖𝑙𝑖𝑑𝑎𝑑𝑓𝑎𝑖𝑙=1 ( 3-5 ) Paso 1: Forward propagation Todos los pesos de la red se asignan de forma aleatoria. Consideramos el nodo de la capa oculta marcado como “V”. Los pesos de las conexiones de las entradas a los nodos son w1,w2 y w3. La red entonces toma como entrada el primer ejemplo de entrenamiento. Por ejemplo, para una entrada 35 y 67, la probabilidad de “pass” es 1. Entrada a la red = [35, 67] Salida deseada de la red (objetivo) = [1, 0] Entonces la salida “V” puede calcularse como: 𝑉=𝑓(1∗𝑤1+35∗𝑤2+67∗𝑤3) Del mismo modo se calcula el otro nodo de la capa oculta. Las salidas de los dos nodos en la capa oculta actúan como entradas a los dos nodos en la capa de salida. Esto permite calcular las probabilidades de salida de los nodos en la capa de salida. Se suponen que las probabilidades de salida de los dos nodos en la capa de salida son 0.4 y 0.6 respectivamente. Podemos ver que las probabilidades calculadas están muy lejos de las probabilidades deseadas (1 y 0 respectivamente), por lo que la red de la figura se dice que tiene una salida incorrecta. Figura 3-4: Esquema del cálculo de la salida de una red. Paso 2: Backpropagation y ajuste de pesos Se calcula el error total en los nodos de salida y se “propagan” estos errores de nuevo a través de la red usando backpropagation para calcular los gradientes. Luego se utiliza un método de optimizaión como Gradient Descent para ajustar todos los pesos en la red con el objetivo de reducir el error en la capa de salida. Esto se muestra en la siguiente figura:
14 Figura 3-5: Esquema de la retropropagación en una red. Si se vuelve a introducir el mismo ejemplo a la red, la red debería funcionar mejor que antes, ya que los pesos se han ajustado para minimizar el error de predicción. Como se muestra en la siguiente figura, los errores en los nodos de salida ahora se reducen a [0.2, -0.2] en comparación con [0.6, -0.4] anterior. Esto significa que la red ha aprendido a clasificar correctamente el primer ejemplo de entrenamiento. Figura 3-6: Esquema de aprendizaje de una red. Se repite este proceso con todos los demás ejemplos de entrenamiento del conjunto de datos y la red aprenderá esos ejemplos. 3.2.2 Explicación matemática Se supone que se tiene un conjunto de entrenamiento {(𝒙(1),𝑦(1)),…,(𝒙(𝑚),𝑦(𝑚)))} de 𝑚 ejemplos de entrenamiento. Se puede entrenar la red neuronal usando batch gradient descent. Profundizando más, para un ejemplo de entrenamiento (𝒙�,𝑦�), se define la función coste respecto al único ejemplo como: 𝐽(𝑾,𝒃;𝒙,𝑦)=12||ℎ𝑊,𝑏(𝒙)−𝑦||2 ( 3-6 ) Esto es una función coste que tiene forma de mínimos cuadrados. Dado un conjunto de entrenamiento con m ejemplos, se define la función coste total como:
15 𝐽(𝑾,𝒃)=[1 𝑚∑𝐽(𝑾,𝒃;𝒙(𝑖),𝑦(𝑖)) 𝑚 𝑖=1 ]+𝜆2∑∑∑(𝑊𝑗𝑖(𝑙))2 𝑠𝑙+1 𝑖=1 𝑠𝑙 𝑗=1 𝑛𝑙−1 𝑙=1 =[1 𝑚∑12||ℎ𝑊,𝑏(𝒙(𝑖))−𝑦(𝑖)||2 𝑚 𝑖=1 ]+𝜆2∑∑∑(𝑊𝑗𝑖(𝑙))2 𝑠𝑙+1 𝑖=1 𝑠𝑙 𝑗=1 𝑛𝑙−1 𝑙=1 ( 3-7 ) El primer término en la definición de 𝐽(𝑾,𝒃) es una media de la suma de términos referidos a los errores cuadráticos. El segundo término es un término de regularización (también llamado como término weight decay) que tiende a decrementar la magnitud de los pesos y ayuda a prevenir el overfitting. El parámetro weight decay 𝜆 controla la importancia de los dos términos. Se nota que 𝐽(𝑾,𝒃;𝒙,𝑦) es el coste error cuadrático con respecto a un único ejemplo y 𝐽(𝑾,𝒃) es la función coste total (con todos los ejemplos) el cual incluye un término weight decay. Esta función coste se usa con frecuencia tanto para problemas de clasificación como de regresión. Para clasificación, se deja 𝑦=0 o 1 representa dos clases (se recalca que la función de activación sigmoidea tiene una salida comprendida en [0, 1]). Para problemas de regresión, primero se escalan las salidas para asegurarse de que están comprendidas en un rango [0, 1]. El objetivo es minimizar 𝐽(𝑾,𝒃) como una función de W y b. Para entrenar la red neuronal, se inicializa cada parámetro 𝑊𝑖𝑗(𝑙) y cada 𝑏𝑖(𝑙) con unos valores muy próximos a cero y después aplicar un algoritmo optimizado como batch gradient descent. Hay que hacer incapié en que hay que inicializar los parámetros con números aleatorios. Si todos los parámetros comienzan con los mismo valores entonces las unidades de las capas ocultas acabarán aprendiendo la misma función de entrada (𝑊𝑖𝑗(1) será la misma para todos los valores de i, así pues 𝑎1(2)=𝑎2(2)=𝑎3(2)=⋯ para cualquier entrada de x). La inicialización aleatoria sirve para romper esta simetría. Para entender mejor como se actualizan los parámetros W y b se muestra una iteración del gradient descent: 𝑊𝑖𝑗(𝑙)=𝑊𝑖𝑗(𝑙)−𝛼 𝛿 𝛿𝑊𝑖𝑗(𝑙)𝐽(𝑾,𝒃) 𝑏𝑖(𝑙)=𝑏𝑖(𝑙)−�𝛼 𝛿 𝛿𝑏𝑖(𝑙)𝐽(𝑾,𝒃) ( 3-8 ) Donde 𝛼 es el ratio de aprendizaje. El paso importante es el de calcular las derivadas anteriores. A continuación se describe el algoritmo backpropagation, el cual, calcula de forma eficiente estas derivadas parciales. Primero se describe como backpropagation se usa para calcular 𝛿 𝛿𝑊𝑖𝑗(𝑙)𝐽(𝑊,𝑏;𝑥,𝑦) y 𝛿 𝛿𝑏𝑖(𝑙)𝐽(𝑊,𝑏;𝑥,𝑦), la derivada parcial del coste 𝐽(𝑾,𝒃;𝒙,𝑦) definida con respecto a un ejemplo (𝒙�,𝑦�). Una vez se calculen éstos, se observa que la derivada de la función coste total 𝐽(𝑊,𝑏) se calcula como: 𝛿 𝛿𝑊𝑖𝑗(𝑙)𝐽(𝑾,𝒃)=[1 𝑚∑𝛿 𝛿𝑊𝑖𝑗(𝑙)𝐽(𝑾,𝒃;𝒙(𝑖),𝑦(𝑖)) 𝑚 𝑖=1 ]+𝜆𝑊𝑖𝑗(𝑙) 𝛿 𝛿𝑏𝑖(𝑙)𝐽(𝑾,𝒃)=[1 𝑚∑𝛿 𝛿𝑏𝑖(𝑙)𝐽(𝑾,𝒃;𝒙(𝑖),𝑦(𝑖)) 𝑚 𝑖=1 ] ( 3-9 ) La intuición detrás del algoritmo backpropagation es como sigue. Dado un ejemplo de entrenamiento (x, y), primero se ejecuta un paso hacia adelante para calcular todas las activaciones a través de la red, incluyendo el valor de salida hipótetico ℎ𝑊,𝑏(𝒙). Entonces, para cada nodo i en la capa l, nos gustaría calcular un término error 𝛿𝑖(𝑙) que mida cuanto ese nodo fue responsable de cualquier error en la salida. Para un nodo de salida, se puede medir esta diferencia directamente entre las activaciones de la red y el valor verdadero del objetivo, y usar
16 esto para definir 𝛿𝑖(𝑛𝑙) (donde la capa 𝑛𝑙 es la capa de salida) pero esto no se puede hacer para las unidades ocultas. Para las neuronas de las capas ocultas se calculará 𝛿𝑖(𝑙) según una media ponderada de los términos de error de los nodos que toman 𝑎𝑖(𝑙) como una entrada. A continuación se presenta los pasos del algoritmo backpropagation: 1. Realizar un paso feedforward calculando las activaciones para las capas 𝐿2, 𝐿3 así sucesivamente hasta la capa de salida 𝐿𝑛𝑙. 2. Para cada unidad de salida i en la capa 𝑛𝑙 (la capa de salida), fijar 𝛿𝑖(𝑛𝑙)=𝛿 𝛿𝑧𝑖(𝑛𝑙)12||𝑦−ℎ𝑊,𝑏(𝒙)||2=−(𝑦𝑖−𝑎𝑖(𝑛𝑙))�.𝑓′(𝑧𝑖(𝑛𝑙))��� ( 3-10 ) 3. Para 𝑙=𝑛𝑙−1,𝑛𝑙−2,𝑛𝑙−3�,…,2 Para cada nodo i en la capa l, fijar 𝛿𝑖(𝑙)=(∑𝑊𝑗𝑖(𝑙)𝛿𝑗(𝑙+1) 𝑠𝑙+1 𝑗=1 )𝑓´(𝑧𝑖(𝑙)) ( 3-11 ) 4. Calcular las derivadas parciales deseadas, que se dan como: 𝛿 𝛿𝑊𝑖𝑗(𝑙)𝐽(𝑾,𝒃;𝑥,𝑦)=𝑎𝑗(𝑙)𝛿𝑖(𝑙+1) 𝛿 𝛿𝑏𝑖(𝑙)𝐽(𝑾,𝒃;𝑥,𝑦)=𝛿𝑖(𝑙+1) ( 3-12 ) Se puede reescribir e algoritmo usando notación vectorial. El operador “⨀” denota el producto Hadamard. Y teniendo en cuenta que 𝑓´([𝑧1,𝑧2,𝑧3])=[𝑓′(𝑧1),𝑓′(𝑧2),𝑓′(𝑧3)]. El algoritmo se escribe como: 1. Realizar un paso feedforward calculando las activaciones para las capas 𝐿2, 𝐿3 así sucesivamente hasta la capa de salida 𝐿𝑛𝑙. 2. Para cada unidad de salida i en la capa 𝑛𝑙 (la capa de salida), fijar 𝜹�(𝑛𝑙)=−(𝒚−𝒂(𝑛𝑙))⨀𝑓´(𝒛(𝑙)) ( 3-13 ) 3. Para 𝑙=𝑛𝑙−1,𝑛𝑙−2,𝑛𝑙−3�,…,2, fijar 𝜹�(𝑙)=((𝑾(𝑙))𝑇𝜹�(𝑙+1))⨀𝑓´(𝒛(𝑙)) ( 3-14 ) 4. Calcular las derivadas parciales deseadas, que se dan como: ∇𝑊(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦)=𝜹(𝑙+1)(𝒂(𝑙))𝑇 �������∇𝑏(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦)=𝜹(𝑙+1) ( 3-15 ) En los pasos 2 y 3 anteriores, se necesita calcua 𝑓´(𝑧𝑖(𝑙)) para cada valor de i. Asumiendo que 𝑓(𝑧)es la función de activación sigmoidea, tendríamos 𝑎𝑖(𝑙) almacenada tras el paso forward a través la red. Por lo tanto, usando la expresión con la que trabajamos anteriormente para 𝑓´(𝑧), podemos calcular esta como:
17 𝑓´(𝑧𝑖(𝑙))=𝑎𝑖(𝑙)(1−𝑎𝑖(𝑙)) ( 3-16 ) Finalmente, se describe el algoritmo gradient descent completo. En el pseudocódigo siguiente, Δ𝑾(𝑙) es una matriz (de la misma dimensión que 𝑾(𝑙)), y Δ𝒃(𝑙) es un vector (de la misma dimensión que 𝒃). Nosotros implementamos una iteración del bach gradient descent como: 1. Fijar Δ𝑾(𝑙)≔0,Δ𝒃(𝑙)≔0 para todo l. 2. Para i = 1 a m, a. Usar backpropagation para calcular�∇𝑾(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦) y ∇𝒃(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦). b. Fijar Δ𝑾(𝑙)≔Δ𝑾(𝑙)+∇𝑾(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦). c. Fijar Δ𝒃(𝑙)≔Δ𝒃(𝑙)+∇𝒃(𝑙)𝐽(𝑾,𝒃;𝒙,𝑦). 3. Actualizar los parámetros: 𝑾(𝑙)=𝑾(𝑙)−𝛼[(1 𝑚)Δ𝑾(𝑙)+𝜆𝑾(𝑙)] 𝒃(𝑙)=𝒃(𝑙)−𝛼[1 𝑚Δ𝒃(𝑙)] ( 3-17 ) Para entrenar la red se repiten los pasos del gradient descent para reducir la duncion coste 𝐽(𝑾,𝒃). 3.3 Ejemplo del funcionamiento de una red neuronal multicapa El siguiente ejemplo describe el proceso de enseñanza de la red neuronal multicapa empleando backpropagation. Para ilustrar este proceso se utiliza la red neuronal de tres capas con dos entradas y una salida, que se muestra en la siguiente imagen: Figura 3-7: Ejemplo de una red.
18 Cada neurona se compone de dos unidades. La primera unidad agrega productos de coeficientes de pesos y señales de entrada. La segunda unidad realiza la función no lineal, llamada función de activación neuronal. La señal e es la señal de salida del sumador, y y = f (e) es la señal de salida del elemento no lineal. La señal y es también señal de salida de la neurona. Figura 3-8: Funcionamiento de una neurona. Para enseñar a la red neuronal necesitamos un conjunto de datos de entrenamiento. El conjunto de datos de entrenamiento consta de señales de entrada (𝑥1 y 𝑥2) asignadas con el objetivo correspondiente (salida deseada) 𝑧�. El entrenamiento de la red es un proceso iterativo. En cada iteración los coeficientes de pesos de los con forzar ambas señales de entrada del conjunto de entrenamiento. Después de esta etapa se puede determinar valores de señales de salida para cada neurona en cada capa de la red. Las imágenes de abajo ilustran cómo se está propagando la señal a través de la red. Los símbolos 𝑤(𝑥𝑚)𝑛 representan pesos de conexiones entre la entrada de red 𝑥𝑚 y la neurona 𝑛 en la capa de entrada. Los símbolos 𝑦𝑛 representan la señal de salida de la neurona 𝑛.
19 Figura 3-9: Ejemplo de propagación en una red. En la propagación de señales a través de la capa oculta. Los símbolos 𝑤𝑚𝑛 representan pesos de conexiones entre la salida de la neurona 𝑚 y la entrada de la neurona 𝑛 en la siguiente capa. Figura 3-10: Ejemplo de propagación en una red en las capas ocultas.
20 En la propagación de señales a través de la capa de salida. Figura 3-11: Ejemplo de propagación a la salida. En el siguiente paso del algoritmo, la señal de salida de la red y se compara con el valor de salida deseado (el objetivo), que se encuentra en el conjunto de datos de entrenamiento. La diferencia se denomina señal de error 𝛿 de la neurona de la capa de salida. Figura 3-12: Cálculo del error a la salida de la red. Es imposible calcular la señal de error para las neuronas ocultas directamente, porque los valores de salida de estas neuronas son desconocidos. Durante muchos años el método efectivo para entrenar redes multicapa ha sido desconocido. Sólo a mediados de los ochenta se ha elaborado el algoritmo de retropropagación. La idea es propagar la señal de error 𝛿 (calculada en un solo paso de enseñanza) de vuelta a todas las neuronas. Figura 3-13: Ejemplo de retropropagación (I).
27 5 RED CONVOLUCIONAL as redes convolucionales aparecen con el objetivo de disminuir la carga computacional a la hora de resolver un problema de clasificación. Los anteriores clasificadores eran buenos para trabajar con matrices con pocas características (matriz “x” de tamaño reducido). El problema surge cuando estas matrices llegan a ser muy grandes, por ejemplo, un conjunto de ejemplos de entrenamiento de imágenes con dimensiones grandes. La idea de las redes convolucionales es extraer las características más significativas de una imagen con el objetivo de disminuir las matrices de características. Las redes convolucionales son un tipo de redes neuronales que han demostrado tener un buen rendimiento en áreas como el reconocimiento y clasificación de imágenes. Las CNNs han tenido éxito en la identificación de caras, objetos y señales de tráfico, aparte, han aumentado la visión en robots y han mejorado el desarrollo de vehículos autónomos. Unas de las primeras redes convolucionales fue la LeNet desarrollado por Yann LeCun. Esta red se usó principalmente para el reconocimiento de caracteres, como la lectura de códigos postolas, dígitos, etc. A continuación, se explica cómo la arquitectura LeNet aprende a reconocer las imágenes. La LeNet tiene la siguiente forma: Figura 5-1: Esquema de una red convolucional. Esta red convolucional clasifica una imagen de entrada en cuatro categorías: perro, gato, barco o pájaro. La red al recibir como imagen de entrada un barco, la red observa la imagen y la clasifica atendiendo a la probabilidad más alta de que la imagen sea una de las cuatro categorías. La suma de todas las probabilidades en la capa de salida deber ser 1. En la imagen 5-1 se pueden observar diferentes partes de la red: las convoluciones, el pooling y la clasificación (fully connected layer). Cada una de estas partes se explicará posteriormente para una comprensión de las redes convolucionales. L
28 Hay que tener en cuenta que cada imagen, puede ser representada como una matriz con de valores por cada pixel. Estos pixeles serán las entradas de la red. Figura 5-2: Representación de una imagen como una matriz. 5.1 Convolución El normbre de las redes convolucionales viene de la operación convolución. El principal propósito de una Convolución en las redes convolucionales es el de extraer las características de una imagen de entrada. Como se dijo anteriormente, cada imagen puede ser considerada como una matriz de números. Se considera una imagen 5x5 cuyos elementos de la matriz solo pueden ser 0 o 1. Tambén se considera otra matriz de 3x3 como la siguiente:
29 Entonces, la convolucion entre la imagen de 5x5 y la matriz 3x3 puede ser calculada como: Figura 5-3: Convolución. La idea de la convolución es desplazar la matriz naranja sobre la imagen original (verde) con un salto de un pixel y para cada posición se calcula la multiplicación entre las dos matrices y se suman las salidas de la multiplicación para obtener un entero (de solor rosa). Hay que tener en cuenta que la matriz 3x3 solo “ve” una parte de la imagen de entrada en cada paso. En terminología de las redes convoucionales, la matriz 3x3 se conoce como filtro o kernel y la matriz formada al realizar la convolución (rosa) se conoce como “convolved feature” o “feature map”. Es importante tener en cuenta que los fitros actúan como detectores de características de la imagen de entrada. En forma de pseudocódigo, la convolución sería: for each image row in input image: for each pixel in image row: set accumulator to zero for each kernel row in kernel:
30 for each element in kernel row: if element position corresponding* to pixel position then multiply element value corresponding* to pixel value add result to accumulator endif set output image pixel to accumulator Es evidente que se producirán diferentes valores en “feature maps” con diferentes filtros para una misma imagen de entrada. Como ejemplo se considera la siguiente imagen de entrada: En la siguiente tabal, se observa los efectos de la convolucion de la imagen anterior con diferentes filtros.Como se muestra, podemos realizar operaciones como detección de bordes, nitidez y desenfoque simplemente Tabla 5-1: Comparación al aplicar diferentes filtros a una imagen.
31 cambiando los valores numéricos de nuestra matriz de filtro antes de la operación de convolución, esto significa que diferentes filtros pueden detectar diferentes características de una imagen, por ejemplo bordes, curvas, etc. En la práctica, una CNN aprende los valores de estos filtros por sí sola durante el proceso de entrenamiento (aunque aún hay que especificar parámetros como el número de filtros, el tamaño del filtro, la aquitectura de la red, etc, antes del proceso de entreanamiento). Cuanto más número de filtros haya, más caraterístias de la imagen se extraen y mejor será la red para reconocer patrones en imágenes que no han sido utilizadas para el entrenamiento. El tamaño de feauture map se controla por parámetros que se necesitan decidir antes de la convolución La profundidad que corresponde al número de filtros que se han usado para la operación de la convolución. En la imagen siguiente, se muestra la convolución de una imagen de entrada usando tres filtros diferentes, por lo tanto, produce tres diferentes feature maps. Para este caso la profundidad o número de filtros es 3. Figura 5-4: Convolución con varios filtros. 5.2 Pooling Pooling (también llamado subsampling) reduce las dimensiones de cada feature map pero retiene la información más importante. El pooling puede ser de diferentes tipos: máximo, media, suma, etc. En el caso del max pooling, se define un vecindario (por ejemplo, una ventana de 2x2) y toma el elemento más grande de feature maps dentro de esta ventana. En lugar de tomar el elemento más grande también puede tomarse el promedio (poolinf media) o suma de todos los elementos de la venta. En la práctica, se ha demostrado que max pooling funciona mejor.
32 La siguiente imagen presenta un ejemplo de max pooling en un fearue map con una ventana de 2x2. Figura 5-5: Ejemplo de max pool aplicado a una matrix. Se deslica la ventana de 2x2 en 2 celdas y se toma el valor máximo en cada región. Esto reduce la dimensión del feature map. El la red que se muestra a continuación, el pooling se aplica de forma separada para cada feature map. Por lo tanto, debido a esto obtenemos tres mapas de salida de tres mapas de entrada. Figura 5-6: Ejemplo de pooling a varias feature maps.
33 La función de pooling es reducir progresivamente el tamaño espacial de la representación de entrada. En particular, pooling convierte las representaciones de entrada (dimensiones de las características) más pequeñas y manejables, reduce el número de parámetros y cálculos en la red, por lo tanto, controla el overfitting, hace la red invariante a pequeñas transformaciones, distorsiones y translaciones en la imagen de entrada (una pequeña distorsión en la imagen de entrada no cambiará la salida del pooling ya que toma el valor máximo/media en una región), ayuda a llegar a una representación casi invariante a escala de nuestra imagen. Esto es muy útil ya que se puede detectar objetos en una imagen sin importar dónde se encuentren. Figura 5-7: Esquema de la red convolucional y todas sus capas. Hasta ahora se ha visto como funciona la convolucion y el pooling. Es importante entender que estas capas son los componentes básicos de cualquier red convolucional. Como se muesta en la figura anterior, hay dos capas de convolucion y pooling. La segunda capa de convolución realiza la convolución en la salida de la primera capa de pooling usando 6 filtros que producen un total de seis feature maps. A continuación, se realiza la operación max pooling por separado en cada uno de los seis feature maps. Juntas, estas capas extraen las características útiles de las imágenes, introducen la no-linealidad en la red y reducen la dimensión de las matrices de características. La salida de la segunda capa de pooling actúa como entrada a la capa de fully connected que se explicará a continuación. 5.3 Stochastic Gradient Descent (SGD) Algunos métodos de optimición como el LBFGS, usan el conjunto de entrenamiento completo para actualizar los parámetros en cada iteración y tiende a converger de manera eficiente hacia un mínimo absoluto. Sin embargo, en la prácctica el cálculo del coste y del gradient para todo el conjunto de entrenamiento puede resultar muy lento e incluso imposible para una sola máquina si el conjunto de datos es demasiado grande para la memoria principal. Además otro problema con estos métodos de optimización es que no hay forma fácil de incorporar nuevos datos “en línea”. El método de Stochastic Gradient Descent resuelve estos dos problemas siguiendo la dirección negativo del gradiente de la función objetivo tras haber visto solo uno o unos pocos ejemplos de entrenamiento. El alto coste de retropropagar el conjunto complete de entrenamiento hace necesario el uso del SGD. El algoritmo estándar del gradient descent actualize los parámetros 𝜽�de forma: 𝜽=𝜽−𝛼∇θ𝐸[𝐽(𝜽)] ( 5-1 ) En la ecuación anterior, la esperanza se aproxima por el gradient de la función coste usando el conjunto complete de entrenamiento. El método del SGD simplement elimina dicha esperanza y calcula el gradient usando solo unos pocos ejemplos de entrenamiento. La nueva actualización viene ahora dada por: 𝜽=𝜽−𝛼∇θ𝐽(𝜽;𝑥(𝑖),𝑦(𝑖)) ( 5-2 ) El par (𝑥(𝑖),𝑦(𝑖)) es un par del conjunto de entrenamiento.
34 Generalmente, cada actualización en el SGD se calcula con vista a unos pocos ejemplos del set de entrenamiento (llamados minibatch) en lugar de solo a un ejemplo. La razón tras esto es doble: en primer lugar, se reduce la varianza en la actualización de los parámetros y esto permite una convergencia más estable, y, en segundo lugar, permite el uso de álgebra lineal para el eficiente cálculo del coste y del gradiente. El tamaño típico de un minibatch es de 256, aunque el tamaño óptimo puede variar según la aplicación y la arquitectura. En el SGD el ratio de aprendizaje 𝛼� normalmente es mucho más pequeño que el usado en el método del gradient descent pues la varianza en la actualización es menor. Escoger el valor adecuado para el ratio de aprendizaje y sus actualizaciones a lo largo del entrenamiento puede ser realmente complicado. Un método estándar que funciona bien en la práctica es usar un ratio pequeño y constante que proporcione una convergencia estable en la epoch (uso completo del set de entrenamiento mediante minibatches) inicial y reducir a la mitad su valor a medida que la convergencia se ralentiza. Otro método bastante utilizado es actualizar el ratio de aprendizaje en cada iteración 𝑡� como 𝑎�/(𝑏� + 𝑡�), donde 𝑎� es el ratio inicial y 𝑏� la iteración en la que comienta dicha actualización. Los métodos más sofisticados incluyen el uso de backtracking (vuelta atrás) a lo largo de una línea para encontrar la actualización óptima. Por último, pero no menos importante, en el SGD importa el orden en el que los datos son presentados al algoritmo. Si los datos son presentados en un orden cualquiera, ésto puede introducir un offset en el gradiente y conducir a una convergencia más pobre. Un buen método para evitar esto es, por lo general, “barajar” de forma aleatoria los datos antes de cada epoch de entrenamiento. Si la función coste tiene la forma análoga a un largo desfiladero poco profundo que conduce al mínimo y con paredes empinadas a los lados, el SGD tenderá a oscilar a lo ancho del estrecho desfiladero, pues la dirección negativa al gradiente apuntará a alguna de las paredes en lugar de al mínimo. Esta forma, que puede parecer muy poco probable, es bastante común en arquitecturas más complejas y, por tanto, la convergencia puede llegar a ser muy lenta. El método Momentum es más óptimo en este caso pues apunta al mínimo más rápido a lo largo del desfiladero. La actualización de los parámetros con este método es: 𝒗=𝛾𝒗−𝛼∇θ𝐽(𝜽;𝑥(𝑖),𝑦(𝑖)) 𝜽=𝜽−𝒗 ( 5-3 ) En la ecuación anterior 𝒗� es el vector velocidad actual, cuyas dimensiones coinciden con las de 𝜽�. El ratio de aprendizaje 𝛼� se toma como se describió previamente, aunque, al usar este método, 𝛼� tal vez deba ser más pequeño dado que la magnitud del gradiente será mayor. Por último, 𝛾� ∈ (0,1] determina por cuantas iteraciones se incorporarán los gradientes anteriores. Generalmente, 𝛾� se establece a 0.5 hasta que el aprendizaje se estabiliza, momento en el que se aumenta a 0.9. 5.4 Capa Fully Connected La capa fully connected es una red multicapa como la explicada anteriromente en el punto 3, que usa una función softmax en la salida de la capa. El término “fully connected” implica que cada neurona de una capa se conecta con todas las neuronas de la capa siguiente. La salida de las capas convolucionales y de pooling representan características de alto nivel de la imagen de entrada. El propósito de la capa fully connected es utilizar estas características para clasificar la imagen de entrada en varias clases basadas en el dataset de entrenamiento. Por ejemplo, la tarea de clasificación de imágenes que se ha establecido como ejemplo a realizar tiene cuatro salidas posibles como se muestra en la
35 siguiente imagen. Figura 5-8: Ejemplo de una capa fully connected. Aparte de la clasificación, la adición de una capa fully connected es también una manera barata de aprender combinaciones no lineales de estas características. La mayoría de las características de las capas convolucionales y de las capas de poooling pueden ser buenas para la tarea de clasificación, pero las combinaciones de esas características podrían ser aún mejor. La suma de las probabilidades de la capa fully connected es 1. Esto se asegura usando un softmax como función de activación en la capa de salida de la capa fully connected. La función softmax toma un vector con valores reales arbitrarios y lo transforma en un vector cuya suma de componentes es 1. 5.5 Entrenamiento: Backpropagation. Como se explicaba anteriormente, las capas de convolución y de pooling actúan como extractores de características de la imagen de entrada mientras que la capa fully connected actúa como un clasificador. Observesé en la siguiente imagen, dado que la imagen de entrada es un barco, la probabilidad objetico es 1 para la clase barco y 0 para el resto de las demás clases. Figura 5-9: Esquema de las partes de una red convolucional. Todo el proceso de entrenamiento de la red concolucional puede resumirse en los cinco pasos. El primer paso, se inicializan los filtros y parámetros pesos con valores aleatorios. El segundo paso, la red toma una imagen de entrenamiento como entrada, pasa por el segundo paso, propagación hacia adelante, (convolución y operaciones de pooling junto con la propagación hacia adeante en la capa fully connected) y encuentra las probabilidades de salida para cada clase. En el tercer paso, se calcula el error total en la salida de la red (suma
36 de las 4 clases) 𝐸𝑟𝑟𝑜𝑟𝑡𝑜𝑡𝑎𝑙=∑12(𝑝𝑟𝑜𝑏𝑎𝑏𝑖𝑙𝑖𝑑𝑎𝑑𝑜𝑏𝑗𝑒𝑡𝑖𝑣𝑜−𝑝𝑟𝑜𝑏𝑎𝑏𝑖𝑙𝑖𝑑𝑎𝑑𝑠𝑎𝑙𝑖𝑑𝑎)2 ( 5-4 ) El cuarto paso usa backpropagation para calcular el gradiente del error con respecto a los pesos en la red y usa el gradient descent para actualizar todos los valores de los filtros y pesos y parámetros para minimizar el error de la salida. El quinto paso repite los pasos del 2 al 4 con todas las imágenes en el conjunto de entrenamiento. Los pasos anteriores entrenan a la CNN, esto significa que todos los pesos y parámetros de la CNN han sido optimizados para clasificar correctamente las imágenes del conjunto de entrenamiento. Cuando una nueva imagen (nunca antes vista) entra a la red convolucional, la red pasaría por el paso de propagación hacia adelante y emitirá una probabilidad para cada clase (para una nueva imagen, las probabilidades de salida se calculan usando los pesos que han sido optimizados para clasificar correctamente todos los ejemplis de entrenamiento anteriores). Si el conjunto de entrenamiento es lo suficientemente grande para que la red puede clasificar de forma correcta las imágenes de entrada.
43 opencv\build\include. Figura 7-6: Instalación de los programas y librerías (VI). 7. Dentro de la pestaña VinculadorGeneral, añadimos a Directorios de biblioteca adicionales, la ruta opencv\build\x64\vc14\lib. Figura 7-7: Instalación de los programas y librerías (VII). 8. Repetir los pasos desde el punto 4, pero en vez de usar la pestaña Debug | x64, se usa la pestaña Release | x64.
44
45 8 PARTES DEL CÓGIGO El código se compone de varias funciones: - Función main: Esta función sirve para leer las imágenes y labels del conjunto de entrenamiento, permite entrenar a la red y ver la precisión con la que la CNN es capaz de reconocer imágenes de números. - Función read_Mnist: Lee las imágenes del MNIST y las guarda en un vector de matrices. - Función read_Mnist_Label: Lee las etiquetas del MNIST y las guarda en una matriz. - Función sigmoid: Se trata la función sigmoidea. - Función rot90: Rota a la matriz 90*k grados, donde k es un entero que entra en la función. - Función conv2: Realiza la convolución de dos matrices. - Función cnnInitParams: Inizializa los pesos y bias de la red. - Función cnnConvolve: Capa de convolución de la red. - Función cnnPool: Capa de pooling de la red. - Función cnnParamsToStack: Transforma los parámtros de la red en forma vector a pesos y bias. - Función cnnCost: Calcula la probabilidad de pertenecer a cada clase, el coste y el gradiente. - Función minFunSGD: Entrena la red convolucional utilizando el algoritmo SGD con Momentum.
46
47 9 RESULTADOS Y EXPERIMENTOS Al ejecutar el código y tras 702 iteraciones de reducción del coste y calculo del valor de los pesos y bias de la red, la red está entrenada. Para comprobar su funcionamiento se hace uso de un conjunto diferente (con diferentes imágenes que las del entrenamiento). Este nuevo conjunto de imágenes entra en la red, las entradas se multiplican con los parámetros calculados y se obtienen las salidas (los números que la red predice). Con este código se ha logrado un porcentaje de acierto de un 97.08% con un conjunto de 10000 imágenes diferentes. El tiempo necesario para entrenar la red con 60000 imágenes fue de una hora y treinta minutos y el tiempo de testeo fue de tres horas con el set de 10000 imágenes. Figura 9-1: Resultado tras la ejecución del programa.
48
49 10 CONCLUSIONES El mundo de las redes neuronales está avanzando cada vez más y se abre camino entre la industria y ramas de la ciencia. Además, se llega a la conclusión de que no es necesario conocer algoritmos muy complejos de los sistemas para poder obtener una salida con una alta fiabilidad (mayor del 97%), esto hace que las redes neuronales sean una herramienta muy útil y muy rápida (una vez sea entrenada la red). Uno de los problemas de las redes neuronales es encontrar el número de filtros, neuronas ocultas,… óptimos para un buen resultado a la salida. Este problema sólo se puede resolver con la práctica y la experiencia. Si bien es posible hacer un estudio del número de neuronas, éste puede o no ser el mejor. Otro problema es el tiempo de entrenamiento, esta red ha tratado imágenes de 28x28 pixeles, el tiempo de entrenamiento podría ser demasiado lento con imágenes muy grandes y a veces podría ser intratable. Por eso es conveniente disponer de un ordenador con buenas prestaciones y en su defecto simplificar las entradas de la mejor manera posible para reducir el tiempo de entrenamiento y que no se vea afectados los valores de los parámetros de la red. Este proyecto ha sido usado para reconocimiento de números pero también podría haber sido útil para reconocer señales de tráfico (muy útil para los vehículos autónomos), tratamiento de imágenes con el objetivo de reconocer una cierta figura en una zona de una imagen, usar este proyecto en un área industrial, por ejemplo, para clasificar diferentes cajas que contengan diferente materia prima. El área industrial podría ser una buena idea ya que aunque normalmente no se encuentran ordenadores muy modernos con los que entrenar la red pero una vez entrenada la red se pueden guardar los parámetros de la red en un fichero para que un ordenador pueda leerlo y de esta manera el problema de reconocimiento sería muy rápido. Por último, con este proyecto de pretende que el lector pueda tener una introducción a las redes neuronales, y sobretodo a las redes convolucionales y a su método de retropropagación que ha sido desde varios años un misterio en las redes neuronales. Además, se incita al lector a investigar en esta rama de la tecnología y probar a usar el código con otro tipo de imgánes.
50
51 REFERENCIAS [1] Jeff Heaton. (2012). Introduction to the Math of Neural Networks. [2] Beale, M., Hagan, M., Demuth, H. (2016). Neural Networks Toolbox. [3] Dr. Timothy Masters. (2016). Deep Belief Nets in C++ and CUDA C. [4] Christopher M. Bishop. (1996). Neural Networks for Pattern Recognition. [5] Hagan, M., Demuth, H., Beale, M. and De Jesus, O. (2014). Neural network design. 2nd ed. [6] Stackoverflow.com. (2017). Stack Overflow. [online] Available at: https://stackoverflow.com. [7] Ujjwalkarn. (2016). An Intuitive Explanation of Convolutional Neural Networks. [online] Available at: https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/. [8] Ujjwalkarn. (2016). A Quick Intro of Neuronal networks. [online] Available at: https://ujjwalkarn.me/2016/08/09/quick-intro-neural-networks/ [9] Jacob S. (2015). MNIST Hand Written Digits Classification Benchmark. [online] Available at: http://knowm.org/mnist-hand-written-digits-classification-benchmark/ [10] Michael Nielsen. (2017). Deep learning. [online] Available at: http://neuralnetworksanddeeplearning.com/chap6.html [11] Adit Deshpande. (2016). Going Deeper Through the Network. [online] Available at: http://www.kdnuggets.com/2016/09/beginners-guide-understanding-convolutional-neural-networks [12] Mike O'Neill. (2006). Neural Network for Recognition of Handwritten Digits.[online] Available at: https://www.codeproject.com/Articles/16650/Neural-Network-for-Recognition-of-Handwritten-Digi [13] Chris McCormick. (2015). Understanding the DeepLearnToolbox CNN Example. (2015). [online] Available at: http://mccormickml.com/2015/01/10/understanding-the-deeplearntoolbox-cnn-example/ [14] Jefkine. (2015). Backpropagation In Convolutional Neural Networks. [online] Available at: http://www.jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/. [15] Mazur M. (2015). A Step by Step Backpropagation Example. [online] Available at: https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/. [16] N4n0. (2017). Tutorial de Redes Neuronaes con VREP C++ y Linux. [online] Available at: https://robologs.net/2017/01/22/tutorial-de-redes-neuronales-con-vrep-c-y-linux/ [17] Yann LeCun, Corinna Cortes, Christopher J.C. Burges. (2017). The MNIST database. [online] Available at: http://yann.lecun.com/exdb/mnist/
52 [18] OpenCV.com. (2017). OpenCV.[online] Available at: http://opencv.org/ [19] Deeplearning.stanford.edu. (2017). Unsupervised Feature Learning and Deep Learning Tutorial. [online] Available at: http://deeplearning.stanford.edu/tutorial/. [20] Lawebdelprogramador.com. (2017). La Web del Programador. [online] Available at: http://www.lawebdelprogramador.com.
59 { algo = (input[i]); for (int k = 0; k<algo.cols; k++) vconcat(dest, algo.col(k), dest); } } int filas = (size(input)*input[0].size[0] * input[0].size[0]) / numImages; res = Mat(filas, numImages, CV_64F); for (int i = 0; i < numImages; i++) { (dest.colRange(0, 1).rowRange(i*filas, (i + 1)*filas)).copyTo(res.col(i)); } return res; } Mat reshape4D2D(vector<vector<Mat>> &input, int numImages) { Mat algo; Mat dest; Mat res; Mat aux; aux = input[0][0].t(); dest = aux.reshape(0, 1).t(); for (int i = 0; i<size(input); i++) { for (int j = 0; j<size(input[0]); j++) { if (!(i == 0 && j == 0)) { aux = (input[i][j]).t(); aux = aux.reshape(0, 1); aux = aux.t(); vconcat(dest, aux, dest); } } } int filas = (size(input)*size(input[0])*input[0][0].size[0] * input[0][0].size[0]) / numImages; res = Mat(filas, numImages, CV_64F); for (int i = 0; i < numImages; i++) { (dest.colRange(0, 1).rowRange(i*filas, (i + 1)*filas)).copyTo(res.col(i)); } return res; } vector<vector<Mat>> reshape2D4D(Mat &A, int dim1, int dim2, int dim3, int dim4)
60 { Mat m = A.t(); m = m.reshape(0, A.size[0]*A.size[1]); Mat z; Mat spm; Mat dest = Mat(dim3, dim4, CV_64F); vector<vector<Mat>> B; Mat M; int r = 0; for (int k = 0; k<dim1; k++) { B.push_back(vector<Mat>()); for (int l = 0; l<dim2; l++) { z = m(Range(dim3*dim4 * r, dim3*dim4 * r + dim3*dim4), Range(0, 1)); dest = z.reshape(1, dim3); dest = dest.t(); B[k].push_back(dest); r = r++; } } return B; } Mat prediction(Mat &A) { Mat maximo = Mat::zeros(1, A.size[1], CV_64F); Mat m = Mat::zeros(1, A.size[1], CV_64F); for (int i = 0; i < A.size[1]; i++) { maximo.at<double>(0, i) = A.at<double>(0, i); for (int j = 0; j < A.size[0]; j++) { if (maximo.at<double>(0, i) < A.at<double>(j, i)) { maximo.at<double>(0, i) = A.at<double>(j, i); m.at<double>(0, i) = j; } } } return m; } Mat bsxfun_div(Mat &A) { Mat res = Mat::zeros(A.size[0], A.size[1], CV_64F); for (int i = 0; i < A.size[1]; i++) { res.col(i) = A.col(i) / sum(A.col(i))[0]; }
61 return res; } // cnnInitParams Mat cnnInitParams(int imageDim, int filterDim, int numFilters, int poolDim, int numClasses) { assert(filterDim < imageDim); Mat mean = Mat::zeros(1, 1, CV_64F); Mat sigma = Mat::ones(1, 1, CV_64F); Mat Wc = Mat::zeros(filterDim, filterDim*numFilters, CV_64F); randn(Wc, 0, 1); Wc = 0.1*Wc; int outDim = imageDim - filterDim + 1; assert(outDim%poolDim == 0); outDim = outDim / poolDim; int hiddenSize = outDim*outDim*numFilters; double r = sqrt(6) / sqrt(numClasses + hiddenSize + 1); Mat Wd; Wd = Mat::zeros(numClasses, hiddenSize, CV_64F); randn(Wd, 0, 1); Wd = Wd * 2 * r - r; Mat bc; bc = Mat::zeros(numFilters, 1, CV_64F); Mat bd; bd = Mat::zeros(numClasses, 1, CV_64F); Wc = Wc.reshape(0, 1); Wd = Wd.reshape(0, 1); bc = bc.reshape(0, 1); bd = bd.reshape(0, 1); Mat res; hconcat(Wc, Wd, res); hconcat(res, bc, res); hconcat(res, bd, res); return res; } //cnnConvolved vector<vector<Mat>> cnnConvolve(int filterDim, int numFilters, vector<Mat> &images, vector<Mat> &W, Mat &b) { int numImages = size(images); int imageDim = images[0].size[0]; int convDim = imageDim - filterDim + 1; vector<vector<Mat>> convolvedFeatures;
62 for (int i = 0; i<numImages; i++) { convolvedFeatures.push_back(vector<Mat>()); for (int j = 0; j<numFilters; j++) convolvedFeatures[i].push_back(Mat(convDim, convDim, CV_64F, Scalar::all(0))); } Mat filter; Mat convolvedImage; Mat im; for (int imageNum = 0; imageNum < numImages; imageNum++) { im = images[imageNum]; for (int filterNum = 0; filterNum < numFilters; filterNum++) { convolvedImage = Mat::zeros(convDim, convDim, CV_64F); filter = W[filterNum]; filter = rot90(filter, 2); convolvedImage = convolvedImage + conv2(im, filter, CONV_VALID); convolvedImage = convolvedImage + b.at<double>(filterNum, 0); convolvedImage = sigmoid(convolvedImage); convolvedImage.copyTo(convolvedFeatures[imageNum][filterNum]); } } return convolvedFeatures; } //cnnPool vector<vector<Mat>> cnnPool(int poolDim, vector<vector<Mat>> &convolvedFeatures) { int numImages = size(convolvedFeatures); int numFilters = size(convolvedFeatures[0]); int convolvedDim = convolvedFeatures[0][0].size[0]; vector<vector<Mat>> pooledFeatures; for (int i = 0; i<numImages; i++) { pooledFeatures.push_back(vector<Mat>()); for (int j = 0; j<numFilters; j++) pooledFeatures[i].push_back(Mat(convolvedDim / poolDim, convolvedDim / poolDim, CV_64F, Scalar::all(0))); } int poolDimSquared = poolDim * poolDim; Mat poolFilter = Mat::ones(poolDim, poolDim, CV_64F); Mat feature; Mat convolvedFeature = Mat::zeros(19, 19, CV_64F);; Mat A = Mat::zeros(10,10, CV_64F); vector<int> poolIndex = create(poolDim, convolvedDim); Mat extra; for(int imageNum = 0; imageNum < numImages; imageNum++) { for(int filterNum = 0; filterNum < numFilters; filterNum++) { feature = convolvedFeatures[imageNum][filterNum]; convolvedFeature = conv2(feature, poolFilter, CONV_VALID); extra = extractMat(convolvedFeature, poolIndex) / poolDimSquared; extra.copyTo(pooledFeatures[imageNum][filterNum]); } }
63 return pooledFeatures; } vector<Mat> cnnParamsToStack(Mat &theta,int imageDim,int filterDim,int numFilters,int poolDim,int numClasses) { int outDim = (imageDim - filterDim + 1) / poolDim; int hiddenSize = outDim*outDim*numFilters; int indS = 0; int indE = filterDim * filterDim * numFilters; vector<Mat> Wc; Wc = reshape2D3D(theta, numFilters, filterDim, filterDim); indS = indE + 1; indE = indE + hiddenSize*numClasses; Mat Wd = Mat::zeros(numClasses, hiddenSize, CV_64F); Wd = theta(Range(0, 1), Range(indS-1,indS + numClasses*hiddenSize-1)); Wd = Wd.t(); Mat Wd_aux = Mat::zeros(10, hiddenSize, CV_64F); for (int i = 0; i < hiddenSize; i++) { (Wd.colRange(0, 1)).rowRange(i * 10, (i + 1) * 10).copyTo(Wd_aux.col(i)); } indS = indE + 1; indE = indE + numFilters; Mat bc = Mat::zeros(numFilters, 1, CV_64F); bc = theta(Range(0, 1), Range(indS-1, indS + numFilters-1)); bc = bc.t(); indS = indS + 19; Mat bd = Mat::zeros(10, 1, CV_64F); bd = theta(Range(0, 1), Range(indS, indS + 10)); bd = bd.t(); vector<Mat> res; Mat Wc_conc; Wc_conc = reshape3D2D(Wc, 1); res.push_back(Wc_conc); res.push_back(Wd_aux); res.push_back(bc); res.push_back(bd); return res; } vector<Mat> cnnCost(Mat &theta,vector<Mat> &images,Mat &labels,int numClasses,int filterDim,int numFilters,int poolDim,bool pred) {
64 int imageDim = images[0].size[0]; int numImages = size(images); vector<Mat> params; params = cnnParamsToStack(theta, imageDim, filterDim, numFilters, poolDim, numClasses); Mat Wc = params[0]; Mat Wd = params[1]; Mat bc = params[2]; Mat bd = params[3]; vector<Mat> Wc3D; Wc3D = reshape2D3D(Wc, numFilters, filterDim, filterDim); vector<Mat> Wc_grad; for (int i = 0; i < size(Wc3D); i++) { Wc_grad.push_back(Mat::zeros(Wc3D[0].size(), CV_64F)); } Mat Wd_grad = Mat::zeros(Wd.size[1], Wd.size[0], CV_64F); Mat bc_grad = Mat::zeros(bc.size[1], bc.size[0], CV_64F); Mat bd_grad = Mat::zeros(bd.size[1], bd.size[0], CV_64F); int convDim = imageDim - filterDim + 1; int outputDim = (convDim) / poolDim; vector<vector<Mat>> activations; for (int i = 0; i<numImages; i++) { activations.push_back(vector<Mat>()); for (int j = 0; j<numFilters; j++) activations[i].push_back(Mat(convDim, convDim, CV_64F, Scalar::all(0))); } vector<vector<Mat>> activationsPooled; for (int i = 0; i<numImages; i++) { activationsPooled.push_back(vector<Mat>()); for (int j = 0; j<numFilters; j++) activationsPooled[i].push_back(Mat(outputDim, outputDim, CV_64F, Scalar::all(0))); } activations = cnnConvolve(filterDim, numFilters, images, Wc3D, bc); activationsPooled = cnnPool(poolDim, activations); Mat activationsPooled_r = reshape4D2D(activationsPooled, numImages); Mat probs = Mat::zeros(numClasses, numImages, CV_64F); int M = size(images); Mat aux1 = Wd*activationsPooled_r +repeat(bd, 1, M); Mat maxR; reduce(aux1, maxR, 0, REDUCE_MAX); maxR = repeat(maxR, aux1.size[0], 1); Mat aux2 = aux1 - maxR; Mat aux3; exp(aux2, aux3); probs = bsxfun_div(aux3); aux2.release(); aux3.release(); Mat groundTruth = Mat::zeros(10, numImages, CV_64F); for (int i = 0; i < labels.size[1]; i++) { groundTruth.at<double>(labels.at<double>(0, i), i) = 1; } Mat aux4 = groundTruth.mul(probs); aux4 = aux4.reshape(0, 1); Mat aux5;
65 for (int i = 0; i < aux4.size[1]; i++) { if (aux4.ATD(0, i) != 0) { aux5.push_back(log(aux4.ATD(0, i))); } } Mat cost = Mat::zeros(1,1,CV_64F); cost = -mean(aux5)[0]; aux4.release(); aux5.release(); Mat preds; if (pred == true) { preds = prediction(probs); } Mat deriv_1 = Mat(groundTruth.size[1], groundTruth.size[0], CV_64F); double numero = 1.0 / M; deriv_1 = -numero*(groundTruth - probs); groundTruth.release(); Wd_grad = deriv_1*activationsPooled_r.t(); activationsPooled.clear(); activationsPooled_r.release(); bd_grad = deriv_1*(Mat::ones(M, 1, CV_64F)); Mat deriv_2_pooled_sh = Wd.t()*deriv_1; deriv_1.release(); vector<vector<Mat>> deriv_2_pooled; deriv_2_pooled = reshape2D4D(deriv_2_pooled_sh, numImages, numFilters, outputDim, outputDim); vector<vector<Mat>> deriv_2_upsampled; for (int i = 0; i<numImages; i++) { deriv_2_upsampled.push_back(vector<Mat>()); for (int j = 0; j<numFilters; j++) deriv_2_upsampled[i].push_back(Mat(convDim, convDim, CV_64F, Scalar::all(0))); } Mat A = Mat::ones(poolDim, poolDim, CV_64F); Mat f_now; Mat noww = Mat::ones(9,9,CV_64F); Mat im; Mat A1; for(int imageNum=0;imageNum<numImages;imageNum++) { im = images[imageNum]; for(int filterNum=0;filterNum<numFilters;filterNum++) { aux3 = (1.0 / (poolDim * poolDim))*kron(deriv_2_pooled[imageNum][filterNum], A); A1 = (aux3.mul(activations[imageNum][filterNum])).mul(1.0 - activations[imageNum][filterNum]); A1.copyTo(deriv_2_upsampled[imageNum][filterNum]); deriv_2_upsampled[imageNum][filterNum].copyTo(f_now); noww = conv2(im, rot90(f_now, 2), CONV_VALID); Wc_grad[filterNum] = Wc_grad[filterNum] + noww; bc_grad.at<double>(0, filterNum) = bc_grad.at<double>(0, filterNum) + sum(f_now)[0]; } }
66 activations.clear(); deriv_2_pooled.clear(); deriv_2_upsampled.clear(); images.clear(); Mat Wc_conc; Wc_conc = reshape3D2D(Wc_grad, 1); Wc_conc = Wc_conc.reshape(0, 1); // Wd_grad = Wd_grad.t(); // Wd_grad = Wd_grad.reshape(0, 1); bc_grad = bc_grad.reshape(0, 1); bd_grad = bd_grad.reshape(0, 1); Mat grad; hconcat(Wc_conc, Wd_grad, grad); hconcat(grad, bc_grad, grad); hconcat(grad, bd_grad, grad); vector<Mat> res; res.push_back(cost); res.push_back(grad); res.push_back(preds); return res; } Mat minFuncSGD(Mat &theta,vector<Mat> &data,Mat &labels, mystruct &options,int numClasses,int filterDim,int numFilters,int poolDim) { int epochs = options.epochs; double alpha = options.alpha; alpha = 0.2; int minibatch = options.minibatch; int m = 60000; float mom = 0.5; mom = 0.6; float momIncrease = 20; Mat velocity = Mat::zeros(theta.size[0], theta.size[1], CV_64F); vector<int> rp; for (int i = 0; i < m; ++i) rp.push_back(i); vector<Mat> mb_data; Mat mb_labels; double cost = 0; Mat grad; vector<Mat> params; int it = 0; Mat theta_1 = theta; for (int e = 0; e < epochs; e++) { std::random_shuffle(rp.begin(), rp.end()); for (int s = 0; s < m - minibatch; s++) { it = it + 1;
67 if (it == momIncrease) { mom = options.momentum; } for (int i = 0; i < minibatch; i++) { mb_data.push_back(data[i + s]); } mb_labels = labels(Range(0, 1), Range(s, s + minibatch)); params = cnnCost(theta_1, mb_data, mb_labels, numClasses, filterDim, numFilters, poolDim, 0); cost = params[0].at<double>(0, 0); grad = params[1]; velocity = (mom*velocity) + (alpha*grad); theta_1 = theta_1 - velocity; printf("Epoch %d: Cost on iteration %d is %f\n", e, it, cost); s = s + 255; } alpha = alpha / 2.0; } return theta_1; } int main() { int imageDim = 28; int numClasses = 10; int filterDim = 9; int numFilters = 20; int poolDim = 2; static vector<Mat> trainX; static vector<Mat> testX; Mat trainY, testY; readData(trainX, trainY, "mnist/train-images-idx3-ubyte", "mnist/train-labels- idx1-ubyte", 60000); readData(testX, testY, "mnist/t10k-images-idx3-ubyte", "mnist/t10k-labels- idx1-ubyte", 10000); //cout << trainY.cols <<endl; cout << "Read trainX successfully, including " << trainX[0].cols * trainX[0].rows << " features and " << trainX.size() << " samples." << endl; cout << "Read trainY successfully, including " << trainY.cols << " samples" << endl; cout << "Read testX successfully, including " << testX[0].cols * testX[0].rows << " features and " << testX.size() << " samples." << endl; cout << "Read testY successfully, including " << testY.cols << " samples" << endl; cout << testY; Mat theta; theta = cnnInitParams(imageDim, filterDim, numFilters, poolDim, numClasses);
68 mystruct options; options.epochs = 3; options.minibatch = 256; options.alpha = 0.1; options.momentum = 0.95; Mat opttheta; opttheta = minFuncSGD(theta, trainX, trainY, options, numClasses, filterDim, numFilters, poolDim); vector<Mat> final; final = cnnCost(opttheta, testX, testY, numClasses, filterDim, numFilters, poolDim, true); float suma = 0; Mat preds = final[2]; cout << preds <<endl; for (int i = 0; i < preds.size[1]; i++) { if (preds.at<double>(0, i) == testY.at<double>(0, i)) { suma++; } } float acc = (suma / preds.size[1])*100.0; printf("Accuracy is %f\n",acc); cout << preds.type() << endl; cout << testY.type() << endl; system("\n\npause"); return 0; }