移动端机器学习框架ncnn简介与实践

ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架也是腾讯优图实验室成立以来的第一个开源项目ncnn 从设计之初深刻考虑手机端的部署和使用无第三方依赖,跨平台,手机端 CPU 的速度快于目前所有已知的开源框架。基于 ncnn,开发者能够将深度学习算法轻松移植到手机端高效执行,开发出人工智能 App。ncnn 目前已在腾讯多款应用中使用,如 QQQzone微信天天P图等

腾讯优图实验室以计算机视觉见长,ncnn的许多应用方向也都在图像方面,如人像自动美颜照片风格化超分辨率物体识别深度学习算法要在手机上落地,现成的 caffe-android-lib 依赖太多,而且手机上基本不支持cuda,需要又快又小的前向网络实现。单纯的精简 caffe 等框架依然无法满足手机 App 对安装包大小运算速度等的苛刻要求。ncnn 作者认为,只有全部从零开始设计才能做出适合移动端的前向网络实现,因此从最初的架构设计以手机端运行为主要原则,考虑了手机端的硬件和系统差异以及调用方式。

腾讯优图ncnn提供的资料显示:对比目前已知的同类框架,ncnn是 CPU 框架中最快的,安装包体积最小,跨平台兼容性中也是最好的。以苹果主推的 CoreML 为例,CoreML 是苹果主推的 iOS GPU 计算框架,速度非常快,但仅支持 iOS 11 以上的 iPhone 手机,落地受众太狭窄,非开源导致开发者无法自主扩展功能,对开源社区不友好。

ncnn与同框架

对比

caffe

tensorflow

ncnn

CoreML

计算硬件

cpu

cpu

cpu

gpu

是否开源

手机计算速度

很快

极快

手机库大小

较大

手机兼容性

很好

仅支持 iOS 11

功能概述

  • 支持卷积神经网络,支持多输入和多分支结构,可计算部分分支

ncnn 支持卷积神经网络结构,以及多分支多输入的复杂网络结构,如主流的 VGGGoogLeNet、ResNet、SqueezeNet 等。计算时可以依据需求,先计算公共部分和 prob 分支,待 prob 结果超过阈值后,再计算 bbox 分支。如果 prob 低于阈值,则可以不计算 bbox 分支,减少计算量。

  • 无任何第三方库依赖,不依赖 BLAS/NNPACK 等计算框架

caffe-android-lib+openblas

ncnn

boost、gflags、glog、lmdb、openblas、opencv、protobuf

ncnn 不依赖任何第三方库,完全独立实现所有计算过程,不需要 BLAS/NNPACK 等数学计算库。

  • 纯 C++ 实现,跨平台,支持 Android iOS 等

ncnn 代码全部使用 C/C++ 实现,跨平台的 cmake 编译系统,可在已知的绝大多数平台编译运行,如 LinuxWindowsMac OSAndroidiOS 等。由于 ncnn 不依赖第三方库,且采用 C++ 03 标准实现,只用到了 std::vector 和 std::string 两个 STL 模板,可轻松移植到其他系统和设备上。

  • ARM NEON 汇编级良心优化,计算速度极快

ncnn 为手机端 CPU 运行做了深度细致的优化,使用 ARM NEON 指令集实现卷积层全连接层池化层等大部分 CNN 关键层。对于寄存器压力较大的 armv7 架构,手工编写 neon 汇编,内存预对齐,cache 预缓存,排列流水线,充分利用一切硬件资源,防止编译器意外负优化。

  • 精细的内存管理和数据结构设计,内存占用极低

在 ncnn 设计之初已考虑到手机上内存的使用限制,在卷积层、全连接层等计算量较大的层实现中,没有采用通常框架中的 im2col + 矩阵乘法,因为这种方式会构造出非常大的矩阵,消耗大量内存。因此,ncnn 采用原始的滑动窗口卷积实现,并在此基础上进行优化,大幅节省了内存。在前向网络计算过程中,ncnn 可自动释放中间结果所占用的内存,进一步减少内存占用。

内存占用量使用 top 工具的 RSS 项统计,测试手机为 Nexus 6p,Android 7.1.2。

  • 支持多核并行计算加速,ARM big.LITTLE CPU 调度优化

ncnn 提供了基于 OpenMP 的多核心并行计算加速,在多核心 CPU 上启用后能够获得很高的加速收益。ncnn 提供线程数控制接口,可以针对每个运行实例分别调控,满足不同场景的需求。针对 ARM big.LITTLE 架构的手机 CPU,ncnn 提供了更精细的调度策略控制功能,能够指定使用大核心或者小核心,或者一起使用,获得极限性能和耗电发热之间的平衡。例如,只使用1个小核心,或只使用2个小核心,或只使用2个大核心,都尽在掌控之中。

  • 整体库体积小于 500K,并可轻松精简到小于 300K

ncnn 自身没有依赖项,且体积很小,默认编译选项下的库体积小于 500K,能够有效减轻手机 App 安装包大小负担。此外,ncnn 在编译时可自定义是否需要文件加载和字符串输出功能,还可自定义去除不需要的层实现,轻松精简到小于 300K。

  • 可扩展的模型设计,支持 8bit 量化和半精度浮点存储,可导入 caffe 模型

ncnn 使用自有的模型格式,模型主要存储模型中各层的权重值。ncnn 模型中含有扩展字段,用于兼容不同权重值的存储方式,如常规的单精度浮点,以及占用更小的半精度浮点和 8bit 量化数。大部分深度模型都可以采用半精度浮点减小一半的模型体积,减少 App安装包大小和在线下载模型的耗时。ncnn 带有 caffe 模型转换器,可以转换为 ncnn 的模型格式,方便研究成果快速落地。

  • 支持直接内存零拷贝引用加载网络模型

在某些特定应用场景中,如因平台层 API 只能以内存形式访问模型资源,或者希望将模型本身作为静态数据写在代码里,ncnn 提供了直接从内存引用方式加载网络模型的功能。这种加载方式不会拷贝已在内存中的模型,也无需将模型先写入实体的文件再读入,效率极高。

  • 可注册自定义层实现并扩展

ncnn 提供了注册自定义层实现的扩展方式,可以将自己实现的特殊层内嵌到 ncnn 的前向计算过程中,组合出更自由的网络结构和更强大的特性。

注:只包含前向计算,因此无法进行训练,需要导入其他框架训练好的模型参数

框架设计

框架设计与caffe、EasyCNN基本类似,以Blob存储数据,以Layer作为计算单元,以Network作为调度单元。与前2者稍有不同的是ncnn中还有一个Extractor的概念,Extractor可以看做是Network对用户的接口。Network一般单模型只需要一个实例,而Extractor可以有多个实例。这样做的好处是进行多个任务的时候可以节省内存(模型定义模型参数等不需要产生多个拷贝)。

实践

ncnn功能测试

GitHub 上有可以直接运行的 Demo 工程,可以直接在 Android、iOS 上运行测试。

网址:https://github.com/dangbo/ncnn-mobile

运行效果如下:

 Mac 上编译安装 ncnn

  1. 下载编译源码 访问项目主页:https://github.com/Tencent/ncnn ,使用 git 下载项目 :

git clone git@github.com:Tencent/ncnn.git

  1. 安装依赖环境  cmake: brew install cmake:

protobuf: brew install protobuf

  1. 开始编译 :

cd ncnn

mkdir build && cd build

cmake ..

make -j

make install

  1. 编译成功在控制台输出:

Install the project...

   -- Install configuration: "release"

   -- Installing: /Users/liudawei/worksapace/github/ncnn/build/install/lib/libncnn.a /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: /Users/liudawei/worksapace/github/ncnn/build/install/lib/libncnn.a(opencv.cpp.o) has no symbols

-- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/blob.h

      -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/cpu.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/layer.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/layer_type.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/mat.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/net.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/opencv.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/paramdict.h

    -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/layer_type_enum.h

 -- Up-to-date: /Users/liudawei/worksapace/github/ncnn/build/install/include/platform.h

进入ncnn/build/tools目录下查看生成的工具:

liudawei@localhost  ~/worksapace/github/ncnn/build/tools   master  ll

total 728

drwxr-xr-x  5 liudawei  staff   170B 12  7 17:11 CMakeFiles

-rw-r--r--  1 liudawei  staff   7.7K 12  7 16:39 Makefile

drwxr-xr-x  8 liudawei  staff   272B 12  7 17:12 caffe

-rw-r--r--  1 liudawei  staff   1.3K 12  7 16:39 cmake_install.cmake

drwxr-xr-x  6 liudawei  staff   204B 12  7 17:12 mxnet

-rwxr-xr-x  1 liudawei  staff   351K 12  7 17:17 ncnn2mem

caffe模型的转换工具(caffe2ncnn)位于 caffe 目录下

XXXX@localhost  ~/worksapace/github/ncnn/build/tools/caffe ls

CMakeFiles          caffe.pb.cc         caffe2ncnn

Makefile            caffe.pb.h          cmake_install.cmake

mxnet模型的转换工具(mxnet2ncnn)位于 mxnet目录下

XXXX@localhost  ~/worksapace/github/ncnn/build/tools/mxnet ls

CMakeFiles 4Makefile cmake_install.cmake mxnet2ncnn

由于 ncnn 不支持模型训练,需要将其他训练好的模型做转换导入。本文中以 caffe 为例。

  • 将 caffe 模型转换为 ncnn 模型 :

XXXX@localhost  ~/worksapace/github/ncnn/build/tools/caffe ./caffe2ncnn new_deploy.prototxt new_bvlc_alexnet.caffemodel alexnet.param alexnet.bin

  • 去除可见字符串 有 param 和 bin 文件其实已经可以用了,但是 param 描述文件是明文的,如果放在 App 分发出去容易被窥探到网络结构(使用 ncnn2mem 工具转换为二进制描述文件和内存模型,生成 alexnet.param.bin 和两个静态数组的代码文件):

ncnn2mem alexnet.param alexnet.bin alexnet.id.h alexnet.mem.h

  • 加载模型
  1. 直接加载 param 和 bin,适合快速验证效果使用 :

ncnn::Net net;

net.load_param("alexnet.param");

net.load_model("alexnet.bin");

  1. 加载二进制的 param.bin 和 bin,没有可见字符串,适合 App 分发模型资源 :

ncnn::Net net;

net.load_param_bin("alexnet.param.bin");

net.load_model("alexnet.bin");

  1. 从内存引用加载网络和模型,没有可见字符串,模型数据全在代码里头,没有任何外部文件 另外,Android apk 打包的资源文件读出来也是内存块 :

#include "alexnet.mem.h"

ncnn::Net net;

net.load_param(alexnet_param_bin);

net.load_model(alexnet_bin);

以上三种都可以加载模型,其中内存引用方式加载是 zero-copy 的,所以使用 net 模型的来源内存块必须存在。

  • 卸载模型

net.clear();

  • 输入和输出

ncnn 用自己的数据结构 Mat 来存放输入和输出数据输入图像的数据要转换为 Mat,依需要减去均值和乘系数:

#include "mat.h"

unsigned char* rgbdata;// data pointer to RGB image pixels

int w;// image width

int h;// image height

ncnn::Mat in = ncnn::Mat::from_pixels(rgbdata, ncnn::Mat::PIXEL_RGB, w, h);

const float mean_vals[3] = {104.f, 117.f, 123.f};

in.substract_mean_normalize(mean_vals, 0);

执行前向网络,获得计算结果:

#include "net.h"

ncnn::Mat in;// input blob as above

ncnn::Mat out;

ncnn::Extractor ex = net.create_extractor();

ex.set_light_mode(true);

ex.input("data", in);

ex.extract("prob", out);

如果是二进制的 param.bin 方式,没有可见字符串,利用 alexnet.id.h 的枚举来代替 blob 的名字:

#include "net.h"

#include "alexnet.id.h"

ncnn::Mat in;// input blob as above

ncnn::Mat out;

ncnn::Extractor ex = net.create_extractor();

ex.set_light_mode(true);

ex.input(alexnet_param_id::BLOB_data, in);

ex.extract(alexnet_param_id::BLOB_prob, out);

获取 Mat 中的输出数据,Mat 内部的数据通常是三维的,c / h / w,遍历所有的 channel 获得全部分类的分数:

std::vector<float> scores;

scores.resize(out.c);

for (int j=0; j<out.c; j++)

{

    const float* prob = out.data + out.cstep * j;

    scores[j] = prob[0];

}

  • 某些使用技巧 Extractor 有个多线程加速的开关,设置线程数能加快计算

ex.setnumthreads(4); Mat 转换图像的时候可以顺便转换颜色和缩放大小,这些顺带的操作也是有优化的 支持 RGB2GRAY GRAY2RGB RGB2BGR 等常用转换,支持缩小和放大

#include "mat.h"

unsigned char* rgbdata;// data pointer to RGB image pixels

int w;// image width

int h;// image height

int target_width = 227;// target resized width

int target_height = 227;// target resized height

ncnn::Mat in = ncnn::Mat::from_pixels_resize(rgbdata, ncnn::Mat::PIXEL_RGB2GRAY, w, h, target_width, target_height);

Net 有从 FILE* 文件描述加载的接口,可以利用这点把多个网络和模型文件合并为一个,分发时能方便些,内存引用就无所谓了:

$ cat alexnet.param.bin alexnet.bin > alexnet-all.bin

#include "net.h"

FILE* fp = fopen("alexnet-all.bin", "rb");

net.load_param_bin(fp);

net.load_bin(fp);

fclose(fp);

使用ncnn的example

  1. . 修改CMakeList.txt文件,去掉下面注释:

#add_subdirectory(examples)

  1. . 使用 make -j编译ncnn后,进入ncnn/example目录执行:

./squeezenet test.jpg

  1. . 输出结果:

333 = 0.586699(hamster)

186 = 0.053623 (Norwich terrier)

78 = 0.041748  (tick)

测试图片:

附录:

Caffe 编译

  1. 下载编译源码,访问项目主页:git@github.cm:BVLC/caffe.git 使用 git 下载项目

git clone git@github.com:BVLC/caffe.git

2.安装依赖环境

使用 Homebrew 安装所需要的其它依赖,其它依赖有gflags、snappy、glog、hdf5、lmdb、opencv3、boost、leveldb 、protobuf。

通过 pip 安装 pycaffe。

3.开始编译

在 GitHub 上下载 Caffe 源码,地址为:https://github.com/BVLC/caffe,下载后在 Caffe 根目录创建 build 文件夹,将 Makefile.config.example 文件名改为 Makefile.config,修改 Makefile.config 文件。   

    $ vim Makefile.config

然后修改里面的内容,找到如下内容:

    s# CPU-only switch (uncomment to build without GPU support).

    # CPU_ONLY := 1

    # CPU-only switch (uncomment to build without GPU support).

    # CPU_ONLY := 1

    去掉注释,修改后如下:

    # CPU-only switch (uncomment to build without GPU support).

      CPU_ONLY := 1

    # CPU-only switch (uncomment to build without GPU support).

      CPU_ONLY := 1

执行 make -j,看到下面的内容说明安装 Caffe 成功。tools 目录下生成需要的升级工具:

XXXXX@localhost  ~/worksapace/github/caffe/build/tools   master  ll

    total 1536

    drwxr-xr-x  16 liudawei  staff   544B 12  7 17:45 CMakeFiles

    -rw-r--r--   1 liudawei  staff    27K 12  7 17:45 Makefile

    -rwxr-xr-x   1 liudawei  staff   135K 12  7 17:49 caffe

    -rw-r--r--   1 liudawei  staff    13K 12  7 17:45 cmake_install.cmake

    -rwxr-xr-x   1 liudawei  staff    52K 12  7 17:49 compute_image_mean

    -rwxr-xr-x   1 liudawei  staff    84K 12  7 17:49 convert_imageset

    -rwxr-xr-x   1 liudawei  staff    39K 12  7 17:49 device_query

    -rwxr-xr-x   1 liudawei  staff    88K 12  7 17:49 extract_features

    -rwxr-xr-x   1 liudawei  staff    43K 12  7 17:49 finetune_net

    -rwxr-xr-x   1 liudawei  staff    43K 12  7 17:49 net_speed_benchmark

    -rwxr-xr-x   1 liudawei  staff    43K 12  7 17:49 test_net

    -rwxr-xr-x   1 liudawei  staff    43K 12  7 17:49 train_net

    -rwxr-xr-x   1 liudawei  staff    44K 12  7 17:49 upgrade_net_proto_binary

    -rwxr-xr-x   1 liudawei  staff    44K 12  7 17:49 upgrade_net_proto_text

    -rwxr-xr-x   1 liudawei  staff    44K 12  7 17:49 upgrade_solver_proto_text

4.升级 Caffe 模型

liudawei@localhost  ~/worksapace/github/caffe/build/tools ./upgrade_net_proto_text deploy.prototxt new_deploy.prototxt

liudawei@localhost  ~/worksapace/github/caffe/build/tools ./upgrade_net_proto_binary bvlc_alexnet.caffemodel new_bvlc_alexnet.caffemodel

模型下载:

http://dl.caffe.berkeleyvision.org/bvlc_alexnet.caffemodel https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet

参考内容

https://github.com/Tencent/ncnn

https://github.com/Tencent/ncnn/wiki

http://blog.csdn.net/best_coder/article/details/76201275

http://hongbomin.com/2017/09/02/ncnn-analysis/

https://mp.weixin.qq.com/s/3gTp1kqkiGwdq5olrpOvKw

  • 18
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值