Динамический массив хранения нейронных слоев

Стоит несколько слов сказать о динамическом массиве хранения нейронных слоев CArrayLayers. Как уже было озвучено ранее, он создан на основе стандартного класса массива объектов CArrayObj.

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

При детальном рассмотрении алгоритма метода чтения массива из файла Load наше внимание обращает на себя метод создания нового элемента CreateElement.

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

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

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

Следует обратить внимание еще на один момент. Чтобы из метода родительского класса вызывался наш переопределенный метод, его определение должно полностью соответствовать определению метода родительского класса, включая параметры и возвращаемое значение. В этом, конечно, нет ничего сложного, но перед нами становится тот же вопрос, который стоял перед командой создателей родительского класса: какой объект создавать?

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

В то же время хендл файла мы передаем при вызове метода загрузки данных Load. Очевидно, нам надо переопределить и метод загрузки. Но мне бы не хотелось переписывать весь метод. Поэтому я добавил переменную m_iFileHandle, в которую при вызове метода Load сохраняю хендл файла загрузки данных. Затем вызываю аналогичный метод родительского класса.

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

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

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

bool CArrayLayers::CreateElement(const int index)
  {
//--- блок проверки исходных данных
   if(index < 0 || m_iFileHandle==INVALID_HANDLE)
      return false;
//--- резервирование элемента массива под новый объект
   if(!Reserve(index + 1))
      return false;
//--- считываем из файла тип нужного объекта и создаем соответствующий нейронный слой
   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;
     }
//--- контроль создания нового объекта
   if(!temp)
      return false;
//--- добавляем указатель на созданный объект в массив
   if(m_data[index])
      delete m_data[index];
 
   temp.SetOpenCL(m_cOpenCL);
   m_data[index] = temp;
//---
   return true;
  }

Ну и раз был создан новый класс, было решено добавить в него еще пару методов. Первое, что я добавил, — это аналогичный метод генерации нового элемента. Отличие в том, что новый метод создает новый слой по описанию, полученному в параметрах метода. Алгоритм метода практически полностью повторяет вышеприведенный, за исключением некоторых деталей.

bool CArrayLayers::CreateElement(const int indexCLayerDescription *desc)
  {
//--- блок проверки исходных данных
   if(index < 0 || !desc)
      return false;
//--- резервирование элемента массива под новый объект
   if(!Reserve(index + 1))
      return false;
//--- создаем соответствующий нейронный слой
   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;
     }
//--- контроль создания нового объекта
   if(!temp)
      return false;
//--- добавляем указатель на созданный объект в массив
   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;
  }

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

Алгоритм метода довольно прост. Вначале мы проверяем, был ли ранее задан указатель, и при необходимости удаляем старый объект. Затем сохраняем новый указатель и запускаем цикл перебора элементов динамического массива. При этом каждому элементу массива передадим новый указатель на объект OpenCL.

bool CArrayLayers::SetOpencl(CMyOpenCL *opencl)
  {
//--- блок проверки исходных данных
   if(m_cOpenCL)
      delete m_cOpenCL;
 
   m_cOpenCL = opencl;
//--- передача указателя во все элементы массива
   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);
  }