Матрицы и векторы в MQL5. Поделитесь вашим мнением

 

В конце 2021 года мы начали внедрять в MQL5 новые сущности - матрицы и векторы. В январе 2022 года появился новый раздел в документации "Типы данных - матрицы и векторы" и первый анонс "Новая версия платформы MetaTrader 5 build 3180: Векторы и матрицы в MQL5 и повышение удобства работы". Летом 2022 года в документации появился полноценный раздел "Методы матриц и векторов".

И работы продолжаются. Мы пишем прикладные коды (для внутреннего потребления и для статей) и сразу видим, что удобно, что неудобно, что необходимо прямо сейчас, что необходимо в принципе, но может подождать. Тем не менее, нам необходима более широкая обратная связь. У участников сообщества MQL5 наверняка есть идеи и предложения. Поделитесь!


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

//+------------------------------------------------------------------+
//|                                  NeuralNetworkClassification.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| neural network for classification model                          |
//+------------------------------------------------------------------+
class CNeuralNetwork
  {
protected:
   int               m_layers;                           // layers count
   ENUM_ACTIVATION_FUNCTION m_activation_functions[];    // layers activation functions
   matrix            m_weights[];                        // weights matrices between layers
   vector            m_values[];                         // layer values
   vector            m_values_a[];                       // layer values after activation
   vector            m_errors[];                         // error values for back propagation

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CNeuralNetwork(const int layers,const int& layer_sizes[],const ENUM_ACTIVATION_FUNCTION& act_functions[])
     {
      m_layers=layers;
      ArrayCopy(m_activation_functions,act_functions);
      ArrayResize(m_weights,layers-1);
      ArrayResize(m_values,layers);
      ArrayResize(m_values_a,layers);
      ArrayResize(m_errors,layers);

      //--- init weight matrices with uniform random
      for(int i=0; i<m_layers-1; i++)
        {
         double div=32767.0 * layer_sizes[i];  // divider

         m_weights[i].Init(layer_sizes[i],layer_sizes[i+1]);
         for(ulong j=0; j<m_weights[i].Rows()*m_weights[i].Cols(); j++)
            m_weights[i].Flat(j,rand()/div);
        }
     }

   //+------------------------------------------------------------------+
   //| Forward pass                                                                 |
   //+------------------------------------------------------------------+
   void ForwardFeed(const vector& input_data,vector& vector_pred)
     {
      m_values_a[0]=input_data;

      for(int i=0; i<m_layers-1; i++)
        {
         m_values[i+1]=m_values_a[i].MatMul(m_weights[i]);
         m_values[i+1].Activation(m_values_a[i+1],m_activation_functions[i+1]);
        }

      //--- predicted class probabilities
      vector_pred=m_values_a[m_layers-1];
     }

   //+------------------------------------------------------------------+
   //| error back propagation                                           |
   //+------------------------------------------------------------------+
   void BackPropagation(const vector& vector_true,double learning_rate)
     {
      //--- categorical cross entropy loss gradient
      m_errors[m_layers-1]=vector_true-m_values_a[m_layers-1];

      for(int i=m_layers-1; i>0; i--)
         m_errors[i-1].GeMM(m_errors[i],m_weights[i-1],1,0,TRANSP_B);

      //--- weights update
      for(int i=m_layers-1; i>0; i--)
        {
         vector back;
         m_values[i].Derivative(back,m_activation_functions[i]);
         back*=m_errors[i]*learning_rate;

         m_weights[i-1].GeMM(m_values_a[i-1],back,1,1);
        }
     }
  };
//+------------------------------------------------------------------+

Чистого кода всего 60 строк. Исключительно (кроме ArrayResize) матричные и векторные операции и методы. Делалось для "Hello world" нейросетей - распознавание рукописных цифр на наборе картинок MNIST.

Вот концовка лога скрипта тренировки и тестирования нейросети.

...
2023.06.02 16:54:21.608 mnist_original (GBPUSD,H1)      Epoch # 15
2023.06.02 16:54:24.523 mnist_original (GBPUSD,H1)      Time: 2.907 seconds. Right answers: 98.682%
2023.06.02 16:54:24.523 mnist_original (GBPUSD,H1)      Total time: 71 seconds
2023.06.02 16:54:24.558 mnist_original (GBPUSD,H1)      Read 10000 images. Test begin
2023.06.02 16:54:24.796 mnist_original (GBPUSD,H1)      Time: 0.234 seconds. Right answers: 96.630%

Исходный код скрипта и оригинальные данные mnist - в прицепе

Документация по MQL5: Основы языка / Типы данных / Матрицы и векторы
Документация по MQL5: Основы языка / Типы данных / Матрицы и векторы
  • www.mql5.com
Матрицы и векторы - Типы данных - Основы языка - Справочник MQL5 - Справочник по языку алгоритмического/автоматического трейдинга для MetaTrader 5
Файлы:
MQL5.zip  10508 kb
 

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

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

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

 

Из ограничений.
В матрицах столкнулся с невозможностью создать многомерную матрицу X(n, n, n)
При большом количестве признаков, в расчётах очень не удобно, даже сказал бы не практично, под каждый признак аллоцировать отдельную матрицу
X1(n, n);
X2(n, n); 
X3(n, n);
и т.д.


Из документации.
Не понравилось то, что описание методов ведёт на документацию numpy, а не документацию mql5.
И в документации numpy работа с матричными методами описывается как многомерная  A(n, n, n)
что не соответствует действительности для матриц mql5 !


Из представленных методов.
Допилите уже метод Sort()
Без него не актуальны некоторые функции 

Документация по MQL5: Основы языка / Типы данных / Матрицы и векторы
Документация по MQL5: Основы языка / Типы данных / Матрицы и векторы
  • www.mql5.com
Матрицы и векторы - Типы данных - Основы языка - Справочник MQL5 - Справочник по языку алгоритмического/автоматического трейдинга для MetaTrader 5
 

Здесь наверное успех зависит от удобства перехода с других ЯП на mql, допустим с питона, потому что машинлернеры используют его. Есть смысл приближать к нампай, да, или тензорам пайторча с их автоградиентом. А может быть и нет. Ну вы это и так знаете :)

Лично мне было бы так удобнее как обывателю: найти какие-то исследования или архитектуры сетей на торче, коих много в интернетах, и чтобы я смог написать похожую сеть на mql, без сильных страданий

 

Я ошибаюсь, или пропустили в этом примере Документации функцию активации?

//+------------------------------------------------------------------+
//| Метод прямого прохода                                            |
//+------------------------------------------------------------------+
bool FeedForward(matrix &data)
  {
//--- проверяем размер исходных данных
   if(data.Cols() != weights1.Rows() - 1)
      return false;
//--- вычисляем первый нейронный слой
   matrix temp = data;
   if(!temp.Resize(temp.Rows(), weights1.Rows()) ||
      !temp.Col(vector::Ones(temp.Rows()), weights1.Rows() - 1))
      return false;
   output1 = temp.MatMul(weights1);
//--- вычисяем функцию активации
   if(!output1.Activation(temp, ac_func))
      return false;
//--- вычисляем второй нейронный слой
   if(!temp.Resize(temp.Rows(), weights2.Rows()) ||
      !temp.Col(vector::Ones(temp.Rows()), weights2.Rows() - 1))
      return false;
   output2 = temp.MatMul(weights2);
//--- вычисляем функцию активации
   if(!output2.Activation(temp, ac_func))
      return false;
//--- вычисляем третий нейронный слой
   if(!temp.Resize(temp.Rows(), weights3.Rows()) ||
      !temp.Col(vector::Ones(temp.Rows()), weights3.Rows() - 1))
      return false;
   result = temp.MatMul(weights3);
//--- возвращаем результат
   return true;
  }

Может вместо

   result = temp.MatMul(weights3);

нужно как-то так?

matrix output3 = temp.MatMul(weights3);
if(!output3.Activation(result, ac_func))
   return false;
 
Denis Kirichenko #:

Я ошибаюсь, или пропустили в этом примере Документации функцию активации?

Может вместо

нужно как-то так?

Цитата:  после последнего слоя (часто линейного) не ставят функций активации

Кроме того в Обратном проходе также два вызова .Activation

 
Denis Kirichenko #:

Я ошибаюсь, или пропустили в этом примере Документации функцию активации?

Может вместо

нужно как-то так?

Отсутствие функции активации означает простую передачу значения. То есть, на самом деле, функция активации - Linear с парметрами alpha=1 и beta=0 (вот здесь про это говорили)

Это - регрессионная модель. Поэтому Linear вполне используема. Хотя, я бы использовал в этом месте ReLU, потому что по постановке задачи на выходе только неотрицательные значения

Запустите представленный пример и убедитесь, что он работает именно так, как ожидается

2023.06.08 09:27:05.398 TestNeuroNet (GBPUSD,H1)        Epoch 19997, loss 0.01244
2023.06.08 09:27:05.439 TestNeuroNet (GBPUSD,H1)        Epoch 19998, loss 0.01244
2023.06.08 09:27:05.476 TestNeuroNet (GBPUSD,H1)        Epoch 19999, loss 0.01244
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        Test loss 0.00420
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (2.47 + 8.49 + -9.63)^2 / (2.47^2 + 8.49^2 + -9.63^2) =  Net 2.20, Target 2.21
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (-7.07 + 7.57 + -3.77)^2 / (-7.07^2 + 7.57^2 + -3.77^2) =  Net 0.55, Target 0.54
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (-6.84 + 5.00 + 0.35)^2 / (-6.84^2 + 5.00^2 + 0.35^2) =  Net 0.07, Target 0.14
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (2.02 + 9.47 + 8.43)^2 / (2.02^2 + 9.47^2 + 8.43^2) =  Net 2.72, Target 2.67
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (-7.35 + -9.70 + -1.88)^2 / (-7.35^2 + -9.70^2 + -1.88^2) =  Net 4.75, Target 4.72
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (2.81 + -7.58 + -4.99)^2 / (2.81^2 + -7.58^2 + -4.99^2) =  Net 1.79, Target 1.69
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (8.57 + 8.41 + -1.62)^2 / (8.57^2 + 8.41^2 + -1.62^2) =  Net 4.36, Target 4.39
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (-7.49 + -3.08 + -9.71)^2 / (-7.49^2 + -3.08^2 + -9.71^2) =  Net 1.03, Target 1.16
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (-2.62 + -3.05 + 4.43)^2 / (-2.62^2 + -3.05^2 + 4.43^2) =  Net 2.18, Target 2.13
2023.06.08 09:27:05.501 TestNeuroNet (GBPUSD,H1)        (4.90 + 4.10 + -1.84)^2 / (4.90^2 + 4.10^2 + -1.84^2) =  Net 3.90, Target 3.88
Матрицы и векторы в MQL5: функции активации
Матрицы и векторы в MQL5: функции активации
  • www.mql5.com
В данной статье мы опишем только один из аспектов машинного обучения - функции активации. В искусственных нейронных сетях функция активации нейрона вычисляет значение выходного сигнала на основе значений входного сигнала или набора входных сигналов. Мы покажем, что находится "под капотом".
 

Спасибо за уточнение.

Ещё такой вопрос. А почему при вычислении градиента выходного слоя не задействуется функция  matrix::LossGradient() ? Это потому что не применяли функцию активации на последнем слое? И соот-но не нужно брать частную производную...

 
Denis Kirichenko #:

Спасибо за уточнение.

Ещё такой вопрос. А почему при вычислении градиента выходного слоя не задействуется функция  matrix::LossGradient() ? Это потому что не применяли функцию активации на последнем слое? И соот-но не нужно брать частную производную...

Трудно сказать, почему автор данного скрипта не использовал методы Loss и LossGradient.

На самом деле

bool Backprop(matrix &data, matrix &target)
  {
//--- проверяем размерность матрицы целевых значений
   if(target.Rows() != result.Rows() ||
      target.Cols() != result.Cols())
      return false;
//--- определяем отклонение расчетных значений от целевых
   matrix loss = (target - result) * 2;
...

подсвеченная строка это ни что иное, как вычисление градиента от функции потерь MSE.

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

Причина обращения: