小白会长 · 2021年11月30日

Flow01: 快速搭建图片分类 HTTP 服务

我们在实际做项目的时候,单个模型是不够用的,即便是移动端也都是多个流程接一起:

  • 先检测、用检测框扣图、拿扣图做个活体或者分类
  • 算力实在有限,先调一下原始输入,再检测跟踪,最后抽特征
  • 甚至考虑 3D 序列的,用队列攒 10 个再一起推理

需求是多样滴、实现是诡异滴。为了适应这些,MegFlow 用了图的表述结构:上面列出来的三种情况都可以往图里面套(哪怕语音识别也可以)。 又因为是搞计算的,所以叫它“计算图”。

这次从最简单的情况开始,把 resnet18 分类模型,部署成 HTTP 图片服务。

准备模型

MegEngine/models 有现成 imagenet 预训模型。这里把模型 dump 成 .mge,推理更快。

写个简单的 dump.py 按 [1, 3, 224, 224] 尺寸 trace forward 模型,打开推理优化选项,存为 model.mge。

def dump_static_graph(model, graph_name, shape):
    model.eval()

    data = mge.Tensor(np.random.random(shape))

    @jit.trace(capture_as_const=True)
    def pred_func(data):
        # 下一期合并预处理教程将改这里,以后再也不用关心 preprocess 了
        outputs = model(data)
        return outputs

    pred_func(data)
    pred_func.dump(
        graph_name,
        arg_names=["data"],
        optimize_for_inference=True,
        enable_fuse_conv_bias_nonlinearity=True,
    )

PR 已经提交,等 merge 了就在 models repo 里了。用法很简单,支持 resnet 和 shufflenet:

$ python3 dump.py -a resnet18 -s 1 3 224 224
...
$ ll model.mge
...
$ python3 dump.py --help

模型单测

软件开发中,单测可以减少整体的工期,仅仅会在大前期拖慢开发进度。

开发模型的推理封装,调用方传入一张或多张图片、直接获取结果,尽量避免关心内部实现。

    def inference(self, mat):
        img = self.preprocess(mat, input_size=(224,224), scale_im = False, mean=[103.530, 116.280, 123.675], std=[57.375, 57.120, 58.395])

        # build input tensor
        inp_data =self.net.get_io_tensor("data")
        inp_data.set_data_by_share(img)
        
        # forward
        self.net.forward()
        self.net.wait()

        # postprocess
        output_keys = self.net.get_all_output_name()
        output = self.net.get_io_tensor(output_keys[0]).to_numpy()
        return np.argmax(output[0])

测试很简单:

$ python3 lite.py --model model.mge  --path test.jpg
2021-09-14 11:45:02.406 | INFO     | __main__:<module>:81 - 285

\`285\` 是分类模型最后一层的 argmax,对应含义需要查 imagenet 分类表 ,这里是 “Egyptian cat”(下标从 0 开始)。 test.jpg 嘛就是这张
image.png

配置计算图

前面已经介绍了“计算图”的由来。这次只有 1 个结点,带一堆东西“显得”累赘,等后面的教程图复杂起来,就知道这种描述的好处了。

$ cat flow-python/examples/simple_classification/image_cpu.toml

main = "tutorial_01_image"

[[graphs]]
name = "subgraph"
inputs = [{ name = "inp", cap = 16, ports = ["classify:inp"] }]  # 一、输入输出结点
outputs = [{ name = "out", cap = 16, ports = ["classify:out"] }]
connections = [
]

    [[graphs.nodes]] # 二、结点参数
    name = "classify"
    ty = "Classify"
    path = "resnet18.mge"
    device = "cpu"
    device_id = 0
...
    [[graphs.nodes]] # 三、服务类型配置
    name = "source"
    ty = "ImageServer"
    port = 8084 # 端口号 8084
    response = "json"

图片单模型服务只需要关心 3 处:

  1. 计算图输入、输出结点的名字。名字是自定义的,这里都是\`classify\`
  2. \`classify\` 结点的参数。
  3. 最重要的是 \`ty="Classify"\`指明了类名,MegFlow 将在当前目录搜索\`Classify\`类。
  4. 剩下的 path/device/device_id 分别是模型路径/用 CPU 推理/用哪个核,属于用户自定义配置
  5. 服务类型。这里想运行图片服务 \`ty = "ImageServer"\`,如果想运行视频解析服务改 \`ty = "VideoServer"\`

对应配置文件

实现 classify node

\`classif.py\` 就是从 envelope.msg['data'] 取 BGR 输入,把之前单测调起来,结果送出去

@register(inputs=['inp'], outputs=['out'])
class Classify:
    ...
    def exec(self):
        envelope = self.inp.recv()
        if envelope is None:
            return

        data = envelope.msg['data']
        result = self._model.inference(data)
        self.out.send(envelope.repack(json.dumps(str(result))))

建议 \` init \` 加个 warmup,否则第一次 inference 比较慢影响体验。

运行测试

WebUI 方式

浏览器打开 8084 端口服务(例如 http://127.0.0.1:8084/docs ),熟悉的 Web UI,选择一张图“try it out”即可。
image.png

命令行方式

$ curl http://127.0.0.1:8081/analyze/image_name  -X POST --header "Content-Type:image/*"   --data-binary @test.jpeg

image_name 是用户自定义参数,用在需要 POST 内容的场景。这里随便填即可;test.jpeg 是测试图片

Python Client

$ cat ${MegFlow_DIR}/flow-python/examples/simple_classification/client.py

import requests
import cv2

def test():
    ip = 'localhost'
    port = '8084'
    url = 'http://{}:{}/analyze/any_content'.format(ip, port)
    img = cv2.imread("./test.jpg")
    _, data = cv2.imencode(".jpg", img)
    data = data.tobytes()

    headers = {'Content-Length': '%d' % len(data), 'Content-Type': 'image/*'}
    res = requests.post(url, data=data, headers=headers)
    print(res.content)

if __name__ == "__main__":
    test()

其他

一、代码

完整的实现已经放到了 MegFlow simple classification,运行方式和猫猫围栏很接近

$ cd MegFlow/flow-python/examples
$ run_with_plugins -c simple_classification/image_cpu.toml  -p  simple_classification

注意:源码/docker 编译流程用的 cargo 是 Rust 的辅助工具。MegFlow 使用者,只要有

  • 编译好的 run with_ plugins 可执行文件
  • import megflow 正常

就可以直接使用,不用关心 cargo 和 rust

原文链接:知乎
作者: 白牛

推荐阅读

更多嵌入式AI技术相关内容请关注嵌入式AI专栏。
推荐阅读
关注数
18835
内容数
1369
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息