Dynamic array of neural layer storage

It is worth mentioning a few words about the dynamic array CArrayLayers that stores neural layers. As previously announced, it is based on the standard CArrayObj object array class.

The functionality of the parent class almost entirely meets our requirements for a dynamic array. When examining the source code of the parent class, you can find all the functionality related to the dynamic array operations and accessing its elements. Additionally, methods for working with files (writing and reading an array) are also implemented. For this, special thanks to the MetaQuotes team.

When examining the algorithm of the method that reads an array from the Load file in detail, pay attention to the CreateElement method which creates a new element.

In the previous section, when discussing the method for reading a neural network from a file, prior to reading the data, we instantiated an object of the corresponding class. The mentioned method performs similar functionality, but it is not implemented in the parent class. This is quite understandable and reasonable, as the creators of the class couldn't anticipate the specific objects their array would store, and thus couldn't create a method generating an unknown class. Therefore, they left a virtual method to be overridden in the user-defined class.

And as consumers of their product, we create our own dynamic array class by inheriting the core functionality from the parent class. In this case, we override the method of creating a new array element.

class CArrayLayers   :  public CArrayObj
  {
protected:
   CMyOpenCL*        m_cOpenCL;
   int               m_iFileHandle;
public:
                     CArrayLayers(void) : m_cOpenCL(NULL),
                                          m_iFileHandle(INVALID_HANDLE)
                     { }
                    ~CArrayLayers(void) { };
   //---
   virtual bool      SetOpencl(CMyOpenCL *opencl);
   virtual bool      Load(const int file_handleoverride;
   //--- method creating an element of an array
   virtual bool      CreateElement(const int indexoverride;
   virtual bool      CreateElement(const int index,
                                   CLayerDescriptiondescription);
   //--- method identifying the object
   virtual int       Type(voidoverride const { return(defArrayLayers); }
  };

One more point should be noted. In order for our overridden method to be called from the parent class method, its definition must fully match the definition of the parent class method, including parameters and return value. Of course, there is nothing complex in this, but we are faced with the same question that the team of creators of the parent class had: what object to create?

We know it will be a neural layer object, but we don't know what type. We can save the type of the required neural layer to a file before writing the contents of the object itself. However, how can we read it from the file if the method doesn't receive a file handle for loading data?

At the same time, we pass the file handle when we call the data loading method Load. Evidently, we need to override the load method as well. But I wouldn't want to rewrite the whole method. Therefore, I added the variable m_iFileHandle, in which I save the file handle for loading data when the Load method is called. Then I call a similar method of the parent class.

bool CArrayLayers::Load(const int file_handle)
  {
   m_iFileHandle = file_handle;
   return CArrayObj::Load(file_handle);
  }

Now let's look directly at the method of creating a new neural layer in a dynamic array. In the parameters, the method receives the index of the element to be created. At the beginning of the method, we check that the resulting index is not negative, because the index of an element of a dynamic array cannot be less than zero. We will also check the saved file handle for loading — without it, we won't be able to determine the type of the element being created.

Next, we reserve an element in our array, read the type of the element to be created from the file, and create an instance of the type we need. Let's not forget to check the result of creating a new object, pass a pointer to the OpenCL object into the new element, and save the pointer to the new neural layer into our array. In conclusion, let's ensure that the index of the new element does not exceed the maximum number of elements in the array.

bool CArrayLayers::CreateElement(const int index)
  {
//--- source data verification block
   if(index < 0 || m_iFileHandle==INVALID_HANDLE)
      return false;
//--- reserving an array element for a new object
   if(!Reserve(index + 1))
      return false;
//--- read the type of the desired object from the file and create the corresponding neural layer
   CNeuronBase *temp = NULL;
   int type = FileReadInteger(m_iFileHandle);
   switch(type)
     {
      case defNeuronBase:
         temp = new CNeuronBase();
         break;
      case defNeuronConv:
         temp = new CNeuronConv();
         break;
      case defNeuronProof:
         temp = new CNeuronProof();
         break;
      case defNeuronLSTM:
         temp = new CNeuronLSTM();
         break;
      case defNeuronAttention:
         temp = new CNeuronAttention();
         break;
      case defNeuronMHAttention:
         temp = new CNeuronMHAttention();
         break;
      case defNeuronGPT:
         temp = new CNeuronGPT();
         break;
      case defNeuronDropout:
         temp = new CNeuronDropout();
         break;
      case defNeuronBatchNorm:
         temp = new CNeuronBatchNorm();
         break;
      default:
         return false;
     }
//--- control over the creation of a new object
   if(!temp)
      return false;
//--- add a pointer to the created object to the array
   if(m_data[index])
      delete m_data[index];
 
   temp.SetOpenCL(m_cOpenCL);
   m_data[index] = temp;
//---
   return true;
  }

Since a new class has been created, I decided to add a couple more methods to it. The first thing I added was a similar method for generating a new item. The difference is that the new method creates a new layer based on the description obtained in the method parameters. The algorithm of the method is almost completely the same as above, except for some details.

bool CArrayLayers::CreateElement(const int indexCLayerDescription *desc)
  {
//--- source data verification block
   if(index < 0 || !desc)
      return false;
//--- reserve an array element for a new object
   if(!Reserve(index + 1))
      return false;
//--- create the corresponding neural layer
   CNeuronBase *temp = NULL;
   switch(desc.type)
     {
      case defNeuronBase:
         temp = new CNeuronBase();
         break;
      case defNeuronConv:
         temp = new CNeuronConv();
         break;
      case defNeuronProof:
         temp = new CNeuronProof();
         break;
      case defNeuronLSTM:
         temp = new CNeuronLSTM();
         break;
      case defNeuronAttention:
         temp = new CNeuronAttention();
         break;
      case defNeuronMHAttention:
         temp = new CNeuronMHAttention();
         break;
      case defNeuronGPT:
         temp = new CNeuronGPT();
         break;
      case defNeuronDropout:
         temp = new CNeuronDropout();
         break;
      case defNeuronBatchNorm:
         temp = new CNeuronBatchNorm();
         break;
      default:
         return false;
     }
//--- control over the creation of a new object
   if(!temp)
      return false;
//--- add a pointer to the created object to the array
   if(!temp.Init(desc))
      return false;
   if(m_data[index])
      delete m_data[index];
   temp.SetOpenCL(m_cOpenCL);
   m_data[index] = temp;
   m_data_total  = fmax(m_data_totalindex + 1);
//---
   return true;
  }

The second added method is responsible for passing a pointer to the OpenCL object to all previously created layers of our neural network, as the decision to use this technology can be made either before or after the neural network is generated. For example, a neural network can be created and tested for performance without using OpenCL technology. Further, the technology can be leveraged to accelerate the learning process.

The algorithm of the method is quite simple. We first check if the pointer was previously set and delete the old object if necessary. Then we save the new pointer and start the loop for enumerating the elements of the dynamic array. In this case, we will pass a new pointer to an OpenCL object to each element of the array.

bool CArrayLayers::SetOpencl(CMyOpenCL *opencl)
  {
//--- source data verification block
   if(m_cOpenCL)
      delete m_cOpenCL;
 
   m_cOpenCL = opencl;
//--- passing a pointer to all array elements
   for(int i = 0i < m_data_totali++)
     {
      if(!m_data[i])
         return false;
      if(!((CNeuronBase *)m_data[i]).SetOpenCL(m_cOpenCL))
         return false;
     }
//--- 
   return(!!m_cOpenCL);
  }