English 中文 Español Deutsch 日本語 Português
preview
Работа с матрицами, расширение функционала Стандартной библиотеки матриц и векторов

Работа с матрицами, расширение функционала Стандартной библиотеки матриц и векторов

MetaTrader 5Статистика и анализ | 23 марта 2023, 16:00
1 363 0
Omega J Msigwa
Omega J Msigwa

"Вы никогда не достигнете совершенства, потому что всегда есть место для улучшений.

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

Введение

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

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


В статье будут рассмотрены следующие функции и методы.

Содержание:


Чтение матрицы из CSV-файла

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

Первый шаг - чтение CSV-файла. Предполагается, что первая строка CSV-файла содержит только строки, поэтому даже если в первой строке есть значения, они не будут включены в матрицу. Поскольку матрица не может содержать строковых значений, значения в первой строке будут сохранены в массиве csv_header.
      while(!FileIsEnding(handle))
        {
         string data = FileReadString(handle);
         //---
         if(rows ==0)
           {
            ArrayResize(csv_header,column+1);
            csv_header[column] = data;
           }
         if(rows>0)  //Avoid the first column which contains the column's header
            mat_[rows-1,column] = (double(data));
         column++;
         //---

Этот массив помогает отслеживать имена столбцов из CSV-файла, который мы только что прочитали.

Полный код:

matrix CMatrixutils::ReadCsv(string file_name,string delimiter=",")
  {
   matrix mat_ = {};
   int rows_total=0;
   int handle = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,delimiter);
   ResetLastError();
   if(handle == INVALID_HANDLE)
     {
      printf("Invalid %s handle Error %d ",file_name,GetLastError());
      Print(GetLastError()==0?" TIP | File Might be in use Somewhere else or in another Directory":"");
     }
   else
     {
      int column = 0, rows=0;

      while(!FileIsEnding(handle))
        {
         string data = FileReadString(handle);
         //---
         if(rows ==0)
           {
            ArrayResize(csv_header,column+1);
            csv_header[column] = data;
           }
         if(rows>0)  //Avoid the first column which contains the column's header
            mat_[rows-1,column] = (double(data));
         column++;
         //---
         if(FileIsLineEnding(handle))
           {
            rows++;
            mat_.Resize(rows,column);
            column = 0;
           }
        }
      rows_total = rows;
      FileClose(handle);
     }
   mat_.Resize(rows_total-1,mat_.Cols());
   return(mat_);
  }

Использование:

Вот CSV-файл, который мы хотим загрузить в матрицу

Загрузка CSV-файла в матрицу:

   Print("---> Reading a CSV file to Matrix\n");
   Matrix = matrix_utils.ReadCsv("NASDAQ_DATA.csv"); 
   
   ArrayPrint(matrix_utils.csv_header);
   Print(Matrix);
Результат:
CS      0       06:48:21.228    matrix test (EURUSD,H1) "S&P500" "50SMA"  "13RSI"  "NASDAQ"
CS      0       06:48:21.228    matrix test (EURUSD,H1) [[4173.8,13386.6,34.8,13067.5]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4179.2,13396.7,36.6,13094.8]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4182.7,13406.6,37.5,13108]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4185.8,13416.8,37.1,13104.3]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4180.8,13425.2,34.9,13082.2]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4174.6,13432.2,31.8,13052]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4174.9,13440.4,33.2,13082.2]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4170.8,13447.6,32.2,13070.6]
CS      0       06:48:21.228    matrix test (EURUSD,H1)  [4182.2,13457.8,32.5,13078.8]

Я решил создать эту функцию, так как не смог найти способ прочитать CSV-файл из Стандартной библиотеки. Я рассчитывал, что мне удастся загрузить значения из CSV-файла Matrix.FromFile().

Однако этого не случилось. Если кто-то нашел в документации, как это сделать, дайте мне знать в комментариях к статье.

Чтение закодированных значений из CSV-файла

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

weather dataset

Чтобы иметь возможность кодировать этот CSV-файл, мы собираемся считать все его данные в один массив строковых значений, так как матрицы не могут содержать строковые значения. Первое, что нам нужно сделать в функции ReadCsvEncode(), это прочитать столбцы, присутствующие в данном CSV-файле. Во-вторых, мы зацикливаем те столбцы, где, как и на каждой итерации, открывается CSV, и один столбец считывается полностью. Значения этого столбца получают хранятся в массиве всех значений с именемtoArr[].

//--- Obtaining the columns
   matrix Matrix={}; 
//--- Loading the entire Matrix to an Array
   int csv_columns=0,  rows_total=0; 
   int handle = CSVOpen(file_name,delimiter);  
   if (handle != INVALID_HANDLE)
      {
       while (!FileIsEnding(handle))
         {
           string data = FileReadString(handle);

           csv_columns++;
//---
           if (FileIsLineEnding(handle)) break;
         } 
      }      
   FileClose(handle);   
   ArrayResize(csv_header,csv_columns);  
//---
   string toArr[];
    int counter=0; 
    for (int i=0; i<csv_columns; i++)
      {                    
        if ((handle = CSVOpen(file_name,delimiter)) != INVALID_HANDLE)
         {  
          int column = 0, rows=0;
          while (!FileIsEnding(handle))
            {
              string data = FileReadString(handle);
              column++;
   //---      
              if (column==i+1)
                 {                      
                     if (rows>=1) //Avoid the first column which contains the column's header
                       {   
                           counter++;                       
                           ArrayResize(toArr,counter); //array size for all the columns 
                           toArr[counter-1]=data;
                       }
                      else csv_header[column-1]  =  data;
                 }
   //---
              if (FileIsLineEnding(handle))
                {                     
                   rows++;
                   column = 0;
                }
            } 
          rows_total += rows-1; 
        }
       FileClose(handle); 
     }
//---

Теперь toArr[] выглядит так при печати:

CS      0       06:00:15.952    matrix test (US500,D1)  [ 0] "Sunny"    "Sunny"    "Overcast" "Rain"     "Rain"     "Rain"     "Overcast" "Sunny"    "Sunny"    "Rain"     "Sunny"    "Overcast"
CS      0       06:00:15.952    matrix test (US500,D1)  [12] "Overcast" "Rain"     "Hot"      "Hot"      "Hot"      "Mild"     "Cool"     "Cool"     "Cool"     "Mild"     "Cool"     "Mild"    
CS      0       06:00:15.952    matrix test (US500,D1)  [24] "Mild"     "Mild"     "Hot"      "Mild"     "High"     "High"     "High"     "High"     "Normal"   "Normal"   "Normal"   "High"    
CS      0       06:00:15.952    matrix test (US500,D1)  [36] "Normal"   "Normal"   "Normal"   "High"     "Normal"   "High"     "Weak"     "Strong"   "Weak"     "Weak"     "Weak"     "Strong"  
CS      0       06:00:15.952    matrix test (US500,D1)  [48] "Strong"   "Weak"     "Weak"     "Weak"     "Strong"   "Strong"   "Weak"     "Strong"   "No"       "No"       "Yes"      "Yes"     
CS      0       06:00:15.952    matrix test (US500,D1)  [60] "Yes"      "No"       "Yes"      "No"       "Yes"      "Yes"      "Yes"      "Yes"      "Yes"      "No"      

Если вы обратите внимание на этот массив, то заметите, что значения из файла CSV находятся в том порядке, в котором они были получены из него. От индекса 0 до 13 находится первый столбец, от индекса 14 до 27 лежит второй столбец и т.д. Теперь мы можем легко извлечь значения и закодировать их, чтобы наконец сохранить их в матрице.

    ulong mat_cols = 0,mat_rows = 0;    
    if (ArraySize(toArr) % csv_columns !=0)
     printf("This CSV file named %s has unequal number of columns = %d and rows %d Its size = %d",file_name,csv_columns,ArraySize(toArr) / csv_columns,ArraySize(toArr));
    else //preparing the number of columns and rows that out matrix needs
     {
        mat_cols = (ulong)csv_columns;       
        mat_rows = (ulong)ArraySize(toArr)/mat_cols;
     }
//--- Encoding the CSV
     Matrix.Resize(mat_rows,mat_cols);          
//---
     string Arr[]; //temporary array to carry the values of a single column      
     int start =0;      
     vector v = {};      
       for (ulong j=0; j<mat_cols; j++)
         {
            ArrayCopy(Arr,toArr,0,start,(int)mat_rows);          
            v = LabelEncoder(Arr);                        
            Matrix.Col(v, j);            
            start += (int)mat_rows;
         }      
   return (Matrix);

Функция LabelEncoder() ставит метку для похожих признаков в массиве столбцов.

vector CMatrixutils::LabelEncoder(string &Arr[])
 {
   string classes[];   
   vector Encoded((ulong)ArraySize(Arr));   
   Classes(Arr,classes);    
    for (ulong A=0; A<classes.Size(); A++)
      for (ulong i=0; i<Encoded.Size(); i++)
        {
           if (classes[A] == Arr[i])
              Encoded[i] = (int)A;
        }    
   return Encoded;
 }

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

void CMatrixutils::Classes(const string &Array[],string &classes_arr[])
 {
   string temp_arr[];
   ArrayResize(classes_arr,1);
   ArrayCopy(temp_arr,Array);   
   classes_arr[0] = Array[0];   
   for(int i=0, count =1; i<ArraySize(Array); i++)  //counting the different neighbors
     {
      for(int j=0; j<ArraySize(Array); j++)
        {
         if(Array[i] == temp_arr[j] && temp_arr[j] != "nan")
           {
            bool count_ready = false;

            for(int n=0; n<ArraySize(classes_arr); n++)
               if(Array[i] == classes_arr[n])
                    count_ready = true;

            if(!count_ready)
              {
               count++;
               ArrayResize(classes_arr,count);

               classes_arr[count-1] = Array[i]; 

               temp_arr[j] = "nan"; //modify so that it can no more be counted
              }
            else
               break;
            //Print("t vectors vector ",v);
           }
         else
            continue;
        }
     }
 }

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

vector CMatrixutils::Classes(vector &v)

Ниже показано, как использовать эту функцию:

   Matrix = matrix_utils.ReadCsvEncode("weather dataset.csv");
   ArrayPrint(matrix_utils.csv_header);
   Print(Matrix);

Результат:

CS      0       06:35:10.798    matrix test (US500,D1)  "Outlook"     "Temperature" "Humidity"    "Wind"        "Play"       
CS      0       06:35:10.798    matrix test (US500,D1)  [[0,0,0,0,0]
CS      0       06:35:10.798    matrix test (US500,D1)   [0,0,0,1,0]
CS      0       06:35:10.798    matrix test (US500,D1)   [1,0,0,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [2,1,0,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [2,2,1,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [2,2,1,1,0]
CS      0       06:35:10.798    matrix test (US500,D1)   [1,2,1,1,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [0,1,0,0,0]
CS      0       06:35:10.798    matrix test (US500,D1)   [0,2,1,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [2,1,1,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [0,1,1,1,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [1,1,0,1,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [1,0,1,0,1]
CS      0       06:35:10.798    matrix test (US500,D1)   [2,1,0,1,0]]


Запись матрицы в CSV-файл

Это еще одна важная функция, которая может помочь записать матрицу в CSV-файл без необходимости жесткого кодирования каждый раз, когда в матрицу добавляется новый столбец. Гибкая запись в CSV-файл была проблемой для многих участников форума: пост 1, пост 2, пост 3. Вот как это сделать с помощью матриц;
Хитрость заключается в конкатенации строк. Данные в CSV-файле записываются как одна строка, объединенная с разделителями, за исключением последнего элемента в строке.
         row = Matrix.Row(i);
         for(ulong j=0, cols =1; j<row.Size(); j++, cols++)
           {
            concstring += (string)NormalizeDouble(row[j],digits) + (cols == Matrix.Cols() ? "" : ",");
           }

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

Давайте попробуем записать эту матрицу, взятую из CSV-файла, обратно в новый CSV-файл.

   string csv_name = "matrix CSV.csv";
   
   Print("\n---> Writing a Matrix to a CSV File > ",csv_name);
   string header[4] = {"S&P500","SMA","RSI","NASDAQ"};
   
   matrix_utils.WriteCsv(csv_name,Matrix,header);

Ниже представлен новый CSV-файл;


Всё хорошо.


Преобразование матрицы в вектор

Кто-то может спросить, зачем преобразовывать матрицу в вектор? Это удобно во многих областях, поскольку вам может не понадобиться матрица всё время, поэтому эта функция преобразует матрицу в вектор. Например, когда вы читаете матрицу из файла CSV при создании модели линейной регрессии, независимой переменной будет матрица nx1 или 1xn. При попытке использоватьфункцию потерь например, MSE, мы не можем использовать матрицу для оценки модели, вместо этого мы будем использовать вектор этой независимой переменной и вектор значений, предсказанных моделью.

Код представлен ниже:

vector CMatrixutils::MatrixToVector(const matrix &mat)
  {
    vector v = {};
    
    if (!v.Assign(mat))
      Print("Failed to turn the matrix to a vector");
    
    return(v);
  }

Использование:

   matrix mat = {
      {1,2,3},
      {4,5,6},
      {7,8,9}
   };

   Print("\nMatrix to play with\n",mat,"\n");
   
//---

   Print("---> Matrix to Vector");
   vector vec = matrix_utils.MatrixToVector(mat);
   Print(vec);

Результат:

2022.12.20 05:39:00.286 matrix test (US500,D1)  ---> Matrix to Vector
2022.12.20 05:39:00.286 matrix test (US500,D1)  [1,2,3,4,5,6,7,8,9]


Преобразование вектора в матрицу

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

matrix CMatrixutils::VectorToMatrix(const vector &v)
  {   
   matrix mat = {};
   
   if (!mat.Assign(v))
      Print("Failed to turn the vector to a 1xn matrix");
   
   return(mat);
  }

Использование:

   Print("\n---> Vector ",vec," to Matrix");
   mat = matrix_utils.VectorToMatrix(vec); 
   Print(mat);

Результат:

2022.12.20 05:50:05.680 matrix test (US500,D1)  ---> Vector [1,2,3,4,5,6,7,8,9] to Matrix
2022.12.20 05:50:05.680 matrix test (US500,D1)  [[1,2,3,4,5,6,7,8,9]]


Преобразование массива в вектор

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

vector CMatrixutils::ArrayToVector(const double &Arr[])
  {
   vector v((ulong)ArraySize(Arr));

   for(ulong i=0; i<v.Size(); i++)
      v[i] = Arr[i];

   return (v);
  }

Использование:

   Print("---> Array to vector");
   double Arr[3] = {1,2,3};
   vec = matrix_utils.ArrayToVector(Arr);
   Print(vec);

Результат:

CS      0       05:51:58.853    matrix test (US500,D1)  ---> Array to vector
CS      0       05:51:58.853    matrix test (US500,D1)  [1,2,3]


Преобразование вектора в массив

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

bool CMatrixutils::VectorToArray(const vector &v,double &arr[])
  {
   ArrayResize(arr,(int)v.Size());

   if(ArraySize(arr) == 0)
      return(false);

   for(ulong i=0; i<v.Size(); i++)
      arr[i] = v[i];

   return(true);
  }

Использование:

   Print("---> Vector to Array");
   double new_array[];
   matrix_utils.VectorToArray(vec,new_array);
   ArrayPrint(new_array);

Результат:

CS      0       06:19:14.647    matrix test (US500,D1)  ---> Vector to Array
CS      0       06:19:14.647    matrix test (US500,D1)  1.0 2.0 3.0


Удаление столбца матрицы

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

Ниже приведен приведен необходимый код:

void CMatrixutils::MatrixRemoveCol(matrix &mat, ulong col)
  {
   matrix new_matrix(mat.Rows(),mat.Cols()-1); //Remove the one Column

   for (ulong i=0, new_col=0; i<mat.Cols(); i++) 
     {
        if (i == col)
          continue;
        else
          {
           new_matrix.Col(mat.Col(i),new_col);
           new_col++;
          }    
     }

   mat.Copy(new_matrix);
  }

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

   matrix new_matrix(mat.Rows(),mat.Cols()-1); //Remove the one column
В новой матрице игнорируем уже ненужный столбец из нашей новой матрицы, все остальное будет сохранено.

В конце функции копируем новую матрицу в старую.

   mat.Copy(new_matrix);

Давайте удалим столбец с индексом 1 (второй столбец) из матрицы, которую мы только что прочитали из CSV-файла.

   Print("Col 1 ","Removed from Matrix");
   matrix_utils.MatrixRemoveCol(Matrix,1);
   Print("New Matrix\n",Matrix);

Результат:

CS      0       07:23:59.612    matrix test (EURUSD,H1) Column of index 1 removed new Matrix
CS      0       07:23:59.612    matrix test (EURUSD,H1) [[4173.8,34.8,13067.5]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4179.2,36.6,13094.8]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4182.7,37.5,13108]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4185.8,37.1,13104.3]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4180.8,34.9,13082.2]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4174.6,31.8,13052]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4174.9,33.2,13082.2]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4170.8,32.2,13070.6]
CS      0       07:23:59.612    matrix test (EURUSD,H1)  [4182.2,32.5,13078.8]


Удаление нескольких столбцов из матрицы

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

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

Первое, что нам нужно сделать, это перебрать все выбранные столбцы, которые мы хотим удалить, сравнив их со всеми доступными столбцами в старой матрице. Если столбцы должны быть удалены, мы установим все значения в этих столбцах равными нулю.
   vector zeros(mat.Rows());
   zeros.Fill(0);

   for(ulong i=0; i<size; i++)
      for(ulong j=0; j<mat.Cols(); j++)
        {
         if(cols[i] == j)
            mat.Col(zeros,j);
        }
Последнее, что нам нужно сделать, это снова дважды пройтись по матрице, проверить все столбцы, заполненные нулевыми значениями, и удалить их один за другим.
   vector column_vector;
   for(ulong A=0; A<mat.Cols(); A++)
      for(ulong i=0; i<mat.Cols(); i++)
        {
         column_vector = mat.Col(i);
         if(column_vector.Sum()==0)
            MatrixRemoveCol(mat,i);
        }

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

Ниже приведен полный код функции:

void CMatrixutils::MatrixRemoveMultCols(matrix &mat,int &cols[])
  {
   ulong size = (int)ArraySize(cols);

   if(size > mat.Cols())
     {
      Print(__FUNCTION__," Columns to remove can't be more than the available columns");
      return;
     }
   vector zeros(mat.Rows());
   zeros.Fill(0);

   for(ulong i=0; i<size; i++)
      for(ulong j=0; j<mat.Cols(); j++)
        {
         if(cols[i] == j)
            mat.Col(zeros,j);
        }
//---

   vector column_vector;
   for(ulong A=0; A<mat.Cols(); A++)
      for(ulong i=0; i<mat.Cols(); i++)
        {
         column_vector = mat.Col(i);
         if(column_vector.Sum()==0)
            MatrixRemoveCol(mat,i);
        }
  }

Давайте посмотрим на эту функцию в действии. Попробуем удалить столбец с индексом 0 и индексом 2:

   Print("\nRemoving multiple columns");
   int cols[2] = {0,2};
   
   matrix_utils.MatrixRemoveMultCols(Matrix,cols);
   Print("Columns at 0,2 index removed New Matrix\n",Matrix);

Результат:

CS      0       07:32:10.923    matrix test (EURUSD,H1) Columns at 0,2 index removed New Matrix
CS      0       07:32:10.923    matrix test (EURUSD,H1) [[13386.6,13067.5]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13396.7,13094.8]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13406.6,13108]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13416.8,13104.3]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13425.2,13082.2]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13432.2,13052]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13440.4,13082.2]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13447.6,13070.6]
CS      0       07:32:10.923    matrix test (EURUSD,H1)  [13457.8,13078.8]


Удаление строки из матрицы

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

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

void CMatrixutils::MatrixRemoveRow(matrix &mat, ulong row)
  {
   matrix new_matrix(mat.Rows()-1,mat.Cols()); //Remove the one Row
      for(ulong i=0, new_rows=0; i<mat.Rows(); i++)
        {
         if(i == row)
            continue;
         else
           {
            new_matrix.Row(mat.Row(i),new_rows);
            new_rows++;
           }
        }
   mat.Copy(new_matrix);
  }

Использование:

   Print("Removing row 1 from matrix");
    
   matrix_utils.MatrixRemoveRow(Matrix,1);
   
   printf("Row %d Removed New Matrix[%d][%d]",0,Matrix.Rows(),Matrix.Cols());
   Print(Matrix); 

Результат:

CS      0       06:36:36.773    matrix test (US500,D1)  Removing row 1 from matrix
CS      0       06:36:36.773    matrix test (US500,D1)  Row 1 Removed New Matrix[743][4]
CS      0       06:36:36.773    matrix test (US500,D1)  [[4173.8,13386.6,34.8,13067.5]
CS      0       06:36:36.773    matrix test (US500,D1)   [4182.7,13406.6,37.5,13108]
CS      0       06:36:36.773    matrix test (US500,D1)   [4185.8,13416.8,37.1,13104.3]
CS      0       06:36:36.773    matrix test (US500,D1)   [4180.8,13425.2,34.9,13082.2]
CS      0       06:36:36.773    matrix test (US500,D1)   [4174.6,13432.2,31.8,13052]
CS      0       06:36:36.773    matrix test (US500,D1)   [4174.9,13440.4,33.2,13082.2]
CS      0       06:36:36.773    matrix test (US500,D1)   [4170.8,13447.6,32.2,13070.6]


Индекс удаления вектора

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

void CMatrixutils::VectorRemoveIndex(vector &v, ulong index)
  {
   vector new_v(v.Size()-1);

   for(ulong i=0, count = 0; i<v.Size(); i++)
      if(i != index)
        {
         new_v[count] = v[i];
         count++;
        }
    v.Copy(new_v);
  }

Применение функции показано ниже:

   vector v= {0,1,2,3,4};
   Print("Vector remove index 3");
   matrix_utils.VectorRemoveIndex(v,3);
   Print(v);

Результат:

2022.12.20 06:40:30.928 matrix test (US500,D1)  Vector remove index 3
2022.12.20 06:40:30.928 matrix test (US500,D1)  [0,1,2,4]


Разделение матрицы на обучающую и тестовую матрицы

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

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

   Print("---> Train / Test Split");
   matrix TrainMatrix, TestMatrix;
   matrix_utils.TrainTestSplitMatrices(Matrix,TrainMatrix,TestMatrix);

   Print("\nTrain Matrix(",TrainMatrix.Rows(),",",TrainMatrix.Cols(),")\n",TrainMatrix);
   Print("\nTestMatrix(",TestMatrix.Rows(),",",TestMatrix.Cols(),")\n",TestMatrix);

Результат:

CS      0       07:38:46.011    matrix test (EURUSD,H1) Train Matrix(521,4)
CS      0       07:38:46.011    matrix test (EURUSD,H1) [[4173.8,13386.6,34.8,13067.5]
CS      0       07:38:46.011    matrix test (EURUSD,H1)  [4179.2,13396.7,36.6,13094.8]
CS      0       07:38:46.011    matrix test (EURUSD,H1)  [4182.7,13406.6,37.5,13108]
CS      0       07:38:46.011    matrix test (EURUSD,H1)  [4185.8,13416.8,37.1,13104.3]
CS      0       07:38:46.011    matrix test (EURUSD,H1)  [4180.8,13425.2,34.9,13082.2]
....
....

CS      0       07:38:46.012    matrix test (EURUSD,H1) TestMatrix(223,4)
CS      0       07:38:46.012    matrix test (EURUSD,H1) [[4578.1,14797.9,65.90000000000001,15021.1]
CS      0       07:38:46.012    matrix test (EURUSD,H1)  [4574.9,14789.2,63.9,15006.2]
CS      0       07:38:46.012    matrix test (EURUSD,H1)  [4573.6,14781.4,63,14999]
CS      0       07:38:46.012    matrix test (EURUSD,H1)  [4571.4,14773.9,62.1,14992.6]
CS      0       07:38:46.012    matrix test (EURUSD,H1)  [4572.8,14766.3,65.2,15007.1]


Разделение матрицы X и Y

При попытке создать модели обучения с учителем после загрузки набора данных в матрицу нам нужно извлечь целевые переменные из матрицы и сохранить их независимо так же, как мы хотим, чтобы независимые переменные хранились в их матрице значений. Текущая функция делает именно это:
Если столбец не выбран, предполагается, что последний столбец в матрице является столбцом независимых переменных.
   if(y_index == -1)
      value = matrix_.Cols()-1;  //Last column in the matrix

Использование:

   Print("---> X and Y split matrices");
   
   matrix x;
   vector y;
   
   matrix_utils.XandYSplitMatrices(Matrix,x,y);
   
   Print("independent vars\n",x);
   Print("Target variables\n",y);

Результат:

CS      0       05:05:03.467    matrix test (US500,D1)  ---> X and Y split matrices
CS      0       05:05:03.467    matrix test (US500,D1)  independent vars
CS      0       05:05:03.468    matrix test (US500,D1)  [[4173.8,13386.6,34.8]
CS      0       05:05:03.468    matrix test (US500,D1)   [4179.2,13396.7,36.6]
CS      0       05:05:03.468    matrix test (US500,D1)   [4182.7,13406.6,37.5]
CS      0       05:05:03.468    matrix test (US500,D1)   [4185.8,13416.8,37.1]
CS      0       05:05:03.468    matrix test (US500,D1)   [4180.8,13425.2,34.9]
CS      0       05:05:03.468    matrix test (US500,D1)   [4174.6,13432.2,31.8]
CS      0       05:05:03.468    matrix test (US500,D1)   [4174.9,13440.4,33.2]
CS      0       05:05:03.468    matrix test (US500,D1)   [4170.8,13447.6,32.2]
CS      0       05:05:03.468    matrix test (US500,D1)   [4182.2,13457.8,32.5]
....
....
2022.12.20 05:05:03.470 matrix test (US500,D1)  Target variables
2022.12.20 05:05:03.470 matrix test (US500,D1)  [13067.5,13094.8,13108,13104.3,13082.2,13052,13082.2,13070.6,13078.8,...]

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

void CMatrixutils::XandYSplitMatrices(const matrix &matrix_,matrix &xmatrix,vector &y_vector,int y_index=-1)
  {
   ulong value = y_index;
   if(y_index == -1)
      value = matrix_.Cols()-1;  //Last column in the matrix
//---
   y_vector = matrix_.Col(value);
   xmatrix.Copy(matrix_);

   MatrixRemoveCol(xmatrix, value); //Remove the y column
  }


Создание матрицы планирования

Матрица планирования имеет важное значение для алгоритма наименьших квадратов. Она представляет собой матрицу x (матрицу независимых переменных) с первым столбцом, заполненным единицами. Матрица планирования помогает нам получить коэффициенты для модели линейной регрессии. Внутри функции DesignMatrix() создается вектор, заполненный единицами. Затем он вставляется в первый столбец матрицы, а остальные столбцы устанавливаются в следующем порядке:
Полный код:

matrix CMatrixutils::DesignMatrix(matrix &x_matrix)
  {
   matrix out_matrix(x_matrix.Rows(),x_matrix.Cols()+1);
   vector ones(x_matrix.Rows());
   ones.Fill(1);
   out_matrix.Col(ones,0);
   vector new_vector;
   for(ulong i=1; i<out_matrix.Cols(); i++)
     {
      new_vector = x_matrix.Col(i-1);
      out_matrix.Col(new_vector,i);
     }
   return (out_matrix);
  }

Использование:

   Print("---> Design Matrix\n");
   matrix design = matrix_utils.DesignMatrix(x);
   Print(design);

Результат:

CS      0       05:28:51.786    matrix test (US500,D1)  ---> Design Matrix
CS      0       05:28:51.786    matrix test (US500,D1)  
CS      0       05:28:51.786    matrix test (US500,D1)  [[1,4173.8,13386.6,34.8]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4179.2,13396.7,36.6]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4182.7,13406.6,37.5]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4185.8,13416.8,37.1]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4180.8,13425.2,34.9]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4174.6,13432.2,31.8]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4174.9,13440.4,33.2]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4170.8,13447.6,32.2]
CS      0       05:28:51.786    matrix test (US500,D1)   [1,4182.2,13457.8,32.5]


Матрица кодирования с одним активным состоянием

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

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

Для большей ясности рассмотрим следующий пример:

предположим, что это набор данных, которому мы хотим обучить нейронную сеть, чтобы иметь возможность классифицировать три класса: собака (Dog), кошка (Cat) и мышь (Mice) на основе высоты ног (Legs Height) и диаметра их тела (Body Diameter).

Многослойный персептрон будет иметь 2 входных узла/нейрона: один для высоты ног, а другой для диаметра тела на входном слое, в то время как выходной слой будет иметь 3 узла, представляющих 3 результата: собака, кошка и мышь.

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

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

Теперь давайте посмотрим на кодирование с одним открытым состоянием в действии.

Это наша матрица в MetaEditor:

   matrix dataset = {
                     {12,28, 1},
                     {11,14, 1},
                     {7,8, 2},
                     {2,4, 3}
                    };

Это тот же набор данных, что мы видели в CSV, за исключением того, что цели помечены целыми числами, поскольку у нас не может быть строк в матрице (кстати, когда и где можно использовать строки в расчетах?)

   matrix dataset = {
                     {12,28, 1},
                     {11,14, 1},
                     {7,8, 2},
                     {2,4, 3}
                    };
 
   vector y_vector = dataset.Col(2); //obtain the column to encode
   uint classes = 3; //number of classes in the column
   Print("One Hot encoded matrix \n",matrix_utils.OneHotEncoding(y_vector,classes));   

Ниже приведен результат:

CS      0       08:54:04.744    matrix test (EURUSD,H1) One Hot encoded matrix 
CS      0       08:54:04.744    matrix test (EURUSD,H1) [[1,0,0]
CS      0       08:54:04.744    matrix test (EURUSD,H1)  [1,0,0]
CS      0       08:54:04.744    matrix test (EURUSD,H1)  [0,1,0]
CS      0       08:54:04.744    matrix test (EURUSD,H1)  [0,0,1]]
                


Получение классов из вектора

Функция возвращает вектор, содержащий классы, доступные в данном векторе. Например, вектор [1,2,3] имеет три разных класса. Вектор [1,0,1,0] имеет два разных класса. Роль функции состоит в том, чтобы вернуть доступные отдельные классы.

   vector v = {1,0,0,0,1,0,1,0};
   
   Print("classes ",matrix_utils.Classes(v));

Результат:

2022.12.27 06:41:54.758 matrix test (US500,D1)  classes [1,0]


Создание вектора случайных значений

Иногда нам нужно сгенерировать вектор, содержащий случайные величины. Функция занимается именно этим. Чаще всего эта функция используется для генерации весов и вектора смещения для алгоритма ближайших соседей (NN). Вы также можете использовать его для создания рандомизированных выборок данных.

   vector            Random(int min, int max, int size);
   vector            Random(double min, double max, int size);

Использование:

   v = matrix_utils.Random(1.0,2.0,10);
   Print("v ",v);

Результат:

2022.12.27 06:50:03.055 matrix test (US500,D1)  v [1.717642750328074,1.276955473494675,1.346263008514664,1.32959990234077,1.006469924008911,1.992980742820521,1.788445692312387,1.218909268471328,1.732139042329173,1.512405774101993]


Добавление одного вектора к другому

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

vector CMatrixutils::Append(vector &v1, vector &v2)
 {
   vector v_out = v1;   
   v_out.Resize(v1.Size()+v2.Size());   
   for (ulong i=v2.Size(),i2=0; i<v_out.Size(); i++, i2++)
      v_out[i] = v2[i2];   
   return (v_out); 
 }

Использование:

   vector v1 = {1,2,3}, v2 = {4,5,6};
   
   Print("Vector 1 & 2 ",matrix_utils.Append(v1,v2));

Результат:

2022.12.27 06:59:25.252 matrix test (US500,D1)  Vector 1 & 2 [1,2,3,4,5,6]


Копирование одного вектора в другой

И последнее, но не менее важное: скопируем вектор в другой вектор, манипулируя им так же, как и Arraycopy. Нам часто не нужно копировать весь вектор в другой. Часто нам нужна лишь его часть. Метод Copy Стандартной библиотеки в такой ситуации не работает.

bool CMatrixutils::Copy(const vector &src,vector &dst,ulong src_start,ulong total=WHOLE_ARRAY)
 {
   if (total == WHOLE_ARRAY)
      total = src.Size()-src_start;
   
   if ( total <= 0 || src.Size() == 0)
    {
       printf("Can't copy a vector | Size %d total %d src_start %d ",src.Size(),total,src_start);
       return (false);
    }  
   dst.Resize(total);
   dst.Fill(0);  
   for (ulong i=src_start, index =0; i<total+src_start; i++)
      {   
          dst[index] = src[i];         
          index++;
      }
   return (true);
 }

Использование:

   vector all = {1,2,3,4,5,6};   
   matrix_utils.Copy(all,v,3);
   Print("copied vector ",v);   

Результат:

2022.12.27 07:15:41.420 matrix test (US500,D1)  copied vector [4,5,6]


Заключительные мысли

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

Вы можете отслеживать разработку моего класса Utility Matrix в моем репозитории на GitHub > https://github.com/MegaJoctan/MALE5


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/11858

Прикрепленные файлы |
MQL5.zip (13.63 KB)
Использование ONNX-моделей в MQL5 Использование ONNX-моделей в MQL5
ONNX (Open Neural Network Exchange) — открытый стандарт представления моделей нейронных сетей. В данной статье мы рассмотрим процесс создания модели СNN-LSTM для прогнозирования финансовых временных рядов и использование созданной ONNX-модели в MQL5-эксперте.
Теория категорий в MQL5 (Часть 2) Теория категорий в MQL5 (Часть 2)
Теория категорий представляет собой разнообразный и расширяющийся раздел математики, который пока относительно не освещен в MQL5-сообществе. Эта серия статей призвана осветить некоторые из ее концепций для создания открытой библиотеки и дальнейшему использованию этого замечательного раздела в создании торговых стратегий.
Популяционные алгоритмы оптимизации: Электромагнитный алгоритм (ElectroMagnetism-like algorithm, ЕМ) Популяционные алгоритмы оптимизации: Электромагнитный алгоритм (ElectroMagnetism-like algorithm, ЕМ)
Статья описывает принципы, методы и возможности применения Электромагнитного алгоритма EM в различных задачах оптимизации. EM-алгоритм является эффективным инструментом оптимизации, способным работать с большими объемами данных и многомерными функциями.
Популяционные алгоритмы оптимизации: Алгоритм растущих деревьев (Saplings Sowing and Growing up — SSG) Популяционные алгоритмы оптимизации: Алгоритм растущих деревьев (Saplings Sowing and Growing up — SSG)
Алгоритм растущих деревьев (Saplings Sowing and Growing up, SSG) вдохновлен одним из самых жизнестойких организмов на планете, который является замечательным образцом выживания в самых различных условиях.