English 中文 Español Deutsch 日本語 Português
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 22): Условные генеративно-состязательные сети (cGAN)

Возможности Мастера MQL5, которые вам нужно знать (Часть 22): Условные генеративно-состязательные сети (cGAN)

MetaTrader 5Торговые системы | 30 октября 2024, 14:59
591 0
Stephen Njuki
Stephen Njuki

Введение

Условные генеративно-состязательные сети (Conditional Generative Adversarial Networks, cGAN) - разновидность GAN, который позволяет настраивать тип входных данных в генеративной сети. GAN представляют собой пару нейронных сетей: генератор и дискриминатор. Оба обучаются друг на друге, причем генератор совершенствуется в генерации целевого вывода, в то время как дискриминатор обучается отличать истинные данные от ложных у генератора.

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

В условной настройке GAN (cGAN) мы делаем небольшую модификацию, подавая в генеративную сеть в качестве входных данных определенный тип данных, а не случайные данные. Это применимо или полезно в ситуациях, когда тип данных, которые мы передаем дискриминатору, является парным или состоит из двух частей, а цель сети дискриминатора — определить, являются ли входные парные данные действительными или выдуманными.

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

источник



источник

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

Однако в cGAN то, что на схеме называется шумом, по сути является независимыми данными или данными, для которых генератор пытается сгенерировать метку (в нашем адаптированном варианте). Мы не вводим метки в сеть генератора, как показано на схеме выше, однако сеть дискриминатора получает пару данных как шума (или независимых данных), так и соответствующей ему метки, а затем пытается определить, получена ли эта пара из реальных данных или метка, назначенная независимым данным, была получена от генератора.

Каковы преимущества cGAN для прогнозирования финансовых временных рядов? Всё проверяется на практике. Ближе к конце статьи мы проведем несколько тестов. Однако в распознавании изображений GAN показывают себя довольно хорошо, хотя им и далеко до сверточных нейронных сетей (CNN) или визуальных трансформеров (ViT) из-за вычислительных затрат. Тем не менее, по оценкам исследователей, они лучше справляются с синтезом и дополнением изображений.


Настройка окружения

Для построения модели cGAN многослойную сеть перцептрона, представленную здесьв качестве базового класса. Этот базовый класс содержит все инструменты и библиотеки, которые нам понадобятся для запуска и работы cGAN, поскольку и сеть генератора, и сеть дискриминатора будут просто рассматриваться как экземпляры многослойного перцептрона. Класс имеет всего две основные функции: метод прямого распространения Forward() и функцию обратного распространения Backward(). Конечно, есть конструктор класса, который принимает настройки сети, и некоторые методы обслуживания, позволяющие сохранять обученные веса в виде файла, а также некоторые другие функции, которые устанавливают цели обучения и считывают результаты прямого распространения.

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

−log(D(G(zy)y))

где:

  • D() - выходная функция дискриминатора
  • G() - выходная функция генератора
  • z - независимые данные
  • y - зависимые или прогнозные данные либо данные типа метки

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

//+------------------------------------------------------------------+
//| Backward pass through the neural network to update weights       |
//| and biases using gradient descent                                |
//+------------------------------------------------------------------+
void Cgan::Backward(vector<double> &DiscriminatorOutput, double LearningRate = 0.05)
{  if(target.Size() != output.Size())
   {  printf(__FUNCSIG__ + " Target & output size should match. ");
      return;
   }
   if(ArraySize(weights) != hidden_layers + 1)
   {  printf(__FUNCSIG__ + " weights matrix array size should be: " + IntegerToString(hidden_layers + 1));
      return;
   }

        ...

// Update output layer weights and biases
   vector _output_error = -1.0*MathLog(DiscriminatorOutput)*(target - output);//solo modification for GAN
   Back(_output_error, LearningRate);
}

Наша функция Backward() становится перегруженной, поскольку один вариант обычно принимает выходные данные дискриминатора в качестве входных данных, а затем обе эти перегруженные функции вызывают функцию Back(), содержащую большую часть кода, который был в старой функции обратного распространения. Функция Back() введена здесь для уменьшения дублирования. Однако это взвешивание гарантирует, что при обучении генератора мы не только становимся лучше в прогнозировании следующего изменения цены закрытия, но и "совершенствуемся" в обмане дискриминатора, заставляя его поверить, что данные генератора реальны. Тем временем дискриминатор обучается "в противоположном направлении", пытаясь хорошо отличать данные генератора от реальных.

Если бы мы реализовали такую структуру в стороннем приложении, определение текущей сети с применением TensorFlow в Python потребовало бы добавления каждого слоя отдельной командой или строчкой кода. Python, конечно, предоставляет больше настроек, чем наш базовый класс, но как инструмент прототипирования для запуска cGAN в среде MQL5 наш вариант предпочтительней. Кроме того, использование Python и любой из его библиотек нейронных сетей требует наличия "адаптеров", таких как ONNX или эквивалентных пользовательских реализаций, которые позволят экспортировать результаты обучения обратно в MQL5. Такой подход, безусловно, имеет свои преимущества, когда модель предназначена для обучения один раз на этапе разработки и последующего развертывания или для периодического обучения, но в автономном режиме (без развертывания).

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


Пользовательский класса сигналов

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

Это будут функции GetOutput(), Setoutput() и Norm(). Их роль здесь будет очень похожа на ту, что описана в предыдущей статье, в том смысле, что функция get будет якорной функцией, отвечающей за определение рыночных условий, в то время как функция set, как и прежде, будет доступна для записи весов сети после каждого прохода обучения, в то время как функция norm играет решающую роль в нормализации наших входных данных перед прямым распространением.

Мы представляем три новые дополнительные функции для класса пользовательских сигналов cGAN, которые связаны с разделением обработки сети генератора и сети дискриминатора.

Архитектура генеративной сети выбрана произвольно и имеет 7 слоев - входной слой, 5 скрытых слоев и выходной слой. Правильное определение можно осуществить с помощью поиска нейронной архитектуры, описанной здесь, но для наших целей этих допущений будет достаточно для демонстрации cGAN. Настройки сети определены в массиве settings, который мы используем для инициализации экземпляра сетевого класса под названием GEN.

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

Таким образом, сочетание этих четырех предыдущих изменений с прогнозируемым значением составит входные данные для сети дискриминатора, которую мы рассмотрим позже. Используемый базовый класс сети активируется с помощью фиксированного softplus. Поскольку полный исходный код предоставлен, читатели могут легко настроить его в соответствии со своими потребностями. Таким образом, единственными настраиваемыми параметрами нашего класса сигнала будут скорость обучения, количество эпох обучения и размер набора данных для обучения. Им присвоены имена m_learning_rate, m_epochs и m_train_set, соответственно. Ниже показано, как мы загружаем входные данные сети, осуществляем прямое распространение и обучаем сеть на каждом новом баре в функции get output:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSignalCGAN::GetOutput(double &GenOut, bool &DisOut)
{  GenOut = 0.0;
   DisOut = false;
   for(int i = m_epochs; i >= 0; i--)
   {  for(int ii = m_train_set; ii >= 0; ii--)
      {  vector _in, _out;
         vector _in_new, _out_new, _in_old, _out_old;
         _in_new.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_INPUTS);
         _in_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1 + 1, __GEN_INPUTS);
         _in = Norm(_in_new, _in_old);
         GEN.Set(_in);
         GEN.Forward();
         if(ii > 0)// train
         {  _out_new.CopyRates(m_symbol.Name(), m_period, 8, ii, __GEN_OUTPUTS);
            _out_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_OUTPUTS);
            _out = Norm(_out_new, _out_old);
            
                ...

         }
         else if(ii == 0 && i == 0)
         { 
                ...
         }
      }
   }
}

Наша GAN является условной, поскольку входы в сеть генератора не являются случайными, а входы в сеть дискриминатора являются двойными, фиксирующими вход в генератор и его выходы. Таким образом, роль сети дискриминатора заключается в определении того, получены ли ее входные данные из последовательности реального времени из пяти последовательных изменений цен закрытия, ИЛИ они представляют собой сопряжение входных данных сети генератора с ее выходными данными. Другими словами, она определяет, являются ли входные данные "реальными" или "поддельными".

Это означает, что выходные данные сети дискриминатора очень простые - булевые. Либо входные данные являются полностью рыночными (true), либо они частично созданы генератором (false). Эти варианты представлены как 1 и 0 соответственно. В результате тестовых запусков после обучения возвращаемое значение представляет собой число с плавающей точкой в диапазоне от 0,0 до 1,0. Итак, чтобы обучить нашу сеть дискриминатора, мы будем попеременно передавать ей реальные изменения цен закрытия в виде 5 точек данных (то есть 5 последовательных изменений) и еще 5 изменений цен закрытия, из которых только 4 являются реальными, а пятое - прогнозное значение сети генератора. Обучение на реальных данных частично выполняется функцией R, код которой приведен ниже:

//+------------------------------------------------------------------+
//| Process Real Data in Discriminator                               |
//+------------------------------------------------------------------+
void CSignalCGAN::R(vector &IN, vector &OUT)
{  vector _out_r, _out_real, _in_real;
   _out_r.Copy(OUT);
   _in_real.Copy(IN);
   Sum(_in_real, _out_r);
   DIS.Set(_in_real);
   DIS.Forward();
   _out_real.Resize(__DIS_OUTPUTS);
   _out_real.Fill(1.0);
   DIS.Get(_out_real);
   DIS.Backward(m_learning_rate);
}

а для обучения поддельных данных используется функция F:

//+------------------------------------------------------------------+
//| Process Fake Data in Discriminator                               |
//+------------------------------------------------------------------+
void CSignalCGAN::F(vector &IN, vector &OUT)
{  vector _out_f, _out_fake, _in_fake;
   _out_f.Copy(OUT);
   _in_fake.Copy(IN);
   Sum(_in_fake, _out_f);
   DIS.Set(_in_fake);
   DIS.Forward();
   _out_fake.Resize(__DIS_OUTPUTS);
   _out_fake.Fill(0.0);
   DIS.Get(_out_fake);
   DIS.Backward(m_learning_rate);
}

Эти две функции вызываются внутри функции get output, как показано ниже:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSignalCGAN::GetOutput(double &GenOut, bool &DisOut)
{  GenOut = 0.0;
   DisOut = false;
   for(int i = m_epochs; i >= 0; i--)
   {  for(int ii = m_train_set; ii >= 0; ii--)
      {  
                ...

         if(ii > 0)// train
         {  _out_new.CopyRates(m_symbol.Name(), m_period, 8, ii, __GEN_OUTPUTS);
            _out_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_OUTPUTS);
            _out = Norm(_out_new, _out_old);
            //
            int _dis_sort = MathRand()%2;
            if(_dis_sort == 0)
            {  F(_in, GEN.output);
               GEN.Get(_out);
               GEN.Backward(DIS.output, m_learning_rate);
               R(_in, _out);
            }
            else if(_dis_sort == 1)
            {  R(_in, _out);
               GEN.Get(_out);
               GEN.Backward(DIS.output, m_learning_rate);
               F(_in, GEN.output);
            }
         }
         else if(ii == 0 && i == 0)
         {  GenOut = GEN.output[0];
            DisOut = (((DIS.output[0] >= 0.5 && GenOut >= 0.5)||(DIS.output[0] < 0.5 && GenOut < 0.5)) ? true : false);
         }
      }
   }
}

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

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


Интеграция cGAN в класс сигналов MQL5

Чтобы cGAN работала в классе сигналов, нам нужно встроить функции условия покупки и продажи для вызова функции GetOutput(), которая возвращает две переменные. Расчетное изменение цены закрытия, которое фиксируется double-переменной GenOut, и логическая переменная DisOut, которая показывает, смогло ли это изменение прогноза закрытия обмануть сеть дискриминатора. Читатель может свободно применить настройки, в которых для определения рыночных условий используются только выходные данные генератора, как это обычно происходит при генерации изображений, что является наиболее распространенным применением GAN. Однако проверка этих прогнозов сетью дискриминатора является дополнительной мерой безопасности при оценке условий, и именно поэтому она включена здесь.

Все входные значения сети нормализованы и находятся в диапазоне от -1,0 до +1,0, и таким же образом мы ожидаем, что выходные значения будут по большей части находиться в аналогичном диапазоне. Это означает, что наш генератор дает нам прогнозируемое процентное изменение цены закрытия. Мы можем умножить его на 100, чтобы получить значение, не превышающее 100. Знак этого значения, положительный или отрицательный, укажет, следует ли нам открывать длинную или короткую позицию соответственно. Таким образом, для обработки условий и получения целочисленного выходного значения в диапазоне от 0 до 100 нам понадобятся наши функции условий на покупку:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalCGAN::LongCondition(void)
{  int result = 0;
   double _gen_out = 0.0;
   bool _dis_out = false;
   GetOutput(_gen_out, _dis_out);
   _gen_out *= 100.0;
   if(_dis_out && _gen_out > 50.0)
   {  result = int(_gen_out);
   }
   //printf(__FUNCSIG__ + " generator output is: %.5f, which is backed by discriminator as: %s", _gen_out, string(_dis_out));
   return(result);
}

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


Тестирование и проверка

Если мы выполним тестовые прогоны с советником, собранным в Мастере MQL5 (инструкции приведены здесь и здесь), мы получим следующие результаты в одном из прогонов:

r2

c2

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

Из-за этого дополнительного подтверждения наши результаты нелегко воспроизвести при каждом тестовом прогоне, тем более что архитектура нашей сети очень мала, учитывая, что мы использовали только 5 скрытых слоев, каждый размером всего лишь в 5. Чтобы получить более последовательные результаты с помощью проверки сетью дискриминатора, следует обучать сети с 5–25 скрытыми слоями, где размер каждого, вероятно, не должен быть меньше 100. Размер слоя, а не его количество, как правило, является ключевым фактором для получения более надежных результатов сети.

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


Заключение

Мы увидели, как условные генеративно-состязательные сети (cGAN) можно превратить в пользовательский класс сигналов, который можно собрать в виде советника с помощью Мастера MQL5. cGAN представляет собой модификацию GAN, поскольку она использует неслучайные данные при обучении сети генератора. Входные данные для данных дискриминатора в нашем случае представляли собой сопряжение входных данных сети генератора с выходными данными генератора. Нейронные сети в процессе обучения изучают веса, поэтому хорошей практикой является наличие и использование возможностей для регистрации этих весов сети по завершении процесса обучения. Мы не рассматривали и не исследовали эти преимущества в ходе тестовых прогонов, проведенных для этой статьи.

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

Кроме того, важный предварительный вопрос о поиске нейронной архитектуры был "урезан", поскольку ввыходил за рамки нашей темы. Однако любой, кто знаком с сетями, знает, что это очень чувствительный к производительности аспект нейронных сетей, который требует внимания перед обучением и последующим развертыванием любой сети. Таким образом, эти три ключевых аспекта не были должным образом рассмотрены, хотя они и важны. Это значит, что читателю настоятельно рекомендуется использовать их в качестве отправной точки для разработки и дальнейшего развития этого класса cGAN, прежде чем его можно будет использовать в торговле. Как всегда, идеи, изложенные в статье, не следует рассматривать как инвестиционные советы. Читателям следует проявлять осмотрительность. Удачной охоты!

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

Прикрепленные файлы |
cgan.mq5 (6.54 KB)
SignalWZ_22_1.mqh (11.53 KB)
Cgan.mqh (12.51 KB)
Методы оптимизации библиотеки Alglib (Часть II) Методы оптимизации библиотеки Alglib (Часть II)
В статье продолжим изучение оставшихся методов оптимизации из библиотеки ALGLIB, уделяя особое внимание их тестированию на сложных многомерных функциях. Это позволит нам не только оценить эффективность каждого из алгоритмов, но и выявить их сильные и слабые стороны в различных условиях.
Нейросети в трейдинге: Адаптивное представление графов (NAFS) Нейросети в трейдинге: Адаптивное представление графов (NAFS)
Предлагаем познакомиться с методом NAFS (Node-Adaptive Feature Smoothing) — это непараметрический подход к созданию представлений узлов, который не требует обучения параметров. NAFS извлекает характеристики каждого узла, учитывая его соседей, и затем адаптивно комбинирует эти характеристики для формирования конечного представления.
Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков
В статье рассмотрим создание индикатора, отображающего на интерактивной панели статистику торговли по счёту и в разрезе символов и торговых стратегий. Код напишем, основываясь на примерах из Документации и статьи о работе с базами данных.
Разработка системы репликации (Часть 54): Появление первого модуля Разработка системы репликации (Часть 54): Появление первого модуля
В этой статье мы рассмотрим, как собрать первый из действительно функциональных модулей для использования в системе репликации/моделирования, который также будет иметь общее назначение, чтобы служить и другим целям. Мы говорим о модуле индикатора мыши.