Português
preview
Red neuronal en la práctica: Lectura de PNG del MNIST

Red neuronal en la práctica: Lectura de PNG del MNIST

MetaTrader 5 — Aprendizaje automático |
33 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Red neuronal en la práctica: Retropropagación manual, expliqué un poco cómo podía realizarse la retropropagación dentro de un conjunto de perceptrones conectados en secuencia. Aunque no sea la mejor manera de explicar ese tema, como veremos más adelante, lo visto allí nos permite abordar otra cuestión de forma más inmediata.

Muy bien, como no disponemos de ciertas cosas para utilizarlas de inmediato, tendremos que implementarlas para poder dar algunos pasos nuevos en lo que pretendemos explicar sobre las redes de perceptrones.

Nuestro problema actual consiste en cargar imágenes de una base de datos pública. En este caso, la base MNIST. Normalmente, las imágenes de esta base tienen un formato específico, pero, en otros casos, podemos necesitar trabajar con formatos diferentes, como PNG. Y este será el objetivo de este artículo: crear un mecanismo lo más básico posible para cargar este tipo de formato de imagen.


De vuelta a la mesa de trabajo

En este momento nos encontramos en una encrucijada. Esto se debe a que, dependiendo de la decisión que tomemos, terminaremos implementando un tipo u otro de sistema de perceptrones.

Sé que puede parecer extraño decir esto, ya que muchos creen que las redes de perceptrones tienen un propósito general. Sin embargo, esto no es así. Muchas veces, cuando vemos algún sistema de perceptrones creado mediante mecanismos genéricos, en realidad estamos utilizando algún framework destinado a implementar este tipo de sistemas.

Este no es el objetivo que pretendemos alcanzar con estos artículos. La meta es explicar cómo pueden construirse estos sistemas en un lenguaje como MQL5, sin utilizar ningún tipo de herramienta externa. De este modo, aunque la red que implementaremos será bastante limitada, te mostrará cómo funciona realmente todo entre bastidores. Es solo una cuestión de escala. Si aumentamos la escala y disponemos de suficientes datos para entrenar algún modelo, quién sabe, quizá podamos crear un mini GPT o algo parecido. Pero ese no es nuestro propósito.

Entonces, ¿cuál es nuestro problema actual? Bien, amigo mío, el problema son los datos. No importa qué tipo de red de perceptrones quieras implementar: el problema siempre serán los datos. Los datos son el alma de cualquier sistema de perceptrones. Sin ellos, estos sistemas no pueden construirse de ninguna manera. Aunque existe una forma de hacer que el sistema de perceptrones genere sus propios datos. Pero no abordaremos este tipo de cuestiones en esta serie. Así evitaremos confundir sus pobres cabecitas.

Sabiendo que el problema son los datos, datos que se utilizarán durante el entrenamiento y ajuste de la propia red de perceptrones, necesitamos, al igual que necesitaron los investigadores en el pasado, algún tipo de base de datos. En este caso, se hizo un gran esfuerzo para recopilar y clasificar manualmente —sí, todo fue realizado por personas— esos datos con el máximo cuidado y esmero. Esto se debe a que esos datos son realmente muy valiosos.

Existen diversas bases de datos públicas disponibles para su uso. Sin embargo, creemos que la más nostálgica e interesante para mucha gente es la de reconocimiento de caracteres. Esto se debe a que es algo con lo que podemos interactuar inmediatamente y cuyo contenido, en cierto modo, nos resulta bastante familiar. La base de datos a la que nos referimos es MNIST, basada en dígitos indoarábigos.

Bien, una vez decidido que utilizaremos una de las bases de datos MNIST, veamos el motivo de esta decisión. Hay toda una historia detrás de esta base de datos. Pero corresponde a cada uno investigar para comprender mejor por qué existe y cómo empezó todo. Si buscas en la WEB, encontrarás muchas referencias al respecto. Sobre todo, encontrarás a mucha gente explicando cómo desarrollar una red de perceptrones sencilla utilizando Python para reproducir los resultados que se obtienen al utilizar frameworks.

Básicamente, muchos entusiastas comienzan a estudiar estos frameworks precisamente por esta base de datos MNIST y por los ejemplos de código en Python que se han publicado. Al fin y al cabo, es allí donde se explora gran parte de los recursos disponibles.

Pero no nos interesa Python, y no me malinterpretes por decir esto: nuestro objetivo es MQL5 y comprender cómo funcionan realmente las cosas. Así que dejaremos de lado la posibilidad de hacerlo en Python y nos centraremos en MQL5. Cuando todo esté debidamente implementado, podrás entender cómo consigue realmente la red de perceptrones hacer lo que hace, sin ocultar el funcionamiento interno tras frameworks ya preparados y llenos de detalles poco transparentes, por así decirlo.

Muy bien, el camino será largo y laborioso. Pero hay que dar el primer paso. Y, básicamente, lo que tendremos que hacer se aleja un poco del propósito principal de MetaTrader 5. La plataforma fue concebida para utilizarse como sistema de trading. Sin embargo, aquí tendremos que ir más allá y utilizar la plataforma de una forma completamente distinta. Y muchas de las cosas que necesitamos no están disponibles en la biblioteca de MQL5. Entre ellas, la lectura de imágenes en formato PNG.

En esencia, buena parte de MNIST se encuentra en este formato, aunque en algunos casos también puede estar en formato binario. Sin embargo, vamos a considerar que utilizaremos datos en formato PNG, con una enorme cantidad de imágenes de 28 por 28 píxeles. Normalmente serán imágenes en escala de grises. Así que, si descargas esta base de datos, podrás ver algo muy parecido a la siguiente imagen:

Imagen 01

La base de datos que utilizaremos contendrá, básicamente, este tipo de información. Cada una de las imágenes es un archivo diferente y, por tanto, puede utilizarse para crear todo un conjunto de entrenamiento o experimentar con una red de perceptrones específica.

Si buscas con calma, encontrarás muchas referencias al uso de TensorFlow en este tipo de red que vamos a construir. Pero TensorFlow es solo uno de los muchos métodos disponibles para implementar el sistema de aprendizaje. Está claro que, si utilizáramos algo así, desde el principio tendríamos a nuestra disposición todo lo necesario, sin tener que estudiar cómo implementar toda la matemática involucrada. Aunque esto puede resultar muy práctico, en algunos casos no es conveniente utilizar una solución de este tipo. Esto se debe a que podríamos acabar necesitando mucha más potencia de cálculo de la que realmente sería necesaria si implementáramos una solución específica para un problema concreto. Ten en cuenta lo siguiente: no estoy diciendo que este o aquel framework sea bueno o malo, ni que debamos abandonarlos todos y empezar de nuevo. No se trata de eso. Lo que quiero es mostrar cómo funcionan estas cosas internamente.

Para hacer esto, es necesario adentrarse en un mundo cuyo funcionamiento real muchos de ustedes ni siquiera imaginan. Por eso los sistemas de perceptrones son un tema tan complejo y, al mismo tiempo, tan interesante. Siempre que creemos que ya sabemos algo, aparece algo nuevo que aprender.

Bien, volvamos a nuestro problema original. En este momento, la clase C_Neuron todavía no está en condiciones ideales para utilizarse en una implementación como la que necesitamos crear. Aún no disponemos de medios para configurar una topología de red neuronal que facilite considerablemente el desarrollo del sistema. Tampoco podemos leer archivos PNG, porque la biblioteca de MQL5 no proporciona esta funcionalidad de forma predeterminada. Además, todavía necesitamos comprender otras cuestiones que no se han explicado, sencillamente porque hasta ahora no había sido necesario abordar esos puntos. En definitiva, parece que tenemos un largo camino por delante. Pero, como dice el refrán: todo comienza dando el primer paso. Y ese paso acabamos de darlo al decidir avanzar hacia un sistema de reconocimiento de dígitos manuscritos, que forman parte de la base de datos MNIST.

Bien, lo primero que tendremos que hacer será conseguir leer una imagen PNG utilizando MQL5. Así que, para separar correctamente los temas, vamos a abrir un nuevo apartado, ya que conviene entender bien cómo hacerlo.


Comprender la imagen PNG

Muchos pensarán que las cosas en informática son una especie de magia. O, como mínimo, que quienes las crean son alguna clase de entidad divina o semidiós, ya que al principio todo parece tener muy poco sentido. Sin embargo, en esencia, en informática todo, absolutamente todo, se reduce a una sola cosa: BITS. Ten siempre esto presente y podrás hacer cualquier cosa en informática. El secreto está en cómo se organizan los bits, y esa organización nos indica qué hacen realmente. Pero no solo eso: cada modelo de organización, por sí solo, no nos dice nada acerca de lo que contiene un conjunto de bits. A ese conjunto lo llamamos archivo.

Para que este conjunto de bits, o archivo, tenga algún sentido, es necesario que esté organizado de una forma muy específica. No existe un archivo completamente aleatorio. Si se construyera un archivo así, su contenido no sería más que basura. Pero incluso un archivo que parece contener basura puede, en realidad, albergar información de gran valor. Todo depende de saber cómo se organizaron los datos para construir finalmente el archivo. Sabiendo esto, puedes hacer cualquier cosa.

Entonces, al observar un archivo de imagen, concretamente un archivo PNG, podrás ver algo parecido a la siguiente imagen.

Imagen 02

Ahora te pregunto: ¿qué sentido tienen para ti los datos que aparecen en la imagen superior? Posiblemente ninguno. Pero, si utilizas un programa o una aplicación capaz de interpretar esos datos, podrás obtener algo como lo que se muestra en la siguiente imagen.

Imagen 03

Bien, ahora esos mismos datos empiezan a tener algún sentido, ¿no? Incluso podría no tratarse de la información real, ya que existen métodos de cifrado y compresión cuyo objetivo es precisamente crear algo que tenga sentido para algunos, pero que en realidad sirva para transmitir otro mensaje que permanecerá oculto, salvo que utilices la aplicación adecuada para descubrirlo. En fin, quizá en el futuro podamos hablar de esto con más detalle y explicar mejor cómo funciona y cómo puede implementarse. También es un tema muy interesante y, por así decirlo, hace que este trabajo con redes de perceptrones parezca un juego de niños.

Pero dejemos esto por ahora y volvamos a nuestro tema principal. Si observas y estudias archivos binarios, tarde o temprano notarás algo que siempre se repite dependiendo del contenido del archivo. Normalmente se encuentra justo al principio y se conoce como FILE SIGNATURE o, literalmente, firma del archivo. En la imagen del archivo binario podemos verlo. Observa la región marcada que se muestra a continuación.

Imagen 04

Esta parte es la FILE SIGNATURE, que indica el formato o tipo de archivo. Ahora presta atención a lo siguiente: aquí no necesitamos implementar todas las variantes posibles del formato PNG; al menos, no pretendo hacerlo por ahora. Solo necesitamos que el código lea el tipo de imagen. Y los datos que aparecen en las imágenes anteriores representan exactamente el tipo de información que tendremos que leer del archivo PNG. Así que no intentes utilizar el código como si pudiera leer cualquier imagen PNG, porque en la práctica no podrá hacerlo. Dicho esto, podemos continuar.

Lo primero que haremos será crear un código base. Su objetivo será comprobar si estamos trabajando con un archivo PNG o intentando leerlo. Esto es bastante sencillo de implementar. Sin embargo, desde el principio estructuraremos el código para no tener que rehacerlo después. Así, la primera parte del código se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_PNG
05.   {
06. private:
07. 
08. public:
09.                      C_PNG(const string szFileName)
10.      {
11. #define macroERROR(msg) { Print(msg); FileClose(f_handle); return; }
12. 
13.       const uchar    File_Signature[] =
14.         {
15.          0x89, 0x50, 0x4E, 0x47,
16.          0x0D, 0x0A, 0x1A, 0x0A
17.         };
18. 
19.       int            f_handle;
20.       uint           size;
21.       uchar          buff[];
22. 
23.       if((f_handle = FileOpen(szFileName, FILE_BIN)) < 0)
24.          macroERROR(StringFormat("Error accessing file: %s", szFileName));
25. 
26.       size = FileReadArray(f_handle, buff, 0, File_Signature.Size());
27.       if(size != File_Signature.Size())
28.          macroERROR(StringFormat("Error reading the file: %s", szFileName));
29. 
30.       if(ArrayCompare(buff, File_Signature, 0, 0, size) != 0)
31.          macroERROR("File is not a PNG image.");
32. 
33.       Print("ok..");
34. 
35.       FileClose(f_handle);
36. 
37. #undef macroERROR
38.      }
39.   };
40. //+------------------------------------------------------------------

Código 01

Este código es nuestra clase inicial y, para probarla, necesitaremos otro código. Como apenas estamos empezando, podemos utilizar un script, ya que permite mantener más simple el código inicial. Se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
04. #include <Neural Network\C_PNG.mqh>
05. //+------------------------------------------------------------------+
06. //| Script program start function                                    |
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09.   {
10.    C_PNG            png("mnist/10032.png");
11.   }
12. //+------------------------------------------------------------------

Código 02

Como estos dos códigos son bastante sencillos de entender, no entraremos en detalles sobre su funcionamiento. Sin embargo, al ejecutar este código 02, siempre que exista el archivo indicado en la línea ocho, podrás ver el siguiente resultado en el terminal de MetaTrader 5.

Imagen 05

Esta es la misma información que muestra la línea treinta de la clase C_PNG. Si ha aparecido, significa que todo está bien y que podemos continuar con la parte en la que comenzaremos a leer la imagen propiamente dicha. Pero, para que resulte más didáctico, abriremos un nuevo tema. Así será más fácil entender lo que haremos a continuación.


Bloques de BYTE

Una vez completada la etapa anterior, ya podemos saber si estamos trabajando con una imagen o con otro tipo de archivo. Si estamos trabajando con una imagen, podemos pasar a la parte en la que obtendremos su estructura. Normalmente, este tipo de organización varía de un archivo a otro. Pero, en términos generales, todos tienen una estructura en la que los datos se organizan de forma determinada. Al observar el archivo en sí, todo puede parecer un gran desorden. Pero, créeme, existe una organización interna. De lo contrario, cualquier dato contenido allí se perdería para siempre.

En nuestro caso concreto, trabajaremos con una imagen PNG. Está organizada en bloques de datos muy bien definidos. Si vuelves a observar la imagen del contenido interno, verás que aparece cierta información en una posición específica del archivo. Puede verse en la siguiente imagen.

Imagen 06

Este es un punto que nos interesa, ya que contiene metadatos que nos proporcionan diversa información sobre la imagen almacenada en el archivo. Básicamente, entender qué representan estos valores y cómo están organizados nos ayudará a reconstruir la imagen. Aquí es donde conocer bien el lenguaje que estamos utilizando nos permitirá acceder a los datos con mayor precisión y facilidad. Existe un problema que afecta a todo software: el ancho en bits de los tipos de datos.

Los archivos están organizados en bits, aunque muchas veces los leemos como si fueran palabras, números o cualquier otra cosa. En el fondo, no son más que bits. Pero estos bits pueden organizarse de distintas formas, si observamos la cuestión desde el punto de vista del ancho en bits de los tipos de datos. Y, dependiendo del lenguaje utilizado, tendremos que adaptar el código para trabajar con el ancho correcto. De lo contrario, todo el contenido leído será inútil, porque algunos bits ocuparán posiciones que no les corresponden y los datos quedarán alterados. Afortunadamente, MQL5 dispone de tipos de datos con los anchos adecuados. Aunque no tiene la misma flexibilidad que C/C++, las estructuras que nos proporciona MQL5 serán suficientes. Así que tendremos que crear una estructura interna para leer el bloque que nos proporciona algunos datos sobre la imagen. Esto es bastante sencillo de hacer, tanto al crearla como al leerla, como puedes ver en el código siguiente.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_PNG
05.   {
06. private:
07.    struct st_IHDR
08.      {
09.       uint           lenght;
10.       uchar          type[4];
11.       uint           width;
12.       uint           height;
13.       uchar          depth;
14.       uchar          colour;
15.       uchar          compression;
16.       uchar          filter;
17.       uchar          intelace;
18.      };
19. 
20. public:
21.                      C_PNG(const string szFileName)
22.      {
23. #define macroERROR(msg) { Print(msg); FileClose(f_handle); return; }
24. #define PrintX(a)       PrintFormat("%s: %d => 0x%x", #a, a, a);
25. 
26.       const uchar    File_Signature[] =
27.         {
28.          0x89, 0x50, 0x4E, 0x47,
29.          0x0D, 0x0A, 0x1A, 0x0A
30.         };
31. 
32.       int            f_handle;
33.       uint           size;
34.       uchar          buff[];
35.       st_IHDR        ihdr;
36. 
37.       if((f_handle = FileOpen(szFileName, FILE_BIN)) < 0)
38.          macroERROR(StringFormat("Error accessing file: %s", szFileName));
39. 
40.       size = FileReadArray(f_handle, buff, 0, File_Signature.Size());
41.       if(size != File_Signature.Size())
42.          macroERROR(StringFormat("Error reading the file: %s", szFileName));
43. 
44.       if(ArrayCompare(buff, File_Signature, 0, 0, size) != 0)
45.          macroERROR("File is not a PNG image.");
46. 
47.       FileReadStruct(f_handle, ihdr);
48. 
49.       PrintX(ihdr.lenght);
50.       PrintX(ihdr.width);
51.       PrintX(ihdr.height);
52. 
53.       FileClose(f_handle);
54. 
55. #undef PrintX
56. #undef macroERROR
57.      }
58.   };
59. //+------------------------------------------------------------------

Código 03

La estructura propiamente dicha se crea en la línea ocho. Con esto, al volver a ejecutar el código, podremos ver la siguiente información.

Imagen 07

Qué maravilla. Qué espectáculo. Mira qué bonita porquería. Acabamos de descubrir que hay un bug en el lenguaje de programación. Esto se debe a que los datos que deberían proceder del archivo son exactamente los que se mostraban en la imagen anterior. Es decir, los valores están invertidos. En este momento, muchos se rinden y empiezan a maldecir desde DIOS hasta el DIABLO, lanzando toda clase de insultos contra el lenguaje de programación utilizado, porque al usar otro lenguaje los valores aparecen correctamente, tal como se esperaba. Pero, antes de que empieces a pensar que el lenguaje realmente tiene un bug, veamos qué está ocurriendo en realidad.

Seguramente estás acostumbrado a ver valores enteros almacenados en orden LITTLE ENDIAN. Sin embargo, también existe el orden BIG ENDIAN, en el que los valores se almacenan de una forma algo diferente. ¿Y por qué ocurre esto? Pues por la CPU. Esto no tiene nada que ver con el lenguaje de programación. Algunas CPU utilizan el orden LITTLE ENDIAN, mientras que otras utilizan el orden BIG ENDIAN. Sé que parece una locura. Durante mucho tiempo tuve problemas para entender este detalle sobre los valores que realmente interpreta la CPU, ya que, dependiendo de la implementación, la forma de representar los valores puede cambiar radicalmente. Así que lo que acabas de ver no es un defecto del lenguaje de programación, sino una cuestión relacionada con la implementación de la CPU.

Hay varias formas de resolverlo. Pero, básicamente, lo que tenemos que hacer es reorganizar los bits para obtener una representación que realmente podamos comprender. Los valores esperados eran 28 de alto y 28 de ancho, y no esos valores disparatados. Como quiero fomentar al máximo el uso de MQL5 puro, utilizaremos lo que el propio lenguaje nos ofrece. Por muy bien que programemos, no tendremos acceso a la CPU de la misma forma que los desarrolladores del lenguaje. Por eso, es preferible utilizar funciones de la biblioteca estándar en lugar de crear una función con el mismo propósito. Una vez corregido el código, tendremos lo que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_PNG
05.   {
06. private:
07.    union uint32
08.      {
09.       uint           value;
10.       uchar          c[sizeof(uint)];
11.      };
12. 
13.    struct st_IHDR
14.      {
15.       uint32         lenght;
16.       uchar          type[4];
17.       uint32         width;
18.       uint32         height;
19.       uchar          depth;
20.       uchar          colour;
21.       uchar          compression;
22.       uchar          filter;
23.       uchar          intelace;
24.      };
25. 
26. public:
27.                      C_PNG(const string szFileName)
28.      {
29. #define macroERROR(msg) { Print(msg); FileClose(f_handle); return; }
30. #define PrintX(a)       PrintFormat("%s: %d => 0x%X", #a, a, a);
31. 
32.       const uchar    File_Signature[] =
33.         {
34.          0x89, 0x50, 0x4E, 0x47,
35.          0x0D, 0x0A, 0x1A, 0x0A
36.         };
37.       const uchar    Header[] = { 'I', 'H', 'D', 'R' };
38. 
39.       int            f_handle;
40.       uint           size;
41.       uchar          buff[];
42.       st_IHDR        ihdr;
43. 
44.       if((f_handle = FileOpen(szFileName, FILE_BIN)) < 0)
45.          macroERROR(StringFormat("Error accessing file: %s", szFileName));
46. 
47.       size = FileReadArray(f_handle, buff, 0, File_Signature.Size());
48.       if(size != File_Signature.Size())
49.          macroERROR(StringFormat("Error reading the file: %s", szFileName));
50. 
51.       if(ArrayCompare(buff, File_Signature, 0, 0, size) != 0)
52.          macroERROR("File is not a PNG image.");
53. 
54.       FileReadStruct(f_handle, ihdr);
55.       if(ArrayCompare(ihdr.type, Header) != 0)
56.          macroERROR("Error reading the header.");
57. 
58.       ArrayReverse(ihdr.lenght.c);
59.       ArrayReverse(ihdr.width.c);
60.       ArrayReverse(ihdr.height.c);
61. 
62.       PrintX(ihdr.lenght.value);
63.       PrintX(ihdr.width.value);
64.       PrintX(ihdr.height.value);
65. 
66.       FileClose(f_handle);
67. 
68. #undef PrintX
69. #undef macroERROR
70.      }
71.   };
72. //+------------------------------------------------------------------

Código 04

Es cierto que esto parece más complicado de lo que realmente es. Pero, créeme, resulta más sencillo hacerlo así. De esta manera, cuando volvamos a ejecutar el código, obtendremos el resultado que vemos a continuación.

Imagen 08

Muy bien, ya tenemos la primera parte de lo que necesitamos. Ahora solo tenemos que leer la imagen para poder mostrarla o utilizarla de alguna manera. Pero, para hacerlo correctamente, necesitamos recurrir a la documentación del formato. Esto nos permitirá reconstruir realmente la imagen de la forma adecuada. Al final de este artículo dejaré, en el apartado de referencias, información sobre dónde puedes consultar más detalles, por si deseas utilizar imágenes diferentes de la que mostraré aquí.

De cualquier forma, la parte básica de la lectura ya está terminada. Así que, para facilitar la comprensión, pasemos a un nuevo tema.


Cargando la imagen PNG

Todo lo que hemos hecho hasta este momento forma parte de un bloque de construcción que se repite constantemente, independientemente de lo que estemos haciendo. Ya sea cargar una imagen, intentar descomprimir un archivo o cualquier otra cosa. Básicamente, todo se hace de esta misma manera. Como aquí nos interesa cargar una imagen, todo lo realizado hasta este punto es válido para cualquier imagen PNG que vayamos a cargar. Sin embargo, lo que haremos ahora estará orientado específicamente a lo que necesitamos. Así que, si deseas hacerlo de otra manera, tendrás que estudiar la documentación del formato. El bloque de datos de la imagen se muestra a continuación.

Imagen 09

Los datos de la imagen se encuentran en la región marcada que aparece a continuación.

Imagen 10

Pero presta atención: los datos que estamos viendo están comprimidos. Muy bien, ya sabemos todo lo que necesitamos saber. Sin embargo, podemos simplificar un poco el código. Aunque el mostrado hasta ahora funciona, tuvimos que hacer algunas cosas que, utilizando MQL5, resultan algo innecesarias. Por eso, todo el código anterior puede mejorarse hasta obtener el que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_PNG
05.   {
06. private:
07.    union uint32
08.      {
09.       uint           value;
10.       uchar          c[sizeof(uint)];
11.      };
12. 
13.    union un_IHDR
14.      {
15.       struct stIHDR
16.         {
17.          uint32      width;
18.          uint32      height;
19.          uchar       depth;
20.          uchar       colour;
21.          uchar       compression;
22.          uchar       filter;
23.          uchar       intelace;
24.         } infos;
25.       uchar          c[sizeof(stIHDR)];
26.      };
27. 
28.    bool              Read_Chunk(int &file,const uchar &type[],uchar &buff[],uint &crc)
29.      {
30.       uint32         len;
31.       uchar          loc[];
32. 
33.       FileReadArray(file, len.c, 0, 4);
34.       ArrayReverse(len.c);
35.       FileReadArray(file, loc, 0, 4);
36. 
37.       if(len.value > 0)
38.          FileReadArray(file, buff, 0, len.value);
39. 
40.       FileReadArray(file, len.c, 0, 4);
41.       ArrayReverse(len.c);
42.       crc = len.value;
43. 
44.       return(ArrayCompare(type, loc) == 0);
45.      }
46. 
47. public:
48.                      C_PNG(const string szFileName)
49.      {
50. #define macroERROR(msg) { Print(msg); FileClose(f_handle); return; }
51. 
52.       const uchar    File_Signature[] =
53.         {
54.          0x89, 0x50, 0x4E, 0x47,
55.          0x0D, 0x0A, 0x1A, 0x0A
56.         };
57.       const uchar    Chunk_IHDR[] = { 'I', 'H', 'D', 'R' };
58.       const uchar    Chunk_IDAT[] = { 'I', 'D', 'A', 'T' };
59.       const uchar    Chunk_IEND[] = { 'I', 'E', 'N', 'D' };
60. 
61.       int            f_handle;
62.       uint           tmp;
63.       uchar          buff[];
64.       uchar          img[];
65.       un_IHDR        ihdr;
66. 
67.       if((f_handle = FileOpen(szFileName, FILE_BIN)) < 0)
68.          macroERROR(StringFormat("Error accessing file: %s", szFileName));
69. 
70.       tmp = FileReadArray(f_handle, buff, 0, File_Signature.Size());
71.       if(tmp != File_Signature.Size())
72.          macroERROR(StringFormat("Error reading the file: %s", szFileName));
73. 
74.       if(ArrayCompare(buff, File_Signature, 0, 0, tmp) != 0)
75.          macroERROR("File is not a PNG image.");
76. 
77.       if(!Read_Chunk(f_handle, Chunk_IHDR, ihdr.c, tmp))
78.          macroERROR("Error in image data.");
79. 
80.       ArrayReverse(ihdr.infos.width.c);
81.       ArrayReverse(ihdr.infos.height.c);
82. 
83.       if(!Read_Chunk(f_handle, Chunk_IDAT, img, tmp))
84.          macroERROR("Error in image data.");
85.       if(!Read_Chunk(f_handle, Chunk_IEND, buff, tmp))
86.          macroERROR("Error in image data.");
87. 
88.       FileClose(f_handle);
89. 
90.       PrintFormat("width: %d", ihdr.infos.width.value);
91.       PrintFormat("height: %d", ihdr.infos.height.value);
92.       ArrayPrint(img);
93. 
94. #undef macroERROR
95.      }
96.   };
97. //+------------------------------------------------------------------+

Código 05

Ahora, al ejecutar este código, obtenemos el siguiente resultado:

Imagen 11

Es decir, ya tenemos de una vez todos los datos que necesitamos. Ahora solo falta descomprimir la imagen almacenada en el array IMG que se mostró anteriormente. Observa que el código ha quedado mucho más sencillo de leer y, como únicamente nos falta descomprimir la imagen, podemos pasar al código final, que se muestra íntegramente a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. class C_PNG
005.   {
006. private:
007.    union uint32
008.      {
009.       uint           value;
010.       uchar          c[sizeof(uint)];
011.      };
012. 
013.    union un_IHDR
014.      {
015.       struct stIHDR
016.         {
017.          uint32      width;
018.          uint32      height;
019.          uchar       depth;
020.          uchar       colour;
021.          uchar       compression;
022.          uchar       filter;
023.          uchar       intelace;
024.         } infos;
025.       uchar          c[sizeof(stIHDR)];
026.      } m_IHDR;
027. 
028.    uchar             m_Pixels[];
029. 
030.    bool              Read_Chunk(int &file,const uchar &type[],uchar &buff[],uint &crc)
031.      {
032.       uint32         len;
033.       uchar          loc[];
034. 
035.       FileReadArray(file, len.c, 0, 4);
036.       ArrayReverse(len.c);
037.       FileReadArray(file, loc, 0, 4);
038. 
039.       if(len.value > 0)
040.          FileReadArray(file, buff, 0, len.value);
041. 
042.       FileReadArray(file, len.c, 0, 4);
043.       ArrayReverse(len.c);
044.       crc = len.value;
045. 
046.       return(ArrayCompare(type, loc) == 0);
047.      }
048. 
049.    bool              Decode(const uchar &img[],uint width,uint height)
050.      {
051.       uchar          buff[];
052.       uchar          key[];
053. 
054.       if(!CryptDecode(CRYPT_ARCH_ZIP, img, key, buff))
055.          return(false);
056. 
057.       ArrayResize(m_Pixels, width * height);
058.       for(uint c = 0, c1 = 0, t = 1, m = buff.Size(); c < m; c++, c1++, t++)
059.         {
060.          m_Pixels[c1] = buff[c];
061.          if(t == width)
062.            {
063.             c++;
064.             t = 0;
065.            }
066.         }
067. 
068.       return(true);
069.      }
070. 
071. public:
072.                      C_PNG(const string szFileName)
073.      {
074. #define macroERROR(msg) { Print(msg); FileClose(f_handle); return; }
075. 
076.       const uchar    File_Signature[] =
077.         {
078.          0x89, 0x50, 0x4E, 0x47,
079.          0x0D, 0x0A, 0x1A, 0x0A
080.         };
081.       const uchar    Chunk_IHDR[] = { 'I', 'H', 'D', 'R' };
082.       const uchar    Chunk_IDAT[] = { 'I', 'D', 'A', 'T' };
083.       const uchar    Chunk_IEND[] = { 'I', 'E', 'N', 'D' };
084. 
085.       int            f_handle;
086.       uint           tmp;
087.       uchar          buff[];
088.       uchar          img[];
089. 
090.       ArrayResize(m_Pixels, 0);
091. 
092.       if((f_handle = FileOpen(szFileName, FILE_BIN)) < 0)
093.          macroERROR(StringFormat("Error accessing file: %s", szFileName));
094. 
095.       tmp = FileReadArray(f_handle, buff, 0, File_Signature.Size());
096.       if(tmp != File_Signature.Size())
097.          macroERROR(StringFormat("Error reading the file: %s", szFileName));
098. 
099.       if(ArrayCompare(buff, File_Signature, 0, 0, tmp) != 0)
100.          macroERROR("File is not a PNG image.");
101. 
102.       if(!Read_Chunk(f_handle, Chunk_IHDR, m_IHDR.c, tmp))
103.          macroERROR("Error in image data.");
104. 
105.       ArrayReverse(m_IHDR.infos.width.c);
106.       ArrayReverse(m_IHDR.infos.height.c);
107. 
108.       if(!Read_Chunk(f_handle, Chunk_IDAT, img, tmp))
109.          macroERROR("Error in image data.");
110.       if(!Read_Chunk(f_handle, Chunk_IEND, buff, tmp))
111.          macroERROR("Error in image data.");
112. 
113.       FileClose(f_handle);
114. 
115.       if(!Decode(img, m_IHDR.infos.width.value, m_IHDR.infos.height.value))
116.          macroERROR("Error in image data.");
117. 
118. #undef macroERROR
119.      }
120. 
121.    uint              GetImage(uchar &arr[])
122.      {
123.       if(!m_Pixels.Size())
124.          return(0);
125. 
126.       ArrayCopy(arr, m_Pixels);
127. 
128.       return(m_IHDR.infos.width.value);
129.      }
130.   };
131. //+------------------------------------------------------------------+

Código 06

Observa que, en la línea veintisiete, tenemos un array. Este recibirá la imagen ya descomprimida. El proceso de descompresión es relativamente sencillo, porque MQL5 dispone de una llamada que nos permite hacerlo sin dificultad. Esta llamada aparece en la línea cincuenta y uno. El array Key puede dejarse vacío, ya que aquí no se utiliza ningún tipo de clave de seguridad. Si el proceso se ejecuta correctamente, el array buff contendrá los datos descomprimidos.

Sin embargo, esos datos todavía contienen información que no necesitamos para la imagen final. Necesitamos filtrar el contenido antes de terminar. Así, en la línea cincuenta y tres reservamos el espacio necesario para la imagen. Y, en la línea cincuenta y cuatro, entramos en un bucle cuyo objetivo es eliminar algunos valores que no forman parte de la imagen final. Cuando termine este bucle, tendremos en el array m_Pixels la imagen que queremos y que utilizaremos.

Para finalizar esta clase, en la línea ciento nueve tenemos una función cuyo objetivo es devolver la imagen, así como su anchura. De esta forma, podemos realizar la lectura con mucha más tranquilidad y sin sobresaltos.

Bien, ahora vamos a ver el código que utiliza esta clase. Puede verse íntegramente a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include <Neural Network\C_PNG.mqh>
05. #define protected public
06. #include <Canvas\Canvas.mqh>
07. #undef protected
08. //+------------------------------------------------------------------+
09. void SaveInText(string szFileName,const uchar &buff[],const uint width)
10.   {
11.    int               file;
12.    string            s = "";
13. 
14.    if((file = FileOpen(szFileName + ".txt", FILE_WRITE | FILE_TXT)) == INVALID_HANDLE)
15.       return;
16. 
17.    for(uint c = 0, t = 1; c < buff.Size(); c++, t++)
18.      {
19.       s += StringFormat("%3d ", buff[c]);
20.       if(t == width)
21.         {
22.          s += "\n\r";
23.          FileWriteString(file, s);
24.          s = "";
25.          t = 0;
26.         }
27.      }
28. 
29.    s += "\n\r";
30.    FileWriteString(file, s);
31. 
32.    FileClose(file);
33.   }
34. //+------------------------------------------------------------------+
35. void View_IMAGE(uint x,uint y,uchar s,const uchar &buff[],const uint width)
36.   {
37.    CCanvas          canvas;
38.    const uint       real = width * s;
39. 
40.    if(!canvas.CreateBitmapLabel(0, 0, "tmp", 100, 100, real, real,
41.                                 COLOR_FORMAT_ARGB_NORMALIZE))
42.      {
43.       Print("Error creating canvas: ", GetLastError());
44.       return;
45.      }
46. 
47.    color            bmp[];
48.    double           fx = (real * 1.0) / width;
49.    uint             pyi, pyf, pxi, pxf, tmp, uc;
50. 
51.    ArrayResize(bmp, real * real);
52.    for(uint cy = 0, y = 0; cy < width; cy++, y += width)
53.      {
54.       pyf = (uint)(fx * cy) * real;
55.       tmp = pyi = (uint)(fx * (cy - 1)) * real;
56. 
57.       for(uint x = 0; x < width; x++)
58.         {
59.          pxf = (uint)(fx * x);
60.          pxi = (uint)(fx * (x - 1));
61.          uc = ColorToARGB(buff[y + x], 255);
62.          bmp[pxf + pyf] = (color)uc;
63. 
64.          for(pxi++; pxi < pxf; pxi++)
65.             bmp[pxi + pyf] = (color)uc;
66.         }
67. 
68.       for(pyi += real; pyi < pyf; pyi += real)
69.          for(uint x = 0; x < real; x++)
70.             bmp[x + pyi] = bmp[x + tmp];
71.      }
72. 
73.    ArrayCopy(canvas.m_pixels, bmp);
74.    ArrayFree(bmp);
75. 
76.    canvas.Update();
77.   }
78. //+------------------------------------------------------------------+
79. //| Script program start function                                    |
80. //+------------------------------------------------------------------+
81. void OnStart(void)
82.   {
83.    uchar             pixels[];
84.    uint              width;
85. 
86.    C_PNG            png("mnist/10032.png");
87.    if(!(width = png.GetImage(pixels)))
88.       return;
89. 
90.    View_IMAGE(10, 10, 10, pixels, width);
91.    SaveInText("mnist", pixels, width);
92. 
93.    Print("ok...");
94.   }
95. //+------------------------------------------------------------------+

Código 07

Como este código es bastante sencillo, no veo ningún punto que requiera especial atención. Pero, al ejecutarlo, verás en el gráfico de MetaTrader 5 el resultado que muestra la siguiente imagen.

Imagen 12

Un detalle: esta imagen utiliza colores falsos, ya que lo correcto sería realizar el trazado en escala de grises. Pero, en cualquier caso, lo que nos interesa es que la imagen del archivo PNG se ha cargado correctamente y ya puede utilizarse. Además, como también estamos creando un archivo de texto cuyo contenido será el valor de cada uno de los píxeles, tendremos algo como lo que se muestra en la siguiente imagen.

Imagen 13

Y son precisamente estos valores los que nos interesan al final. Serán los que utilizaremos posteriormente para alimentar la entrada de nuestra red de perceptrones. Pero lo que hemos hecho hasta ahora es suficiente para abordar las siguientes cuestiones, que veremos ya en el próximo artículo.


Consideraciones finales

En este artículo comenzamos a implementar los elementos que necesitamos para demostrar el funcionamiento de lo que quizá sea la red de perceptrones más didáctica de todas.

Esta red se utiliza en numerosos experimentos y sirve para explicar muchas cosas. Este modelo de red de perceptrones utiliza la base de datos MNIST, que puede descargarse para que tú también puedas construir una red con fines didácticos. Como se trata de cuatro archivos comprimidos y descargarlos depende del interés de cada uno, dejaré la descarga en tus manos, al menos por ahora. En cualquier caso, incluiré siempre algunos PNG en los archivos adjuntos para que también dispongas de material que puedas utilizar.

Una observación importante: como el código visto en este artículo está destinado exclusivamente a trabajar con archivos PNG que forman parte de MNIST, no podrá renderizar correctamente otros tipos de archivos PNG, ya que ese no es su propósito. Y, como extra, también indicaremos dónde puedes descargar un código fuente para renderizar otros tipos de PNG, desarrollado por otro programador de MQL5.

Si vamos a dar crédito, démoselo a quien lo merece.
Archivo MQ5 Descripción
Scripts\Example 01 Demostración básica

Referencias

PNG (Portable Network Graphics)

PNG - library for MetaTrader 5

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/14017

Archivos adjuntos |
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Del nivel básico al intermedio: Archivo de plantilla (III) Del nivel básico al intermedio: Archivo de plantilla (III)
En el pasado publiqué un artículo que generó mucha confusión y fue poco comprendido por muchos de quienes lo leyeron. Pues bien, en este artículo vamos a retomar precisamente aquel concepto que en su momento no parecía tener ningún sentido y a explicarlo mucho mejor. Es decir, veremos cómo colocar más de un indicador en una sola subventana, de forma que puedan leerse y comprenderse fácilmente.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 12): Construcción de clasificadores lineales mediante factorización matricial Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 12): Construcción de clasificadores lineales mediante factorización matricial
Este artículo explora el importante papel de la factorización matricial en el trading algorítmico, específicamente en aplicaciones MQL5. Desde modelos de regresión hasta clasificadores de múltiples objetivos, analizamos ejemplos prácticos que demuestran la facilidad con la que estas técnicas pueden integrarse utilizando las funciones integradas de MQL5. Ya sea que esté prediciendo la dirección de los precios o modelando el comportamiento de los indicadores, esta guía sienta una base sólida para construir sistemas de trading inteligentes utilizando métodos matriciales.