ffcnn icon indicating copy to clipboard operation
ffcnn copied to clipboard

ffcnn is a cnn neural network inference framework, written in 600 lines C language.

+----------------------------+ ffcnn 卷积神经网络前向推理库 +----------------------------+

ffcnn 是一个 c 语言编写的卷积神经网络前向推理库 只用了 500 多行代码就实现了完整的 yolov3、yolo-fastest 网络的前向推理 不依赖于任何第三方库,在标准 c 环境下就可以编译通过,在 VC、msys2+gcc、ubuntu+gcc 等多个平台上都可以正确的编译运行

这个代码相对于 darknet、ncnn 来说,没做特殊指令集优化,但代码更加简洁易懂,可以 作为大家学习卷积神经网络的一个参考

darknet 与 yolov3 的一些总结

yolov3 的网络结构里面,只有卷积层、dropout 层、shortcut 层、route 层、maxpool 层、 upsample 层和 yolo 层这几种类型。因此要实现起来还是比较容易的

卷积层:

  1. 要搞明白卷积的含义和计算方法
  2. 卷积运算的 pad、stride 的含义
  3. 每个卷积核还有一个 bias 参数,计算完每个点后需要加上这个 bias 没有归一化的情况(batch_normalize),其计算方法: x += bias; x = activate(x, type);
  4. 要搞明白什么是分组卷积
  5. 卷积运算每个输出的点,都要经过激活函数
  6. 卷积层如果有归一化操作(batch_normalize),其计算方法: x = (x - rolling_mean) / sqrt(rolling_variance + 0.00001f) x *= scale; x += bias; x = activate(x, type); 其中 rolling_mean、rolling_variance、scale、bias 在 darknet 的 weights 文件中可以读取到

dropout 层: 前向推理时,这一层可以当做不存在,输入数据不做任何处理,直接传给下一层即可

shortcut 层: 把指定层的数据和当前层的数据相加,然后结果输出到下一层

route 层: 把指定的层(最多可以有 4 个)做拼接,宽高不变,channel 个数增加,然后结果输出到下一层

maxpool 层: max 池化层,将 filter 覆盖的数据取最大值作为结果

upsample 层: 上采样层,可以理解为把图像放大,stride 指定了放大倍数,一般用最近邻法就可以了

yolo 层: 这一层主要是根据输入的 feature map 计算出 bbox 以 yolo-fastest 为例,总共有两个 yolo 层,其输入分别是 10x10x255 和 20x20x255 其中 255 表示有 255 个通道,其每个数据的含义如下: 255 = 3 * (4 + 1 + 80) 3 表示这个 grid 里面有 3 个 bbox 结果数据 每个 bbox 结果数据里面,4 个 x, y, w, h 坐标数据,1 个 object score 评分,然后是 80 个分类的评分 每个 bbox 里面在 80 个分类中找出评分最高的,作为这个 bbox 的分类,评分如果小于阈值(ignore_thresh)则丢弃 将符合要求的全部 bbox 放入一个列表保存,然后再做一个 nms 操作,就得到最终结果了

每个 bbox 的评分和 (x, y, w, h) 计算方法: 设 tx, ty, tw, th, bs 分别对应channel 0, 1, 2, 3, 4 的值(后面还有 80 个分类的评分)

评分的计算方法:score = sigmoid(bs); (80 个分类评分计算方法是一样的) 坐标计算方法: float bbox_cx = (j + sigmod(tx)) * grid_width; (grid_width 就是网络输入层即 0 层的宽度除以格子数目,即每个格子的像素宽度) float bbox_cy = (i + sigmod(ty)) * grid_height; (方法与 bbox_cx 一致) float bbox_w = (float)exp(tw) * anchor_box_w; (如果有缩放系数还要乘以这个系数) float bbox_h = (float)exp(th) * anchor_box_h; (方法与 bbox_w 一致)

bbox_cx、bbox_cy 是中心点坐标,bbox_w、bbox_h 是宽高,转换一下得到: x1 = bbox_cx - bbox_w * 0.5f; y1 = bbox_cy - bbox_h * 0.5f; x2 = bbox_cx + bbox_w * 0.5f; y2 = bbox_cy + bbox_h * 0.5f;

darknet 的 weights 文件

文件最前面有一个文件头: #pragma pack(1) typedef struct { int32_t ver_major, ver_minor, ver_revision; uint64_t net_seen; } WEIGHTS_FILE_HEADER; #pragma pack()

然后就是全部的权重数据,yolov3、yolo-fastest 的模块基本上就只有卷积层的权重,其它层是没有权重数据的。 图像和卷积核(filter)的数据都是 NCHW 格式,卷积层的权重数据存放顺序为:

n 个 bias if (batchnorm) { n 个 scale n 个 rolling_mean n 个 rolling_variance } n * c * h * w 个权重数据

ffcnn 的特点

  1. 极为简洁易懂的 c 语言代码实现
  2. 核心算法仅仅 600 行
  3. 不依赖于任何第三方库
  4. 可以很方便的移植到各种平台
  5. 推理时会自动释放不需要的 layer 减小内存占用
  6. 现阶段是 make it work first 后面有时间再优化性能
  7. 直接使用 darknet 的 .cfg 和 .weights 文件(不需要再转换)

ffcnn vs ncnn 性能评测

测试环境:

  1. Intel(R) Core(TM) i5-4250U CPU @ 1.30GHz 1.90GHZ, 8GB RAM
  2. win7 64bit 操作系统 + msys2 + mingw32 + gcc version 10.3.0
  3. ffcnn + yolo-fastest 代码:https://github.com/rockcarry/ffcnn
  4. ncnn + yolo-fastest 代码:https://github.com/rockcarry/ffyolodet
  5. 测试图片 test.bmp 100 次推理

测试结果: +----------+--------------+-------------------+------------------+ | 测试项目 | ffcnn-v1.2.0 | ncnn with avx off | ncnn with avx on | +----------+--------------+-------------------+------------------+ | 耗 时 | 14555ms | 14649 ms | 8424 ms | +----------+--------------+-------------------+------------------+ | 内存占用 | 5MB | 41MB | 41MB | +----------+--------------+-------------------+------------------+ | 程序体积 | 68KB | 1.2MB | 1.2MB | +----------+--------------+-------------------+------------------+

可以看到 ffcnn 已经逼近 ncnn(不开启 avx 指令优化)的性能

[email protected] 20:22 2021/8/7