*本篇文章基於 PaddlePaddle 0.11.0、Python 2.7

前言


如果讀者使用過百度等的一些圖像識別的接口,比如百度的細粒度圖像識別接口,應該瞭解這個過程,省略其他的安全方面的考慮。這個接口大體的流程是,我們把圖像上傳到百度的網站上,然後服務器把這些圖像轉換成功矢量數據,最後就是拿這些數據傳給深度學習的預測接口,比如是PaddlePaddle的預測接口,獲取到預測結果,返回給客戶端。這個只是簡單的流程,真實的複雜性遠遠不止這些,但是我們只需要瞭解這些,然後去搭建屬於我們的圖像識別接口。

環境


  1. 系統是:64位 Ubuntu 16.04
  2. 開發語言是:Python2.7
  3. web框架是:flask
  4. 預測接口是:圖像識別

flask的熟悉


安裝flask

安裝flask很簡單,只要一條命令就可以了:

pip install flask

同時我們也使用到flask_cors,所以我們也要安裝這個庫

pip install flask_cors

主要安裝的是這兩個庫,如果還缺少哪些庫,可以使用pip命令安裝,*代表讀者缺少的庫:

pip install *

測試flask框架

我們來編寫一個簡單的程序,來測試我們安裝的框架,使用@app.route('/')是指定訪問的路徑:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Welcome to PaddlePaddle'

if __name__ == '__main__':
    app.run()

然後在瀏覽器上輸入以下地址:

http://127.0.0.1:5000

然後瀏覽器會返回之前寫好的字符串:

Welcome to PaddlePaddle

文件上傳

我們來編寫一個上傳文件的程序,這個程序比上面複雜了一點點,我們要留意這些:
secure_filename是爲了能夠正常獲取到上傳文件的文件名
flask_cors可以實現跨越訪問
methods=['POST']指定該路徑只能使用POST方法訪問
f = request.files['img']讀取表單名稱爲img的文件
f.save(img_path)在指定路徑保存該文件

from werkzeug.utils import secure_filename
from flask import Flask, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route('/upload', methods=['POST'])
def upload_file():
    f = request.files['img']
    img_path = './data/' + secure_filename(f.filename)
    print img_path
    f.save(img_path)
    return 'success'

然後我們編寫一個HTML的網頁index.html,方便我們測試這個接口:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>預測圖像</title>
</head>
<body>
<form action="http://127.0.0.1:5000/upload" enctype="multipart/form-data" method="post">
    選擇要預測的圖像:<input type="file" name="img"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

最後我們在瀏覽器上打開着網頁,選擇要上傳的文件,點擊提交,如果返回的是success,那代表我們已經上傳成功了,我們可以去到保存的位置查看文件是否存在。

使用PaddlePaddle預測


獲取預測模型

我們這次使用的是第二章的MNIST手寫數字識別的例子,因爲這個訓練比較快,可以更快的獲取到我們需要的預測模型,代碼也是類似的,詳細可以讀到第二章的代碼,只是添加了生成拓撲的功能

# 保存預測拓撲圖
inference_topology = paddle.topology.Topology(layers=out)
with open("../models/inference_topology.pkl", 'wb') as f:
    inference_topology.serialize_for_inference(f)

同時把測試部分去掉了,這樣訓練起來速度會更快:

result = trainer.test(reader=paddle.batch(paddle.dataset.mnist.test(), batch_size=128))
print "\nTest with Pass %d, Cost %f, %s\n" % (event.pass_id, result.cost, result.metrics)
lists.append((event.pass_id, result.cost, result.metrics['classification_error_evaluator']))

最後會獲取到這連個文件:

  1. param.tar模型參數文件
  2. inference_topology.pkl預測拓撲文件

把PaddlePaddle部署到服務器

首先我們要創建一個隊列,我們要在隊列中使用PaddlePaddle進行預測

app = Flask(__name__)
CORS(app)
# 創建主隊列
sendQ = Queue()

同樣我們要編寫一個上傳文件的接口:

@app.route('/infer', methods=['POST'])
def infer():
    # 獲取上傳的圖像
    f = request.files['img']
    img_path = './data/' + secure_filename(f.filename)
    print img_path
    # 保存上傳的圖像
    f.save(img_path)
    # 把讀取上傳圖像轉成矢量
    data = []
    data.append((load_image(img_path),))
    # print '預測數據爲:', data

    # 創建子隊列
    recv_queue = Queue()
    # 使用主隊列發送數據和子隊列
    sendQ.put((data, recv_queue))
    # 獲取子隊列的結果
    success, resp = recv_queue.get()
    if success:
        # 如果成功返回預測結果
        return successResp(resp)
    else:
        # 如果失敗返回錯誤信息
        return errorResp(resp)

對於上傳文件和保存文件的介紹在上一部分已經講,接下來就是把圖像文件讀取讀取成矢量數據:

data = []
data.append((load_image(img_path),))

load_image()這函數在之前使用的是一樣的

def load_image(img_path):
    im = Image.open(img_path).convert('L')
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).astype(np.float32).flatten()
    im = im / 255.0
    return im

然後就是使用主隊列發送圖像的數據和子隊列。使用子隊列的作用是爲了在PaddlePaddle的預測線程中把預測結果發送回來。

# 創建子隊列
recv_queue = Queue()
# 使用主隊列發送數據和子隊列
sendQ.put((data, recv_queue))

下面就是我們的PaddlePaddle預測線程

  • 因爲PaddlePaddle的初始化和加載模型只能執行一次,所以要放在循環的外面。
  • 在循環中,要從主隊列中獲取圖像數據和子隊列
  • 使用圖像數據預測並獲得結果
  • 使用recv_queue把預測結果返回
# 創建一個PaddlePaddle的預測線程
def worker():
    # 初始化PaddlePaddle
    paddle.init(use_gpu=False, trainer_count=2)

    # 加載模型參數和預測的拓撲生成一個預測器
    with open('../models/param.tar', 'r') as param_f:
        params = paddle.parameters.Parameters.from_tar(param_f)

    # 獲取分類器
    out = convolutional_neural_network()

    while True:
        # 獲取數據和子隊列
        data, recv_queue = sendQ.get()
        try:
            # 獲取預測結果
            result = paddle.infer(output_layer=out,
                                  parameters=params,
                                  input=data)

            # 處理預測結果
            lab = np.argsort(-result)
            print lab
            # 返回概率最大的值和其對應的概率值
            result = '{"result":%d,"possibility":%f}' % (lab[0][0], result[0][(lab[0][0])])
            print result
            recv_queue.put((True, result))
        except:
            # 通過子隊列發送異常信息
            trace = traceback.format_exc()
            print trace
            recv_queue.put((False, trace))
            continue

回到infer()函數中,剛纔已經是把數據發送出去了,並有預測結果發送回來,我們這裏就接收預測數據,並把預測結果返回給客戶端。

# 獲取子隊列的結果
success, resp = recv_queue.get()
if success:
    # 如果成功返回預測結果
    return successResp(resp)
else:
    # 如果失敗返回錯誤信息
    return errorResp(resp)

最後的兩個函數是格式化返回的數據,生成的是一個json格式的數據。

# 錯誤請求
def errorResp(msg):
    return jsonify(code=-1, message=msg)

# 成功請求
def successResp(data):
    return jsonify(code=0, message="success", data=data)

最後就是啓動我們的預測線程和服務了:

if __name__ == '__main__':
    t = threading.Thread(target=worker)
    t.daemon = True
    t.start()
    # 已經把端口改成80
    app.run(host='0.0.0.0', port=80, threaded=True)

同樣在瀏覽器上打開剛纔創建的HTML網頁index.html,要注意的是提交的action改成http://127.0.0.1/infer,選擇要預測的圖像,點擊提交,便可以獲取預測結果

{
  "code": 0, 
  "data": "{\"result\":3,\"possibility\":1.000000}", 
  "message": "success"
}


上一章:《我的PaddlePaddle學習之路》筆記十二——可視化工具VisualDL的使用
下一章:《我的PaddlePaddle學習之路》筆記十四——把PaddlePaddle遷移到Android設備上


項目代碼


GitHub地址:https://github.com/yeyupiaoling/LearnPaddle

參考資料


  1. http://paddlepaddle.org/
  2. http://blog.csdn.net/u011054333/article/details/70151857
小夜