模仿 darknet 的实现和 华盛顿大学深度学习课程
实现的图像分类,感兴趣的直接看源码, 能在 linux 和 mac 上运行
看懂代码需要一些基础
- C 语言基础,特别是对指针的理解
- 线性代数,最主要的矩阵运算的一些基础就好
- 微积分,知道如何求导数
相关数据结构
由于没有实现多维的 tensor ,也没有计算图自动微分这些, 数据在网络中的存储和传递都是通过 matrix
这个结构体
typedef struct matrix{
int rows;
int cols;
float *data;
} matrix;
rows
和 cols
存储矩阵的 行数和列数, *data
是浮点型的指针存储具体的数据。
我们知道模型训练的时候输入是一个 batch 的图像,也可以用 matrix 表示, 一行就是一个图像,相当于把图像拉成一个向量,每个 channel 按行优先的顺序一次排成一个向量。
typedef struct {
matrix X;
matrix y;
} data;
这里的 data 就是代表训练数据的, X 是一个 batch 的图像,y 是对应的 label . X,y 的行数是 batch_size, X 的列就是图像像素的数量, 如果28 X 28 X 3 的彩色图像, X 的列就是 2352
有了数据,网络结构的定义主要抽象为 layer . layer 会记录输入,梯度,等各种参数,这里的抽象并没有对每一种 layer 抽象一个, 而是 全连接, 卷积层, 统统定义在 这个layer 里面,darknet 基本也是这样实现的。值得注意的是 layer 最下面的三个函数指针 forward, backward,update 。每种不同的 layer 实现这三个方法就好了,他们的功能从函数名就能看出来。
typedef struct layer {
matrix *in;
matrix *out;
matrix *delta;
// weight
matrix w;
matrix dw;
// bias;
matrix b;
matrix db;
//
int width, height, channels;
int size, stride, filters;
// activation
ACTIVATION activation;
LAYER_TYPE type;
// batch normal
int batchnorm;
matrix *x;
matrix rolling_mean;
matrix rolling_variance;
matrix x_norm;
// functions
matrix (*forward) (struct layer, struct matrix);
void (*backward) (struct layer, struct matrix);
void (*update) (struct layer, float rate, float momentum, float decay);
} layer;
在 layer 的上层,定义网络 net net 是多个 layer 的组合,前向计算的时候依次调用 forward 反向的时候调用 backward
更新参数的时候调用 update
typedef struct {
layer *layers;
int n;
} net;
在网络里数据流动的主要有两部分,前向计算的时候上一层的输出会通过 forward 函数传递到下一次, 反向传播的时候前一层输出的梯度通过 backward 传递然后在本层计算,也就是本层的输入的梯度其实就是前一层输出的梯度,因为本层输入就是前一层输出。
激活函数
激活函数的实现其实相对简单,就是对输入矩阵的每个元素执行一个运算然后输出就可以了, 要说明一下的是梯度计算的时候,由于把 softmax 的梯度计算放到了计算网络的 loss 里面,所以这里的softmax 的 导数就是 1 所以啥也不干。
void gradient_matrix(matrix m, ACTIVATION a, matrix d){
int i, j;
for(i=0; i<m.rows; ++i){
for(j=0;j<m.cols; ++j){
double x = m.data[i*m.cols + j];
if(a==LOGISTIC){
d.data[i*m.cols + j] *= x*(1-x);
} else if(a==RELU){
d.data[i*m.cols +j] *=x>0 ? 1:0;
} else if(a==LRELU){
d.data[i*m.cols +j] *= x>0? 1: 0.01;
} else if(a==SOFTMAX || a==LINEAR){
}
else{
fprintf(stdout, "action name error\n");
}
}
}
}
卷积的计算和 caffe 的实现类似都是转成矩阵计算,具体可去看华盛顿大学深度学习课程 的课件,我觉得这个课件很好。
batchnorm 的实现,默认 $ \alpha $ 为 1 $\beta $ 为 0,所以简单了很多。
在 mnist 上能跑到 99% 或 98% 的准确率