Возможные решения для возврата структур как значений без выделения кучи: использование нейронных сетей - PullRequest
0 голосов
/ 20 ноября 2018

MNIST - это привет мир машинного обучения, и я практиковал его с TensorFlow и с pure python и numpy.

Для большей практики я пытаюсь написать это на C самостоятельно, используя только стандартную библиотеку, потому что я относительно новичок в C, и это отличный способ учиться.

Это заняло три недели, и много из SEGFAULTS, но я получил точность 81%. Не очень хорошо, но это для обучения.

Самым неприятным был, конечно, malloc/free для данных в матричной структуре, как показано ниже:

typedef struct matrix{
    int rows, cols;
    float *data;
} matrix;

В проходах вперед и назад есть такие вещи, как:

1) matrix dot product
2) matrix add
3) matrix subtract
4) activation function (sigmoid in this case)

Чтобы избежать утечек памяти, я выделяю три структуры следующим образом:

void matrix_add(matrix *a, matrix *b, matrix *res);

Если res требует изменения размеров по сравнению с предыдущим слоем, тогда я free сделаю это и сделаю новый malloc следующим образом:

void zero_out_data(matrix *res, int rows, int cols)
{
  if (res->rows != rows || res->cols != cols)
    {
      if ((res->rows*res->cols) != (rows*cols))
    {
      free(res->data);
      res->data = NULL;
      free(res);
      res = NULL;
      res = malloc(sizeof(matrix));
      // make_matrix will calloc the data based on rows*cols
      // any other init stuff that could be needed
      make_matrix(res, rows, cols);
    }
      res->rows = rows;
      res->cols = cols;
    }
  else {
    res->rows = rows;
    res->cols = cols;
    for (int i =0; i < (rows*cols); i++)
      {
    res->data[i] = 0.0;
      }
  }
}

Тогда я могу использовать это так:

void sigmoid(matrix *z, matrix *res)
{
  zero_out_data(res, z->rows, z->cols); 
  for (int i = 0; i < (z->rows*z->cols); i++)
    {
      res->data[i] = 1.0/(1.0+exp(-z->data[i]));
    }
}

Получается очень грязно , потому что один прямой проход имеет следующее:

/* forward pass */
for (int k=0; k < (network->num_layers-1); k++)
  {
    matrix_dot(network->weights[k], activation, dot);
    matrix_add(dot, network->biases[k], zs[k]);
    sigmoid(zs[k], activation);
    sigmoid(zs[k], activations[k+1]);
}
/* end forward pass */

Как вы можете себе представить, backprop становится много грязнее. Мне нужно предварительно создать 8 различных матриц, а также множество других указателей на указатели таких матриц, как activations и zs выше, для градиентного спуска.

Я хотел бы иметь возможность вернуть матрицу из функции, подобной matrix_dot, чтобы я мог сделать:

sigmoid(matrix_add(matrix_dot(network->weights[k], activation), network->biases[k]));

Это похоже на стиль python / numpy.

Конечно, я не могу вернуть локальную переменную из функции, потому что она удаляется из стека после возврата из функции.

Если я верну указатель, то приведенный выше стиль вызовет серьезные утечки памяти.

Обратите внимание: я не пытаюсь написать свою собственную библиотеку / фреймворк. Я просто пытаюсь изучать нейронные сети и кодирование на C. Я был разработчиком на Python около 7 лет, и мои навыки в C нуждаются в улучшении.

1 Ответ

0 голосов
/ 20 ноября 2018

Утечка памяти в void zero_out_data(matrix *res, int rows, int cols)

matrix *res malloc выйдет из функции и перейдет к zero_out_data. В zero_out_data, res свободен и снова malloc. Если вы хотите изменить значение указателя res, вам нужен такой параметр, как matrix **res.

Если вы хотите обнулить данные, вам не нужна malloc новая матрица, просто malloc часть data. Я думаю, что ваша make_matrix функция может распределять память на data.

void zero_out_data(matrix *res, int rows, int col) {
      if (res->data == NULL) {
           make_matrix(res, rows, cols);
      } else if (res->rows != rows || res->cols != cols) {
          if ((res->rows*res->cols) != (rows*cols))
             {
               free(res->data);
               res->data = NULL;
               make_matrix(res, rows, cols);
             }
      }
      res->rows = rows;
      res->cols = cols;
      for (int i =0; i < (rows*cols); i++)
      {
            res->data[i] = 0.0;
      } 
}

Как это реализовать: sigmoid(matrix_add(matrix_dot(network->weights[k], activation), network->biases[k]));?

Вы можете использовать static или глобальные переменные для реализации того, что вы хотите. Это не будет потокобезопасным и реентерабельным. Примеры ниже:

matrix *matrix_dot(matrix *in_a, matrix *in_b)
{
        static matrix res = {0, 0, NULL};  // static variable

        // calculate the res's cols and rows number
        zero_out_data(&res, res_cols, res_rows);    // malloc new data

        // do some math.

        return &res;
}

// matrix_add will be just like matrix_dot

// I was wrong about sigmod no need new matrix. sigmod can also do it like matrix_dot.

Вы можете использовать глобальную переменную для замены статической переменной.

Если вы хотите поточно-ориентированный или реентерабельный, просто используйте локальную переменную, тогда вы можете сделать это следующим образом.

matrix *matrix_dot(matrix *in_a, matrix *in_b, matrix *res) 
{
      zero_out_data(res, xxx, xxx);
      // do some math
      return res;
}
// matrix_add will be the same.

// define local variables.
matrix add_res, dot_res, sig_res;
add_res->data = NULL;
dot_res->data = NULL;
sig_res->data = NULL;

sigmod(matrix_add(matrix_dot(network->weights[k], activation, &dot_res), network->biases[k], &add_res),  &sig_res)

// Now remember to free data in matrix
free(add_res->data);
free(dot_res->data);
free(sig_res->data);
...