*本篇文章基於 PaddlePaddle 0.11.0、Python 2.7
前言¶
如果讀者使用過百度等的一些圖像識別的接口,比如百度的細粒度圖像識別接口,應該瞭解這個過程,省略其他的安全方面的考慮。這個接口大體的流程是,我們把圖像上傳到百度的網站上,然後服務器把這些圖像轉換成功矢量數據,最後就是拿這些數據傳給深度學習的預測接口,比如是PaddlePaddle的預測接口,獲取到預測結果,返回給客戶端。這個只是簡單的流程,真實的複雜性遠遠不止這些,但是我們只需要瞭解這些,然後去搭建屬於我們的圖像識別接口。
環境¶
- 系統是:64位 Ubuntu 16.04
- 開發語言是:Python2.7
- web框架是:flask
- 預測接口是:圖像識別
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']))
最後會獲取到這連個文件:
param.tar模型參數文件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
參考資料¶
- http://paddlepaddle.org/
- http://blog.csdn.net/u011054333/article/details/70151857