基于web端和C++的两种深度学习模型部署方式

  • 深度学习Author:louwillMachine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理。一种是基于web服务端的模型部署,一种是基...

     

    深度学习

    Author:louwill

    Machine Learning Lab

         

          本文对深度学习两种模型部署方式进行总结和梳理。一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署。

          基于web服务端的模型部署,主要是通过REST API的形式来提供接口方便调用。而基于C++的深度学习模型部署,主要是通过深度学习框架的C++前端版本,将模型集成到软件服务中。

          本文分别对上述两种模型部署方式进行流程梳理,并分别举例进行说明。

    1. 基于web端的模型部署

    1.1 web服务与技术框架

         下面以ResNet50预训练模型为例,旨在展示一个轻量级的深度学习模型部署,写一个较为简单的图像分类的REST API。主要技术框架为Keras+Flask+Redis。其中Keras作为模型框架、Flask作为后端Web框架、Redis则是方便以键值形式存储图像的数据库。各主要package版本:

    tensorflow 1.14
    keras 2.2.4
    flask 1.1.1
    redis 3.3.8
    

         先简单说一下Web服务,一个Web应用的本质无非就是客户端发送一个HTTP请求,然后服务器收到请求后生成一个HTML文档作为响应返回给客户端的过程。在部署深度学习模型时,大多时候我们不需要搞一个前端页面出来,一般是以REST API的形式提供给开发调用。那么什么是API呢?很简单,如果一个URL返回的不是HTML,而是机器能直接解析的数据,这样的一个URL就可以看作是一个API。

    先开启Redis服务:

    redis-server
    

    1.2 服务配置

         定义一些配置参数:

    IMAGE_WIDTH = 224
    IMAGE_HEIGHT = 224
    IMAGE_CHANS = 3
    IMAGE_DTYPE = "float32"
    IMAGE_QUEUE = "image_queue"
    BATCH_SIZE = 32
    SERVER_SLEEP = 0.25
    CLIENT_SLEEP = 0.25
    

         指定输入图像大小、类型、batch_size大小以及Redis图像队列名称。

         然后创建Flask对象实例,建立Redis数据库连接:

    app = flask.Flask(__name__)
    db = redis.StrictRedis(host="localhost", port=6379, db=0)
    model = None
    

         因为图像数据作为numpy数组不能直接存储到Redis中,所以图像存入到数据库之前需要将其序列化编码,从数据库取出时再将其反序列化解码即可。分别定义编码和解码函数:

    def base64_encode_image(img):
        return base64.b64encode(img).decode("utf-8")
    
    
    def base64_decode_image(img, dtype, shape):
        if sys.version_info.major == 3:
            img = bytes(img, encoding="utf-8")
        img = np.frombuffer(base64.decodebytes(img), dtype=dtype)
        img = img.reshape(shape)
        return img
    

         另外待预测图像还需要进行简单的预处理,定义预处理函数如下:

    def prepare_image(image, target):
        # if the image mode is not RGB, convert it
        if image.mode != "RGB":
            image = image.convert("RGB")
        # resize the input image and preprocess it
        image = image.resize(target)
        image = img_to_array(image)
        # expand image as one batch like shape (1, c, w, h)
        image = np.expand_dims(image, axis=0)
        image = imagenet_utils.preprocess_input(image)
        # return the processed image
        return image
    

    1.3 预测接口定义

         准备工作完毕之后,接下来就是主要的两大部分:模型预测部分和app后端响应部分。先定义模型预测函数如下:

    def classify_process():
        # 导入模型
        print("* Loading model...")
        model = ResNet50(weights="imagenet")
        print("* Model loaded")
        while True:
            # 从数据库中创建预测图像队列
            queue = db.lrange(IMAGE_QUEUE, 0, BATCH_SIZE - 1)
            imageIDs = []
            batch = None
            # 遍历队列
            for q in queue:
                # 获取队列中的图像并反序列化解码
                q = json.loads(q.decode("utf-8"))
                image = base64_decode_image(q["image"], IMAGE_DTYPE,
                                            (1, IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_CHANS))
                # 检查batch列表是否为空
                if batch is None:
                    batch = image
                # 合并batch
                else:
                    batch = np.vstack([batch, image])
                # 更新图像ID
                imageIDs.append(q["id"])
             if len(imageIDs) > 0:
                print("* Batch size: {}".format(batch.shape))
                preds = model.predict(batch)
                results = imagenet_utils.decode_predictions(preds)
                # 遍历图像ID和预测结果并打印
                for (imageID, resultSet) in zip(imageIDs, results):
                    # initialize the list of output predictions
                    output = []
                    # loop over the results and add them to the list of
                    # output predictions
                    for (imagenetID, label, prob) in resultSet:
                        r = {"label": label, "probability": float(prob)}
                        output.append(r)
                    # 保存结果到数据库
                    db.set(imageID, json.dumps(output))
                # 从队列中删除已预测过的图像
                db.ltrim(IMAGE_QUEUE, len(imageIDs), -1)
            time.sleep(SERVER_SLEEP)
    

         然后定义app服务:

    @app.route("/predict", methods=["POST"])
    def predict():
        # 初始化数据字典
        data = {"success": False}
        # 确保图像上传方式正确
        if flask.request.method == "POST":
            if flask.request.files.get("image"):
                # 读取图像数据
                image = flask.request.files["image"].read()
                image = Image.open(io.BytesIO(image))
                image = prepare_image(image, (IMAGE_WIDTH, IMAGE_HEIGHT))
                # 将数组以C语言存储顺序存储
                image = image.copy(order="C")
                # 生成图像ID
                k = str(uuid.uuid4())
                d = {"id": k, "image": base64_encode_image(image)}
                db.rpush(IMAGE_QUEUE, json.dumps(d))
                # 运行服务
                while True:
                    # 获取输出结果
                    output = db.get(k)
                    if output is not None:
                        output = output.decode("utf-8")
                        data["predictions"] = json.loads(output)
                        db.delete(k)
                        break
                    time.sleep(CLIENT_SLEEP)
                data["success"] = True
            return flask.jsonify(data)
    

         Flask使用Python装饰器在内部自动将请求的URL和目标函数关联了起来,这样方便我们快速搭建一个Web服务。

    1.4 接口测试

         服务搭建好了之后我们可以用一张图片来测试一下效果:

    curl -X POST -F image=@test.jpg 'http://127.0.0.1:5000/predict'
    

    模型端的返回:

    预测结果返回:

         最后我们可以给搭建好的服务进行一个压力测试,看看服务的并发等性能如何,定义一个压测文件stress_test.py 如下:

    from threading import Thread
    import requests
    import time
    # 请求的URL
    KERAS_REST_API_URL = "http://127.0.0.1:5000/predict"
    # 测试图片
    IMAGE_PATH = "test.jpg"
    # 并发数
    NUM_REQUESTS = 500
    # 请求间隔
    SLEEP_COUNT = 0.05
    def call_predict_endpoint(n):
        # 上传图像
        image = open(IMAGE_PATH, "rb").read()
        payload = {"image": image}
        # 提交请求
        r = requests.post(KERAS_REST_API_URL, files=payload).json()
        # 确认请求是否成功
        if r["success"]:
            print("[INFO] thread {} OK".format(n))
        else:
            print("[INFO] thread {} FAILED".format(n))
    # 多线程进行
    for i in range(0, NUM_REQUESTS):
        # 创建线程来调用api
        t = Thread(target=call_predict_endpoint, args=(i,))
        t.daemon = True
        t.start()
        time.sleep(SLEEP_COUNT)
    time.sleep(300)
    

    测试效果如下:

    2. 基于C++的模型部署

    2.1 引言

         PyTorch作为一款端到端的深度学习框架,在1.0版本之后已具备较好的生产环境部署条件。除了在web端撰写REST API进行部署之外(参考),软件端的部署也有广泛需求。尤其是最近发布的1.5版本,提供了更为稳定的C++前端API。

         工业界与学术界最大的区别在于工业界的模型需要落地部署,学界更多的是关心模型的精度要求,而不太在意模型的部署性能。一般来说,我们用深度学习框架训练出一个模型之后,使用Python就足以实现一个简单的推理演示了。但在生产环境下,Python的可移植性和速度性能远不如C++。所以对于深度学习算法工程师而言,Python通常用来做idea的快速实现以及模型训练,而用C++作为模型的生产工具。目前PyTorch能够完美的将二者结合在一起。实现PyTorch模型部署的核心技术组件就是TorchScript和libtorch。

         所以基于PyTorch的深度学习算法工程化流程大体如下图所示:

    2.2 TorchScript

         TorchScript可以视为PyTorch模型的一种中间表示,TorchScript表示的PyTorch模型可以直接在C++中进行读取。PyTorch在1.0版本之后都可以使用TorchScript的方式来构建序列化的模型。TorchScript提供了Tracing和Script两种应用方式。

         Tracing应用示例如下:

    class MyModel(torch.nn.Module):
        def __init__(self):
            super(MyModel, self).__init__()
            self.linear = torch.nn.Linear(4, 4)
    
    
        def forward(self, x, h):
            new_h = torch.tanh(self.linear(x) + h)
            return new_h, new_h
    
    
    # 创建模型实例 
    my_model = MyModel()
    # 输入示例
    x, h = torch.rand(3, 4), torch.rand(3, 4)
    # torch.jit.trace方法对模型构建TorchScript
    traced_model = torch.jit.trace(my_model, (x, h))
    # 保存转换后的模型
    traced_model.save('model.pt')
    

         在这段代码中,我们先是定义了一个简单模型并创建模型实例,然后给定输入示例,Tracing方法最关键的一步在于使用torch.jit.trace方法对模型进行TorchScript转化。我们可以获得转化后的traced_model对象获得其计算图属性和代码属性。计算图属性:

    print(traced_model.graph)
    
    graph(%self.1 : __torch__.torch.nn.modules.module.___torch_mangle_1.Module,
          %input : Float(3, 4),
          %h : Float(3, 4)):
      %19 : __torch__.torch.nn.modules.module.Module = prim::GetAttr[name="linear"](%self.1)
      %21 : Tensor = prim::CallMethod[name="forward"](%19, %input)
      %12 : int = prim::Constant[value=1]() # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %13 : Float(3, 4) = aten::add(%21, %h, %12) # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %14 : Float(3, 4) = aten::tanh(%13) # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %15 : (Float(3, 4), Float(3, 4)) = prim::TupleConstruct(%14, %14)
      return (%15)
    

    代码属性:

    print(traced_cell.code)
    
    
    
    def forward(self,
        input: Tensor,
        h: Tensor) -> Tuple[Tensor, Tensor]:
      _0 = torch.add((self.linear).forward(input, ), h, alpha=1)
      _1 = torch.tanh(_0)
      return (_1, _1)
    

         这样我们就可以将整个模型都保存到硬盘上了,并且经过这种方式保存下来的模型可以加载到其他其他语言环境中。

         TorchScript的另一种实现方式是Script的方式,可以算是对Tracing方式的一种补充。当模型代码中含有if或者for-loop等控制流程序时,使用Tracing方式是无效的,这时候可以采用Script方式来进行实现TorchScript。实现方法跟Tracing差异不大,关键在于把jit.tracing换成jit.script方法,示例如下。

    scripted_model = torch.jit.script(MyModel)
    scripted_model.save('model.pt')
    

         除了Tracing和Script之外,我们也可以混合使用这两种方式,这里不做详述。总之,TorchScript为我们提供了一种表示形式,可以对代码进行编译器优化以提供更有效的执行。

    2.3 libtorch

         在Python环境下对训练好的模型进行转换之后,我们需要C++环境下的PyTorch来读取模型并进行编译部署。这种C++环境下的PyTorch就是libtorch。因为libtorch通常用来作为PyTorch模型的C++接口,libtorch也称之为PyTorch的C++前端。

         我们可以直接从PyTorch官网下载已经编译好的libtorch安装包,当然也可以下载源码自行进行编译。这里需要注意的是,安装的libtorch版本要与Python环境下的PyTorch版本一致。

         安装好libtorch后可简单测试下是否正常。比如我们用TorchScript转换一个预训练模型,示例如下:

    import torch
    import torchvision.models as models
    vgg16 = models.vgg16()
    example = torch.rand(1, 3, 224, 224).cuda() 
    model = model.eval()
    traced_script_module = torch.jit.trace(model, example)
    output = traced_script_module(torch.ones(1,3,224,224).cuda())
    traced_script_module.save('vgg16-trace.pt')
    print(output)
    

    输出为:

    tensor([[ -0.8301, -35.6095, 12.4716]], device='cuda:0',
            grad_fn=)
    

         然后切换到C++环境,编写CmakeLists文件如下:

    cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
    project(libtorch_test)
    find_package(Torch REQUIRED)
    message(STATUS "Pytorch status:")
    message(STATUS "libraries: ${TORCH_LIBRARIES}")
    add_executable(libtorch_test test.cpp)
    target_link_libraries(libtorch_test "${TORCH_LIBRARIES}")
    set_property(TARGET libtorch_test PROPERTY CXX_STANDARD 11)
    

         继续编写test.cpp代码如下:

    #include "torch/script.h"
    #include "torch/torch.h"
    #include 
    #include 
    using namespace std;
    
    
    int main(int argc, const char* argv[]){
        if (argc != 2) {
            std::cerr << "usage: example-app \n";
            return -1;
        }
    
    
        // 读取TorchScript转化后的模型
        torch::jit::script::Module module;
        try {
            module = torch::jit::load(argv[1]);
        }
    
    
        catch (const c10::Error& e) {
            std::cerr << "error loading the model\n";
            return -1;
        }
    
    
        module->to(at::kCUDA);
        assert(module != nullptr);
        std::cout << "ok\n";
    
    
        // 构建示例输入
        std::vector inputs;
        inputs.push_back(torch::ones({1, 3, 224, 224}).to(at::kCUDA));
    
    
        // 执行模型推理并输出tensor
        at::Tensor output = module->forward(inputs).toTensor();
        std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';}
    

         编译test.cpp并执行,输出如下。对比Python环境下的的运行结果,可以发现基本是一致的,这也说明当前环境下libtorch安装没有问题。

    ok
    -0.8297, -35.6048, 12.4823
    [Variable[CUDAFloatType]{1,3}]
    

    2.4 完整部署流程

         通过前面对TorchScript和libtorch的描述,其实我们已经基本将PyTorch的C++部署已经基本讲到了,这里我们再来完整的理一下整个流程。基于C++的PyTorch模型部署流程如下。

    第一步:

         通过torch.jit.trace方法将PyTorch模型转换为TorchScript,示例如下:

    import torch
    from torchvision.models import resnet18
    model =resnet18()
    example = torch.rand(1, 3, 224, 224)
    tracing.traced_script_module = torch.jit.trace(model, example)
    

    第二步:

         将TorchScript序列化为.pt模型文件。

    traced_script_module.save("traced_resnet_model.pt")
    

    第三步:

         在C++中导入序列化之后的TorchScript模型,为此我们需要分别编写包含调用程序的cpp文件、配置和编译用的CMakeLists.txt文件。CMakeLists.txt文件示例内容如下:

    cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
    project(custom_ops)
    find_package(Torch REQUIRED)
    add_executable(example-app example-app.cpp)
    target_link_libraries(example-app "${TORCH_LIBRARIES}")
    set_property(TARGET example-app PROPERTY CXX_STANDARD 14)
    

         包含模型调用程序的example-app.cpp示例编码如下:

    #include  // torch头文件.
    #include #include 
    
    
    int main(int argc, const char* argv[]) {
      if (argc != 2) {
        std::cerr << "usage: example-app \n";
        return -1;
      }
    
    
      torch::jit::script::Module module;
      try {
        // 反序列化:导入TorchScript模型
        module = torch::jit::load(argv[1]);
      }
    
    
      catch (const c10::Error& e) {
        std::cerr << "error loading the model\n";
        return -1;
      }
      std::cout << "ok\n";}
    

         两个文件编写完成之后便可对其执行编译:

    mkdir example_test
    cd example_test
    cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
    cmake --example_test . --config Release
    

    第四步:

    给example-app.cpp添加模型推理代码并执行:

    std::vector inputs;inputs.push_back(torch::ones({1, 3, 224, 224}));
    // 执行推理并将模型转化为Tensor
    output = module.forward(inputs).toTensor();std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';
    

         以上便是C++中部署PyTorch模型的全过程,相关教程可参考PyTorch官方:

    https://pytorch.org/tutorials/

    总结

         模型部署对于算法工程师而言非常重要,关系到你的工作能否产生实际价值。相应的也需要大家具备足够的工程能力,比如MySQL、Redis、C++、前端和后端的一些知识和开发技术,需要各位算法工程师都能够基本了解和能够使用。

     

    收起 

    展开全文 

     python java 深度学习 人工智能 tensorflow

  • 深度学习部署

    2020-05-06 11:38:46

    如果说前几年深度学习主题追求的是越来越深的模型及越来越准的方法,那么最近两年关注的点是模型的轻量化及部署,这是一个比较大而且比较复杂的话题。即训练好的模型怎样部署在实际的项目中,也就是我们常说的train...

     

    转载自:https://www.cnblogs.com/supersayajin/p/11445401.html

    如果说前几年深度学习主题追求的是越来越深的模型及越来越准的方法,那么最近两年关注的点是模型的轻量化及部署,这是一个比较大而且比较复杂的话题。即训练好的模型怎样部署在实际的项目中,也就是我们常说的train和inference(或者forward,test,deploy)主要关注以下几点:

    (1) 场景和平台选择,是移动端还是服务器端的,CPU还是GPU的,速度和准确率要求等等。有的时候像TensorFlow、PyTorch、MXNet等框架不太适合直接拿来部署,即使用这些框架部署也要结合硬件平台手动做一些配置上的加速,比如Intel CPU可以用MKL或者SSE、AVX等SIMD指令集加速等等。

    (2) 移动端有很多大厂的forward框架比如主流的NCNN,上半年阿里开源的MNN,以及比较低调的armNN等等,目前看NCNN用的最多,MNN很有潜力,移动端的GPU支持貌似不是很好,NVIDIA GPU的话主流还是他们自家的TensorRT加速。训练好的模型部署到移动端需要做模型转换,要注意有些框架只支持几个大厂的格式,比如TensorFlow,PyTorch、Caffe、onnx等,话说这里的onnx貌似发展的并不怎么样,感觉caffe本身反而是最时候做中间转换的桥梁,哈哈。。比如MXNet->Caffe->MNN;另外诸多框架的op也尚未完全统一,很多需要自己自定义op实现,还有的方法是做截断,也就是拿支持的那部分网络的op做转换,先计算得到这部分的结果,后接自己实现的剩余部分。

    (3) 用TVM做编译优化,这个看起来比较有意思,很可能是深度学习部署优化的方向之一,针对不同框架、不同语言的前端(目前使用relay)生成中间层IR,然后针对不同的硬件平台做针对性的优化工作。优化的内容包括内存分配、算子融合、GEMM优化等等。之前试过mobilefacenet的TVM版本,差不多能加速30%左右,14ms->9.5ms还是非常给力的,但是目前看默认的优化选项并不是最优的,比如我也试过mxnet gluoncv中的模型还不如不用TVM来得快,更多的时候需要用tvm中auto-tuning方法搜索一个最优的配置,这个过程比较耗时,一个模型可能需要几个小时来实验。

    (4) 手动写forward方法,如果做到完全脱离第三方库,需要自己手写forward方法,基本思路还是im2col+GEMM等等,做好内存分配,数据对齐重排,使用指定架构的寄存器提高访存效率、提高cache命中率等等,模型压缩和优化的坑太深,这里不多说了。。。。

    收起 

    展开全文 
  • 深度学习】基于web端和C++的两种深度学习模型部署方式

    2020-08-22 00:33:43

    深度学习Author:louwillMachine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理。一种是基于web服务端的模型部署,一种是基...

     

    深度学习

    Author:louwill

    Machine Learning Lab

         

          本文对深度学习两种模型部署方式进行总结和梳理。一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署。

          基于web服务端的模型部署,主要是通过REST API的形式来提供接口方便调用。而基于C++的深度学习模型部署,主要是通过深度学习框架的C++前端版本,将模型集成到软件服务中。

          本文分别对上述两种模型部署方式进行流程梳理,并分别举例进行说明。

    1. 基于web端的模型部署

    1.1 web服务与技术框架

         下面以ResNet50预训练模型为例,旨在展示一个轻量级的深度学习模型部署,写一个较为简单的图像分类的REST API。主要技术框架为Keras+Flask+Redis。其中Keras作为模型框架、Flask作为后端Web框架、Redis则是方便以键值形式存储图像的数据库。各主要package版本:

    tensorflow 1.14
    keras 2.2.4
    flask 1.1.1
    redis 3.3.8
    

         先简单说一下Web服务,一个Web应用的本质无非就是客户端发送一个HTTP请求,然后服务器收到请求后生成一个HTML文档作为响应返回给客户端的过程。在部署深度学习模型时,大多时候我们不需要搞一个前端页面出来,一般是以REST API的形式提供给开发调用。那么什么是API呢?很简单,如果一个URL返回的不是HTML,而是机器能直接解析的数据,这样的一个URL就可以看作是一个API。

    先开启Redis服务:

    redis-server
    

    1.2 服务配置

         定义一些配置参数:

    IMAGE_WIDTH = 224
    IMAGE_HEIGHT = 224
    IMAGE_CHANS = 3
    IMAGE_DTYPE = "float32"
    IMAGE_QUEUE = "image_queue"
    BATCH_SIZE = 32
    SERVER_SLEEP = 0.25
    CLIENT_SLEEP = 0.25
    

         指定输入图像大小、类型、batch_size大小以及Redis图像队列名称。

         然后创建Flask对象实例,建立Redis数据库连接:

    app = flask.Flask(__name__)
    db = redis.StrictRedis(host="localhost", port=6379, db=0)
    model = None
    

         因为图像数据作为numpy数组不能直接存储到Redis中,所以图像存入到数据库之前需要将其序列化编码,从数据库取出时再将其反序列化解码即可。分别定义编码和解码函数:

    def base64_encode_image(img):
        return base64.b64encode(img).decode("utf-8")
    
    
    def base64_decode_image(img, dtype, shape):
        if sys.version_info.major == 3:
            img = bytes(img, encoding="utf-8")
        img = np.frombuffer(base64.decodebytes(img), dtype=dtype)
        img = img.reshape(shape)
        return img
    

         另外待预测图像还需要进行简单的预处理,定义预处理函数如下:

    def prepare_image(image, target):
        # if the image mode is not RGB, convert it
        if image.mode != "RGB":
            image = image.convert("RGB")
        # resize the input image and preprocess it
        image = image.resize(target)
        image = img_to_array(image)
        # expand image as one batch like shape (1, c, w, h)
        image = np.expand_dims(image, axis=0)
        image = imagenet_utils.preprocess_input(image)
        # return the processed image
        return image
    

    1.3 预测接口定义

         准备工作完毕之后,接下来就是主要的两大部分:模型预测部分和app后端响应部分。先定义模型预测函数如下:

    def classify_process():
        # 导入模型
        print("* Loading model...")
        model = ResNet50(weights="imagenet")
        print("* Model loaded")
        while True:
            # 从数据库中创建预测图像队列
            queue = db.lrange(IMAGE_QUEUE, 0, BATCH_SIZE - 1)
            imageIDs = []
            batch = None
            # 遍历队列
            for q in queue:
                # 获取队列中的图像并反序列化解码
                q = json.loads(q.decode("utf-8"))
                image = base64_decode_image(q["image"], IMAGE_DTYPE,
                                            (1, IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_CHANS))
                # 检查batch列表是否为空
                if batch is None:
                    batch = image
                # 合并batch
                else:
                    batch = np.vstack([batch, image])
                # 更新图像ID
                imageIDs.append(q["id"])
             if len(imageIDs) > 0:
                print("* Batch size: {}".format(batch.shape))
                preds = model.predict(batch)
                results = imagenet_utils.decode_predictions(preds)
                # 遍历图像ID和预测结果并打印
                for (imageID, resultSet) in zip(imageIDs, results):
                    # initialize the list of output predictions
                    output = []
                    # loop over the results and add them to the list of
                    # output predictions
                    for (imagenetID, label, prob) in resultSet:
                        r = {"label": label, "probability": float(prob)}
                        output.append(r)
                    # 保存结果到数据库
                    db.set(imageID, json.dumps(output))
                # 从队列中删除已预测过的图像
                db.ltrim(IMAGE_QUEUE, len(imageIDs), -1)
            time.sleep(SERVER_SLEEP)
    

         然后定义app服务:

    @app.route("/predict", methods=["POST"])
    def predict():
        # 初始化数据字典
        data = {"success": False}
        # 确保图像上传方式正确
        if flask.request.method == "POST":
            if flask.request.files.get("image"):
                # 读取图像数据
                image = flask.request.files["image"].read()
                image = Image.open(io.BytesIO(image))
                image = prepare_image(image, (IMAGE_WIDTH, IMAGE_HEIGHT))
                # 将数组以C语言存储顺序存储
                image = image.copy(order="C")
                # 生成图像ID
                k = str(uuid.uuid4())
                d = {"id": k, "image": base64_encode_image(image)}
                db.rpush(IMAGE_QUEUE, json.dumps(d))
                # 运行服务
                while True:
                    # 获取输出结果
                    output = db.get(k)
                    if output is not None:
                        output = output.decode("utf-8")
                        data["predictions"] = json.loads(output)
                        db.delete(k)
                        break
                    time.sleep(CLIENT_SLEEP)
                data["success"] = True
            return flask.jsonify(data)
    

         Flask使用Python装饰器在内部自动将请求的URL和目标函数关联了起来,这样方便我们快速搭建一个Web服务。

    1.4 接口测试

         服务搭建好了之后我们可以用一张图片来测试一下效果:

    curl -X POST -F image=@test.jpg 'http://127.0.0.1:5000/predict'
    

    模型端的返回:

    预测结果返回:

         最后我们可以给搭建好的服务进行一个压力测试,看看服务的并发等性能如何,定义一个压测文件stress_test.py 如下:

    from threading import Thread
    import requests
    import time
    # 请求的URL
    KERAS_REST_API_URL = "http://127.0.0.1:5000/predict"
    # 测试图片
    IMAGE_PATH = "test.jpg"
    # 并发数
    NUM_REQUESTS = 500
    # 请求间隔
    SLEEP_COUNT = 0.05
    def call_predict_endpoint(n):
        # 上传图像
        image = open(IMAGE_PATH, "rb").read()
        payload = {"image": image}
        # 提交请求
        r = requests.post(KERAS_REST_API_URL, files=payload).json()
        # 确认请求是否成功
        if r["success"]:
            print("[INFO] thread {} OK".format(n))
        else:
            print("[INFO] thread {} FAILED".format(n))
    # 多线程进行
    for i in range(0, NUM_REQUESTS):
        # 创建线程来调用api
        t = Thread(target=call_predict_endpoint, args=(i,))
        t.daemon = True
        t.start()
        time.sleep(SLEEP_COUNT)
    time.sleep(300)
    

    测试效果如下:

    2. 基于C++的模型部署

    2.1 引言

         PyTorch作为一款端到端的深度学习框架,在1.0版本之后已具备较好的生产环境部署条件。除了在web端撰写REST API进行部署之外(参考),软件端的部署也有广泛需求。尤其是最近发布的1.5版本,提供了更为稳定的C++前端API。

         工业界与学术界最大的区别在于工业界的模型需要落地部署,学界更多的是关心模型的精度要求,而不太在意模型的部署性能。一般来说,我们用深度学习框架训练出一个模型之后,使用Python就足以实现一个简单的推理演示了。但在生产环境下,Python的可移植性和速度性能远不如C++。所以对于深度学习算法工程师而言,Python通常用来做idea的快速实现以及模型训练,而用C++作为模型的生产工具。目前PyTorch能够完美的将二者结合在一起。实现PyTorch模型部署的核心技术组件就是TorchScript和libtorch。

         所以基于PyTorch的深度学习算法工程化流程大体如下图所示:

    2.2 TorchScript

         TorchScript可以视为PyTorch模型的一种中间表示,TorchScript表示的PyTorch模型可以直接在C++中进行读取。PyTorch在1.0版本之后都可以使用TorchScript的方式来构建序列化的模型。TorchScript提供了Tracing和Script两种应用方式。

         Tracing应用示例如下:

    class MyModel(torch.nn.Module):
        def __init__(self):
            super(MyModel, self).__init__()
            self.linear = torch.nn.Linear(4, 4)
    
    
        def forward(self, x, h):
            new_h = torch.tanh(self.linear(x) + h)
            return new_h, new_h
    
    
    # 创建模型实例 
    my_model = MyModel()
    # 输入示例
    x, h = torch.rand(3, 4), torch.rand(3, 4)
    # torch.jit.trace方法对模型构建TorchScript
    traced_model = torch.jit.trace(my_model, (x, h))
    # 保存转换后的模型
    traced_model.save('model.pt')
    

         在这段代码中,我们先是定义了一个简单模型并创建模型实例,然后给定输入示例,Tracing方法最关键的一步在于使用torch.jit.trace方法对模型进行TorchScript转化。我们可以获得转化后的traced_model对象获得其计算图属性和代码属性。计算图属性:

    print(traced_model.graph)
    
    graph(%self.1 : __torch__.torch.nn.modules.module.___torch_mangle_1.Module,
          %input : Float(3, 4),
          %h : Float(3, 4)):
      %19 : __torch__.torch.nn.modules.module.Module = prim::GetAttr[name="linear"](%self.1)
      %21 : Tensor = prim::CallMethod[name="forward"](%19, %input)
      %12 : int = prim::Constant[value=1]() # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %13 : Float(3, 4) = aten::add(%21, %h, %12) # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %14 : Float(3, 4) = aten::tanh(%13) # /var/lib/jenkins/workspace/beginner_source/Intro_to_TorchScript_tutorial.py:188:0
      %15 : (Float(3, 4), Float(3, 4)) = prim::TupleConstruct(%14, %14)
      return (%15)
    

    代码属性:

    print(traced_cell.code)
    
    
    
    def forward(self,
        input: Tensor,
        h: Tensor) -> Tuple[Tensor, Tensor]:
      _0 = torch.add((self.linear).forward(input, ), h, alpha=1)
      _1 = torch.tanh(_0)
      return (_1, _1)
    

         这样我们就可以将整个模型都保存到硬盘上了,并且经过这种方式保存下来的模型可以加载到其他其他语言环境中。

         TorchScript的另一种实现方式是Script的方式,可以算是对Tracing方式的一种补充。当模型代码中含有if或者for-loop等控制流程序时,使用Tracing方式是无效的,这时候可以采用Script方式来进行实现TorchScript。实现方法跟Tracing差异不大,关键在于把jit.tracing换成jit.script方法,示例如下。

    scripted_model = torch.jit.script(MyModel)
    scripted_model.save('model.pt')
    

         除了Tracing和Script之外,我们也可以混合使用这两种方式,这里不做详述。总之,TorchScript为我们提供了一种表示形式,可以对代码进行编译器优化以提供更有效的执行。

    2.3 libtorch

         在Python环境下对训练好的模型进行转换之后,我们需要C++环境下的PyTorch来读取模型并进行编译部署。这种C++环境下的PyTorch就是libtorch。因为libtorch通常用来作为PyTorch模型的C++接口,libtorch也称之为PyTorch的C++前端。

         我们可以直接从PyTorch官网下载已经编译好的libtorch安装包,当然也可以下载源码自行进行编译。这里需要注意的是,安装的libtorch版本要与Python环境下的PyTorch版本一致。

         安装好libtorch后可简单测试下是否正常。比如我们用TorchScript转换一个预训练模型,示例如下:

    import torch
    import torchvision.models as models
    vgg16 = models.vgg16()
    example = torch.rand(1, 3, 224, 224).cuda() 
    model = model.eval()
    traced_script_module = torch.jit.trace(model, example)
    output = traced_script_module(torch.ones(1,3,224,224).cuda())
    traced_script_module.save('vgg16-trace.pt')
    print(output)
    

    输出为:

    tensor([[ -0.8301, -35.6095, 12.4716]], device='cuda:0',
            grad_fn=)
    

         然后切换到C++环境,编写CmakeLists文件如下:

    cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
    project(libtorch_test)
    find_package(Torch REQUIRED)
    message(STATUS "Pytorch status:")
    message(STATUS "libraries: ${TORCH_LIBRARIES}")
    add_executable(libtorch_test test.cpp)
    target_link_libraries(libtorch_test "${TORCH_LIBRARIES}")
    set_property(TARGET libtorch_test PROPERTY CXX_STANDARD 11)
    

         继续编写test.cpp代码如下:

    #include "torch/script.h"
    #include "torch/torch.h"
    #include 
    #include 
    using namespace std;
    
    
    int main(int argc, const char* argv[]){
        if (argc != 2) {
            std::cerr << "usage: example-app \n";
            return -1;
        }
    
    
        // 读取TorchScript转化后的模型
        torch::jit::script::Module module;
        try {
            module = torch::jit::load(argv[1]);
        }
    
    
        catch (const c10::Error& e) {
            std::cerr << "error loading the model\n";
            return -1;
        }
    
    
        module->to(at::kCUDA);
        assert(module != nullptr);
        std::cout << "ok\n";
    
    
        // 构建示例输入
        std::vector inputs;
        inputs.push_back(torch::ones({1, 3, 224, 224}).to(at::kCUDA));
    
    
        // 执行模型推理并输出tensor
        at::Tensor output = module->forward(inputs).toTensor();
        std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';}
    

         编译test.cpp并执行,输出如下。对比Python环境下的的运行结果,可以发现基本是一致的,这也说明当前环境下libtorch安装没有问题。

    ok
    -0.8297, -35.6048, 12.4823
    [Variable[CUDAFloatType]{1,3}]
    

    2.4 完整部署流程

         通过前面对TorchScript和libtorch的描述,其实我们已经基本将PyTorch的C++部署已经基本讲到了,这里我们再来完整的理一下整个流程。基于C++的PyTorch模型部署流程如下。

    第一步:

         通过torch.jit.trace方法将PyTorch模型转换为TorchScript,示例如下:

    import torch
    from torchvision.models import resnet18
    model =resnet18()
    example = torch.rand(1, 3, 224, 224)
    tracing.traced_script_module = torch.jit.trace(model, example)
    

    第二步:

         将TorchScript序列化为.pt模型文件。

    traced_script_module.save("traced_resnet_model.pt")
    

    第三步:

         在C++中导入序列化之后的TorchScript模型,为此我们需要分别编写包含调用程序的cpp文件、配置和编译用的CMakeLists.txt文件。CMakeLists.txt文件示例内容如下:

    cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
    project(custom_ops)
    find_package(Torch REQUIRED)
    add_executable(example-app example-app.cpp)
    target_link_libraries(example-app "${TORCH_LIBRARIES}")
    set_property(TARGET example-app PROPERTY CXX_STANDARD 14)
    

         包含模型调用程序的example-app.cpp示例编码如下:

    #include  // torch头文件.
    #include #include 
    
    
    int main(int argc, const char* argv[]) {
      if (argc != 2) {
        std::cerr << "usage: example-app \n";
        return -1;
      }
    
    
      torch::jit::script::Module module;
      try {
        // 反序列化:导入TorchScript模型
        module = torch::jit::load(argv[1]);
      }
    
    
      catch (const c10::Error& e) {
        std::cerr << "error loading the model\n";
        return -1;
      }
      std::cout << "ok\n";}
    

         两个文件编写完成之后便可对其执行编译:

    mkdir example_test
    cd example_test
    cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
    cmake --example_test . --config Release
    

    第四步:

    给example-app.cpp添加模型推理代码并执行:

    std::vector inputs;inputs.push_back(torch::ones({1, 3, 224, 224}));
    // 执行推理并将模型转化为Tensor
    output = module.forward(inputs).toTensor();std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';
    

         以上便是C++中部署PyTorch模型的全过程,相关教程可参考PyTorch官方:

    https://pytorch.org/tutorials/

    总结

         模型部署对于算法工程师而言非常重要,关系到你的工作能否产生实际价值。相应的也需要大家具备足够的工程能力,比如MySQL、Redis、C++、前端和后端的一些知识和开发技术,需要各位算法工程师都能够基本了解和能够使用。

    往期精彩回顾
    
    
    
    
    适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
    AI基础下载机器学习的数学基础专辑获取一折本站知识星球优惠券,复制链接直接打开:https://t.zsxq.com/662nyZF本站qq群1003271085。加入微信群请扫码进群(如果是博士或者准备读博士请说明):
    

    收起 

    展开全文 

     人工智能 python 深度学习 java 大数据

  • 深度学习模型在移动端的部署

    2019-04-27 14:51:17

    深度学习也成了机器学习领域内的热点,现在人工智能、大数据更是越来越贴近我们的日常生活,越来越多的人工智能应用开始在移植移动端上,那能够快速高效地在移动端部署深度学习模型就变成亟待解决的问题了。...

     

    简介

    自从 AlphaGo 出现以来,机器学习无疑是当今最火热的话题,而深度学习也成了机器学习领域内的热点,现在人工智能、大数据更是越来越贴近我们的日常生活,越来越多的人工智能应用开始在移植到移动端上,那能够快速高效地在移动端部署深度学习模型就变成亟待解决的问题了。

    现阶段 app 上使用的深度学习主要有两种模式:

    Online 方式

    首先在移动端做初步预处理,然后把数据传到服务器进行预测后返回移动端。
    优点:部署相对简单,现成的框架(caffe,theano,mxnet,Torch) 做下封装就可以直接拿来用;使用服务器进行计算,性能强,能够处理比较大的模型
    缺点:必须使用网络,而且展示效果依赖网速,不适合实时性要求高的应用。

    Offline 方式

    根据硬件的性能选择模型,在服务器训练得到模型,在手机上进行预测的过程。
    优点:不需要使用网络,可以保护隐私
    缺点:计算的性能、耗时等取决于移动端的性能,有些模型只能使用CPU,精度可能会有影响,无法进行类似云端的大规模分布式训练;移动端部署相对较麻烦,需要针对移动端进行优化;大模型耗费大量的资源(计算、内存、存储、电)。

    部署方法

    下面主要介绍offline的部署方法。

    主要分两个阶段,第一个阶段是训练并得到模型,第二个阶段则是在得到模型后,在移动端进行部署。本文主要讲解的为第二阶段。

    训练模型

    在第一阶段训练模型中,已经有很成熟的开源框架和算法进行实现,但是为了能部署到移动端,还需要进行压缩加速。

    压缩网络

    目前深度学习在各个领域轻松碾压传统算法,不过真正用到实际项目中却会有很大的问题:

    1. 计算量非常巨大;
    2. 模型占用很高内存;
      由于移动端系统资源有限,而深度学习模型可能会高达几百M,因此很难将深度学习应用到移动端系统中去。

    压缩方法

    综合现有的深度模型压缩方法,它们主要分为四类:

    方法名称 描述 应用场景 方法细节
    参数修剪和共享(parameter pruning and sharing) 删除对准确率影响不大的参数 卷积层和全连接层 对不同设置具有鲁棒性,可以达到较好效果,支持从零训练和预训练
    低秩因子分解(low-rank factorization) 使用矩阵对参数进行分解估计 卷积层和全连接层 标准化的途径,很容易实施,支持从零训练和预训练
    转移/紧凑卷积滤波器(transferred/compact convolutional filters) 设计特别的卷积核来保存参数 只有卷积层 算法依赖于应用程序,通常可以取得好的表现,只能从零开始训练
    知识蒸馏(knowledge distillation) 训练一个更紧凑的神经网络来从大的模型蒸馏知识 卷积层和全连接层 模型表现对应用程序和网络结构较为敏感,只能从零开始训练

    基于参数修剪和共享的方法针对模型参数的冗余性,试图去除冗余和不重要的项。基于低秩因子分解的技术使用矩阵/张量分解来估计深度学习模型的信息参数。基于传输/紧凑卷积滤波器的方法设计了特殊的结构卷积滤波器来降低存储和计算复杂度。知识蒸馏方法通过学习一个蒸馏模型,训练一个更紧凑的神经网络来重现一个更大的网络的输出。

    一般来说,参数修剪和共享,低秩分解和知识蒸馏方法可以用于全连接层和卷积层的CNN,但另一方面,使用转移/紧凑型卷积核的方法仅支持卷积层。低秩因子分解和基于转换/紧凑型卷积核的方法提供了一个端到端的流水线,可以很容易地在CPU/GPU环境中实现。相反参数修剪和共享使用不同的方法,如矢量量化,二进制编码和稀疏约束来执行任务,这导致常需要几个步骤才能达到目标。

    移动端部署

    目前,很多公司都推出了开源的移动端深度学习框架,基本不支持训练,只支持前向推理。这些框架都是 offline 方式,它可确保用户数据的私有性,可不再依赖于因特网连接。

    Caffe2

    2017年4月19日 Facebook在F8开发者大会上推出Caffe2。项目是专门为手机定制的深度框架,是在caffe2 的基础上进行迁移的,目的就是让最普遍的智能设备——手机也能广泛高效地应用深度学习算法。
    Caffe2 is a deep learning framework made with expression, speed, and modularity in mind. It is an experimental refactoring of Caffe, and allows a more flexible way to organize computation.

    开源地址: https://github.com/caffe2/caffe2

    TensorFlow Lite

    2017年5月17日 Goole在I/O大会推出TensorFlow Lite,是专门为移动设备而优化的 TensorFlow 版本。TensorFlow Lite 具备以下三个重要功能:
    轻量级(Lightweight):支持机器学习模型的推理在较小二进制数下进行,能快速初始化/启动
    跨平台(Cross-platform):可以在许多不同的平台上运行,现在支持 Android 和 iOS
    快速(Fast):针对移动设备进行了优化,包括大大减少了模型加载时间、支持硬件加速

    模块如下:
    TensorFlow Model: 存储在硬盘上已经训练好的 TensorFlow 模型
    TensorFlow Lite Converter: 将模型转换为 TensorFlow Lite 文件格式的程序
    TensorFlow Lite Model File: 基于 FlatBuffers 的模型文件格式,针对速度和大小进行了优化。

    TensorFlow Lite 目前支持很多针对移动端训练和优化好的模型。

    Core ML

    2017年6月6日 Apple在WWDC大会上推出Core ML。对机器学习模型的训练是一项很重的工作,Core ML 所扮演的角色更多的是将已经训练好的模型转换为 iOS 可以理解的形式,并且将新的数据“喂给”模型,获取输出。抽象问题和创建模型虽然并不难,但是对模型的改进和训练可以说是值得研究一辈子的事情,这篇文章的读者可能也不太会对此感冒。好在 Apple 提供了一系列的工具用来将各类机器学习模型转换为 Core ML 可以理解的形式。籍此,你就可以轻松地在你的 iOS app 里使用前人训练出的模型。这在以前可能会需要你自己去寻找模型,然后写一些 C++ 的代码来跨平台调用,而且难以利用 iOS 设备的 GPU 性能和 Metal (除非你自己写一些 shader 来进行矩阵运算)。Core ML 将使用模型的门槛降低了很多。

    Core ML 在背后驱动了 iOS 的视觉识别的 Vision 框架和 Foundation 中的语义分析相关 API。普通开发者可以从这些高层的 API 中直接获益,比如人脸图片或者文字识别等。这部分内容在以前版本的 SDK 中也存在,不过在 iOS 11 SDK 中它们被集中到了新的框架中,并将一些更具体和底层的控制开放出来。比如你可以使用 Vision 中的高层接口,但是同时指定底层所使用的模型。这给 iOS 的计算机视觉带来了新的可能。

    MACE

    小米开源了深度学习框架MACE,有几个特点:异构加速、汇编级优化、支持各种框架的模型转换。

    有了异构,就可以在CPU、GPU和DSP上跑不同的模型,实现真正的生产部署,比如人脸检测、人脸识别和人脸跟踪,可以同时跑在不同的硬件上。小米支持的GPU不限于高通,这点很通用,很好,比如瑞芯微的RK3299就可以同时发挥出cpu和GPU的好处来。
    MACE 是专门为移动设备优化的深度学习模型预测框架,MACE 从设计之初,便针对移动设备的特点进行了专门的优化:
    速度:对于放在移动端进行计算的模型,一般对整体的预测延迟有着非常高的要求。在框架底层,针对ARM CPU进行了NEON指令级优化,针对移动端GPU,实现了高效的OpenCL内核代码。针对高通DSP,集成了nnlib计算库进行HVX加速。同时在算法层面,采用Winograd算法对卷积进行加速。
    功耗:移动端对功耗非常敏感,框架针对ARM处理器的big.LITTLE架构,提供了高性能,低功耗等多种组合配置。针对Adreno GPU,提供了不同的功耗性能选项,使得开发者能够对性能和功耗进行灵活的调整。
    系统响应:对于GPU计算模式,框架底层对OpenCL内核自适应的进行分拆调度,保证GPU渲染任务能够更好的进行抢占调度,从而保证系统的流畅度。
    初始化延迟:在实际项目中,初始化时间对用户体验至关重要,框架对此进行了针对性的优化。
    内存占用:通过对模型的算子进行依赖分析,引入内存复用技术,大大减少了内存的占用。
    模型保护:对于移动端模型,知识产权的保护往往非常重要,MACE支持将模型转换成C++代码,大大提高了逆向工程的难度。
    此外,MACE 支持 TensorFlow 和 Caffe 模型,提供转换工具,可以将训练好的模型转换成专有的模型数据文件,同时还可以选择将模型转换成C++代码,支持生成动态库或者静态库,提高模型保密性。

    目前MACE已经在小米手机上的多个应用场景得到了应用,其中包括相机的人像模式,场景识别,图像超分辨率,离线翻译(即将实现)等。

    开源地址:https://github.com/XiaoMi/mace

    MACE Model Zoo
    随着MACE一起开源的还有MACE Model Zoo项目,目前包含了物体识别,场景语义分割,图像风格化等多个公开模型。

    链接: https://github.com/XiaoMi/mace-models

    FeatherCNN 和 NCNN

    FeatherCNN 由腾讯 AI 平台部研发,基于 ARM 架构开发的高效神经网络前向计算库,核心算法已申请专利。该计算库支持 caffe 模型,具有无依赖,速度快,轻量级三大特性。该库具有以下特性:

    1. 无依赖:该计算库无第三方组件,静态库或者源码可轻松部署于 ARM 服务器,和嵌入式终端,安卓,苹果手机等移动智能设备。
    2. 速度快:该计算库是当前性能最好的开源前向计算库之一,在 64 核 ARM 众核芯片上比 Caffe 和 Caffe2 快 6 倍和 12 倍,在 iPhone7 上比 Tensorflow lite 快 2.5 倍。
    3. 轻量级:该计算库编译后的后端 Linux 静态库仅 115KB , 前端 Linux 静态库 575KB , 可执行文件仅 246KB 。
      FeatherCNN 采用 TensorGEMM 加速的 Winograd 变种算法,以 ARM 指令集极致提升 CPU 效率,为移动端提供强大的 AI 计算能力。使用该计算库可接近甚至达到专业神经网络芯片或 GPU 的性能,并保护用户已有硬件投资。

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

    这两个框架都是腾讯公司出品,FeatherCNN来自腾讯AI平台部,NCNN来自腾讯优图。
    重点是:都开源,都只支持cpu
    NCNN开源早点,性能较好,用户较多。FeatherCNN开源晚,底子很好。

    FeatherCNN开源地址:http://github.com/tencent/FeatherCNN
    NCNN开源地址:https://github.com/Tencent/ncnn

    MDL

    百度的mobile-deep-learning,MDL 框架主要包括模型转换模块(MDL Converter)、模型加载模块(Loader)、网络管理模块(Net)、矩阵运算模块(Gemmers)及供 Android 端调用的 JNI 接口层(JNI Interfaces)。其中,模型转换模块主要负责将 Caffe 模型转为 MDL 模型,同时支持将 32bit 浮点型参数量化为 8bit 参数,从而极大地压缩模型体积;模型加载模块主要完成模型的反量化及加载校验、网络注册等过程,网络管理模块主要负责网络中各层 Layer 的初始化及管理工作;MDL 提供了供 Android 端调用的 JNI 接口层,开发者可以通过调用 JNI 接口轻松完成加载及预测过程。

    作为一款移动端深度学习框架,需要充分考虑到移动应用自身及运行环境的特点,在速度、体积、资源占用率等方面有严格的要求。同时,可扩展性、鲁棒性、兼容性也是需要考虑的。为了保证框架的可扩展性,MDL对 layer 层进行了抽象,方便框架使用者根据模型的需要,自定义实现特定类型的层,使用 MDL 通过添加不同类型的层实现对更多网络模型的支持,而不需要改动其他位置的代码。为了保证框架的鲁棒性,MDL 通过反射机制,将 C++ 底层异常抛到应用层,应用层通过捕获异常对异常进行相应处理,如通过日志收集异常信息、保证软件可持续优化等。目前行业内各种深度学习训练框架种类繁多,而 MDL 不支持模型训练能力,为了保证框架的兼容性,MDL提供 Caffe 模型转 MDL 的工具脚本,使用者通过一行命令就可以完成模型的转换及量化过程。

    开源地址:https://github.com/baidu/mobile-deep-learning

    SNPE

    这是高通骁龙的官方SDK,不开源。主要支持自家的DSP、GPU和CPU。模型训练在流行的深度学习框架上进行(SNPE支持Caffe,Caffe2,ONNX和TensorFlow模型。)训练完成后,训练的模型将转换为可加载到SNPE运行时的DLC文件。 然后,可以使用此DLC文件使用其中一个Snapdragon加速计算核心执行前向推断传递。

    实践应用

    以智云视图的人脸标定算法为例,开源项目地址为:
    https://github.com/zeusees/HyperLandmark

    1. 使用的是腾讯的 NCNN 进行部署,首先通过 caffe 或者 tensorflow 等进行训练,例如使用 caffe 训练会生成模型:
      deploy.prototxt
      snapshot_10000.caffemodel

    2. 然后,使用 caffe 自带了工具可以把老版本的 caffe 网络和模型转换为新版(ncnn的工具只认识新版)
      upgrade_net_proto_text [老prototxt] [新prototxt]
      upgrade_net_proto_binary [老caffemodel] [新caffemodel]

    3. 使用 caffe2ncnn 工具转换为 ncnn 的网络描述和模型
      caffe2ncnn deploy.prototxt bvlc_alexnet.caffemodel alexnet.param alexnet.bin
      但是 param 描述文件是明文的,可以使用 ncnn2mem 工具转换为二进制描述文件和内存模型,生成 alexnet.param.bin 和两个静态数组的代码文件

    4. 然后可以使用 NCNN 加载二进制的 param.bin 和 bin 模型
      ncnn::Net net;
      net.load_param_bin(“alexnet.param.bin”);
      net.load_model("alexnet.bin”);

    5. 获取输入并计算输出结果
      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"
    #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);
    

    具体的代码可以参考 github 中的demo

    • 开源地址
    • 北京智云视图科技有限公司

    收起 

    展开全文 

     深度学习 移动端 部署

  • 深度学习】如何部署一个轻量级的深度学习项目?

    2019-10-16 00:35:15

    深度学习100问Author:louwillMachine Learning Lab 无论是写论文还是打比赛,我们的深度学习模型始终局限于一种实验的状态...

     

    收起 

  • spring boot部署深度学习模型(java/pytorch)

    2020-07-02 23:27:42

    使用java部署图片识别模型(yolo)spring boot部署深度学习模型java service使用flask提供http接口pytorch部署深度学习模型,以yolov3为例 spring boot部署深度学习模型 在之前训练好深度学习模型后,遇到了部署模型...

     

    收起 

     java 深度学习 pytorch python

  • 陈天奇团队发布TVM:把深度学习部署到手机、树莓派等更多硬件

    2019-05-08 00:34:17

    本文来自AI新媒体量子位(QbitAI) △陈天奇,华盛顿大学计算机系博士生,此前毕业于上海交通大学ACM班...所谓TVM,按照正式说法:就是一种将深度学习工作负载部署到硬件的端到端IR(中间表示)堆栈。换一种说法...

     

    收起 

  • 深度学习多线程部署—学习笔记

    2019-12-10 15:33:10

    文章目录Flaskgunicorn 部署Flask项目gunicorn架构并发 vs. 并行安装gunicorn启动gunicorngunicorn配置文件列出所有进程 Flask Flask默认是单进程、单线程阻塞的任务模式,在项目上线的时候可以通过nginx+gunicorn的...

     

    收起 

     gunicorn flask 深度学习部署

  • 深度学习训练好的模型或者从别处得来的模型如何部署到生产中?

    2019-06-05 12:47:18

    这种方式可能是简单的pyinstaller库进行简单的封装、也可以是pyqt进行界面集成、接口调用,或者使用flask或者Django框架进行前端和后台服务器的嵌入,这些总体来说,都算是模型部署。 使用docke...

     

    收起 

  • tensorflow模型部署系列————浏览器前端部署(附代码)

    2019-07-22 16:08:15

    本文为系列博客tensorflow模型部署系列的一部分,用于javascript实现通用模型的部署。本文主要实现用javascript接口调用tensorflow模型进行推理。实现了tensorflow在浏览器前端计算方案,将计算任务分配在终端,可以...

     

    收起 

     javascript 模型部署 keras

  • 前端自动化部署深度实践

    2020-02-04 13:07:54

    年前我也在自动化部署这方面下了点功夫,将自己的学习所得在自动化部署的一小步,前端搬砖的一大步这篇博客中做了分享。感谢两位网友@_shanks和@TomCzHen的意见,让我有了继续优化部署流程的动力。本文主要是在自动...

     

    收起 

     node.js linux 自动化部署

  • 深度学习100问-10:如何部署一个轻量级的深度学习项目?

    2019-11-05 17:31:07

    深度学习100问Author:louwillMachine Learning Lab 无论是写论文还是打比赛,我们的深度学习模型始终局限于一种实验的状态...

     

    收起 

  • 深度学习框架的来龙去脉——史上最全面最新的深度学习框架对比分析

    2019-03-13 02:22:30

    一、深度学习框架概述与深度学习技术的四大阵营 人工智能从学术理论研究生产应用的产品化开发过程中通常会涉及多个不同的步骤和工具,这使得人工智能开发依赖的环境安装、部署、测试以及不断迭代改进准确性和...

     

    收起 

     人工智能 深度学习 机器学习 PyTorch

  • 从神经元到深度学习

    2016-10-31 14:23:31

    神经网络浅讲:从神经元到深度学习  神经网络是一门重要的机器学习技术。它是目前最为火热的研究方向--深度学习的基础。学习神经网络不仅可以让你掌握一门强大的机器学习方法,同时也可以更好地帮助你理解深度...

     

    收起 

  • 在云上玩深度学习

    2017-05-14 12:40:43

    一、综述 二、提纲 三、准备云服务器 四、利用docker快速搭建caffe环境 五、开玩深度学习

     

    收起 

  • 【个人笔记】深度学习模型部署--2019.8.6下一步学习计划

    2019-08-06 13:10:35

    搭建深度学习后台服务器 https://blog.csdn.net/qq_38957170/article/details/82820541 本篇文章的原创为国外的一篇文章(一个可扩展的Keras深度学习REST API),链接为: ...

     

    收起 

  • Tensorflow深度学习之十七:队列与多线程

    2017-08-19 21:50:46

    声明:本文参考《Tensorflow:实战Google深度学习框架》一书在使用TensorFlow进行异步计算时,队列是一种强大的机制。正如TensorFlow中的其他组件一样,队列就是TensorFlow图中的节点。这是一种有状态的节点,就像...

     

    收起 

     深度学习 Tensorflow

  • 深度学习图像分类开源项目(3)-使用flask web调用深度学习模型

    2019-12-20 17:48:05

你可能感兴趣的