English Русский Español Deutsch 日本語 Português
preview
矩阵实用工具,扩展矩阵和向量的标准库功能

矩阵实用工具,扩展矩阵和向量的标准库功能

MetaTrader 5统计分析 | 2 五月 2023, 10:51
587 0
Omega J Msigwa
Omega J Msigwa

“您永远不会达到完美,因为总有改进的余地。

然而,在通往完美的道路上,您会学会变得更好。”

概述

在 python 中,Utils 类是一个通用的实用工具类,含有函数和代码行,我们可以重用它们,而无需创建类的实例。

针对矩阵的标准库为我们提供了一些非常重要的功能和方法,我们可以用来初始化转换操纵矩阵、以及更多,但就像任何其它构建的函数库一样,它可以加以扩展,从而执行某些应用程序中也许必须/所需的额外内容。


下面介绍的函数和方法没有特定的顺序;

内容:


从 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 文件,譬如查看最流行的天气数据集;

天气数据集

为了能够对此 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 文件中的数值是按照从 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 文件一直是一件困难的事情 > Post1, Post2, Post3。 以下是矩阵写入的方法;
诀窍在于字符串连接。 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);

我们从刚刚自 CSV 文件中读取的矩阵中将列放在索引 1(第二列)处

   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);
  }

Below is how to use it:

   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 矩阵(独立变量矩阵),第一列由数字 1 填充。 此设计矩阵可帮助我们获取线性回归模型的系数。 在函数 DesignMatrix() 中,创建向量,并填充数字 1,然后将其插入到矩阵的第一列,而其余列则仍然遵循原来顺序;
完整代码:

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]


独热编码矩阵

我们完成一些独热编码。 这是您在尝试创建分类神经网络时需要的一个非常重要的函数。 这个函数被证明非常重要,因为它清楚地表明 MLP 与最后一层神经元输出与实际值相关联。

在独热编码矩阵中,每行的向量由零值组成,除了一个值,该值用于我们要命中的正确值,其值为 1。

为了更好地理解这一点,我们来看下面的例子:

假设这是我们想要训练神经网络的数据集,以便能够在狗、猫和老鼠三类之间,基于它们的腿高和体径进行分类。

多层感知器会有 2 个输入节点/神经元,一个用于腿高,另一个则是输入层的身体直径,同时输出层将有 3 个节点代表 3 个结果:狗、猫和老鼠。

现在,假设我们给这个 MLP 投喂的高度和直径分别为 12 和 20,我们希望神经网络将其归类为狗,对不对? 独热编码的作用是将数字 1 放在给定训练数据集具有正确值的节点中,在这种情况下,在属于狗的节点上,将输入数字 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] 的向量有 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]


创建随机值向量。

有几回,我们需要生成一个充满随机变量的向量,那这个函数就可以提供帮助。 此函数最常见的用途是为神经网络生成权重和偏置向量。 您还可以用其来创建随机数据集样本。

   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]


后记

我们可以做很多事情,我们可以向实用工具文件添加更多函数,但我认为本文中介绍的函数和方法是那些我认为您会需要的,如果您已经使用矩阵一段时间了,并且在尝试创建复杂的交易系统(例如受机器学习启发的系统)时,您会发现经常需要它们。

跟踪此矩阵类实用工具的开发,请查阅我的 GitHub 存储库 > https://github.com/MegaJoctan/MALE5


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/11858

附加的文件 |
MQL5.zip (13.63 KB)
DoEasy. 控件(第三十部分):动画态滚动条控件 DoEasy. 控件(第三十部分):动画态滚动条控件
在本文中,我将继续开发滚动条(ScrollBar)控件,并开始实现鼠标交互功能。 此外,我将扩展鼠标状态标志和事件的列表。
MQL5 中的范畴论 (第 1 部分) MQL5 中的范畴论 (第 1 部分)
范畴论是数学的一个多样化和不断扩展的分支,到目前为止,在 MQL 社区中还相对难以发现。 这些系列文章旨在介绍和研究其一些概念,其总体目标是建立一个开放的函数库,吸引评论和研讨,同时希望在交易者的策略开发中进一步在运用这一非凡的领域。
数据科学与机器学习(第 10 部分):岭回归 数据科学与机器学习(第 10 部分):岭回归
岭回归是一种简单的技术,可降低模型复杂度,并防止简单线性回归可能导致的过度拟合。
种群优化算法:鱼群搜索(FSS) 种群优化算法:鱼群搜索(FSS)
鱼群搜索(FSS)是一种新的优化算法,其灵感来自鱼群中鱼的行为,其中大多数(高达 80%)游弋在有组织的亲属群落中。 经证明,鱼类的聚集在觅食效率和保护捕食者方面起着重要作用。