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 нуждаются в улучшении.