Rede neural na prática: Lendo PNG do MNIST
Introdução
No artigo anterior Rede neural na prática: Retro propagação manual, foi explicado um pouco como poderia ser feita a retro propagação dentro daquilo que seria um conjunto de perceptrons ligados em sequência. Apesar de não ser a melhor maneira de explicar aquele tópico, como será visto futuramente. O que foi visto ali, nos permite entrar em outra questão de maneira mais imediata.
Muito bem, então como não temos certas coisas disponíveis para uso imediato, precisaremos implementar as mesmas para podermos dar alguns passos novos naquilo que pretendemos explicar sobre redes perceptron.
De volta a prancheta
Neste momento nos encontramos em uma encruzilhada. Isto por que dependendo do tipo de decisão que tomemos, obteremos um tipo ou outro de sistema perceptron sendo de fato implementado.
Sabemos que pode parecer estranho dizer isto, já que muitos acham que redes perceptron, tem um proposito geral. Contudo, isto não é a realidade da coisa. Muitas das vezes, quando vemos algum tipo de sistema perceptron sendo criado, e usando mecanismos genéricos, estamos na realidade usando algum tipo de framework, voltado para implementar tais sistemas.
Porém, este tipo de objetivo não é o que pretendemos alcançar com estes artigos. A meta é, explicar como tais sistemas podem ser confeccionados em uma linguagem como MQL5, sem fazer uso de nenhum tipo de ferramenta externa. Com isto, apesar da rede que implementaremos ser bastante limitada. Ela irá mostrar a você como a coisa toda de fato funciona por debaixo dos panos. Sendo apenas uma questão de escala. Se a escala for aumentada e tivermos dados suficientes para treinar algum modelo, podemos quem sabe, criar um mini GPT, ou algo do tipo. Mas estes não é nosso proposito.
Então qual o nosso atual problema neste momento? Bem, meu amigo, o problema são dados. Não importa o tipo de rede perceptron que você queira implementar, o problema sempre será os dados. Dados são a alma de qualquer tipo de sistema perceptron. Sem eles tais sistemas não podem ser construídos de maneira alguma. Se bem, que existe uma forma de fazer o sistema perceptron gerar seus próprios dados. Mas não abordaremos este tipo de coisa nesta sequência. Isto para não gerar confusão em suas pobres cabecinhas.
Sabendo que o problema são dados, dados estes que serão utilizados durante o treinamento e ajuste da própria rede perceptron. Precisamos, assim como no passado pesquisadores precisaram, de algum tipo de banco de dados. No caso, houve um esforço muito grande para que a coleta e classificação manual, sim foi tudo feito por humanos, de tais dados fossem feitas com todo o cuidado e zelo. Isto por que, tais informações são de fato algo bastante precioso.
Existem diversos bancos de dados públicos disponíveis para serem utilizados. Contudo, acreditamos que o mais nostálgico e interessante para muita gente é o de reconhecimento de caracteres. Isto por que, é realmente algo que podemos interagir logo de imediato, tendo um conteúdo que de certa forma nos é bastante familiar. O banco de dados ao qual estamos nos referindo é o MNIST baseado em caracteres numéricos hindu-arábicos.
Ok, tendo decidido que faremos uso de um dos bancos de dados MNIST, vamos entender o motivo desta decisão. Existe toda uma história por trás deste banco de dados. Mas isto, fica a cargo de cada um pesquisar para compreender melhor, porque ele existe, e como tudo começou. Se você pesquisar na WEB, encontrará muitas referências a respeito dele. Mas principalmente encontrará muitos, explicando como desenvolver uma rede perceptrons simples, que fazem uso do Python, para conseguir de alguma forma replicar os resultados demonstrados quando usamos frameworks.
Basicamente, muitos entusiastas, começam a estudar os tais frameworks, devido justamente a este banco de dados MNIST e dos modelos de código em Python divulgados. Já que é ali é aonde grande parte dos recursos são explorados.
Mas não somos interessados em Python, e não me julguem mal por tais palavras, mas nosso objetivo é o MQL5 e entender como as coisas funcionam de fato. Assim, vamos abandonar a possibilidade de fazer as coisas em Python e focar no MQL5. Contudo, quando tudo estiver devidamente implementado, você conseguirá entender como de fato a rede perceptron consegue fazer o que faz. Não mascarando nenhum tipo de conhecimento, com coisas como frameworks já prontos e cheios de detalhes obscuros, por assim dizer.
Muito bem, a caminhada será longa e morosa. Mas o primeiro passo precisa ser dado. E basicamente, oque precisaremos fazer, foge um pouco daquilo que seria o proposito principal do MetaTrader 5. A plataforma foi pensada para ser usada como sistema de negociação. Contudo, aqui precisaremos extrapolar e levar a plataforma para um nível de utilização totalmente diferente. E muitas das coisas que precisamos, não estão presentes na biblioteca do MQL5. Entre elas, a leitura de imagens no formato PNG.

Imagem 01
O banco que utilizaremos, basicamente terá este tipo de informação. Cada uma das imagens, de fato é um arquivo diferente. Podendo assim ser utilizado para criar todo um treinamento, ou experimentar o uso de uma rede perceptron específica.
Se você procurar, com calma, encontrará muitos mencionando o uso do TensorFlow neste tipo de rede que será construída. Mas o TensorFlow, é apenas um, entre tantos métodos de implementar o sistema de aprendizagem. É claro que fazendo uso de tal coisa, teríamos logo de início, todas as coisas que precisaríamos em mãos. Não precisando de forma alguma estudar uma maneira de implementar toda a matemática envolvida. Mas apesar de isto, ser de fato uma mão na roda. Em alguns casos fazer uso de tal solução, não é algo desejável. Isto por que, podemos acabar necessitando de muito mais poder computacional, do que realmente seria necessário, se implementássemos uma solução específica para um dado problema. Perceba o seguinte, não estou falando que este ou aquele framework é bom, ou ruim. Ou que devemos abandonar todos e começar de novo. Não é isto. O que quero é mostrar como tais coisas funcionam internamente.
Porém, para se fazer isto, é preciso, mergulhar em um mundo, do qual muitos de vocês, se quer tem noção de como realmente funciona. Por isto que sistemas perceptron é um assunto tão complexo e, ao mesmo tempo, muito interessante. Pois sempre que imaginamos já saber algo, surge algo novo para aprendermos.
Ok, mas vamos voltar o nosso problema original. No atual momento, a classe C_Neuron, ainda não está no ponto ideal para ser usada em uma implementação, como a que precisamos de fato criar. Ainda não temos meios de configurar uma topologia de rede neural, que nos ajudará bastante na confecção do que será implementado. Não temos meios de ler arquivos PNG, isto por que a biblioteca do MQL5, não nos fornece tais meios por padrão. E além de tudo, ainda precisamos entender outras coisas, das quais ainda não foram explicadas. Isto devido ao fato, de não ter tido de fato, necessidade de entrar em tais pontos. Enfim, parece que teremos uma longa jornada pela frente. Mas como diz o ditado. Tudo se inicia, ao se dar o primeiro passo. E este acabamos de dar, ao decidirmos seguir na direção do sistema de reconhecimento de dígitos manuscritos. Que fazem parte do banco de dados MNIST.
Entendendo a imagem PNG
Muitos de vocês, deve achar que as coisas em computação, são algum tipo de mágica. Ou no mínimo, as pessoas que criam as coisas, são algum tipo de entidade divina ou algum tipo de semi deus. Já que tudo parece não fazer muito sentido, quanto se está começando. Mas essencialmente, em computação, tudo e absolutamente tudo, se resume a uma única coisa: BITS. Tenha sempre isto em mente, e você conseguirá fazer qualquer coisa em computação. Como os bits, são organizados é o segredo e nos diz o que eles de fato fazem. Mas não apenas isto, cada modelo de organização, por si só não diz nada a respeito do que está presente em um banco de bits. Tal banco é chamado de arquivo.
Para que este banco de bits, ou arquivo, faça algum sentido, precisamos de que eles estejam organizados de uma forma muito específica. Não existe, de forma alguma um arquivo totalmente aleatório. Pois se tal arquivo fosse construído, ele teria como conteúdo apenas lixo. Mas mesmo um arquivo que parece ser lixo, na verdade, pode conter algum tipo de informação de grande valor. Tudo é uma questão de saber como o arquivo foi de fato organizado, para ser finalmente construído. Sabendo disto, você pode fazer qualquer coisa.
Então ao olharmos um arquivo de imagem. No caso específico de um arquivo PNG. Você poderá ver algo parecido com a imagem abaixo.

Imagem 02
Agora lhe pergunto: Qual o sentido que estas informações, vistas na imagem acima, tem para você? Possivelmente não faz nenhum sentido. Mas se você usar um programa, ou aplicação que consiga entender estes dados. Você poderá ser apresentado a algo como mostrado na imagem logo a seguir.

Imagem 03
Bem, agora aquelas mesmas informações passam a fazer algum sentido, não é verdade? Pode até não ser a informação real. Já que existem modelos de criptografia e compactação, que tem justamente este objetivo. Criar algo que faça sentido para uns. Mas que, na verdade, tem como intuito, transmitir alguma outra mensagem, que estará oculta. Exceto se você use a aplicação correta para descobrir a mensagem correta. Enfim, isto é algo para quem sabe no futuro, possamos falar em mais detalhes, explicando melhor como este tipo de coisa funciona e pode ser implementada. Já que também é algo muito interessante, e torna este trabalho em redes perceptron, brincadeira de criança, por assim dizer.

Imagem 04
Esta parte, é a tal FILE SIGNATURE, que diz o tipo de estrutura na qual o arquivo foi criado. Agora preste atenção ao seguinte: Aqui não temos necessidade de implementar todos os tipos possíveis de construção de imagens PNG, pelo menos não pretendo fazer isto neste momento. Já que precisamos apenas que o código leia, o tipo de imagem. E estas informações vistas nestas imagens acima, representam exatamente o tipo de coisa na qual precisaremos ler do arquivo PNG. Então não tente usar o código, como se ele pudesse ler qualquer imagem PNG, pois isto não ocorrerá na prática. Dito isto podemos continuar.
A primeira coisa de fato que faremos, será criar um código base. Este pretende testar se estamos ou não inserindo, ou tentando ler uma imagem PNG. Este tipo de coisa é bastante simples de ser criada. Mas já vamos fazer as coisas estruturando o código. De modo a não precisarmos refazer o código depois. Com isto, a primeira parte do código é visto logo abaixo.
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 é a nossa classe inicial, e para experimentar ela, precisaremos de outro código. Como a coisa está apenas começando, podemos usar um script, por ser um código mais limpo inicialmente. Este pode ser visto logo abaixo.
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 estes dois códigos são bastante simples de entender, não entraremos em detalhes sobre como eles funcionam. Contudo, ao executar este código 02, e contando que o arquivo indicado na linha oito de fato exista, você poderá ver o seguinte resultado no terminal do MetaTrader 5.

Imagem 05
Blocos de BYTE
Uma vez cumprido a etapa anterior. Já temos como saber se estamos lidando com uma imagem, ou com outro tipo de coisa. Caso estejamos lidando com uma imagem, podemos passar para a parte onde capturaremos a estrutura da mesma. Normalmente, este tipo de coisa vária de arquivo para arquivo. Mas de uma forma geral, todos têm algum tipo de estrutura, onde as coisas serão organizadas de uma determinada maneira. Olhando o arquivo em si, pode tudo parecer uma grande bagunça. Mas acreditem, existe de fato uma organização interna sendo feita. Caso contrário, qualquer informação ali, seria perdida para sempre.

Imagem 06
Este é um ponto que nos interessa. Já que ele contém metadados, que nos diz diversas coisas sobre a imagem que se encontra no arquivo. Basicamente, entender o que, e como estes valores estão organizados, nos ajudará a reconstruir a imagem em si. Mas aqui é onde, entender a linguagem que estamos usando, nos permitirá acessar as coisas com mais precisão e facilidade. Existe um problema é que acomete todo software, sendo este, a largura de bits.
Arquivos são organizados em bits, apesar de muitas das vezes, os lemos como sendo palavras, números ou qualquer outra coisa. Eles bem lá no fundo, são apenas bits. Mas estes bits podem ser organizados de forma diferente. Isto olhando as coisas em termos de largura de bits. E dependendo da linguagem usada, precisamos adaptar o código, a fim de ter a largura correta. Caso contrário, todo o conteúdo lido será inútil, pois ele estará comprometido por certos bits estarem invadindo área, que não lhes pertence. Mas felizmente o MQL5, tem as larguras corretas. Apesar de não ter a mesma flexibilidade que o C / C++, aquilo que o MQL5, nos fornece de estrutura, já será o suficiente. Então precisaremos criar uma estrutura interna, a fim de ler o bloco que nos informa algumas coisas sobre a imagem. Isto é bem simples de ser feito, tanto a criação, quanto a leitura, como você pode ver no código abaixo.
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
A estrutura em si, está sendo criada na linha oito, com isto, ao executarmos novamente o código, poderemos ver as seguintes informações.

Imagem 07
Mais que maravilha. Que espetáculo. Olha só que bela porcaria. Acabamos de perceber que existe um bug na linguagem de programação. Isto por que, as informações que deveriam vir do arquivo, são exatamente as que foram mostradas na imagem anterior. Ou seja, os valores estão investidos. Neste momento, muitos desistem e começam a xingar de DEUS ao DIABO, lançando todo tipo de ofensas a linguagem de programação usada. Já que quando ele usa outra, as coisas vêm corretas, como esperado. Mas antes que você, comece a pensar que de fato a linguagem tem um bug. Vamos entender, o que, na verdade, está acontecendo aqui.
O detalhe, é que você deve estar acostumado a ver valores inteiros do tipo LITTLE ENDIAN. Contudo, existe também o tipo BIG ENDIAN, onde os valores são armazenados de maneira um pouco diferente. Mas, por que disto? Bem, o motivo é a CPU. Este tipo de coisa, nada tem a ver com a linguagem de programação. O detalhe, é que algumas CPUs, para usam a notação LITTLE ENDIAN, enquanto outras a notação BIG ENDIAN. Sei que parece coisa de maluco. Durante muito tempo tive problemas em entender, este detalhe sobre os valores que a CPU de fato entende. Já que dependendo da implementação, como os valores são representados pode mudar radicalmente. Então o que você viu, não é um defeito na linguagem de programação. Mas sim uma questão da implementação da CPU.
Existem diversas formas de se resolver isto. Mas basicamente, o que precisamos fazer, é girar os bits para ter uma representação, que nos seja realmente compreensível. Já que os valores esperados eram de 28 de altura e 28 de largura. E não aqueles valores malucos. Mas como estou querendo incentivar ao máximo o uso do MQL5 puro. Vamos usar o que ele tem a nos oferecer. Pois, por melhor que programemos, não teremos acesso a CPU da mesma forma que os desenvolvedores da linguagem terão. Assim, é preferível usar funções da biblioteca padrão, envés de criar alguma função com o mesmo propósito. Corrigindo o código, passaremos a ter aquilo que é mostrado logo abaixo.
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
É verdade que isto parece ser mais complicado do que realmente é. Mas acredite, é mais simples fazer as coisas assim. Desta maneira, quando executarmos novamente o código, o resultado é o que vemos abaixo.

Imagem 08
Muito bem, já temos a primeira parte do que precisamos. Tudo que temos de fazer agora, é ler a imagem para poder apresentar, ou utilizar ela de alguma forma. Mas para fazer isto da maneira correta, precisamos recorrer à documentação do formato. Isto a fim de realmente conseguir remontar a imagem da maneira correta. No final deste artigo, vou deixar no campo de referências, onde você pode conseguir mais informações. Já que talvez você queira utilizar imagens diferentes da que estarei mostrando aqui.
Carregando a imagem PNG
Tudo que foi feito até este momento, faz parte de um bloco de construção que sempre se repete. Independentemente do tipo de coisa que estamos fazendo. Seja carregando uma imagem. Tentando descompactar um arquivo, ou qualquer outra coisa. Tudo é basicamente feito desta mesma maneira. Mas como aqui estamos interessando em carregar uma imagem. O que foi feito até este ponto, é idêntico a qualquer imagem PNG que será carregada. Porém, o que faremos agora é algo voltado ao que de fato precisamos. Então se você desejar fazer diferente, terá que estudar a documentação do formato. Nosso bloco de imagem é visto logo abaixo.

Imagem 09
Já a imagem em si, se encontra presente nesta região demarcada abaixo.

Imagem 10
Porém, preste atenção, estes dados que estamos vendo, estão compactados. Muito bem, já sabemos o que precisamos saber. Mas podemos deixar o código um tanto quanto mais simples. Apesar deste mostrado até o momento funcionar. Tivemos de fazer algumas coisas, que usando o MQL5, se tornam um tanto quanto desnecessárias. Assim, todo o código visto anteriormente, pode ser melhorado para outro visto logo abaixo.
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
Agora ao executarmos este código, temos o resultado mostrado abaixo:

Imagem 11
Ou seja, já temos todas as informações que precisamos, de uma só fez. Agora tudo que precisamos, é descompactar a imagem que se encontra no array IMG, que foi mostrada acima. Note como o código, ficou muito mais simples de ser lido, e como só falta descompactar a imagem., podemos partir para o código final que pode ser visto abaixo, na íntegra.
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
Note que na linha vinte e sete, temos um array. Este é o que receberá a imagem já descompactada. Já o processo de descompactação é algo relativamente simples. Isto por que o MQL5, conta com uma chamada que nos ajuda a fazer isto sem dificuldades. A tal chamada é vista na linha cinquenta e um. O valor para o array, Key pode ficar como sendo vazio. Já que não existe nenhum tipo de chave de segurança sendo utilizada aqui. Se o processo ocorrer de maneira correta, no array buff, teremos as informações descompactadas.
Porém, elas ainda contêm informações que apenas nos atrapalharão. Precisamos filtrar o conteúdo, antes de ter tudo terminado. Assim na linha cinquenta e três, alocamos o espaço necessário para a imagem. E na linha cinquenta e quatro entramos em um laço, cujo objetivo e remover alguns valores, que não fazem parte da imagem final. Quando este laço terminar, teremos no array m_Pixels, a imagem que queremos e utilizaremos.
Para finalizar, esta classe. Na linha cento e nove, temos uma função que visa, retornar a imagem, assim como também a largura da mesma. Desta forma, podemos fazer a leitura de maneira muito mais tranquila e sem sobressaltos.
Ok, agora vamos ver o código que faz uso desta classe. O mesmo pode ser visto na íntegra logo abaixo.
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 é bem simples, não vejo nenhum ponto onde realmente precisamos dar algum destaque especial. Mas ao executar ele, você terá como resultado o que é mostrado na imagem abaixo. Isto olhando o gráfico criado no MetaTrader 5.

Imagem 12
Um detalhe: Esta imagem acima, está usando cores falsas. Já que o correto seria que fizéssemos a plotagem em tons de cinza. Mas de qualquer forma, o que nos interessa, é que a imagem do arquivo PNG, foi devidamente carregada e já poderá ser utilizada. Porém, como também estamos criando um arquivo de texto, e este terá como sendo o seu conteúdo, o valor de cada um dos pixels. Também teremos algo que pode ser visto na imagem abaixo.

Imagem 13
Considerações Finais
Neste artigo, começamos a implementar, os pontos dos quais precisamos para demonstrar o funcionamento, do que talvez seja a rede perceptron, mais didática de todas.
Visto que ela é usada em diversos experimentos servindo para explicar muitas coisas. Tal modelo de rede perceptron, faz uso do banco de dados MNIST. Que pode ser baixado, a fim de que você também construa uma rede com fins didáticos. Como se trata de quatro arquivos compactados. E baixar eles, depende do interesse de cada um. Vou deixar a parte referente a isto, a cargo de cada um de vocês, pelo menos por hora. De qualquer forma, deixarei sempre alguns dos PNGs disponíveis no anexo. Isto para que você também tenha algo utilizável.
Uma observação importante: Como o código visto neste artigo, visa exclusivamente cobrir arquivo PNG, que fazem parte do MNIST. O mesmo não conseguirá renderizar corretamente outros modelos de arquivos PNG. Visto que não é este o propósito do código. E como bônus, também deixaremos, onde você pode baixar, um código-fonte de outro programador MQL5, voltado para renderizar outros tipos de PNG.
| Arquivo MQ5 | Descrição |
|---|---|
| Scripts\Example 01 | Demonstração básica |
Referencias
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Do básico ao intermediário: Arquivo template (III)
Está chegando o novo MetaTrader 5 e MQL5
Técnicas do MQL5 Wizard que você deve saber (Parte 59): Aprendizado por Reforço (DDPG) com Padrões da Média Móvel e do Oscilador Estocástico
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso