Нейронная сеть от плавающей запятой до промежуточных весов

#c #neural-network

#c #нейронная сеть

Вопрос:

Я создал простую программу, которая использует нейронную сеть с весами с плавающей запятой на C. Теперь я хочу использовать веса int8_t или int16_t, как мне следует изменить код? Обучение является проблемной частью с целыми числами.

Вот простой пример:

Эта часть является определением сети

 #include <stdio.h>
#include <stdlib.h>

typedef double NNType;
// numer of inputs
#define IN 2
// number neurons layer hidden
#define HID 8
// numer of outputs
#define OUT 1
// learning constant
#define EPS 0.1

NNType input[IN]; // input
NNType hidden[HID]; // layer hidden
NNType output[OUT]; // output
NNType weightH[HID][IN]; // weights layer hidden
NNType biasesH[HID]; // biases layer hidden
NNType weightO[OUT][HID]; // weights output
NNType biasesO[OUT]; // biases output
  

Эта часть представляет собой вычисление сети и часть обучения

 inline NNType Activation(NNType x)
{
   return x>0?x:0;
}

inline NNType Derivative(NNType x)
{
   return x>0?1:0;
}

NNType NetworkResult(NNType inp1,NNType inp2)
{
   // load the inputs
   input[0]=inp1;
   input[1]=inp2;
   // compute hidden layer
   for (int i=0;i<HID;i  )
   {
      hidden[i]=biasesH[i];
      for (int j=0;j<IN;j  )
         hidden[i]  = input[j]*weightH[i][j];
      hidden[i]=Activation(hidden[i]);
   }
   // compute output
   for (int i=0;i<OUT;i  )
   {
      output[i]=biasesO[i];
      for (int j=0;j<HID;j  )
         output[i]  = hidden[j]*weightO[i][j];
      output[i]=Activation(output[i]);
   }
   return output[0];
}

void TrainNet(NNType inp1,NNType inp2,NNType result)
{
   NetworkResult(inp1,inp2);
   NNType DeltaO[OUT];
   NNType DeltaH[HID];
   // layer output
   NNType err= result-output[0];
   DeltaO[0]=err*Derivative(output[0]);
   // layer hidden
   for (int i=0;i<HID;i  )
   {
      NNType err=0;
      for (int j=0;j<OUT;j  )
         err = DeltaO[j]*weightO[j][i];
      DeltaH[i]=err*Derivative(hidden[i]);
   }
   // change weights
   // layer output
   for (int i=0;i<OUT;i  )
   {
      for (int j=0;j<HID;j  )
         weightO[i][j] =EPS*DeltaO[i]*hidden[j];
      biasesO[i] =EPS*DeltaO[i];
   }
   // layer hidden
   for (int i=0;i<HID;i  )
   {
      for (int j=0;j<IN;j  )
         weightH[i][j] =EPS*DeltaH[i]*input[j];
      biasesH[i] =EPS*DeltaH[i];
   }
}
  

Это основная часть программы, которая обучает сеть для изучения операции xor

 // constant for weights initializations
#define CONSTINIT 0.1

int main(int argc, char *argv[])
{
   srand(1);
   // initalize weights and biases
   for (int i=0;i<HID;i  )
   {
      for (int j=0;j<IN;j  )
         weightH[i][j]= 2.0 * ( (rand()/((NNType)RAND_MAX)) - 0.5 ) * CONSTINIT;
      biasesH[i]=0.1;
   }
   for (int i=0;i<OUT;i  )
   {
      for (int j=0;j<HID;j  )
         weightO[i][j]= 2.0 * ( (rand()/((NNType)RAND_MAX)) - 0.5 ) * CONSTINIT;
      biasesO[i]=0.1;
   }
   // calculate the results with the random weights
   printf("0 0 = %fn",NetworkResult(0,0));
   printf("0 1 = %fn",NetworkResult(0,1));
   printf("1 0 = %fn",NetworkResult(1,0));
   printf("1 1 = %fn",NetworkResult(1,1));
   printf("n");
   // Train the net to recognize an xor operation
   for (int i=0;i<1000;i  )
   {
      TrainNet(0,0,0); // input 0 0 result 0
      TrainNet(0,1,1); // input 0 1 result 1
      TrainNet(1,0,1); // input 1 0 result 1
      TrainNet(1,1,0); // input 1 1 result 0
   }
   // calculate the results after the train
   printf("0 0 = %fn",NetworkResult(0,0));
   printf("0 1 = %fn",NetworkResult(0,1));
   printf("1 0 = %fn",NetworkResult(1,0));
   printf("1 1 = %fn",NetworkResult(1,1));
   printf("n");
   return 0;
}
  

Комментарии:

1. Хотя вы, безусловно, могли бы использовать целые числа для представления значения с фиксированной запятой в диапазоне -1, вы, вероятно, не хотите этого. В то время как ваш простой (классический) пример XOR будет обучаться с 8-битными представлениями, что-либо более сложное может вызвать проблемы. Одной из технологий, позволяющих использовать NN в последнее время, является стоимость дешевых микропроцессоров, которые могут работать с плавающей запятой не менее 64 бит, потому что полезно иметь такой большой динамический диапазон.

Ответ №1:

Теперь я хочу использовать веса int8_t или int16_t, как мне следует изменить код? Обучение является проблемной частью с целыми числами.

Код использует значения с плавающей запятой для сохранения значений в диапазоне [-1.0 … 1.0].

Переход непосредственно к целочисленным типам дает только 3 значения -1,0,1. Я ожидаю, что с целыми числами вы захотите использовать масштабирование. С int8_t , может быть [-64…64]. При каждом использовании кода * продукт необходимо масштабировать.

 // input[j] * weightH[i][j]
input[j] * weightH[i][j] / 64;
  

Для выбора сложения / вычитания необходимы тесты для обнаружения переполнения.


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

 // weightH[i][j]= 2.0 * ( (rand()/((NNType)RAND_MAX)) - 0.5 ) * CONSTINIT;
 
// Break CONSTINIT into even numerator/denominator parts.
// Map [0...RAND_MAX] to [-64 ... 64]* CONSTINIT
#define scale_up (2LL*(64 - -64   1)*CONSTINIT_NUMERATOR)
#define scale_dn (2LL*RAND_MAX*CONSTINIT_DENOMINATOR)
long long r = scale_up*rand() - scale_up*RAND_MAX/2;
// add proper signed one-half divisor
r  = (r < 0) ? -scale_dn/2 : scale_dn/2;
weightH[i][j]= r/scale_dn;  // scale down
  

Ответ №2:

Я создал простую программу, которая использует нейронную сеть с весами с плавающей запятой на C. Теперь я хочу использовать веса int8_t или int16_t, как мне следует изменить код? Обучение является проблемной частью с целыми числами.

Я не понимаю, почему обучение было бы проблематичным, и я думаю, вы имели в виду, что это проблематично с плавающими значениями.

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

Конечно, возможно использование целых чисел. В конце концов, значение с плавающей запятой x.yy будет целым числом при умножении на 100, но это было бы излишеством для реализации, поскольку все пришлось бы сдвинуть таким образом.