Português
preview
Нейросети в трейдинге: Мультизадачное обучение на основе модели ResNeXt

Нейросети в трейдинге: Мультизадачное обучение на основе модели ResNeXt

MetaTrader 5Торговые системы |
473 1
Dmitriy Gizlyk
Dmitriy Gizlyk

Введение

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

Среди современных архитектур сверточных моделей особое внимание привлекает ResNeXt, представленная в работе "Aggregated Residual Transformations for Deep Neural Networks". ResNeXt демонстрирует способности выявлять локальные и глобальные зависимости, а также эффективно работать с многомерными данными, снижая вычислительную сложность за счет групповой свертки.

Одним из ключевых направлений финансового анализа на основе глубокого обучения является многозадачное обучение (Multi-Task Learning, MTL). Этот подход позволяет одновременно решать несколько связанных задач, улучшая точность моделей и их способность к обобщению. В отличие от классического подхода, когда каждая модель решает отдельную задачу, многозадачное обучение использует совместные представления данных, что делает модель более устойчивой к рыночным изменениям и улучшает процесс обучения. Такой подход особенно полезен для прогнозирования рыночных трендов, оценки рисков и определения стоимости активов, поскольку финансовые рынки динамичны и зависят от множества факторов.

В работе "Collaborative Optimization in Financial Data Mining Through Deep Learning and ResNeXt" был представлен фреймворк интеграции архитектуры ResNeXt в многозадачные модели. Представленное решение открывает новые возможности в обработке временных рядов, выявлении пространственно-временных закономерностей и формировании точных прогнозов. Групповая свертка и остаточные блоки ResNeXt повышают скорость обучения и снижают вероятность потери важных признаков, что делает этот метод особенно актуальным для финансового анализа.

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


Архитектура ResNeXt

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

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

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

Обычные нейроны в искусственных нейронных сетях выполняют взвешенную сумму входных значений, что является основной операцией в сверточных и полносвязных слоях. Этот процесс можно разделить на три основных этапа: разбиение, преобразование и агрегация. Однако, в ResNeXt вместо обычного преобразования вводится более гибкий подход, при котором функции преобразования могут быть сложнее и даже представлять собой мини-модели. Это приводит к появлению концепции "модель в нейроне" (Network-in-Neuron), которая расширяет возможности архитектуры, благодаря новому измерению — количеству каналов (cardinality). В отличие от ширины или глубины модели, количество каналов определяет число независимых сложных преобразований в каждом блоке. Экспериментальные исследования показывают, что увеличение этого параметра может быть эффективнее, чем увеличение глубины или ширины сети, поскольку обеспечивает лучший баланс между производительностью и вычислительной эффективностью.

Все блоки в ResNeXt имеют одинаковую структуру, использующую bottleneck-модуль. Он состоит из:

  • начального сверточного слоя 1×1, который уменьшает размерность признаков,
  • основного сверточного слоя 3×3, выполняющего основную обработку данных,
  • финального сверточного слоя 1×1, возвращающего исходную размерность.

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

Одним из основных усовершенствований ResNeXt является использование групповых свёрток (Grouped Convolutions). В этом подходе входные данные разбиваются на несколько независимых групп, каждая из которых обрабатывается отдельным сверточным фильтром, после чего результаты объединяются. Такой механизм позволяет уменьшить количество параметров модели, сохраняет высокую пропускную способность сети и улучшает вычислительную эффективность без значительных потерь в точности.

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

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

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

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

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

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

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

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



Реализация средствами MQL5

После рассмотрения теоретических аспектов фреймворка мультизадачного обучения на основе ResNeXt, мы переходим к реализации собственного видения предложенных подходов средствами MQL5. И начнем мы свою работу с построения базовых блоков архитектуры ResNeXtbottleneck-модулей.

Bottleneck-модуль


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

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

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

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

Описанную выше архитектуру мы реализуем в рамках объекта CNeuronResNeXtBottleneck, структура которого представлена ниже.

class CNeuronResNeXtBottleneck :  public CNeuronConvOCL
  {
protected:
   CNeuronConvOCL          cProjectionIn;
   CNeuronBatchNormOCL     cNormalizeIn;
   CNeuronTransposeRCDOCL  cTransposeIn;
   CNeuronConvOCL          cFeatureExtraction;
   CNeuronBatchNormOCL     cNormalizeFeature;
   CNeuronTransposeRCDOCL  cTransposeOut;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtBottleneck(void){};
                    ~CNeuronResNeXtBottleneck(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint window,
                          uint step, uint units_count, uint group_size, uint groups,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtBottleneck;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

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

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

bool CNeuronResNeXtBottleneck::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint chanels_in, uint chanels_out, uint window,
                                   uint step, uint units_count, uint group_size, uint groups,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1;
   if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, group_size * groups, group_size * groups,
                                              chanels_out, units_out, 1, optimization_type, batch))
      return false;

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

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

//--- Projection In
   uint index = 0;
   if(!cProjectionIn.Init(0, index, OpenCL, chanels_in, chanels_in, group_size * groups,
                                                    units_count, 1, optimization, iBatch))
      return false;
   index++;
   if(!cNormalizeIn.Init(0, index, OpenCL, cProjectionIn.Neurons(), iBatch, optimization))
      return false;
   cNormalizeIn.SetActivationFunction(LReLU);

Полученные проекции мы нормализуем и добавим LReLU в качестве функции активации.

Следует обратить внимание, что в результате данных операций мы получаем данные в виде трехмерного тензора [Time, Group, Dimеnsion]. Для целей построения алгоритма независимой обработки отдельных групп мы вынесем размерность идентификатора группы на первое место с помощью объекта транспонирования трехмерного тензора.

   index++;
   if(!cTransposeIn.Init(0, index, OpenCL, units_count, groups, group_size, optimization, iBatch))
      return false;
   cTransposeIn.SetActivationFunction((ENUM_ACTIVATION)cNormalizeIn.Activation());

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

//--- Feature Extraction
   index++;
   if(!cFeatureExtraction.Init(0, index, OpenCL, group_size * window, group_size * step, group_size,
                                                           units_out, groups, optimization, iBatch))
      return false;

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

За сверточным слоем мы добавляем пакетную нормализацию с функцией активации LReLU.

   index++;
   if(!cNormalizeFeature.Init(0, index, OpenCL, cFeatureExtraction.Neurons(), iBatch, optimization))
      return false;
   cNormalizeFeature.SetActivationFunction(LReLU);

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

//--- Projection Out
   index++;
   if(!cTransposeOut.Init(0, index, OpenCL, groups, units_out, group_size, optimization, iBatch))
      return false;
   cTransposeOut.SetActivationFunction((ENUM_ACTIVATION)cNormalizeFeature.Activation());
//---
   return true;
  }

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

Следующим этапом нашей работы является построение алгоритма прямого прохода, который мы реализуем в методе feedForward.

bool CNeuronResNeXtBottleneck::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Projection In
   if(!cProjectionIn.FeedForward(NeuronOCL))
      return false;

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

Результаты проекции нормализуем и транспонируем в представление отдельных групп.

   if(!cNormalizeIn.FeedForward(cProjectionIn.AsObject()))
      return false;
   if(!cTransposeIn.FeedForward(cNormalizeIn.AsObject()))
      return false;

В блоке извлечения признаков мы осуществляем операции групповой свертки и нормализуем полученные результаты.

//--- Feature Extraction
   if(!cFeatureExtraction.FeedForward(cTransposeIn.AsObject()))
      return false;
   if(!cNormalizeFeature.FeedForward(cFeatureExtraction.AsObject()))
      return false;

Извлечённые признаки отдельных групп обратно транспонируем в единую многомерную последовательность и проецируем в заданное пространство признаков средствами родительского класса.

//--- Projection Out
   if(!cTransposeOut.FeedForward(cNormalizeFeature.AsObject()))
      return false;
   return CNeuronConvOCL::feedForward(cTransposeOut.AsObject());
  }

Логический результат выполнения операций возвращаем вызывающей программе и завершаем работу метода.

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

Модуль остаточных связей


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

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

В рамках нашей работы мы создали подобный модуль в виде объекта CNeuronResNeXtResidual, структура которого приведена ниже.

class CNeuronResNeXtResidual:  public CNeuronConvOCL
  {
protected:
   CNeuronTransposeOCL     cTransposeIn;
   CNeuronConvOCL          cProjectionTime;
   CNeuronBatchNormOCL     cNormalizeTime;
   CNeuronTransposeOCL     cTransposeOut;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtResidual(void){};
                    ~CNeuronResNeXtResidual(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out,
                          uint units_in, uint units_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtResidual;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

При разработке данного объекта мы использовали подходы, аналогичные построению модулями Bottleneck, но адаптированных к другим измерениям исходного тензора.

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

bool CNeuronResNeXtResidual::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                  uint chanels_in, uint chanels_out,
                                  uint units_in, uint units_out,
                                  ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_in, chanels_out,
                            units_out, 1, optimization_type, batch))
      return false;

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

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

   int index=0;
   if(!cTransposeIn.Init(0, index, OpenCL, units_in, chanels_in, optimization, iBatch))
      return false;

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

   index++;
   if(!cProjectionTime.Init(0, index, OpenCL, units_in, units_in, units_out, chanels_in, 1, optimization, iBatch))
      return false;

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

   index++;
   if(!cNormalizeTime.Init(0, index, OpenCL, cProjectionTime.Neurons(), iBatch, optimization))
      return false;

Далее нам предстоит скорректировать пространство признаков. Для этого мы осуществляем обратное транспонирование данных. А непосредственно проекция осуществляется средствами родительского класса.

   index++;
   if(!cTransposeOut.Init(0, index, OpenCL, chanels_in, units_out, optimization, iBatch))
      return false;
//---
   return true;
  }

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

Следующим этапом мы переходим к построению алгоритма прямого прохода в рамках метода feedForward.

bool CNeuronResNeXtResidual::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Projection Timeline
   if(!cTransposeIn.FeedForward(NeuronOCL))
      return false;

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

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

   if(!cProjectionTime.FeedForward(cTransposeIn.AsObject()))
      return false;

Полученные данные нормализуются.

   if(!cNormalizeTime.FeedForward(cProjectionTime.AsObject()))
      return false;

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

//--- Projection Chanels
   if(!cTransposeOut.FeedForward(cNormalizeTime.AsObject()))
      return false;
   return CNeuronConvOCL::feedForward(cTransposeOut.AsObject());
  }

Последняя проекция осуществляется средствами родительского класса. Логический результат выполнения операций возвращаем вызывающей программе и завершаем работу метода прямого прохода.

Легко заметить, что алгоритм метода прямого прохода имеет линейную структуру. Это приводит к линейности потоков распределения градиентов ошибки в ходе операций обратного прохода. Поэтому методы обратного прохода я предлагаю оставить для самостоятельного изучения, аналогично объекту CNeuronResNeXtBottleneck. Полный код указанных объектов и всех их модулей вы найдете во вложении.

Блок ResNeXt


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

class CNeuronResNeXtBlock :  public CNeuronBaseOCL
  {
protected:
   uint                     iChanelsOut;
   CNeuronResNeXtBottleneck cBottleneck;
   CNeuronResNeXtResidual   cResidual;
   CBufferFloat             cBuffer;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtBlock(void){};
                    ~CNeuronResNeXtBlock(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint window,
                          uint step, uint units_count, uint group_size, uint groups,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtBlock;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

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

Все внутренние объекты объявлены статично, что позволяет нам оставить пустыми конструктор и деструктор класса. Инициализация всех объявленных и унаследованных объектов осуществляется, как и ранее, в методе Init. Структура параметров данного метода полностью заимствована у объекта CNeuronResNeXtBottleneck.

bool CNeuronResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint chanels_in, uint chanels_out, uint window,
                               uint step, uint units_count, uint group_size, uint groups,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1;
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_out * chanels_out, optimization_type, batch))
      return false;

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

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

   iChanelsOut = chanels_out;

И инициализируем внутренние объекты построенных выше информационных потоков.

   int index = 0;
   if(!cBottleneck.Init(0, index, OpenCL, chanels_in, chanels_out, window, step, units_count,
                       group_size, groups, optimization, iBatch))
      return false;
   index++;
   if(!cResidual.Init(0, index, OpenCL, chanels_in, chanels_out, units_count, units_out, optimization, iBatch))
      return false;

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

   if(!cResidual.SetGradient(cBottleneck.getGradient(), true))
      return false;
   if(!SetGradient(cBottleneck.getGradient(), true))
      return false;
//---
   return true;
  }

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

Далее мы переходим к построению алгоритмов прямого прохода в рамках метода feedForward. Здесь все довольно просто.

bool CNeuronResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cBottleneck.FeedForward(NeuronOCL))
      return false;
   if(!cResidual.FeedForward(NeuronOCL))
      return false;

В параметрах метода мы получаем указатель на объект исходных данных, который сразу передаем в одноименные методы объектов двух информационных потоков. Полученные результаты мы суммируем и нормализуем.

   if(!SumAndNormilize(cBottleneck.getOutput(), cResidual.getOutput(), Output, iChanelsOut, true, 0, 0, 0, 1))
      return false;
//--- result
   return true;
  }

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

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

bool CNeuronResNeXtBlock::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

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

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

   if(!NeuronOCL.calcHiddenGradients(cBottleneck.AsObject()))
      return false;

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

   CBufferFloat *temp = NeuronOCL.getGradient();

Затем, проверим соответствие вспомогательного буфера размерности буфера градиентов. При необходимости, скорректируем вспомогательный буфер.

   if(cBuffer.GetOpenCL() != OpenCL ||
      cBuffer.Total() != temp.Total())
     {
      if(!cBuffer.BufferInitLike(temp))
         return false;
     }

И передадим его указатель в объект исходных данных.

   if(!NeuronOCL.SetGradient(GetPointer(cBuffer), false))
      return false;

Теперь мы можем смело передать градиент ошибки по второму информационному потоку без страха потери данных.

   if(!NeuronOCL.calcHiddenGradients(cResidual.AsObject()))
      return false;

Значения двух информационных потоков суммируем и возвращаем указатели на буферы данных в исходное состояние.

   if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1))
      return false;
   if(!NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

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

На этом мы завершаем рассмотрение алгоритмов построения методов объекта ResNeXt-блока. С полным кодом указанного объекта и всех его методов вы можете самостоятельно ознакомиться во вложении.

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



Заключение

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

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

Ссылки


Программы, используемые в статье

#ИмяТипОписание
1Research.mq5СоветникСоветник сбора примеров
2ResearchRealORL.mq5
Советник
Советник сбора примеров методом Real-ORL
3Study.mq5СоветникСоветник обучения моделей
4Test.mq5СоветникСоветник для тестирования модели
5Trajectory.mqhБиблиотека классаСтруктура описания состояния системы и архитектуры моделей
6NeuroNet.mqhБиблиотека классаБиблиотека классов для создания нейронной сети
7NeuroNet.clБиблиотекаБиблиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (2430.99 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Renat Akhtyamov
Renat Akhtyamov | 10 февр. 2025 в 11:34

Дмитрий, у Вас огромное количество статей по нейросетям.

Вы предпочитаете зарабатывать на написании статей, а не на трейдинге.

Получается что с помощью нейросети заработать не возможно?

Разработка интерактивного графического пользовательского интерфейса на MQL5 (Часть 2): Добавление элементов управления и адаптивности Разработка интерактивного графического пользовательского интерфейса на MQL5 (Часть 2): Добавление элементов управления и адаптивности
Расширение панели графического интерфейса на MQL5 с помощью динамических функций может существенно улучшить торговый опыт пользователей. Благодаря включению интерактивных элементов, эффектов наведения и обновлению данных в реальном времени эта панель становится мощным инструментом современного трейдера.
Биологический нейрон для прогнозирования финансовых временных рядов Биологический нейрон для прогнозирования финансовых временных рядов
Выстраиваем биологически верную систему нейронов для прогнозирования временных рядов. Внедрение плазмоподобной среды в архитектуру нейронной сети создало своеобразный "коллективный разум", где каждый нейрон влияет на работу системы не только через прямые связи, но и посредством дальнодействующих электромагнитных взаимодействий. Как покажет себя нейронная система моделирования мозга на рынке?
Разработка системы репликации (Часть 65): Нажатие кнопки воспроизведения в сервисе (VI) Разработка системы репликации (Часть 65): Нажатие кнопки воспроизведения в сервисе (VI)
В данной статье мы рассмотрим, как реализовать и решить проблему с указателем мыши при его использовании в сочетании с приложением репликации/моделирования. Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.
От начального до среднего уровня: Оператор SWITCH От начального до среднего уровня: Оператор SWITCH
В данной статье мы узнаем, как использовать оператор SWITCH в ее самой простой и базовой форме. Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.