Español Português
preview
Нейронная сеть на практике: Чтение PNG-изображений из MNIST

Нейронная сеть на практике: Чтение PNG-изображений из MNIST

MetaTrader 5 — Машинное обучение |
43 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье «Нейронная сеть на практике: Ручное обратное распространение» я немного рассказал о том, как можно выполнять обратное распространение в наборе последовательно соединённых перцептронов. Хотя это и не лучший способ объяснить эту тему, как мы увидим дальше, изложенное там позволяет нам более непосредственно перейти к другому вопросу.

Хорошо, поскольку некоторых вещей, нужных нам прямо сейчас, у нас нет, придётся реализовать их самим, чтобы сделать ещё несколько шагов в объяснении того, что мы хотим показать о сетях перцептронов.

Наша текущая задача — загружать изображения из общедоступной базы данных. В данном случае — из базы данных MNIST. Обычно изображения из этой базы данных имеют специфический формат, но в других случаях нам может понадобиться работать с иными форматами, например PNG. Именно в этом и состоит цель данной статьи: создать как можно более простой механизм загрузки изображений этого формата.


Возвращаемся к работе

Сейчас мы оказались на распутье. Это связано с тем, что в зависимости от принятого решения мы в итоге реализуем тот или иной тип системы перцептронов.

Знаю, это может показаться странным, поскольку многие считают, что перцептронные сети имеют универсальное назначение. Однако это не так. Зачастую, когда мы видим какую-либо систему перцептронов, созданную с помощью универсальных механизмов, на деле оказывается, что мы используем некий фреймворк, предназначенный для реализации таких систем.

Это не та цель, которой мы хотим достичь в этой серии статей. Цель в том, чтобы объяснить, как можно строить такие системы на языке MQL5, не используя никаких внешних инструментов. Таким образом, хотя сеть, которую мы будем реализовывать, будет довольно ограниченной, она покажет вам, как на самом деле всё работает за кулисами. Это всего лишь вопрос масштаба. Если увеличить масштаб и располагать достаточным объёмом данных для обучения какой-нибудь модели, кто знает, может, нам удастся создать мини-GPT или что-то подобное. Но это не наша цель.

Итак, в чём наша текущая проблема? Ну что ж, мой друг, проблема — в данных. Неважно, какую именно сеть перцептронов вы хотите реализовать: проблема всегда будет в данных. Данные — душа любой системы перцептронов. Без них такие системы никак не построить. Хотя есть способ заставить систему перцептронов генерировать собственные данные. Но в этой серии мы не будем затрагивать подобные вопросы. Так мы не запутаем ваши бедные головки.

Понимая, что проблема — в данных, тех самых данных, которые будут использоваться при обучении и настройке самой сети перцептронов, нам, как и исследователям прошлого, нужна какая-нибудь база данных. В данном случае были приложены огромные усилия, чтобы собрать и вручную классифицировать эти данные — да, всё это делали люди — с максимальной тщательностью и старанием. Это потому, что эти данные действительно очень ценны.

Существуют разные общедоступные базы данных, доступные для использования. Тем не менее мы считаем, что для многих самой ностальгической и интересной остаётся база данных для распознавания рукописных символов. Речь идёт о MNIST, основанной на индо-арабских цифрах.

Итак, раз уж мы решили использовать одну из баз данных MNIST, давайте посмотрим, почему было принято именно такое решение. За этой базой данных стоит целая история. Но каждому стоит самостоятельно изучить этот вопрос, чтобы лучше понять, почему она существует и с чего всё началось. Если поискать в Интернете, можно найти множество материалов на эту тему. Прежде всего, вы найдёте множество материалов, где объясняется, как разработать простую сеть перцептронов с помощью Python, чтобы воспроизвести результаты, получаемые при использовании фреймворков.

По сути, многие энтузиасты начинают изучать эти фреймворки именно из-за этой базы данных MNIST и опубликованных примеров кода на Python. В конце концов, именно там изучается значительная часть доступных средств и библиотек.

Но нас не интересует Python, и не поймите меня неправильно: наша цель — MQL5 и понимание того, как всё на самом деле работает. Поэтому мы оставим в стороне возможность сделать это на Python и сосредоточимся на MQL5. Когда всё будет должным образом реализовано, вы сможете понять, как на самом деле сеть перцептронов делает то, что делает, не скрывая внутреннее устройство за готовыми фреймворками, полными, так сказать, не слишком прозрачных деталей.

Хорошо, путь будет долгим и трудоёмким. Но нужно сделать первый шаг. И, по сути, то, что нам предстоит сделать, несколько выходит за рамки основного назначения MetaTrader 5. Платформа изначально создавалась для использования в качестве торговой платформы. Однако здесь нам придётся пойти дальше и использовать платформу совершенно иначе. И многих вещей, которые нам нужны, в библиотеке MQL5 нет. В частности, нет чтения изображений в формате PNG.

По сути, значительная часть базы данных MNIST представлена в этом формате, хотя в некоторых случаях данные могут быть и в двоичном виде. Тем не менее будем считать, что мы будем использовать данные в формате PNG, содержащие огромное количество изображений размером 28 × 28 пикселей. Обычно это будут изображения в градациях серого. Итак, если вы скачаете эту базу данных, то увидите нечто очень похожее на следующее изображение:

Изображение 01

База данных, которую мы будем использовать, будет в основном содержать именно такую информацию. Каждое изображение — это отдельный файл и, следовательно, может использоваться для создания целого набора обучающих данных или для экспериментов с конкретной сетью перцептронов.

Если вы не спеша поищете, то найдёте множество упоминаний об использовании TensorFlow в сети именно такого типа, которую мы собираемся построить. Однако TensorFlow — лишь один из множества доступных способов реализации системы обучения. Понятно, что, если бы мы использовали нечто подобное, с самого начала у нас было бы в распоряжении всё необходимое и нам не пришлось бы разбираться, как реализовать всю связанную с этим математику. Хотя это может оказаться весьма удобным, в некоторых случаях использовать подобное решение нецелесообразно. Это связано с тем, что в итоге нам может потребоваться гораздо больше вычислительной мощности, чем действительно было бы нужно, если бы мы реализовали решение специально для конкретной задачи. Имейте в виду следующее: я не говорю, что тот или иной фреймворк хорош или плох, и не утверждаю, что нам следует отказаться от них всех и начать с нуля. Дело не в этом. Я хочу показать, как всё это устроено изнутри.

Для этого необходимо окунуться в мир, о реальном устройстве которого многие из вас даже не представляют. Именно поэтому система перцептронов — столь сложная и в то же время интересная тема. Как только нам начинает казаться, что мы уже что-то знаем, сразу появляется что-то новое, чему нужно учиться.

Хорошо, вернёмся к нашей исходной задаче. На данный момент класс C_Neuron ещё не вполне готов к использованию в реализации, которую нам нужно создать. У нас пока нет средств для настройки топологии нейронной сети, которая могла бы значительно упростить разработку системы. Мы также не можем читать файлы PNG, поскольку библиотека MQL5 по умолчанию не предоставляет такой функциональности. Кроме того, нам ещё нужно разобраться в других вопросах, которые пока не были объяснены, просто потому, что до сих пор не было необходимости затрагивать эти моменты. В общем, похоже, что впереди нас ждёт долгий путь. Но, как гласит пословица: всё начинается с первого шага. И этот шаг мы только что сделали, решив двигаться к системе распознавания рукописных цифр, которые входят в базу данных MNIST.

Итак, первое, что нам нужно будет сделать, — это научиться считывать PNG-изображение с помощью MQL5. Итак, чтобы правильно разделить темы, давайте откроем новый раздел, поскольку важно хорошо понять, как это сделать.


Понимание PNG-изображения

Многие, наверное, думают, что в информатике всё похоже на какую-то магию. Или, по крайней мере, что те, кто всё это создаёт, — какие-то божественные существа или полубоги, ведь поначалу всё это кажется совершенно бессмысленным. Однако, по сути, в информатике всё, абсолютно всё, сводится к одному: БИТАМ. Всегда помните об этом, и вы сможете сделать в информатике что угодно. Секрет в том, как организованы биты, и именно эта организация показывает нам, что они на самом деле делают. Но и это ещё не всё: каждая модель организации сама по себе ничего не говорит нам о том, что содержит набор битов. Этот набор мы называем файлом.

Чтобы этот набор битов, или файл, имел какой-то смысл, он должен быть организован совершенно определённым образом. Полностью случайного файла не существует. Если бы такой файл был создан, его содержимое было бы не чем иным, как мусором. Но даже файл, который кажется мусором, на самом деле может содержать очень ценную информацию. Всё зависит от того, как были организованы данные, из которых в итоге был сформирован файл. Зная это, вы сможете сделать что угодно.

Итак, рассматривая файл изображения, а точнее PNG-файл, вы увидите нечто похожее на следующее изображение.

Изображение 02

Теперь я хочу спросить вас: какой смысл для вас имеют данные, представленные на изображении выше? Возможно, никакого. Однако, если вы воспользуетесь программой или приложением, способным интерпретировать эти данные, вы сможете получить нечто вроде того, что показано на следующем изображении.

Изображение 03

Ну вот, теперь эти данные начинают обретать какой-то смысл, не так ли? Возможно, это даже не реальная информация, поскольку существуют методы шифрования и сжатия, цель которых как раз и заключается в том, чтобы создать нечто, имеющее смысл для одних, но на самом деле служащее для передачи другого сообщения, которое останется скрытым, если только вы не воспользуетесь соответствующим приложением, чтобы его обнаружить. В общем, возможно, в будущем мы сможем поговорить об этом подробнее и лучше объяснить, как это работает и как это можно реализовать. Это тоже очень интересная тема, и, так сказать, на её фоне работа с сетями перцептронов кажется детской забавой.

Но давайте пока оставим это в стороне и вернёмся к нашей основной теме. Если вы будете наблюдать за двоичными файлами и изучать их, рано или поздно заметите нечто, что всегда повторяется в зависимости от содержимого файла. Обычно этот фрагмент находится в самом начале файла и называется сигнатурой файла, то есть, буквально, «подписью файла». На изображении двоичного файла мы можем её увидеть. Обратите внимание на выделенную область, показанную ниже.

Изображение 04

Эта часть — сигнатура файла, которая указывает формат или тип файла. Теперь обратите внимание на следующее: здесь нам не нужно реализовывать все возможные варианты формата PNG; по крайней мере, я пока не собираюсь этого делать. Нам нужно лишь, чтобы код считывал тип изображения. А данные, представленные на предыдущих изображениях, как раз и представляют собой ту информацию, которую нам придётся считывать из файла PNG. Поэтому не пытайтесь использовать этот код так, будто он может считывать любое изображение PNG, потому что на практике он этого не сможет. Итак, можем продолжать.

Сначала мы создадим основу кода. Его цель — проверить, работаем ли мы с PNG-файлом или пытаемся его прочитать. Это довольно просто реализовать. Однако с самого начала мы выстроим код так, чтобы потом не пришлось его переделывать. Итак, первая часть кода приведена ниже.

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. //+------------------------------------------------------------------

Код 01

Этот код — наш начальный класс, и для его проверки нам понадобится ещё один фрагмент кода. Поскольку мы только начинаем, можем использовать скрипт, так как это позволяет сделать исходный код проще. Он показан ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
04. #include <Neural Network\C_PNG.mqh>
05. //+------------------------------------------------------------------+
06. //| Функция запуска программы-скрипта                                    |
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09.   {
10.    C_PNG            png("mnist/10032.png");
11.   }
12. //+------------------------------------------------------------------

Код 02

Поскольку эти два фрагмента кода довольно просты для понимания, мы не будем вдаваться в подробности их работы. Однако при выполнении этого кода 02, если указанный в строке 08 файл существует, в терминале MetaTrader 5 появится следующий результат.

Изображение 05

Это та же информация, которую выводит строка тридцать класса C_PNG. Если это появилось, значит, всё в порядке, и мы можем перейти к той части, где начнём непосредственно читать изображение. Но, чтобы изложение было более наглядным, мы откроем новую тему. Так будет проще понять, что мы будем делать дальше.


Блоки BYTE

После завершения предыдущего этапа мы уже можем определить, работаем ли мы с изображением или с файлом другого типа. Если мы работаем с изображением, можем перейти к той части, где получим его структуру. Как правило, такая организация варьируется от одного файла к другому. Однако в целом у всех есть структура, в которой данные организованы определённым образом. Если посмотреть на сам файл, всё может показаться сплошным беспорядком. Но, поверьте, внутренняя организация там есть. В противном случае все содержащиеся там данные были бы потеряны навсегда.

В нашем конкретном случае мы будем работать с PNG-изображением. Оно организовано в виде чётко определённых блоков данных. Если вы снова посмотрите на изображение внутреннего содержимого, то увидите, что в определённой позиции файла присутствует некоторая информация. Это можно увидеть на следующем изображении.

Изображение 06

Этот момент нас интересует, поскольку здесь содержатся метаданные, предоставляющие нам различную информацию об изображении, хранящемся в файле. По сути, понимание того, что представляют собой эти значения и как они организованы, поможет нам восстановить изображение. Именно здесь хорошее знание используемого нами языка позволит нам получать доступ к данным с большей точностью и лёгкостью. Существует проблема, актуальная для любого ПО: разрядность типов данных.

Файлы состоят из битов, хотя часто мы читаем их так, будто это слова, числа или что-то ещё. По сути, это не более чем биты. Однако эти биты могут быть организованы по-разному, если рассматривать вопрос с точки зрения битовой ширины (разрядности) типов данных. И в зависимости от используемого языка программирования нам придётся адаптировать код, чтобы он работал с правильной разрядностью. В противном случае всё прочитанное будет бесполезным, поскольку некоторые биты займут не свои позиции, и данные исказятся. К счастью, в MQL5 есть типы данных с подходящей разрядностью. Хотя MQL5 не обладает такой же гибкостью, как C/C++, будет вполне достаточно тех структур, которые предоставляет MQL5. Поэтому нам придётся создать внутреннюю структуру для чтения блока, который предоставляет нам некоторые данные об изображении. Это довольно просто сделать — и при создании структуры, и при её чтении, как вы можете видеть в коде ниже.

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. //+------------------------------------------------------------------

Код 03

Сама структура создаётся в восьмой строке. После этого, если снова запустить код, мы увидим следующую информацию.

Изображение 07

Вот это да. Просто зрелище. Посмотрите, какая красивая ерунда. Мы только что обнаружили баг в языке программирования. Это связано с тем, что данные, которые должны поступать из файла, в точности совпадают с теми, что были показаны на предыдущем рисунке. То есть значения записаны в обратном порядке. В этот момент многие сдаются и начинают проклинать всё на свете — от БОГА до ДЬЯВОЛА, осыпая используемый язык программирования всевозможными ругательствами, потому что при использовании другого языка значения отображаются правильно, именно так, как и ожидалось. Но прежде чем вы начнёте думать, что в языке действительно есть баг, давайте разберёмся, что на самом деле происходит.

Скорее всего, вы привыкли видеть целочисленные значения, хранящиеся в порядке LITTLE ENDIAN. Однако существует и порядок BIG ENDIAN, при котором значения хранятся немного иначе. И почему же так происходит? Ну, всё из-за процессора. Это никак не связано с языком программирования. Некоторые процессоры используют порядок LITTLE ENDIAN, а другие — порядок BIG ENDIAN. Знаю, звучит как безумие. Долгое время мне было трудно понять этот нюанс, связанный со значениями, которые на самом деле интерпретирует процессор, поскольку в зависимости от реализации способ представления значений может радикально меняться. Таким образом, то, что вы только что увидели, — не недостаток языка программирования, а вопрос, связанный с реализацией процессора.

Есть несколько способов это исправить. Но, по сути, нам нужно просто поменять порядок байтов, чтобы получить представление, которое мы действительно сможем понять. Ожидаемыми значениями были 28 по высоте и 28 по ширине, а не эти нелепые числа. Поскольку я хочу максимально поощрять использование чистого MQL5, мы будем использовать то, что предлагает нам сам язык. Как бы хорошо мы ни программировали, у нас не будет такого же доступа к процессору, как у разработчиков языка. Поэтому лучше использовать функции стандартной библиотеки, чем создавать функцию с той же целью. После исправления кода мы получим то, что показано ниже.

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. //+------------------------------------------------------------------

Код 04

Это и правда выглядит сложнее, чем есть на самом деле. Но, поверьте, так сделать проще. Таким образом, когда мы снова запустим код, получим результат, который видим ниже.

Изображение 08

Отлично, у нас уже есть первая часть того, что нам нужно. Теперь нам осталось только прочитать изображение, чтобы мы могли его отрисовать или как-то использовать. Но, чтобы сделать это правильно, нам нужно обратиться к документации формата. Это позволит нам действительно правильно восстановить изображение. В конце этой статьи я оставлю в разделе «Ссылки» информацию о том, где можно посмотреть более подробные сведения, на случай, если вы захотите использовать изображения, отличные от того, которое я покажу здесь.

В любом случае, основная часть чтения уже завершена. Итак, чтобы было проще понять, перейдём к новой теме.


Загрузка PNG-изображения

Всё, что мы сделали до этого момента, — часть базового блока, который постоянно повторяется, независимо от того, что именно мы делаем. Будь то загрузка изображения, попытка распаковать файл или что-то ещё. По сути, всё делается именно так. Поскольку здесь нас интересует загрузка изображения, всё, что было сделано до этого момента, применимо к любому PNG-изображению, которое мы собираемся загружать. Однако то, что мы будем делать сейчас, будет ориентировано именно на то, что нам нужно. Поэтому, если вы хотите сделать это по-другому, вам придётся изучить документацию по формату. Блок данных изображения показан ниже.

Изображение 09

Данные изображения находятся в выделенной области, показанной ниже.

Изображение 10

Но обратите внимание: данные, которые мы видим, сжаты. Отлично, теперь мы знаем всё, что нам нужно. Однако мы можем немного упростить код. Хотя всё показанное до сих пор работает, нам пришлось сделать кое-что, что при использовании MQL5 оказывается несколько излишним. Поэтому весь приведённый выше код можно улучшить и привести к виду, показанному ниже.

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. //+------------------------------------------------------------------+

Код 05

Теперь, при выполнении этого кода, мы получаем следующий результат:

Изображение 11

То есть у нас сразу есть все нужные нам данные. Теперь осталось только распаковать изображение, хранящееся в массиве IMG, который был показан выше. Обратите внимание, что код стал гораздо проще для чтения и, поскольку нам осталось только распаковать изображение, мы можем перейти к окончательному варианту кода, который полностью приведён ниже.

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. //+------------------------------------------------------------------+

Код 06

Обратите внимание, что в строке двадцать семь у нас есть массив. В него будет помещено уже распакованное изображение. Процесс распаковки относительно прост, поскольку в MQL5 есть вызов, который позволяет сделать это без особых трудностей. Этот вызов находится в строке пятьдесят один. Массив Key можно оставить пустым, так как здесь не используется никакой ключ безопасности. Если процесс пройдёт успешно, массив buff будет содержать распакованные данные.

Однако эти данные по-прежнему содержат информацию, которая не нужна нам для конечного изображения. Нам нужно отфильтровать содержимое, прежде чем закончить. Таким образом, в строке пятьдесят три мы выделяем необходимое место для изображения. А в строке пятьдесят четыре мы входим в цикл, цель которого — удалить некоторые значения, не входящие в итоговое изображение. Когда этот цикл завершится, в массиве m_Pixels будет находиться нужное нам изображение, которое мы и будем использовать.

В завершение этого класса в строке сто девять у нас есть функция, которая возвращает изображение, а также его ширину. Таким образом, мы можем выполнять чтение гораздо спокойнее и без лишних неожиданностей.

Итак, теперь давайте посмотрим на код, в котором используется этот класс. Его полный текст можно увидеть ниже.

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. //| Функция запуска программы-скрипта                                    |
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. //+------------------------------------------------------------------+

Код 07

Поскольку этот код довольно прост, я не вижу в нём ничего, что требовало бы особого внимания. Но при его запуске вы увидите на графике MetaTrader 5 результат, показанный на следующем изображении.

Изображение 12

Одно замечание: на этом изображении использованы ложные цвета, поскольку правильно было бы выполнить отрисовку в градациях серого. Но в любом случае нас интересует то, что изображение из PNG-файла было загружено корректно и уже может использоваться. Кроме того, поскольку мы также создаём текстовый файл, содержимым которого будет значение каждого пикселя, у нас получится нечто вроде того, что показано на следующем изображении.

Изображение 13

И именно эти значения нас в конечном счёте и интересуют. Именно их мы впоследствии будем использовать, чтобы подавать данные на вход нашей сети перцептронов. Однако того, что мы сделали до сих пор, вполне достаточно, чтобы перейти к следующим вопросам, которые мы рассмотрим уже в следующей статье.


Заключительные замечания

В этой статье мы начали реализовывать элементы, необходимые для демонстрации работы, пожалуй, самой наглядной сети перцептронов из всех.

Эта сеть используется во множестве экспериментов и помогает объяснить многие вещи. В этой модели сети перцептронов используется база данных MNIST, которую можно скачать, чтобы вы тоже могли построить такую сеть в учебных целях. Поскольку речь идёт о четырёх сжатых файлах, а скачивать их или нет — дело личного интереса, я оставлю это на ваше усмотрение, по крайней мере пока. В любом случае я всегда буду добавлять несколько PNG-файлов во вложения, чтобы у вас тоже был материал, которым вы сможете воспользоваться.

Важное замечание: поскольку код, приведённый в этой статье, предназначен исключительно для работы с PNG-файлами, входящими в базу данных MNIST, он не сможет правильно отрисовывать PNG-файлы других типов, так как это не входит в его задачу. И в качестве бонуса мы также укажем, где можно скачать исходный код для отрисовки других типов PNG, разработанный другим программистом MQL5.

Если уж отдавать должное, давайте отдадим его тому, кто этого заслуживает.
Файл MQ5 Описание
Script\Example 01 Базовая демонстрация

Ссылки

PNG (Portable Network Graphics)

PNG — библиотека для MetaTrader 5

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/14017

Прикрепленные файлы |
За пределами максимальной просадки: Создание анализатора ДНК просадки на MQL5 За пределами максимальной просадки: Создание анализатора ДНК просадки на MQL5
Максимальная просадка — это одно число, за которым скрывается то, что действительно важно: как часто снижается кривая эквити, как долго она остается ниже предыдущего пика и как быстро восстанавливается. В этой статье мы создадим нативный инструмент на MQL5, который восстанавливает кривую просадки, разбивает ее на отдельные эпизоды (глубина, продолжительность, время восстановления), рассчитывает индекс Ulcer, индекс боли и фактор восстановления, а затем объединяет эти показатели в единую оценку устойчивости с практическими рекомендациями. Никаких внешних библиотек, никакого Python, никакого ИИ.
От начального к среднему уровню: Файл шаблона графика (III) От начального к среднему уровню: Файл шаблона графика (III)
Некоторое время назад я опубликовал статью, которая вызвала большую путаницу, и многие из тех, кто её прочитал, плохо её поняли. Итак, в этой статье мы как раз вернёмся к тому понятию, которое в своё время, казалось, не имело никакого смысла, и объясним его гораздо лучше. То есть мы рассмотрим, как разместить несколько индикаторов в одном подокне так, чтобы их можно было легко читать и понимать.
Теория экстремальных значений в MQL5: Создание индикатора риска обвала с оценкой хвоста распределения — за рамками VaR по методу Монте-Карло Теория экстремальных значений в MQL5: Создание индикатора риска обвала с оценкой хвоста распределения — за рамками VaR по методу Монте-Карло
Стандартные инструменты оценки риска на MQL5 используют недавние исторические данные и не учитывают, насколько тяжелым может быть левый хвост распределения. Мы реализуем теорию экстремальных значений (EVT) в MetaTrader 5: подгонку обобщенного распределения Парето (GPD) методом превышений над порогом (POT) с помощью ALGLIB, индикатор, который в реальном времени выводит VaR и ES на основе EVT, а также параметр формы хвоста, и советник, рассчитывающий размер позиций по оценке хвоста распределения. Тестирование на исторических данных в контролируемых условиях демонстрирует снижение просадки при неизменных точках входа.
Индикатор средней цены входа маркет-мейкера Индикатор средней цены входа маркет-мейкера
В статье исследуется, можно ли по обычным данным MetaTrader 5 оценить среднюю стоимость позиции пассивной стороны рынка, если реальные сделки и позиции маркет-мейкеров недоступны. Пошагово строится модель: от оценки направленного потока и виртуальной позиции до Inventory Basis, Reservation Price по Avellaneda–Stoikov и адаптивных Bid/Ask с учётом токсичности потока.