
Нейросети в трейдинге: Эффективное извлечение признаков для точной классификации (Построение объектов)
Введение
В предыдущей статье мы познакомились с теоретическими аспектами фреймворка Mantis — фундаментальной моделью для классификации временных рядов, лишённой тяжеловесности регрессионных алгоритмов и при этом не уступающей им в точности. Она превращает сырые данные анализируемой последовательности в ясные сигналы с понятными уровнями уверенности, предлагая новое качество анализа.
Вообразите себе классическую модель прогнозирования: она учится минимизировать ошибку предсказания, но часто терпит поражение перед внезапными всплесками волатильности. Такие алгоритмы дрейфуют вслед за рынком. Их сложно адаптировать. Они подвержены переобучению и не дают прозрачных оценок надёжности. Нам нужно больше, чем просто прогнозирование будущего значения. Нужно распознавание режима рынка в реальном времени, когда цена движется по типичному или аномальному сценарию. Именно здесь Mantis берёт своё начало как модель, обученная не столько на регрессии, сколько на понимании паттернов.
В основе Mantis лежит идея токенизации временного ряда, заимствованная у визуальных трансформеров. Вместо того чтобы сканировать десятки тысяч тиков или секунд, ряд делится на строго заданное число патчей. Благодаря этому временные ряды различного размера обрабатываются идентичным числом шагов, что избавляет от необходимости перекраивать архитектуру под разные масштабы. Потом происходит магия гибридного внимания: в одном окне зрения модель исследует микро изменения через свёртки и пулинг, а во втором захватывает долгосрочные тенденции, используя глобальное внимание. Комбинация этих двух взглядов позволяет модели одновременно увидеть мельчайшие колебания и ощутить общий вектор движения.
Ключевой секретный ингредиент модели — контрастное предварительное обучение. Представьте два слегка искажённых варианта одного фрагмента истории цены. Mantis учится притягивать их эмбеддинги в пространстве, словно сжать несколько полос света в один луч, и одновременно отталкивать совсем разные сегменты, как электрон и позитрон. Такой контраст дает модели устойчивое восприятие паттерна, что особенно ценно при изменениях амплитуды, небольших сдвигах во времени или нестандартном шуме.
Завершает процесс температурное шкалирование — финальная калибровка выводов. Трейдера интересует не просто факт «разворот/не разворот», а степень уверенности. Mantis умеет выдавать не абстрактные значения, а правдоподобные апостериорные вероятности. Практика показывает: если модель утверждает «80% разворота», то в реальности примерно 80 из 100 таких сигналов окажутся верными.
Настоящая ценность Mantis раскрывается, когда на вход поступает мультиканальная информация. Танцующий в такт RSI, объемы, скользящие средние и корреляции валютных пар формируют сложную картину. Прямая конкатенация этих сигналов ведёт к взрыву числа параметров, а раздельная обработка теряет перекрёстные связи. Решение Mantis — лёгкие адаптеры, которые сжимают межканальные взаимодействия, оставляя только главное: силу связи между индикаторами. Модель экономит память, а трейдер — время на настройку.
Чтобы глубже понять архитектуру Mantis, пройдёмся по её шагам. Первый этап — первичная свёртка: временной ряд с d каналами проходит через слой с 256 выходами, формируя плотное представление. Затем тензор делится на равные части и поканальный mean-pooling превращает эти данные в 32 токена, каждый из которых несёт локальную информацию.
Параллельно строится дифференциальный поток: разности первого порядка исходных данных усиливают чувствительность к краткосрочной динамике. Полученные данные проходят тот же путь патчинга.
Третий и четвертый поток — статистический — собирают среднее и стандартное отклонение исходных данных в рамках тех же 32 окон, передавая общий фон волатильности и уровня.
Эти четыре потока проходят через индивидуальные линейные проекторы для выравнивания размерности, а затем объединяются и проецируются в глобальные токены заданного размера.
К последовательности токенов, кодирующих анализируемую последовательность добавляется класс-токен и синусоидальное позиционное кодирование, чтобы модель не потеряла информацию о порядке. После этого данные вводятся в трансформер: 6 слоёв, многооконное внимание с 8 головами, нормализация, двухуровневый FFN с GELU и Dropout 10% на этапе обучения.
Модель обучалась в 2 этапа. На первом, контрастном, — на объединённом корпусе из десяти публичных датасетов, более 7 миллионов временных рядов, 100 эпох, батч 2048 на 4 NVIDIA Tesla V100. Это напоминает марш-бросок интенсивной закалки. На втором этапе добавляется классификационная голова и применяется температурная калибровка выходов. Такой двухфазный подход позволяет достичь редкой для нейросетей комбинации — высокой точности и надёжной интерпретируемости.
Наконец, о гибкости. Чтобы справляться с задачами разного масштаба и обувать модели в разные ботинки в зависимости от бюджета и структуры данных, авторы фреймворка Mantis предлагают использовать пять типов адаптеров:
- PCA и Truncated SVD — классические методы линейного снижения размерности, проверенные временем;
- Random Projection — быстрый и простой, но мощный способ сократить вектор признаков;
- Variance-Based Selector — выбирает только самые говорящие каналы по дисперсии;
- Differentiable Linear Combiner (LComb) — обучаемый адаптер, который подстраивается под конкретную задачу вместе с основной моделью.
Эти адаптеры позволяют находить компромисс между скоростью и точностью, экономить вычислительные ресурсы и при этом не терять критически важные межканальные связи.
В итоге Mantis представляет собой не просто модель, а целый инженерный комплекс для глубокого анализа временных рядов. Её сила не в громких словах, а в детальном исполнении: внимательное отношение к каждому патчу, бережное соединение локального и глобального контекстов, строгая калибровка и бережное сжатие каналов. Такой союз аналитики и машинного разума позволяет не угадывать, а уверенно действовать, опираясь на статистику и проверенные алгоритмические принципы. В эпоху, когда скорость и точность решают всё, Mantis становится тем инструментов, который помогает удержать равновесие на тонком канате рынка.
Авторская визуализация фреймворка Mantis представлена ниже.
В практической части предыдущей статьи мы реализовали базовый компонент обработки временных рядов — модуль CNeuronConcatDiff. Этот объект объединил в себе алгоритм вычисления первой разности и механизм конкатенации тензоров из различных информационных потоков. С него началась практическая реализация ядра фреймворка Mantis.
Сегодня мы продолжим строительство этой архитектуры, переходя к следующим важным кирпичикам нейросетевой модели. На этапе планирования, в предыдущей статье, мы договорились о необходимости внедрения временного кодирования в поток данных. Это решение было продиктовано желанием улучшить интерпретируемость и учесть позиционную структуру исходных данных, не теряя при этом производительности. Изобретать велосипед мы, конечно, не будем: благо, у нас уже есть проверенный временем объект — CMamba4CastEmbedding. Мы создавали его в рамках работы над фреймворком Mamba4Cast, и теперь он как нельзя лучше подходит для реализации данной задачи.
Следующий логический шаг — организация патчинга временного ряда. Именно на этом этапе модель теряет привязку к длине исходной последовательности и получает возможность оперировать фиксированным количеством фрагментов, независимо от масштаба данных. Это, без преувеличения, один из самых важных модулей в архитектуре классификатора, так как от его реализации зависит стабильность всей последующей обработки. С него мы и начнём сегодняшнюю работу — шаг за шагом создадим механизм, который разобьёт поток исходной информации на компактные, информативные патчи.
Объект сегментации
Авторы оригинального фреймворка Mantis подошли к задаче сегментации временных рядов с неожиданной, но элегантной стороны. Вместо банального нарезания последовательности на равные отрезки, они предложили предварительно преобразовать данные через свёрточную операцию. Причём не просто применить свёртку ради галочки, а использовать её для превращения одномерного временного ряда в многоканальный тензор. Эта идея, на первый взгляд, может показаться избыточной, но на практике даёт весомое преимущество: с помощью свёртки с малым окном модель начинает чувствовать локальные колебания сигнала и фиксировать важные нюансы поведения рынка, которые в противном случае могли бы затеряться на фоне общей тенденции.
После такого преобразования у нас на руках оказывается многоканальный тензор, в котором каждый канал представляет собой отдельную проекцию исходного ряда — условно говоря, мини-представление сигнала под своим углом зрения. Этот тензор далее равномерно разбивается на фиксированное количество фрагментов. Это и есть патчи — строительные блоки, с которыми далее будет работать модель. И вот тут вступает в игру следующий этап: агрегация информации внутри каждого сегмента.
Авторы Mantis предложили использовать классическую операцию поканального усреднения, известную как mean-pooling. Она проста, интуитивно понятна и даёт хорошее сглаженное представление сегмента. В отличие от max-pooling, где остаётся только экстремальное значение, mean-pooling сохраняет контекст, позволяя каждому элементу временного ряда внести свой вклад в финальный токен. Это особенно полезно, когда каждый патч охватывает достаточно крупный фрагмент данных — в таких случаях нужно учитывать весь объём информации, а не только пики и впадины. Полученные таким образом токены образуют компактное и информативное представление временной структуры исходных данных, пригодное для дальнейшего анализа трансформером.
Однако, как показывает практика, mean-pooling может быть слишком вежливым — он усредняет не только информацию, но и возможные аномалии или резкие повороты в структуре сигнала. Для фундаментальной модели это может быть приемлемым компромиссом: она остаётся простой, быстрой и обобщаемой. Но мы решили пойти чуть дальше. В своей реализации мы заменили стандартный mean-pooling на комбинацию поканальной свёртки с последующим max-pooling.
Что это даёт на практике? Сначала свёртка извлекает признаки внутри каждого канала, обеспечивая первичное фильтрование и акцентирование на локальных изменениях. Затем max-pooling выделяет наиболее выраженные проявления этих признаков. Такой подход делает модель более острозаточенной под динамику временного ряда. Вместо того чтобы усреднять всё подряд, она фиксирует ключевые особенности, а значит быстрее и точнее реагирует на резкие движения. Это свойство становится критически важным в условиях высокочастотного трейдинга или при работе с волатильными активами.
Именно благодаря этой модификации патчинга наш классификатор получает способность адаптироваться под форму и структуру каждого конкретного временного ряда. Он больше не просто разбивает данные на сегменты, а анализирует, извлекает, фильтрует, оценивает. Один патч в такой системе уже не просто фрагмент сигнала, а полноценный, насыщенный информацией токен, который несёт в себе контекст и форму сигнала.
Предложенный алгоритм реализован в рамках класса CNeuronMantisPatching. Это специализированный модуль, отвечающий за сегментацию временного ряда на структурированные патчи и формирование высокоуровневого представления, пригодного для последующей обработки. Здесь начинается настоящее осмысление данных: из простого одномерного потока извлекаются локальные закономерности, которые преобразуются в плотные векторные описания.
Класс CNeuronMantisPatching построен как многоступенчатый вычислительный конвейер. Он содержит несколько внутренних объектов, каждый из которых выполняет узкоспециализированную задачу. Структура класса представлена ниже.
class CNeuronMantisPatching : public CNeuronTransposeOCL { protected: CNeuronTransposeOCL cToVarSeq; CNeuronConvOCL cProjecting; CNeuronTransposeVRCOCL cToVarProjSeq; CNeuronConvOCL cPatchingProj; CNeuronProofOCL cProof; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronMantisPatching(void) {}; ~CNeuronMantisPatching(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint count, uint patchs, uint variables, uint embedding_size, uint patch_filters, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual int Type(void) override const { return defNeuronMantisPatching; } //--- virtual void SetOpenCL(COpenCLMy *obj) override; virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
Все внутренние объекты объявлены статично, что позволяет оставить пустыми конструктор и деструктор класса. А их конфигурация осуществляется в методе Init, в параметрах которого мы получаем ряд констант, позволяющих однозначно определить архитектуру создаваемого объекта.
bool CNeuronMantisPatching::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint count, uint patchs, uint variables, uint embedding_size, uint patch_filters, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronTransposeOCL::Init(numOutputs, myIndex, open_cl, variables * embedding_size, patchs, optimization_type, batch)) return false;
Стоит обратить внимание, что на вход объекта CNeuronMantisPatching мы ожидаем получить матрицу значений многоканального временного ряда, где каждый столбец — это отдельный канал. Причём каждый канал представляет собой последовательность числовых измерений по времени. Иначе говоря, мы имеем дело с матрицей размерности [T * C], где T — количество временных шагов, а C — число каналов (переменных).
Для организации независимой обработки отдельных каналов внутри модуля используются объекты транспонирования данных. Это необходимо, чтобы обеспечить раздельную обработку по каналам и временным осям — такую изоляцию сложно реализовать без явного перемещения осей. Более того, сам класс CNeuronMantisPatching наследуется от CNeuronTransposeOCL, что даёт возможность выполнять обратную транспозицию средствами родительского класса уже после выполнения всех преобразований, возвращая выходной тензор в ожидаемую форму.
И первым шагом метода инициализации является вызов одноимённого метода родительского класса CNeuronTransposeOCL. Именно он подготавливает базовую структуру и выполняет предварительную инициализацию параметров. Этот шаг необходим для корректной работы всех наследуемых механизмов.
После успешной инициализации родительского класса начинается настройка внутренних компонентов. И первым среди них мы конфигурируем объект cToVarSeq — это слой транспонирования матрицы, обеспечивающий удобный доступ к отдельным временным последовательностям каждого канала. Его задача — переориентировать исходные данные таким образом, чтобы каждый канал стал независимым временным рядом, пригодным для дальнейшей локальной свёртки.
int index = 0; if(!cToVarSeq.Init(0, index, OpenCL, count, variables, optimization, iBatch)) return false;
Затем инициализируем объект локального кодирования cProjecting. Это свёрточный слой, который играет роль "внимательного наблюдателя", проходящего по каждому каналу временного ряда с фиксированным окном шириной 3 и шагом 1. Его задача — вычленить краткосрочные локальные паттерны и отобразить их в пространстве фиксированной размерности embedding_size.
index++; if(!cProjecting.Init(0, index, OpenCL, 3, 1, embedding_size, count - 2, variables, optimization, iBatch)) return false; cProjecting.SetActivationFunction(SoftPlus);
Важно подчеркнуть, что обработка по-прежнему остаётся строго поканальной: каждый канал анализируется изолированно, что позволяет модели подстраиваться под индивидуальные особенности сигналов. Более того, каждому каналу соответствует свой уникальный набор свёрточных фильтров, что придаёт процессу особую гибкость и точность.
Результатом этой операции становится тензор размерности [C * T' * D], где:
- C — количество каналов (признаков),
- T' — длина временной оси с учётом обрезки после свёртки с окном 3,
- D — размерность нового представления после кодирования.
Этот тензор — локально сжатая и уже частично структурированная форма исходных данных, которая ляжет в основу дальнейшей сегментации и агрегации. Так мы получаем своего рода сжатый портрет краткосрочной динамики внутри каждого признака — нечто, что традиционные методы анализа временных рядов редко способны уловить без громоздких и ресурсоёмких преобразований.
Далее наступает этап сегментации. И здесь важно понимать одну тонкость. Сама по себе операция сегментации в нашем контексте предполагает деление данных по временной оси, то есть разбиение последовательности на отрезки фиксированной длины. Однако на предыдущем шаге результат свёртки представлен в виде тензора, где временная ось — это второе измерение. Для удобства дальнейшей обработки и согласования с архитектурой последующих слоёв, нам необходимо переставить оси местами.
Поэтому мы используем объект транспонирования трехмерного тензора, вынося временную ось на первый план. В результате получаем тензор с новой структурой [C * D * T'].
index++; if(!cToVarProjSeq.Init(0, index, OpenCL, variables, count-2, embedding_size, optimization, iBatch)) return false;
Такое представление данных удобно и логично: теперь мы можем напрямую оперировать последовательностью по времени, разбивая её на равные фрагменты, каждый из которых будет рассматриваться как отдельный семантический сегмент. Это соответствует нашему изначальному намерению — перейти от анализа точек к анализу паттернов.
Именно в этой новой форме тензор поступает на следующий этап, где и осуществляется основная операция сегментации и агрегации информации. Основная задача — разбить временную последовательность на фиксированное количество сегментов и извлечь из каждого информативное представление. Но поскольку изначально нам задано лишь количество необходимых сегментов (patchs), первым делом необходимо рассчитать размер одного сегмента — сколько временных шагов попадёт в каждый патч.
После этого мы инициализируем сверточный слой cPatchingProj, который и будет выполнять сегментацию. Он использует окно, равное рассчитанному размеру патча, и скользит по временной оси с таким же шагом, что гарантирует создание не перекрывающихся сегментов.
index++; int patch_size = (int(count + patchs) - 3) / int(patchs); if(!cPatchingProj.Init(0, index, OpenCL, patch_size, patch_size, patch_filters, patchs, variables * embedding_size, optimization, iBatch)) return false; cPatchingProj.SetActivationFunction(SoftPlus);
Важно отметить, что в отличие от простого mean-pooling, здесь применяется полноценная сверточная операция с множеством фильтров (patch_filters). Это позволяет модели не просто усреднять информацию в сегменте, а извлекать наиболее релевантные признаки каждого отрезка, реагируя на характерные локальные шаблоны.
После прохождения тензора через слой cPatchingProj, мы получаем четырёхмерный тензор размерности [C × D × P × F], где:
- P — количество сегментов (патчей),
- F — количество фильтров, применённых к каждому патчу.
Этот тензор содержит обогащённые представления каждого сегмента каждого канала по всем фильтрам. Однако дальнейшая обработка требует агрегации этих данных. Именно здесь применяется операция max-pooling по последнему измерению — то есть по оси фильтров F. Эта операция выбирает наиболее выраженное значение по каждому измерению и позволяет избавиться от избыточных или слабо активных фильтров. После этого размерность тензора становится [C × D × P].
index++; if(!cProof.Init(0, index, OpenCL, patch_filters, patch_filters, patchs*variables*embedding_size, optimization, iBatch)) return false; //--- return true; }
Финальное преобразование данных осуществляется средствами родительского класса, который мы уже инициализировали ранее. Поэтому завершаем работу метода, вернув логический результат выполнения операций вызывающей программе.
Стоит обратить внимание, что архитектура CNeuronMantisPatching спроектирована с высокой степенью модульности. Каждый внутренний слой работает с каналами независимо, что делает структуру чрезвычайно масштабируемой. Вне зависимости от числа анализируемых каналов логика обработки остаётся одинаковой, поскольку каждый канал воспринимается как самостоятельный поток данных.
Более того, такая независимая структура позволяет эффективно распараллеливать вычисления. Поскольку все используемые нейронные слои реализованы на OpenCL, они автоматически используют доступный графический процессор или любое другое совместимое устройство вычислений. Это позволяет запускать одновременно сотни параллельных потоков, обрабатывающих отдельные каналы без ощутимой потери времени.
Основную логику работы класса CNeuronMantisPatching и взаимодействие его внутренних компонентов мы подробно изложили при разборе структуры метода инициализации. Именно в нём последовательно выстраиваются все ключевые этапы обработки многоканального временного ряда: от первичного транспонирования до сегментации и агрегации информации.
Чтобы не перегружать статью избыточными деталями, мы сознательно опустили описание методов прямого (feedForward) и обратного (calcInputGradients, updateInputWeights) проходов. В них реализован строго последовательный вызов одноимённых методов внутренних объектов, соответствующих архитектуре построенного конвейера.
Полный исходный код данного класса, включая реализацию всех вспомогательных методов, представлен во вложении. Читатель при желании может ознакомиться с ним самостоятельно и глубже изучить все технические нюансы.
Модуль внимания
Согласно общей логике фреймворка Mantis, предварительно обработанная последовательность временного ряда — уже в виде токенов, сформированных из патчей отдельных каналов — передаётся на вход модулю Transformer. Именно здесь и происходит финальная обработка пространственно-временной информации: к последовательности токенов добавляется специальный класс-токен и встраивается позиционное кодирование.
Класс-токен служит своеобразным сборщиком информации. Во время прохождения 6 слоёв многоголового механизма самовнимания (Self-Attention) он агрегирует структурную информацию обо всей последовательности. Именно его значение на выходе трансформера интерпретируется как идентификатор класса анализируемого временного ряда — неважно, речь идёт о распознавании рыночной фазы, классификации состояния индикаторов или прогнозировании типа будущего движения.
В нашей реализации мы внесли определённые коррективы в данный этап. Прежде всего, позиционное кодирование было исключено. Причина в том, что временная структура последовательности уже явно зашита в сами токены благодаря встроенному временному кодированию, заимствованному из архитектуры Mamba4Cast. Этот подход позволяет кодировать абсолютное и относительное положение каждого элемента в пределах канала, не прибегая к синусоидальным или обучаемым позиционным векторам, как это принято в классических трансформерах.
Такая замена даёт сразу два преимущества. Во-первых, она делает модель менее чувствительной к сдвигам и искажениям исходной последовательности. Во-вторых, устраняется конкуренция между жёстким позиционным кодом и реальным временным контекстом, который уже присутствует в данных. На реальных рыночных временных рядах это позволяет повысить устойчивость модели, а трансформеру сосредоточиться на содержании входного сигнала.
Кроме того, в авторской реализации Mantis на выходе Transformer используется исключительно класс-токен — специальный вектор, предназначенный для аккумуляции обобщённой информации о последовательности. Данный токен предварительно добавляется к входной последовательности, проходит все слои трансформера и извлекается на выходе.
Однако в нашей реализации было принято альтернативное решение. Вместо традиционной вставки класс-токена в начало последовательности и последующего извлечения, мы используем модуль кросс-внимания (Cross-Attention). Его особенность в том, что класс-токен подаётся как главный запрос (query), а токены исходной последовательности — в качестве контекста (keys и values). Такая конфигурация позволяет класс-токену сфокусироваться на наиболее значимых элементах последовательности, минимизируя шум и усиливая релевантные связи.
Более того, мы применили вариант кросс-внимания с независимыми каналами, в котором каждый канал анализируется изолированно. Это позволяет оценивать вклад каждого канала во внимание по отдельности. Подобный подход особенно полезен при анализе мультимодальных или разнотипных сигналов, поскольку позволяет избежать доминирования одного канала над другими и формировать объективное агрегированное представление всей структуры временного ряда.
Таким образом, итоговый вектор-контейнер, полученный на выходе блока кросс-внимания, уже содержит обобщённое и сбалансированное представление о всей временной структуре, пригодное для последующей классификации или прогнозирования.
С технической точки зрения, всё это реализовано в классе CNeuronMantisAttentionUnit, который наследуется от CNeuronSoftMaxOCL. Это означает, что на выходе мы сразу получаем вероятность принадлежности анализируемой последовательности к тому или иному классу. Структура нового объекта представлена ниже.
class CNeuronMantisAttentionUnit : public CNeuronSoftMaxOCL { protected: CNeuronBaseOCL cClassToken[2]; CNeuronMVCrossAttentionMLKV cAttention; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronMantisAttentionUnit(void) {}; ~CNeuronMantisAttentionUnit(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint token_size, uint window, uint window_key, uint heads, uint units_count, uint layers, uint variables, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual int Type(void) override const { return defNeuronMantisAttentionUnit; } //--- virtual void SetOpenCL(COpenCLMy *obj) override; virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
Внутри нового класса мы видим 2 основные компоненты:
- cClassToken[2] — двухслойная MLP для генерации обучаемого класс-токена;
- cAttention — непосредственно объект многослойного кросс-внимания.
Все внутренние объекты объявлены статично, что позволяет оставить пустыми конструктор и деструктор класса. Инициализация внутренних объектов, как обычно, осуществляется в методе Init.
bool CNeuronMantisAttentionUnit::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint token_size, uint window, uint window_key, uint heads, uint units_count, uint layers, uint variables, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronSoftMaxOCL::Init(numOutputs, myIndex, open_cl, token_size, optimization_type, batch)) return false;
Здесь мы сначала осуществляем вызов одноименного метода родительского класса, в котором, как Вы знаете, уже организован процесс инициализации унаследованных объектов и интерфейсов. А после успешного выполнения операций метода родительского класса переходим к инициализации внутренних объектов. Вначале инициализируем MLP генерации обучаемого класс-токена. Первый слой всегда имеет один элемент фиксированного значения, а второй при помощи обучаемых параметров генерирует класс-токен необходимого размера.
int index = 0; if(!cClassToken[0].Init(token_size, index, OpenCL, 1, optimization, iBatch)) return false; if(!cClassToken[0].getOutput().Fill(1)) return false; index++; if(!cClassToken[1].Init(0, index, OpenCL, token_size, optimization, iBatch)) return false; cClassToken[1].SetActivationFunction(SIGMOID);
С целью получения элементов токена в заданном диапазоне значений мы используем сигмовидную функцию активации на последнем слое.
Следующим шагом инициализируем блок кросс-внимания. Все данные о его конфигурации мы получаем от пользователя в параметрах нашего метода инициализации.
index++; if(!cAttention.Init(0, index, OpenCL, token_size, window_key, heads * variables, window, heads, 1, units_count, layers, 1, 1, variables, optimization, iBatch)) return false; //--- return true; }
И завершаем работу метода, вернув логический результат выполнения операций вызывающей программе.
Таким же лаконичным представляется и метод прямого прохода feedForward. В параметрах метода получаем указатель на объект контекста анализируемой последовательности и сразу проверяем его актуальность.
bool CNeuronMantisAttentionUnit::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Далее нам необходимо сгенерировать класс-токенн. Однако эта операция выполняется только при обучении модели. В процессе эксплуатации параметры модели не изменяются и, как следствие, класс-токен остается фиксированным. И нам нет необходимости генерировать его повторно на каждой итерации.
//--- if(bTrain) { if(!cClassToken[1].FeedForward(cClassToken[0].AsObject())) return false; }
Сгенерированный класс-токен вместе с контекстом анализируемой последовательности подаются в блок внимания.
if(!cAttention.FeedForward(cClassToken[1].AsObject(), NeuronOCL.getOutput())) return false;
Результат работы блока внимания — обогащенный класс-токен — передаётся на вход CNeuronSoftMaxOCL, родительскому классу. Это шаг переводит прогнозное значение вероятностную интерпретацию. На выходе мы получаем вероятностное распределение по классам.
if(!CNeuronSoftMaxOCL::feedForward(cAttention.AsObject())) return false; //--- return true; }
В заключение отметим, что методы обратного прохода (calcInputGradients и updateInputWeights) реализуют последовательный вызов соответствующих функций вложенных компонентов. Читатель может самостоятельно изучить их реализацию, чтобы полностью освоить процесс обучения и обновления весов в модуле CNeuronMantisAttentionUnit. Полный код данного класса и всех его методов представлен во вложении.
Мы выполнили весь объём запланированных на сегодня работ. В следующей статье мы рассмотрим архитектуру моделей и оценим эффективность реализованных решений на реальных исторических данных.
Заключение
Мы прошли весь путь от первичной подготовки данных — расчёта первых разностей, конкатенации признаков и временного кодирования на базе CMamba4CastEmbedding — до сложной обработки патчей и интеллектуального агрегирования через кросс-внимание с помощью класс-токена. Каждая ступень этой цепочки — от транспонирования и локальных свёрток до max-pooling и многоголового внимания — была тщательно спроектирована для сохранения максимально полной картины временной динамики и в то же время для оптимизации вычислительных ресурсов с акцентом на параллелизм OpenCL.
В результате мы получили модульную архитектуру, в которой легко настраивать число каналов, размер патчей, глубину эмбеддинга и число голов внимания, не меняя базовой логики. Это позволяет быстро адаптировать модель к самым разным финансовым сценариям — от высокочастотной торговли до анализа долгосрочных трендов.
В следующей части мы представим полную схему обучаемой модели на базе описанных компонентов и покажем результаты её работы на исторических данных.
Ссылки
- Mantis: Lightweight Calibrated Foundation Model for User-Friendly Time Series Classification
- Другие статьи серии
Программы, используемые в статье
# | Имя | Тип | Описание |
---|---|---|---|
1 | Research.mq5 | Советник | Советник сбора примеров |
2 | ResearchRealORL.mq5 | Советник | Советник сбора примеров методом Real-ORL |
3 | StudyContrast.mq5 | Советник | Советник контрастивного обучения Энкодера |
4 | Study.mq5 | Советник | Советник офлайн обучения моделей |
5 | StudyOnline.mq5 | Советник | Советник онлайн обучения моделей |
6 | Test.mq5 | Советник | Советник для тестирования модели |
7 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
8 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
9 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL-программы |





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования