用 C 实现 CNN 图像分类

正午 2020-06-27 PM 27℃ 0条

模仿 darknet 的实现和 华盛顿大学深度学习课程
实现的图像分类,感兴趣的直接看源码, 能在 linux 和 mac 上运行

看懂代码需要一些基础

  1. C 语言基础,特别是对指针的理解
  2. 线性代数,最主要的矩阵运算的一些基础就好
  3. 微积分,知道如何求导数

相关数据结构

由于没有实现多维的 tensor ,也没有计算图自动微分这些, 数据在网络中的存储和传递都是通过 matrix 这个结构体

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

rowscols 存储矩阵的 行数和列数, *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% 的准确率

标签: none

非特殊说明,本博所有文章均为博主原创。

上一篇 图像二值化算法-传统方法
下一篇 没有了

评论