preview
Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Основные компоненты)

Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Основные компоненты)

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

Введение

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

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

Основу архитектуры составляет фреймворк SEW-ResNet, обеспечивающий устойчивое и эффективное извлечение признаков без потери информации на глубине. На него наслаиваются более умные механизмы: пространственно-временное внимание SSAM и модуль STFS. Первый связывает события в последовательности, формируя причинно обоснованные зависимости, а второй отвечает за адаптивную выборку признаков во времени и пространстве. Всё это превращает S3CE-Net в живую систему восприятия, где каждый импульс несёт смысл.

Суть архитектуры можно выразить коротко: она учится видеть структуру там, где другие видят шум. Благодаря событийной природе, модель работает не на фиксированных интервалах, а на уровне импульсов, что позволяет ей быть ближе к реальному ритму рынка. SSAM акцентирует внимание на тех фрагментах массива анализируемых данных, где действительно происходят изменения, а STFS добавляет гибкость и устойчивость, позволяя модели обучаться без избыточной зависимости от конкретных сценариев. Это делает S3CE-Net целостной системой смыслового анализа потока событий.

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

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

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

Так, теория постепенно обретает дыхание. И возможно, именно в способности модели улавливать ритм и импульс финансового потока скрыта та грань, которая отличает искусственный интеллект от простого алгоритма. Мы продолжаем работу, превращая S3CE-Net в инструмент, способный чувствовать рынок.

Авторская визуализация фреймворка S3CE-Net представлена ниже.

Авторская визуализация фреймворка S3CE-Net



Модуль SSAM

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

Сегодня мы поднимаемся на уровень выше — в структуру основной программы, где закладывается логика взаимодействия между всеми модулями фреймворка. Здесь мы создаём класс CNeuronSSAM, который становится полноправным представителем слоя спайкового внимания внутри архитектуры. Если на стороне OpenCL мы имели дело с чистой математикой, то теперь речь идёт об архитектурной композиции, которая объединяет низкоуровневые вычисления с высокоуровневой нейронной логикой.

class CNeuronSSAM    :  public CNeuronSpikeActivation
  {
protected:
   uint                 iUnits;
   uint                 iHeads;
   uint                 iDimension;
   bool                 bMaskFuture;
   CNeuronSpikeConv     cQKV;
   CParams              cDiaganalBias;
   int                  ciScore;
   CNeuronBaseOCL       cMHAttentionOut;
   CNeuronSpikeConv     cW0;
   CNeuronBaseOCL       cResidual;
   CNeuronBatchNormOCL  cNorm;
   //---
   virtual bool      SpikeMHAttention(void);
   virtual bool      SpikeMHAttentionGrad(void);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSSAM(void) {};
                    ~CNeuronSSAM(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint heads, uint dimension_k,
                          bool mask_future, ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronSSAM;   }
   //--- methods for working with files
   virtual bool      Save(const int file_handle) override;
   virtual bool      Load(const int file_handle) override;
   virtual void      SetOpenCL(COpenCLMy *obj)   override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

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

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

Сердцем класса становится набор внутренних объектов, через которые проходит весь поток вычислений. Объект cQKV отвечает за генерацию тройки тензоров внутренних сущностей Q, K, V, базовый нейронный слой cMHAttentionOut аккумулирует результат многоголового внимания, а cResidual реализует классические приёмы остаточного соединения и нормализацию.

Однако особое место занимает cDiaganalBias — компонент, обеспечивающий корректировку внимания в пространственной проекции. Он воплощает идею Intra-Frame Relative Position Bias, предложенную авторами фреймворка S3CE-Net, — механизм относительного позиционного смещения внутри кадра, который позволяет модели лучше понимать локальные взаимосвязи между токенами.

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

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

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

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

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

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

На этой прочной, статически выверенной основе разворачивается весь дальнейший процесс инициализации, реализованный в методе Init, который превращает класс в полноценный элемент фреймворка S3CE-Net, готовый к работе в реальном времени.

bool CNeuronSSAM::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                       uint units, uint window, uint heads, uint dimension_k,
                       bool mask_future, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeActivation::Init(numOutputs, myIndex, open_cl, units * window, optimization_type, batch))
      return false;

Метод Init отвечает за формирование готового к работе экземпляра нейронного блока и связывает между собой все его внутренние элементы. Работа метода начинается с вызова одноименного метода родительского класса, который задаёт основные параметры слоя.

Далее происходит настройка внутренней архитектуры слоя. Параметры iHeads и iDimension определяют конфигурацию многоголового внимания.

   iHeads = (uint)MathMax(heads, 1);
   iDimension = (uint)MathMax(dimension_k, 1);
   iUnits = units;
   bMaskFuture = mask_future;

Значения проходят через функцию MathMax, чтобы исключить возможность нулевых размеров. Это особенно важно при генерации буферов на стороне OpenCL.

Следующим шагом инициализируются внутренние компоненты — те самые кирпичики, из которых складывается логика слоя. Сначала создаётся объект cQKV, отвечающий за формирование трёх базовых тензоров внимания — Query, Key и Value. Его размер задается как тройное произведение числа голов и размерности векторов, что обеспечивает корректное распределение параметров по всем подпространствам внимания.

   uint index = 0;
   if(!cQKV.Init(0, index, OpenCL, window, window, 3 * iHeads * iDimension, iUnits, 1, optimization, iBatch))
      return false;

Затем инициализируется cDiaganalBias, который создаёт структуру для хранения и вычисления матрицы позиционных смещений — того самого Intra-Frame Relative Position Bias, добавляющего интеллектуальную поправку на относительное расположение элементов внутри кадра. Этот блок можно считать тонкой настройкой внимания: он позволяет модели чувствовать близость токенов в пространстве и корректировать фокусировку в зависимости от их взаимного положения.

   index++;
   if(!cDiaganalBias.Init(0, index, OpenCL, iUnits, optimization, iBatch))
      return false;
   cDiaganalBias.SetActivationFunction(None);
   CBufferFloat* temp=cDiaganalBias.getWeightsParams();
   if(!temp ||
      !temp.Fill(0))
     return false;

На начальном этапе объект cDiaganalBias инициализируется нулевыми значениями, что вполне логично для стадии первичного запуска модели. В этот момент нейронная сеть ещё не располагает знаниями о взаимном расположении элементов внутри кадра — она слепа к внутренней геометрии входного пространства. Нулевая инициализация даёт чистую стартовую позицию, где все относительные смещения равны, а механизм внимания действует исключительно за счёт базовых тензоров Q, K и V.

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

   ciScore = OpenCL.AddBuffer(sizeof(float) * iUnits * iUnits * iHeads, CL_MEM_READ_WRITE);
   if(ciScore == INVALID_HANDLE)
      return false;

Далее идёт последовательное подключение остальных модулей слоя. Объект cMHAttentionOut отвечает за агрегацию результатов многоголового внимания, инициализируется с общей размерностью iUnits * iDimension * iHeads и отключает функцию активации, чтобы сохранить чистое значение после операций внимания.

   index++;
   if(!cMHAttentionOut.Init(0, index, OpenCL, iUnits * iDimension * iHeads, optimization, iBatch))
      return false;
   cMHAttentionOut.SetActivationFunction(None);

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

   index++;
   if(!cW0.Init(0, index, OpenCL, iHeads * iDimension, iHeads * iDimension, window, iUnits, 1, optimization, iBatch))
      return false;

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

   index++;
   if(!cResidual.Init(0, index, OpenCL, Neurons(), optimization, iBatch))
      return false;
   cResidual.SetActivationFunction(None);
   if(!cResidual.SetGradient(cW0.getGradient(), true))
      return false;

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

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

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

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

bool CNeuronSSAM::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cQKV.FeedForward(NeuronOCL))
      return false;

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

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

   if(bTrain)
      if(!cDiaganalBias.FeedForward())
         return false;

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

Следующим этапом вызывается SpikeMHAttention, который фактически является обёрткой одноимённого кернела OpenCL-программы. Его задача подготовить и поставить кернел в очередь выполнения. Здесь формируются все необходимые буферы, задаются параметры, передаются ссылки на Q, K, V, а также на позиционные смещения cDiaganalBias.

   if(!SpikeMHAttention())
      return false;

Вся вычислительная нагрузка, включая многоголовое внимание, нормализацию весов и учёт Intra-Frame Relative Position Bias, происходит исключительно на GPU, используя возможности параллельного исполнения. Такой подход позволяет обрабатывать большие тензоры и сложные операции внимания с высокой производительностью, оставляя CPU свободным для управления потоком данных и контроля состояния модели.

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

   if(!cW0.FeedForward(cMHAttentionOut.AsObject()))
      return false;

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

   if(!SumAndNormilize(NeuronOCL.getOutput(), cW0.getOutput(), cResidual.getOutput(),
                       Neurons() / iUnits, false, 0, 0, 0, 1))
      return false;

Далее необходимо подготовить полученные значения для спайковой активации. Мы сохраняем подход Element-Wise: каждая ячейка выходного тензора обрабатывается независимо, что позволяет точно контролировать динамику спайков.

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

   if(!cNorm.FeedForward(cResidual.AsObject()))
      return false;

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

   return CNeuronSpikeActivation::feedForward(cNorm.AsObject());
  }

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

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

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

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

   if(!CNeuronSpikeActivation::calcInputGradients(cNorm.AsObject()))
      return false;

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

   if(!cResidual.CalcHiddenGradients(cNorm.AsObject()))
      return false;

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

   if(!SpikeMHAttentionGrad())
      return false;

Здесь учитываются все веса внимания, тензоры QKV и позиционные смещения cDiaganalBias. Этот этап критически важен: именно он позволяет корректировать параметры внимания в зависимости от локальной и глобальной структуры анализируемого сигнала.

После этого градиенты передаются к исходным данным. Вначале распределяем ошибку от компонентов Q, K и V.

   if(!NeuronOCL.CalcHiddenGradients(cQKV.AsObject()))
      return false;

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

   if(NeuronOCL.Activation() == None)
     {
      if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getGradient(),
                          NeuronOCL.getGradient(), Neurons() / iUnits, false, 0, 0, 0, 1))
         return false;
     }
   else
     {
      if(!DeActivation(NeuronOCL.getOutput(), cResidual.getPrevOutput(),
                       cResidual.getGradient(), NeuronOCL.Activation()))
         return false;
      if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getPrevOutput(),
                          NeuronOCL.getGradient(), Neurons() / iUnits, false, 0, 0, 0, 1))
         return false;
     }
//---
   return true;
  }

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

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

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

bool CNeuronSSAM::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cQKV.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cDiaganalBias.UpdateInputWeights())
      return false;
   if(!cW0.UpdateInputWeights(cMHAttentionOut.AsObject()))
      return false;
   if(!cNorm.UpdateInputWeights(cResidual.AsObject()))
      return false;
//---
   return CNeuronSpikeActivation::updateInputWeights(cNorm.AsObject());
  }

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

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

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


Интеграция со спайковой версией ResNeXt

Следующим этапом нашей работы станет интеграция модуля SSAM в ранее реализованный алгоритм SEW-ResNeXt. В авторском описании фреймворка S3CE‑Net подчёркивается, что несколько модулей внимания можно интегрировать между блоками SEW-ResNet на разных уровнях. И такой подход естественно укладывается в структуру последовательных моделей без необходимости создавать дополнительные модули или каркасы.

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

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

Следовательно, интеграция SSAM в обе ветви блока SEW‑ResNeXt становится естественным продолжением архитектурной концепции S3CE‑Net, обеспечивая гибкость, устойчивость и возможность точного захвата пространственно-временных зависимостей анализируемых данных.

Именно в рамках описанной концепции создаётся новый модуль CNeuronSSAMResNeXtBlock, который наследует базовый функционал от CNeuronSpikeResNeXtBlock.

class CNeuronSSAMResNeXtBlock   :  public CNeuronSpikeResNeXtBlock
  {
protected:
   CNeuronSSAM        caSSAM[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSSAMResNeXtBlock(void) {};
                    ~CNeuronSSAMResNeXtBlock(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint units_in,
                          uint units_out, uint group_size, uint groups,
                          uint heads, uint dimension_k,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronSSAMResNeXtBlock;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

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

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

bool CNeuronSSAMResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint chanels_in, uint chanels_out, uint units_in,
                                   uint units_out, uint group_size, uint groups,
                                   uint heads, uint dimension_k,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeResNeXtBlock::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_out,
                                      units_in, units_out, group_size, groups, optimization_type, batch))
      return false;

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

Далее инициализируются два модуля SSAM — по одному для каждого информационного потока блока. Первый экземпляр работает с выходами Bottleneck во временном измерении, применяя маскирование будущих шагов (mask_future = true).

   if(!caSSAM[0].Init(0, 0, OpenCL, units_out, chanels_out, heads, dimension_k, true, optimization, iBatch))
      return false;

Второй экземпляр обрабатывает результаты пространственного Bottleneck. И здесь мы уже не используем маскирование (mask_future = false).

   if(!caSSAM[1].Init(0, 0, OpenCL, chanels_out, units_out, heads, dimension_k, false, optimization, iBatch))
      return false;

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

   CBufferFloat* temp = caBottleneck[0].getGradient();
   if(!caBottleneck[0].SetGradient(caSSAM[0].getGradient(), false))
      return false;
   if(!caSSAM[0].SetGradient(temp, false))
      return false;
//---
   return true;
  }

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

В результате метод Init создаёт полностью готовый к работе модуль, где SEW‑ResNeXt и SSAM интегрированы в единую вычислительную цепочку, а градиенты и буферы организованы поддерживать стабильность обучения и максимальную эффективность.

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

bool CNeuronSSAMResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!caTranspose[0].FeedForward(NeuronOCL))
      return false;

Сначала входной сигнал проходит через транспонирующий слой caTranspose[0], подготавливая данные для параллельной обработки и выравнивая их по пространственному измерению. Далее последовательные Bottleneck SEW‑ResNeXt обрабатывают входной поток: первый анализирует исходные данные напрямую в виде временной последовательности.

   if(!caBottleneck[0].FeedForward(NeuronOCL))
      return false;

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

   if(!caBottleneck[1].FeedForward(caTranspose[0].AsObject()))
      return false;

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

   for(int i = 0; i < 2; i++)
      if(!caSSAM[i].FeedForward(caBottleneck[i].AsObject()))
         return false;

Результат второго модуля дополнительно проходит через транспонирующий слой, что обеспечивает правильное выравнивание перед объединением с остаточным сигналом cResidual.

   if(!caTranspose[1].FeedForward(caSSAM[1].AsObject()))
      return false;

Затем выполняется суммирование выходов SSAM и магистрали остаточных связей. Результат сохраняется в буфере первого слоя модуля Element-Wise функции cG. Это обеспечивает согласованное объединение всех потоков информации и стабилизирует значения перед передачей на последующие элементы блока.

   if(!cResidual.FeedForward(NeuronOCL))
      return false;
//---
   CNeuronBaseOCL* current = cG[0];
   if(!current)
      return false;
   if(!SumAndNormilize(caSSAM[0].getOutput(), caTranspose[1].getOutput(),
                       current.getOutput(), caTranspose[1].GetCount(), false, 0, 0, 0, 1) ||
      !SumAndNormilize(current.getOutput(), cResidual.getOutput(),
                       current.getOutput(), caTranspose[1].GetCount(), false, 0, 0, 0, 1))
      return false;

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

   for(int i = 1; i < cG.Total(); i++)
     {
      current = cG[i];
      if(!current ||
         !current.FeedForward(cG[i - 1]))
         return false;
     }

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

   return CNeuronSpikeActivation::feedforward(cG[-1]);
  }

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

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

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

В целом, CNeuronSSAMResNeXtBlock выступает как интеграционный слой, который гармонично объединяет SEW‑ResNeXt и SSAM, позволяя одновременно учитывать локальные и глобальные пространственно-временные зависимости данных, без нарушения модульной структуры модели. Полный код класса и всех его методов представлен во вложении.

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


Заключение

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

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3193.9 KB)
От начального до среднего уровня: Struct (IV) От начального до среднего уровня: Struct (IV)
В данной статье мы рассмотрим, как создавать так называемый структурный код, в котором весь контекст и способы манипулирования переменными и информацией помещаются в структуру, чтобы создать подходящий контекст для реализации любого кода. Итак, мы рассмотрим необходимость использования приватной (private) части кода, чтобы отделить то, что является общедоступным, от того, что не является таковым, соблюдая тем самым правило инкапсуляции и сохраняя контекст, для которого была создана структура данных.
От начального до среднего уровня: Struct (III) От начального до среднего уровня: Struct (III)
В этой статье мы рассмотрим, что такое структурированный код. Многие люди путают структурированный код с организованным кодом, однако между этими двумя понятиями есть разница. Об этом и будет рассказано в этой статье. Несмотря на кажущуюся сложность, которую вы почувствуете при первом знакомстве с этим типом написания кода, я постарался подойти к этому вопросу как можно проще. Но данная статья - лишь первый шаг к чему-то большему.
Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров
Переходим к использованию сразу нескольких экземпляров терминала на сервере, организовав простую панель управления запуском и остановкой. Теперь пришло время расширять функциональность и переходить к следующим этапам — реализации более сложных возможностей, таких как управление несколькими экземплярами, хранение состояния, интеграция с MetaTrader5 API и веб-интерфейс с полной информацией о терминалах.
Автоматизация торговых стратегий на MQL5 (Часть 13): Создание торгового алгоритма для паттерна "Голова и Плечи" Автоматизация торговых стратегий на MQL5 (Часть 13): Создание торгового алгоритма для паттерна "Голова и Плечи"
В настоящей статье мы автоматизируем паттерн «Голова-Плечи» на MQL5. Мы анализируем его архитектуру, реализуем советник для его обнаружения и торговли, а также тестируем результаты на истории. Этот процесс раскрывает практичный торговый алгоритм, который можно усовершенствовать.