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个不同的矩阵,再加上更多指向上述activations
和zs
之类的矩阵指针的指针,以进行梯度下降。
我想做的是从matrix_dot
之类的函数返回一个矩阵,以便我可以这样做:
sigmoid(matrix_add(matrix_dot(network->weights[k], activation), network->biases[k]));
有点像python / numpy的风格。
我当然不能从函数中返回局部变量,因为一旦函数返回,它就会从堆栈中删除。
如果我返回一个指针,则以上样式将导致严重的内存泄漏。
请注意:我不是要编写自己的库/框架。我只是想学习C语言中的神经网络和编码。我已经成为python开发人员已有7年左右了,我的C技能需要提高。
答案 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);