
Возможности Мастера MQL5, которые вам нужно знать (Часть 18): Поиск нейронной архитектуры с использованием собственных векторов
Предисловие
Мы продолжаем серию статей о применении Мастера MQL5. Здесь мы рассмотрим поиск нейронной архитектуры (Neural Architecture Search, NAS). При этом особое внимание будет уделено роли собственных векторов (Eigen Vectors) в более эффективном ускорении обучения сети. Нейронные сети можно назвать подгонкой кривой под набор данных, поскольку они помогают вывести формульное выражение, которое при применении к входным данным (x) дает целевое значение (y) точно так же, как квадратное уравнение делает это с кривой. Однако точки данных x и y могут быть (и часто являются) многомерными, поэтому нейронные сети приобрели большую популярность. Тем не менее, принцип выработки формульного выражения сохраняется, поэтому нейронные сети — это не единственный способ достижения цели.Введение
Если мы решим использовать нейронные сети для определения связи между обучающим набором данных и его целью, как в этой статье, то нам придется столкнуться с вопросом: какие настройки будет использовать эта сеть? Существует несколько типов сетей, а это значит, что применимых конструкций и настроек также много. В этой статье мы рассмотрим очень простой случай, который часто называют многослойным перцептроном (multi-layer perceptron, MLP). При использовании этого типа настройки, на которых мы остановимся, будут заключаться только в количестве скрытых слоев и размере каждого скрытого слоя.
NAS обычно может помочь определить эти два параметра и многое другое. Например, даже в случае простых MLP вопрос о том, какой тип активации и начальные веса использовать, а также вопрос о начальных смещениях являются факторами, чувствительными к производительности и точности сети. Однако здесь мы их рассмотрим вскользь, поскольку область поиска очень обширна, а вычислительные ресурсы, необходимые для прямого и обратного распространения даже для набора данных среднего размера, были бы непомерно велики.
Подход NAS, применяемый здесь, относительно новаторский, поскольку он вовлекает собственные векторы и значения в матричном поисковом пространстве для определения идеальных настроек. Традиционно NAS осуществляется либо через обучение с подкреплением, эволюционные алгоритмы, байесовскую оптимизацию или случайный поиск.
Каждый из этих традиционных подходов включает обучение и перекрестную проверку сети с выбранными настройками (так называемой архитектурой) путем сравнения каждой производительности с целевым значением для сравнения. Их отличает то, насколько они исчерпывающие, а также их способ достижения эффективности без исчерпывающего поиска в пространстве. Обучение с подкреплением основано на алгоритме, который предварительно оценивает сеть в пространстве поиска на основе ее настроек, и продолжает совершенствовать этот алгоритм с каждым выбором. Эволюционные алгоритмы объединяют сети в пределах поискового пространства, чтобы получить новые, которые изначально могли и не присутствовать в поисковом пространстве, опять же, оценивая их производительность по сравнению с целевой. Байесовская оптимизация, как и случайный поиск, основана на сортировке пространства поиска в формате массива либо такого пространства, где различные настройки сети могут восприниматься как координаты. Например, если пространство поиска является двумерным и содержит только две переменные: стандартный размер скрытого слоя и количество скрытых слоев, то эти параметры будут распределены по матрице в порядке возрастания (или убывания) по ее диагонали в формате, аналогичном изображению ниже.
При таком подходе производительность любой сети будет привязана к ее "координатам" в пространстве, поэтому статистические методы будут использоваться при каждом последующем выборе для уточнения выбора сети, которая обеспечит наилучшую производительность. В опубликованной несколько дней назад статье о собственных векторах и PCA использовалось пространство поиска матриц для выбора идеального дня недели и применяемой цены индикатора для торговли EURUSD на H4. Это было сделано на основе перекрестной матрицы изменений цен для каждого из пяти будних дней и для каждой из рассмотренных примененных цен.
Мы рассмотрим аналогичный подход к поиску в этой статье. Поскольку исчерпывающее обучение всех сетей — это проблема, которую мы пытаемся "решить", нашими контрольными показателями будут просто прямые проходные баллы от целевых значений в сетях, инициализированных со стандартными весами и смещениями. Мы будем выполнять только прямые прогоны по выборке данных, а средний балл для каждого параметра будет служить его эталоном в матрице.Роль собственных векторов в NAS
Как упоминалось выше, собственная матрица, которую мы будем использовать для NAS, для краткости будет двумерной. Если мы рассмотрим простой MLP, в котором все скрытые слои имеют одинаковый размер, то нам нужно будет ответить только на два вопроса: сколько скрытых слоев должно быть в MLP и каков размер каждого скрытого слоя.
Возможные ответы на эти вопросы можно легко представить в виде матрицы, в которой производительность каждой сети по умолчанию регистрируется для каждой комбинации размера и количества слоев. Сети будут различаться по настройкам, как отражено в матричной таблице, однако их входные и выходные слои будут стандартными. В этой статье у нас будет входной слой размером 4 и выходной слой размером 1. Мы рассматриваем обычный сценарий, в котором мы прогнозируем следующую цену закрытия на основе 4 последних значений цены закрытия.Тестовым символом будет EURJPY H4 за 2022 год. То есть наши данные будут представлять собой цены закрытия четырехчасовых баров за 2022 год. При "обучении" этой модели мы всего лишь регистрируем среднее отклонение от целевых значений в течение года для всех настроек сети. Наши настройки будут охватывать от одного скрытого слоя до 10 скрытых слоев вдоль строк матрицы, в то время как столбцы будут отображать размеры скрытых слоев, которые будут охватывать диапазон от 2 до 11. Эти тестовые настройки являются произвольными, и поскольку полный исходный код приложен в конце статьи, читатель может настроить его по своему усмотрению.
Повторюсь: "обучение" модели будет включать один прямой проход каждой из доступных сетей, все - со стандартными весами и смещениями по умолчанию в течение 2022 года, при этом каждый прогнозный бар будет сравниваться с фактической ценой закрытия. Во время этого "обучения" не будет никакого обратного распространения или типичного обучения сети.
Мы полагаемся на сетевой класс, который мы рассмотрели в этой статье для реализации MLP. Он требует целочисленного массива, размер которого определяет общее количество слоев, где целочисленное значение в каждом индексе задает размер слоя.
Несмотря на то, что в статье и серии статей основное внимание уделяется Мастеру MQL5, упомянутое выше "обучение" будет осуществляться с помощью скрипта, как и в предыдущей статье о собственных векторах, и мы будем использовать полученные результаты/рекомендации для создания экземпляра класса сигнала для тестирования с помощью собранного Мастером советника. Наше тестирование собранного советника будет включать обычное обучение сети на каждом баре или с каждой новой точкой данных. Результат тестирования стратегии рекомендуемой сети будет сравниваться с худшей рекомендацией в качестве контроля, чтобы мы могли оценить, могут ли собственные векторы и значения быть полезными в NAS.
Если мы сделаем краткий обзор того, что мы рассмотрели в предыдущей статье о собственных векторах и значениях, то использованное снижение размерности дало нам единственный вектор из анализируемой матрицы. Таким образом, в нашем случае зарегистрированная производительность каждой сети, которая есть в матрице, будет сведена к вектору. В предыдущей статье мы хотели получить цену за будний день и применяемую цену, которая охватывала большую часть дисперсии пары EURJPY за год на четырехчасовом таймфрейме. Это означает, что мы сосредоточились на максимальных значениях собственных векторов в прогнозируемой матрице, поскольку они в наибольшей степени положительно коррелировали с нашей целью.
Однако в этом случае наша матрица зафиксировала отклонения от целевых значений, что означает, что в нашей матрице имеется коэффициент ошибки каждой сети. Поскольку для целей тестирования мы хотим использовать сеть с наименьшей ошибкой, наши выборки для сети по количеству слоев и размеру каждого слоя будут минимальными в каждом из собственных векторов, извлеченных из матрицы проекции. Как уже упоминалось, предварительная обработка выполняется скриптом и ее можно разделить на пять частей, а именно: а) инициализация сетей:
//initialise networks ArrayResize(__M.row, __SIZE); for(int r = 0; r < __SIZE; r++) { for(int c = 0; c < __SIZE; c++) { ArrayResize(__M.row[r].col, __SIZE); ArrayResize(__M.row[r].col[c].settings, 2 + __LEAST_LAYERS + r); ArrayFill(__M.row[r].col[c].settings, 0, __LEAST_LAYERS + r + 2, __LEAST_SIZE + c); __M.row[r].col[c].settings[0] = __INPUTS; __M.row[r].col[c].settings[__LEAST_LAYERS + r + 1] = __OUTPUTS; __M.row[r].col[c].n = new Cnetwork(__M.row[r].col[c].settings, __initial_weight, __initial_bias); } }
б) сопоставительный анализ сетей:
//benchmark networks int _buffer_size = (52*PeriodSeconds(PERIOD_W1))/PeriodSeconds(Period()); PrintFormat(__FUNCSIG__ + " buffered: %i", _buffer_size); if(_buffer_size >= __INPUTS) { for(int i = _buffer_size - 1; i >= 0; i--) { for(int r = 0; r < __SIZE; r++) { for(int c = 0; c < __SIZE; c++) { vector _in,_out; vector _in_new,_out_new,_in_old,_out_old; _in_new.CopyRates(Symbol(), Period(), 8, i + 1, __INPUTS); _in_old.CopyRates(Symbol(), Period(), 8, i + 1 + 1, __INPUTS); _out_new.CopyRates(Symbol(), Period(), 8, i, __OUTPUTS); _out_old.CopyRates(Symbol(), Period(), 8, i + 1, __OUTPUTS); _in = Norm(_in_new, _in_old); _out = Norm(_out_new, _out_old); __M.row[r].col[c].n.Set(_in); __M.row[r].col[c].n.Forward(); __M.row[r].col[c].benchmark += fabs(__M.row[r].col[c].n.output[0]-_out[0]); } } } }
в) копирование контрольных показателей в матрицу анализа:
//copy benchmarks to analysis matrix matrix _m; _m.Init(__SIZE, __SIZE); _m.Fill(0.0); for(int r = 0; r < __SIZE; r++) { for(int c = 0; c < __SIZE; c++) { _m[r][c] = __M.row[r].col[c].benchmark; } }
г) нормализация матрицы и генерация собственных векторов и значений:
//generating eigens PrintFormat(" for: %s, with: %s", Symbol(), EnumToString(Period())); matrix _z = ZNorm(_m); matrix _cov_col = _z.Cov(false); matrix _e_vectors; vector _e_values; _cov_col.Eig(_e_vectors, _e_values);
e) и, наконец, интерпретация собственных векторов для извлечения идеальных и наихудших номеров слоев сети и размеров слоев из матрицы проекции:
//interpreting the eigens from projection matrix _t = _e_vectors.Transpose(); matrix _p = _m * _t; vector _max_row = _p.Max(0); vector _max_col = _p.Max(1); string _layers[__SIZE]; for(int i=0;i<__SIZE;i++) { _layers[i] = IntegerToString(i + __LEAST_LAYERS)+" layer"; } double _nr_layers[]; _max_row.Swap(_nr_layers); //since network performance inversely relates to network deviation from target PrintFormat(" est. ideal nr. of layers is: %s", _layers[ArrayMinimum(_nr_layers)]); PrintFormat(" est. worst nr. of layers is: %s", _layers[ArrayMaximum(_nr_layers)]); string _sizes[__SIZE]; for(int i=0;i<__SIZE;i++) { _sizes[i] = "size "+IntegerToString(i + __LEAST_SIZE); } double _size_nr[]; _max_col.Swap(_size_nr); PrintFormat(" est. ideal size of layers is: %s", _sizes[ArrayMinimum(_size_nr)]); PrintFormat(" est. worst size of layers is: %s", _sizes[ArrayMaximum(_size_nr)]);Выполнение приведенного выше скрипта в поисковой области из 100 прогонов выполняется в течение нескольких секунд. Это хороший знак. Однако можно утверждать, что пространство недостаточно всеобъемлющее. Это весомый аргумент, поэтому в прилагаемом скрипте атрибуты размера пространства представлены в виде глобальной переменной, которую пользователь может изменять, чтобы создать что-то более качественное. Кроме того, нам нужна была структура для обработки сетевых экземпляров и их тестов. Это определяется в заголовке следующим образом:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct Scol { int settings[]; Cnetwork *n; double benchmark; Scol() { ArrayFree(settings); benchmark = 0.0; } ~Scol(){ delete n; }; }; struct Srow { Scol col[]; Srow(){}; ~Srow(){}; }; struct Smatrix { Srow row[]; Smatrix(){}; ~Smatrix(){}; }; Smatrix __M; //matrix of networks
Тестирование советника
Если мы запустим приведенный выше скрипт, который помогает отсортировать идеальные настройки сети, то получим следующие записи на EURJPY H4:
2024.05.03 18:22:39.336 nas_1_changes (EURJPY.ln,H4) void OnStart() buffered: 2184 2024.05.03 18:22:42.209 nas_1_changes (EURJPY.ln,H4) for: EURJPY.ln, with: PERIOD_H4 2024.05.03 18:22:42.209 nas_1_changes (EURJPY.ln,H4) est. ideal nr. of layers is: 6 layer 2024.05.03 18:22:42.209 nas_1_changes (EURJPY.ln,H4) est. worst nr. of layers is: 9 layer 2024.05.03 18:22:42.209 nas_1_changes (EURJPY.ln,H4) est. ideal size of layers is: size 2 2024.05.03 18:22:42.209 nas_1_changes (EURJPY.ln,H4) est. worst size of layers is: size 4Рекомендуемые настройки сети рассчитаны на 6-слойную сеть, где размер каждого слоя равен 2! В качестве примечания: целевые данные (значения y), используемые для сравнительного анализа матрицы, были нормализованы так, чтобы находиться в диапазоне от 0,0 до 1,0, где значение 0,5 означает, что результирующее изменение цены составило 0, тогда как любое значение меньше 0,5 будет указывать на результирующее падение цены, а значение выше 0,5 будет указывать на рост цены. Ниже приведен код функции, выполняющей эту нормализацию:
//+------------------------------------------------------------------+ //| Normalization (0.0 - 1.0, with 0.5 for 0 | //+------------------------------------------------------------------+ vector Norm(vector &A, vector &B) { vector _n; _n.Init(A.Size()); if(A.Size() > 0 && B.Size() > 0 && A.Size() == B.Size() && A.Min() > 0.0 && B.Min() > 0.0) { int _size = int(A.Size()); _n.Fill(0.5); for(int i = 0; i < _size; i++) { if(A[i] > B[i]) { _n[i] += (0.5*((A[i] - B[i])/A[i])); } else if(A[i] < B[i]) { _n[i] -= (0.5*((B[i] - A[i])/B[i])); } } } return(_n); }Эта нормализация была необходима, поскольку, учитывая небольшой набор данных, мы рассматриваем возможность обучения сети для разработки весов и смещений, способных обрабатывать отрицательные и положительные значения, поскольку для вывода данных потребуются значительно большие наборы данных, более сложные настройки сети и, безусловно, больше вычислительных ресурсов. Ни один из этих сценариев не рассматривается в данной статье, но их можно реализовать, если это будет сочтено возможным. Таким образом, благодаря нашей нормализации мы можем получать значительные результаты от нашей сети при скромном обучении и наборах данных.
Если мы запустим тесты с рекомендуемой конфигурацией сети из 6 слоев при размере 2, то получим отчет и кривую эквити, представленные ниже:
Нейронные сети могут страдать от избыточности в их возможностях, когда различные настройки (или архитектуры) изучают одни и те же базовые взаимосвязи в данных, даже если они имеют разные структуры. Как мы помним, в обоих проходах сети обучались так, чтобы веса и смещения улучшались. Таким образом, в то время как собственные векторы с большей дисперсией охватывают более широкий набор признаков, а векторы с меньшей дисперсией фокусируются на деталях, любая конфигурация сети может изучить основы для обеспечения хорошей производительности.
Хотя количество скрытых слоев и их размер являются решающими факторами, определяющими производительность сети в этой ситуации, другие доминирующие факторы, такие как наш выбор функции активации (мы используем Softplus) или используемая скорость обучения, также могут оказаться важными. Каждый из этих факторов или все они могли оказать непропорционально большое влияние на производительность сетей.
Другим возможным объяснением могут быть ограничения пространства поиска. Мы рассмотрели 10 различных размеров слоев и 10 различных вариантов скрытых слоев, все из которых трансформировались в прямоугольную форму. Это могло ограничить возможные комбинации сетей при отображении этого конкретного набора данных, так что любой из этих нескольких вариантов мог бы легко привести к желаемому решению.
Заключение
Мы увидели, как можно нетрадиционным способом реализовать NAS с помощью собственных векторов и значений, столкнувшись с ограниченным набором конфигураций нейронных сетей, из которых можно выбирать. Этот процесс можно масштабировать и, возможно, даже расширить, включив или приняв во внимание другие факторы, которые не были частью матрицы анализа, добавив скрытую форму слоя (мы рассматривали только прямоугольники) или даже типы активации. Последние проще всего добавить к матрице, подобной той, что рассматривается в этой статье, поскольку существует всего 2–3 основных типа активации, и это может просто означать утроение числа столбцов, а также расширение числа строк, чтобы гарантировать сохранение квадратной матрицы, что является предпосылкой для анализа собственных векторов. Добавление скрытой формы слоя также может быть выполнено аналогичным образом, если различные формы, которые необходимо учитывать, перечислены в виде четких типов. Примечания:
Прикрепленные файлы можно использовать, следуя руководствам по сборке советников в Мастере, опубликованных здесь и здесь.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14845





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