在没有堆分配的情况下将结构作为右值返回的可能解决方案:用例神经网络

时间:2018-11-20 03:36:50

标签: python c neural-network malloc rvalue

MNIST是机器学习的世界,我已经使用TensorFlow以及pure python和numpy进行了实践。

为了进行更多练习,我尝试仅使用标准库就自己用C编写它,因为我对C相对陌生,这是学习的一种好方法。

这花了三周的时间,{strong>很多 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 */

您可以想象,反向传播器会变得 alot 杂乱无章。我必须预先创建8个不同的矩阵,再加上更多指向上述activationszs之类的矩阵指针的指针,以进行梯度下降。

我想做的是从matrix_dot之类的函数返回一个矩阵,以便我可以这样做:

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

有点像python / numpy的风格。

我当然不能从函数中返回局部变量,因为一旦函数返回,它就会从堆栈中删除。

如果我返回一个指针,则以上样式将导致严重的内存泄漏。

请注意:我不是要编写自己的库/框架。我只是想学习C语言中的神经网络和编码。我已经成为python开发人员已有7年左右了,我的C技能需要提高。

1 个答案:

答案 0 :(得分:0)

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