飛槳實戰筆記:自編寫模型如何在服務器和移動端部署

做爲深度學習小白一枚,從一開始摸索如何使用深度學習框架,怎麼讓腳本跑起來,到如今開始逐步讀懂論文,看懂模型的網絡結構,按照飛槳官方文檔進行各類模型訓練和部署,整個過程遇到了無數問題。很是感謝飛槳開源社區的大力支持,並熱情答覆我遇到的各類問題,使得我能夠快速上手。特整理本篇學習筆記,以此回饋網友們的無私付出。你們都共享一點點,一塊兒爲深度學習的推動添磚加瓦(哈哈,很是正能量,有木有!)java

這篇文章詳細記錄瞭如何使用百度深度學習平臺——飛槳進行SSD目標檢測模型的訓練、以及如何將模型部署到服務器和移動端。文末給出了筆者認爲很是有用的資料連接。python

本文的代碼基於百度AI Studio官方示例代碼,並可以在飛槳 1.7.1上跑通,Python版本是3.7。git

SSD模型介紹

若是你對經典的CNN模型比較熟悉的話,那麼SSD也並不難理解。SSD大致上來講是將圖片分爲6種不一樣大小的網格,找到目標中心的落點,肯定物體的位置。在分紅不一樣網格以後,會在此之上取到不一樣數目的先驗框,對先驗框進行迴歸、分類預測。先驗框的數目足夠多,幾乎可以涵蓋整個圖片,所以咱們能夠找到包含物體的不少個先驗框,最後進行非極大抑制就能獲得正確結果。github

b圖就是咱們以每一個網格爲中心,取到的先驗框的示例。c圖的迴歸預測找到了目標的位置信息,分類預測肯定了物體的類別。a圖表明瞭最終的結果。web

上面的兩個圖片摘自論文_SSD: Single Shot MultiBox Detector_,在論文中SSD是插入到VGG-16網絡中的。docker

經過一個表格咱們可以知道咱們從不一樣層中獲得的先驗框尺寸和數目:json

總共咱們會得到8732個先驗框。flask

MobileNet 與 SSD結合

前面說到咱們能夠很方便地將SSD插入到不一樣網絡,那麼考慮到咱們的應用場景,咱們可使用諸如MobileNet網絡來減小計算量。後端

MobileNet將卷積分爲Depthwise和Pointwise兩部分,減小了計算量,同時不會損失過多的精度。也所以在移動設備和嵌入式設備上面有很好的應用前景。更多關於MobileNet的理論信息你們能夠在網上找到,這裏不作過多講述。數組

百度AI Studio上官方開源了基於SSD的目標檢測模型的代碼,代碼很是好讀,並能夠直接在線運行,同時提供了訓練好的SSD模型。從代碼中咱們能夠看到,飛槳提供了paddle.fluid.layers.multi_box_head在不一樣Feature Map上面提取先驗框、計算迴歸座標等,paddle.fluid.layers.ssd_loss計算loss,paddle.fluid.initializer.MSRAInitializer實現以MSRA的方式初始化權重等等。這些API可以減輕咱們的工做量,方便代碼編寫。官方代碼還能夠導出,在本地Python 3和飛槳 1.7上執行

服務器部署

下面咱們來使用Paddle Serving做爲模型即服務後端。隨着飛槳框架推出1.7版本,Paddle Serving也登上了舞臺。Paddle Serving提出了模型即服務的理念,致力於簡化模型部署到服務器操做,甚至一行命令實現模型部署。有了Paddle Serving,能夠大大減輕搭建部署環境的負擔。

須要注意的是Paddle Serving目前不支持arm64架構,而且對一些依賴包的版本有要求,因此強烈建議使用Docker進行部署

首先咱們pull到Docker 鏡像:

# Run CPU Docker
docker pull hub.baidubce.com/paddlepaddle/serving:0.2.0
docker run -p 9292:9292 --name test -dit hub.baidubce.com/paddlepaddle/serving:0.2.0
docker exec -it test bash

# Run GPU Docker
nvidia-docker pull hub.baidubce.com/paddlepaddle/serving:0.2.0-gpu
nvidia-docker run -p 9292:9292 --name test -dit hub.baidubce.com/paddlepaddle/serving:0.2.0-gpu
nvidia-docker exec -it test bash

進入容器以後,因爲官方縮減了鏡像的大小,咱們須要手動安裝須要的依賴包:

python3 -m pip install paddle_serving_server sentencepiece opencv-python pillow -i https://pypi.tuna.tsinghua.edu.cn/simple

鏡像使用的系統是Centos 7,注意直接運行Python的話指向的是Python 2.7.5,你須要使用python3。(Python 2即將中止維護,pip在後續版本也可能不提供支持)。

Paddle Serving與直接利用模型不一樣的是,除了須要導出inference model之外還須要生成配置文件,定義Feed和Fetch的內容。若是你很是熟悉保存預測模型的接口,那麼這並非一件難事。從零開始訓練一個模型,並應用到Paddle Serving,你能夠參考官方的端到端從訓練到部署全流程

(https://github.com/PaddlePaddle/Serving/blob/develop/doc/TRAIN_TO_SERVICE_CN.md)。

這裏咱們能夠直接利用上文提到的AI Studio的開源項目進行提取,真正的提取代碼僅須要兩行:

import paddle_serving_client.io as serving_io
serving_io.save_model(
                    "ssd_model",
                    "ssd_client_conf",
                    {'image': img},
                    {"prediction": box},
                    inference_program)

前兩行定義了咱們的模型和客戶端配置文件保存位置,後面的兩個dict分別表示feed和fetch的內容,官方文檔的例子表示這是咱們在訓練模型時的輸入和輸出。這裏的img和box即爲輸入網絡的img和網絡輸出的box,咱們看下兩個的結構。

img:
name: "img"
type {
  type: LOD_TENSOR
  lod_tensor {
    tensor {
      data_type: FP32
      dims: -1
      dims: 3
      dims: 300
      dims: 300
    }
    lod_level: 0
  }
}
persistable: false
box:
name: "concat_0.tmp_0"
type {
  type: LOD_TENSOR
  lod_tensor {
    tensor {
      data_type: FP32
      dims: 1917
      dims: 4
    }
    lod_level: 0
  }
}
persistable: false

能夠在保存預測模型的時候保存Paddle Serving須要的配置項,或者以後從訓練的代碼中提取出img和box,進行保存。獲得Paddle Serving須要的相關文件以後,利用下面的代碼將其部署到服務器上(均在容器內進行,保證生成的模型和客戶端配置和服務器腳本在同一目錄之下):

import os
import sys
import base64
import numpy as np
import importlib
from paddle_serving_app import ImageReader
from multiprocessing import freeze_support
from paddle_serving_server.web_service import WebService


class ImageService(WebService):
    def preprocess(self, feed={}, fetch=[]):
        reader = ImageReader(image_shape=[3, 300, 300],
                             image_mean=[0.5, 0.5, 0.5],
                             image_std=[0.5, 0.5, 0.5])
        feed_batch = []
        for ins in feed:
            if "image" not in ins:
                raise ("feed data error!")
            sample = base64.b64decode(ins["image"])
            img = reader.process_image(sample)
            feed_batch.append({"image": img})
        return feed_batch, fetch


image_service = ImageService(name="image")
image_service.load_model_config("./ssd_model/")
image_service.prepare_server(
    workdir="./work", port=int(9292), device="cpu")
image_service.run_server()
image_service.run_flask()

在代碼中先對獲得的image進行了resize,而後交給模型處理。這裏使用的是CPU進行預測,須要的話能夠修改幾行代碼使其可以在GPU上預測。使用Paddle Serving並不須要安裝飛槳,因此不會對服務器形成負擔。Paddle Serving內置了數據預處理功能,所以能夠直接對圖片進行裁剪等操做。

在客戶端上,僅僅須要幾行代碼就可以從服務端獲取預測結果:

import requests
import base64
import json
import time
import os
import sys

py_version = sys.version_info[0]


def predict(image_path, server):
    if py_version == 2:
        image = base64.b64encode(open(image_path).read())
    else:
        image = base64.b64encode(open(image_path, "rb").read()).decode("utf-8")
    req = json.dumps({"feed": [{"image": image}], "fetch": ["prediction"]})
    r = requests.post(
        server, data=req, headers={"Content-Type": "application/json"}, timeout=60)
    try:
        print(r.json()["result"]["prediction"])
    except ValueError:
        print(r.text)
    return r


if __name__ == "__main__":
    server = "http://[ip]:[port]/image/prediction"
    image_list = os.listdir("./images")
    start = time.time()
    for img in image_list:
        image_file = "./images/" + img
        res = predict(image_file, server)
    end = time.time()
    print(end - start)

對圖片進行base64編碼,發送到服務端,獲取結果,很是簡潔和方便。在實際部署的過程當中,能夠在服務端進行反代和鑑權,只須要寫一箇中間件便可,這也是模型即服務帶給你們的便利之處。

咱們國內服務端的配置是單核CPU(限制使用時間和頻率),算上網絡傳輸和預測的總用時在0.39秒左右,比較快速。返回的數組第一個值表明了對應類別,第二個值表明置信度,後面的值表明座標比例,實際使用的時候須要設置閾值,放棄可信度較低的值。


移動端部署

移動端部署採用了以前開源的Real-time Object Detector,當時源碼中使用的是YOLO v3模型,這裏咱們將使其適配SSD模型。在端側部署方面咱們使用的是Paddle Lite,這是飛槳系列中的多平臺高性能深度學習預測引擎,提供了多平臺架構下的預測解決方案,還支持C++/Java/Python等語言。

從上次發文到如今,Paddle Lite已經推出了新的版本,2.3版本對不少東西進行了優化,利用手上的安卓手機(麒麟 810)進行SSD目標檢測的用時僅爲500ms。此次咱們還可以直接使用官方提供的預編譯庫進行預測,並不須要本身手動編譯一次。下載下來以後咱們會獲得和上次同樣的文件,PaddlePredictor.jar和一些so連接庫,參考以前的推送文章:如何基於Flutter和Paddle Lite實現實時目標檢測,放到相應位置便可。

由於SSD模型的輸入和YOLO v3不同,咱們須要對安卓端的Predictor.java進行修改,主要考慮輸入的尺寸問題。

// MainActivity.java L41
protected long[] inputShape = new long[]{1, 3, 300, 300};
protected float[] inputMean = new float[]{0.5f, 0.5f, 0.5f};
protected float[] inputStd = new float[]{0.5f, 0.5f, 0.5f};

// Predictor.java L214
// Set input shape
Tensor inputTensor = getInput(0);
inputTensor.resize(inputShape);

// Predictor.java L258
inputTensor.setData(inputData);

// Predictor.java L303
float rawLeft = outputTensor.getFloatData()[i + 2];
float rawTop = outputTensor.getFloatData()[i + 3];
float rawRight = outputTensor.getFloatData()[i + 4];
float rawBottom = outputTensor.getFloatData()[i + 5];

同時咱們對於描框的函數進行修改:

// main.dart L127 var ratioW = sizeRed.width / 300; var ratioH = sizeRed.height / 300;

若是在運行的時候出現了空指針錯誤,極可能你沒有升級到最新的預編譯庫,jar和so文件均須要更新。因爲上次發佈源碼的時候沒有在Gradle腳本中設置自動下載庫,因此須要手動放置預測庫。

寫在最後

從一開始熟悉怎麼去使用飛槳深度學習平臺,怎麼讓腳本跑起來,到如今開始逐步讀懂論文,瞭解模型的架構,看官方文檔,過程當中遇到了很多問題。經過分析飛槳官方圖像分類示例,查看和修改源碼,輸出調試信息,還在飛槳官方QQ羣中獲得了很多幫助,學到了不少東西,並最終完成了此次實踐。很是感謝提供幫助的朋友們。飛槳通過多輪更新,在模型訓練和部署上也變得很是簡單,相信會吸引愈來愈多的開發者使用。

參考連接:

若是您加入官方QQ羣,您將趕上大批志同道合的深度學習同窗。飛槳PaddlePaddle交流3羣:703252161。

若是您想詳細瞭解更多飛槳的相關內容,請參閱如下文檔。

官網地址:

https://www.paddlepaddle.org.cn

飛槳開源框架項目地址:

GitHub:

https://github.com/PaddlePaddle/Paddle

Gitee:

https://gitee.com/paddlepaddle/Paddle

相關文章
相關標籤/搜索