English Русский 中文 Deutsch 日本語 Português
preview
Trabajamos con matrices: ampliando la funcionalidad de la biblioteca estándar de matrices y vectores.

Trabajamos con matrices: ampliando la funcionalidad de la biblioteca estándar de matrices y vectores.

MetaTrader 5Estadística y análisis | 4 abril 2023, 09:40
239 0
Omega J Msigwa
Omega J Msigwa

«Nunca alcanzará la perfección, porque siempre se puede mejorar.

Sin embargo, recorriendo el camino hacia la perfección, aprenderá a mejorar».

Introducción

La clase Utils en Python es una clase de utilidad general con funciones y líneas de código que podemos reutilizar sin crear un ejemplar de una clase.

La biblioteca estándar tiene algunas funciones y métodos muy importantes que podemos usar para la inicialización, la transformación, la manipulación y otro tipo de trabajo con matrices. Pero, al igual que, cualquier biblioteca jamás creada, puede ampliarse para realizar acciones adicionales, eventualmente necesarias en algunas aplicaciones.


En este artículo, hablaremos de las funciones y métodos que puede ver más abajo.

Contenido:


Leer una matriz desde un archivo CSV

Se trata de una función muy importante, pues nos permite cargar sin esfuerzo bases de datos desde un archivo CSV y almacenarlas en una matriz. No es necesario insistir en la importancia de esta función, ya que al escribir código para sistemas comerciales, con frecuencia debemos cargar archivos CSV que contienen señales comerciales o una historia de transacciones a las que nuestro software debe tener acceso.

El primer paso será leer el archivo CSV. Se supone que la primera línea del archivo CSV solo contiene filas, por lo que, aunque haya valores en la primera línea, no se incluirán en la matriz. Como la matriz no puede contener valores de cadena, los valores de la primera fila se almacenarán en el array 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++;
         //---

Esta matriz ayudará a mantener un registro de los nombres de las columnas del archivo CSV que acabamos de leer.

Código completo:

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

Uso:

Aquí tenemos el archivo CSV que queremos cargar en la matriz

Cargamos un archivo CSV en una matriz:

   Print("---> Reading a CSV file to Matrix\n");
   Matrix = matrix_utils.ReadCsv("NASDAQ_DATA.csv"); 
   
   ArrayPrint(matrix_utils.csv_header);
   Print(Matrix);
Resultado:
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]

Hemos decidido crear esta función porque no hallábamos la manera de leer el archivo CSV desde la Biblioteca Estándar. Contábamos con poder cargar valores desde el archivo CSV Matrix.FromFile().

Sin embargo, esto no sucedió. Si alguien ha encontrado en la documentación cómo hacerlo, puede comunicármelo en los comentarios del artículo.

Leer valores codificados desde un archivo CSV

Con frecuencia, podemos necesitar leer un archivo CSV que contiene valores de cadena. Los archivos CSV que contienen cadenas se encuentran a menudo al buscar algoritmos de clasificación. A continuación, le mostramos un conjunto de datos sobre el tiempo;

weather dataset

Para poder codificar este archivo CSV, leeremos todos sus datos en un array de valores de cadena, ya que las matrices no pueden contener valores de cadena. Lo primero que deberemos hacer en ReadCsvEncode() es leer las columnas presentes en este fichero CSV. En segundo lugar, realizaremos un ciclo con las columnas en las que, como en cada iteración, se abre el CSV y se lee una columna por entero. Los valores de esta columna se almacenarán en un array con todos los valores 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); 
     }
//---

Ahora toArr[] tendrá el aspecto siguiente cuando se imprima:

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"      

Si prestamos atención a este array, veremos que los valores del archivo CSV están en el orden en que se recuperaron de él. Del índice 0 al 13 se encuentra la primera columna; del índice 14 al 27 se encuentra la segunda columna, etcétera. Ahora podremos extraer fácilmente los valores y codificarlos para guardarlos finalmente en una matriz.

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

La función LabelEncoder() etiqueta atributos similares en un array de columnas.

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

Esta sencilla función realizará un ciclo entre las clases definidas para todo el array para encontrar y marcar características similares. La mayor parte del trabajo se realiza dentro de la función Classes() (marcada en negro).

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

Esta función obtiene las clases presentes en el array y las transmite al array de referencia classes_arr[]. La función también puede buscar clases en un vector. Encontrará más información sobre la función aquí.

vector CMatrixutils::Classes(vector &v)

A continuación, le mostraremos cómo utilizar esta función:

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

Resultado:

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]]


Escribir una matriz en un archivo CSV

Esta es otra característica importante que puede ser de ayuda al escribir una matriz en un archivo CSV sin necesidad de codificación dura cada vez que se añade una nueva columna a la matriz. La escritura flexible en un archivo CSV ha sido un problema para muchos miembros del foro: post 1, post 2, post 3. Aquí vemos cómo hacerlo usando matrices;
El truco consiste en concatenar las líneas: los datos del archivo CSV se escriben en una línea, unidos con separadores, salvo el último elemento de la línea.
         row = Matrix.Row(i);
         for(ulong j=0, cols =1; j<row.Size(); j++, cols++)
           {
            concstring += (string)NormalizeDouble(row[j],digits) + (cols == Matrix.Cols() ? "" : ",");
           }

Al final tendrá el mismo aspecto que un archivo CSV escrito manualmente con diferentes columnas. Como la matriz no puede contener valores de cadena, hemos tenido que utilizar un array de cadenas para poder escribir el encabezado en este archivo CSV.

Vamos a intentar escribir esta matriz, extraída del archivo CSV, en un nuevo archivo 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);

A continuación, le mostramos el nuevo archivo CSV;


Todo está en orden.


Convertir una matriz en un vector

Cabe preguntarse para qué convertir una matriz en vector. Esto resulta útil en muchas áreas porque podemos no necesitar una matriz todo el tiempo, así que esta función convertirá la matriz en un vector. Por ejemplo, cuando leemos una matriz desde un archivo CSV al crear un modelo de regresión lineal, la variable independiente será una matriz nx1 o 1xn. Al intentar utilizar unafunción de pérdida como el MSE, no podremos usar la matriz para estimar el modelo, en su lugar utilizaremos un vector de esta variable independiente y un vector con los valores predichos por el modelo.

El código se muestra a continuación:

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

Uso:

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

Resultado:

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]


Convertir un vector en una matriz

Es exactamente lo contrario de la función que acabamos de analizar, pero resulta más importante de lo que parece a primera vista. En la mayoría de los modelos de aprendizaje automático, necesitamos efectuar operaciones de multiplicación entre distintas matrices. Como las variables independientes suelen almacenarse en un vector, es posible que tengamos que convertirlas en una matriz nx1 para realizar las operaciones. Estas dos funciones opuestas son importantes porque implementan nuestro código para un menor número de variables, ajustando las ya existentes.

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

Uso:

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

Resultado:

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]]


Convertir un array en un vector

¿Quién necesita muchos arrays en los programas de aprendizaje automático? Los arrays son muy útiles, pero resultan muy sensibles al tamaño. Configurarlos puede ayudar al terminal a eliminar nuestros programas del gráfico cuando se produce un error de salida fuera de los límites del array. Los vectores hacen lo mismo, pero resultan ligeramente más flexibles que los arrays, por no mencionar que están orientados a objetos. Puede retornar un vector de una función, pero si intenta hacer esto con un array, recibirá errores de compilación de código y advertencias.

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

Uso:

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

Resultado:

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]


Convertir un vector en un array

Aún no podemos prescindir por completo de los arrays, ya que pueden darse casos en los que un vector deba convertirse en un array que tenga que conectarse a un argumento de una función, o puede que necesitemos realizar una clasificación de arrays, una segmentación de los mismos o alguna otra acción que no podamos hacer con vectores.

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

Uso:

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

Resultado:

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


Borrar una columna de la matriz

El hecho de que hayamos cargado una columna CSV en una matriz no significa que necesitemos todas las columnas de la misma. Al crear un modelo de aprendizaje automático supervisado, necesitamos eliminar algunas columnas irrelevantes, por no mencionar que podríamos requerir eliminar una variable de respuesta de una matriz llena de variables independientes.

A continuación, le mostraremos el código necesario:

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

Dentro de la función no hay nada raro. Creamos una nueva matriz con el mismo número de filas que la matriz original, pero una columna menos.

   matrix new_matrix(mat.Rows(),mat.Cols()-1); //Remove the one column
En la nueva matriz, ignoramos la columna ya innecesaria de nuestra nueva matriz; todo lo demás se guardará.

Al final de la función, copiamos la nueva matriz en la matriz antigua.

   mat.Copy(new_matrix);

Ahora, vamos a eliminar la columna con el índice 1 (segunda columna) de la matriz que acabamos de leer del archivo CSV.

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

Resultado:

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]


Eliminar varias columnas de una matriz

Hasta ahora, hemos intentado eliminar una columna, pero, a veces, hay matrices grandes que contienen muchas columnas que queremos eliminar al mismo tiempo. Al mismo tiempo, resulta importante evitar borrar las columnas necesarias e intentar acceder a valores fuera del rango para borrar columnas de forma segura.

El número/tamaño de columnas de la nueva matriz debe ser igual al número total de columnas restadas de las columnas que deseamos eliminar. El número de filas de cada columna permanecerá constante (como en la matriz antigua).

Lo primero que deberemos hacer es iterar por todas las columnas seleccionadas que queremos eliminar, comparándolas con todas las columnas disponibles en la matriz antigua. Si las columnas se van a eliminar, pondremos a cero todos los valores de estas columnas.
   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);
        }
Lo último que deberemos hacer es iterar de nuevo por la matriz, volver a comprobar todas las columnas llenas de valores nulos y eliminarlas una a una.
   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);
        }

¿Ha notado los ciclos dobles? Son muy importantes porque el tamaño de la matriz se corrige tras eliminar cada columna, por lo que resulta esencial volver atrás para comprobar si nos hemos saltado alguna columna.

A continuación, le mostraremos el código completo de la función:

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

Veamos esta función en acción. Ahora, intentaremos borrar la columna con el índice 0 y el índice 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);

Resultado:

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]


Eliminar una fila de una matriz

También podría ser necesario eliminar una fila del conjunto de datos porque no sea necesaria, o porque queramos reducir el número de columnas, solo para ver qué funciona y qué no. Esta función no tiene tanta importancia como eliminar una columna de una matriz, por lo que mostramos la función para eliminar solo una fila. Si lo desea, podrá escribir su propio código para eliminar algunas filas.

La misma idea se ha aplicado al intentar eliminar una fila de una matriz. Simplemente ignoraremos la fila innecesaria en la nueva matriz que creamos.

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

Uso:

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

Resultado:

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]


Índice de eliminación de vectores

Esta pequeña función ayudará a eliminar un elemento según el índice específico de un vector. Simplemente ignora el número situado en el índice dado del vector. Los valores restantes se almacenarán en un nuevo vector que se copiará en el vector primario/de referencia del argumento de la función.

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

A continuación, le mostraremos la aplicación de la función:

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

Resultado:

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]


Dividir la matriz en una matriz de entrenamiento y una matriz de prueba

No me canso de repetir lo importante que es esta función a la hora de crear un modelo de aprendizaje automático supervisado. Con frecuencia, necesitamos dividir un conjunto de datos en conjuntos de datos de entrenamiento y de prueba, de modo que podamos entrenar un conjunto de datos con un conjunto y probarlo con otro conjunto de datos que el modelo nunca haya visto antes.

Por defecto, esta función utiliza el 70% del conjunto de datos para la muestra de entrenamiento y el 30% restante para la muestra de prueba: no existe un estado accidental. Los datos se seleccionan según el orden cronológico de la matriz. El primer 70% de los datos se almacena en la matriz de entrenamiento y el 30% restante en la matriz de prueba.

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

Resultado:

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]


Dividir las matrices X e Y

Cuando intentamos crear modelos de aprendizaje supervisado, tras cargar un conjunto de datos en una matriz, necesitamos extraer las variables objetivo de la matriz y almacenarlas de forma independiente del mismo modo que queremos que se guarden las variables independientes en su matriz de valores. La función actual hace precisamente esto:
Si no se selecciona una columna, se supone que la última columna de la matriz será una columna de variable independiente.
   if(y_index == -1)
      value = matrix_.Cols()-1;  //Last column in the matrix

Uso:

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

Resultado:

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,...]

Dentro de la función de división X e Y, la columna seleccionada como variable y se almacena en la matriz Y, mientras que el resto de columnas se almacenan en la matriz 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
  }


Crear una matriz de planificación

La matriz de planificación es importante para el algoritmo de mínimos cuadrados. Es una matriz x (matriz de variables independientes) con la primera columna llena de unidades. La matriz de planificación nos ayuda a obtener los coeficientes del modelo de regresión lineal. Dentro de la función DesignMatrix(), se crea un vector lleno de unidades. A continuación, este se inserta en la primera columna de la matriz, mientras que el resto de columnas se colocan en el siguiente orden:
Código completo:

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

Uso:

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

Resultado:

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]


Matriz de codificación con un estado activo

Se trata de una característica muy importante que necesitaremos al crear redes neuronales de clasificación. La función es muy importante, porque indica al perceptrón multicapa que debe vincular los datos de salida de la neurona de la última capa con los valores reales.

En una matriz de codificación con un estado activo, el vector de cada fila esta formado por valores cero, salvo un valor que se corresponde con el valor correcto de 1 que queremos obtener.

Para mayor claridad, vamos a analizar el siguiente ejemplo:

Supongamos que tenemos un conjunto de datos que queremos entrenar para que una red neuronal sea capaz de clasificar tres clases: perro (dog), gato (cat) y ratón (mice), basándose en la altura de las patas (Legs Height) y el diámetro del cuerpo (Body Diameter).

Un perceptrón multicapa tendrá 2 nodos/neuronas de entrada: uno para la altura de la pata y otro para el diámetro del cuerpo en la capa de entrada, mientras que la capa de salida tendrá 3 nodos que representarán 3 resultados: perro, gato y ratón.

Ahora bien, si suministramos al perceptrón los valores 12 y 20 para la altura y el diámetro respectivamente, podemos esperar que la red neuronal clasifique la clase como perro, ¿verdad? Lo que hace la codificación en caliente es poner un valor igual a la unidad en el nodo que tiene el valor correcto para el conjunto de datos de entrenamiento,así pues, en este caso, en el nodo para el perro se pondrá un valor de 1, mientras que el resto llevará valores cero. 

Cuando codificamos con un estado abierto, podemos calcular la función de coste sustituyendo los valores del vector de codificación por cada una de las probabilidades que nos ha ofrecido el modelo. Este error se propagará nuevamente en la red en los nodos anteriores correspondientes de la capa anterior.

Veamos ahora en acción la codificación con un solo estado abierto.

Esta es nuestra matriz en el MetaEditor:

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

Este es el mismo conjunto de datos que vimos en CSV, salvo que los objetivos están marcados con números enteros, ya que no podemos tener filas en la matriz (por cierto, ¿cuándo y dónde podemos usar filas en los cálculos?)

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

A continuación, le mostraremos el resultado:

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]]
                


Obtener clases a partir de un vector

La función retorna un vector que contiene las clases disponibles en este vector. Por ejemplo, el vector [1,2,3] tiene tres clases diferentes. El vector [1,0,1,0] tiene dos clases diferentes. El papel de la función consiste en retornar las clases individuales disponibles.

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

Resultado:

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


Crear un vector de valores aleatorios

A veces necesitamos generar un vector que contenga variables aleatorias, y la función se encarga precisamente de ello. Esta función se usa sobre todo para generar los pesos y un vector de desplazamiento para el algoritmo del vecino más próximo (NN). También puede usarlo para crear muestras aleatorias de los datos.

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

Uso:

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

Resultado:

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]


Añadir un vector a otro

Se trata de algo semejante a la concatenación de líneas, pero, en lugar de ello, concatenamos vectores. A nuestro juicio, la función puede ser útil en muchos casos. A continuación, podemos mostrarle en qué consiste.

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

Uso:

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

Resultado:

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


Copiar un vector en otro

Por último, pero no menos importante, vamos a copiar un vector en otro vector, manipulándolo de la misma forma que Arraycopy. Con frecuencia, no necesitamos copiar un vector entero en otro. A menudo solo necesitamos una parte. El método Copy de la Biblioteca Estándar no funciona en esta situación.

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

Uso:

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

Resultado:

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


Reflexiones finales

Podemos añadir muchas funciones al archivo de utilidades, pero las funciones y métodos expuestos en este artículo resultarán más necesarios si usamos las matrices para crear sistemas comerciales complejos, en particular sistemas basados en el aprendizaje automático.

Puede seguir el desarrollo de mi clase Utility Matrix en mi repositorio GitHub > https://github.com/MegaJoctan/MALE5


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/11858

Archivos adjuntos |
MQL5.zip (13.63 KB)
Aprendizaje automático y Data Science (Parte 10): Regresión de cresta Aprendizaje automático y Data Science (Parte 10): Regresión de cresta
La regresión de cresta (Ridge Regression) es una técnica simple para reducir la complejidad del modelo y combatir el ajuste que puede derivar de una regresión lineal simple.
DoEasy. Elementos de control (Parte 30): Animando el elemento de control "ScrollBar" DoEasy. Elementos de control (Parte 30): Animando el elemento de control "ScrollBar"
En este artículo, continuaremos desarrollando el control ScrollBar y comenzaremos a crear la funcionalidad de interacción con el ratón. Además, ampliaremos las listas de banderas de estado y eventos de ratón.
Algoritmos de optimización de la población: Algoritmo de luciérnagas (Firefly Algorithm - FA) Algoritmos de optimización de la población: Algoritmo de luciérnagas (Firefly Algorithm - FA)
Hoy analizaremos el método de optimización «Búsqueda con ayuda del algoritmo de luciérnagas» 'Firefly Algorithm Search' (FA). Tras modificar el algoritmo, este ha pasado de ocupar un lugar marginal a convertirse en un verdadero líder en la tabla de calificación.
Redes neuronales: así de sencillo (Parte 35): Módulo de curiosidad intrínseca (Intrinsic Curiosity Module) Redes neuronales: así de sencillo (Parte 35): Módulo de curiosidad intrínseca (Intrinsic Curiosity Module)
Seguimos analizando los algoritmos de aprendizaje por refuerzo. Todos los algoritmos que hemos estudiado hasta ahora requerían la creación de una política de recompensas tal que el agente pudiera evaluar cada una de sus acciones en cada transición de un estado del sistema a otro, pero este enfoque resulta bastante artificial. En la práctica, existe cierto tiempo de retraso entre la acción y la recompensa. En este artículo, le sugerimos que se familiarice con un algoritmo de entrenamiento de modelos que puede funcionar con varios retrasos de tiempo desde la acción hasta la recompensa.