在本教程中,我們將介紹一個簡單的方法來獲取Keras模型並將其部署為REST API。本文所介紹的示例將作為你構建自己的深度學習API的模板/起點——你可以擴展代碼,根據API端點的可伸縮性和穩定性對其進行定製。
具體而言,我們將瞭解:
· 如何(以及如何不)將Keras模型加載到內存中,以便有效地進行推理
· 如何使用Flask web框架為我們的API創建端點
· 如何使用我們的模型進行預測,用JSON-ify轉換它們,並將結果反饋到客戶端
· 如何使用cURL和Python來調用我們的Keras REST API
在本教程結束時,你將能很好地理解創建Keras REST API所需的組件(以最簡單的形式)。
請隨意使用本指南中提供的代碼作為你自己的深度學習REST API起點。
配置開發環境
假設Keras已經配置並安裝在你的機器上。如果沒有,請確保使用官方安裝說明安裝Keras(https://keras.io/#installation)。
然後,需要安裝Flask (http://flask.pocoo.org/)(及其相關的依賴項),一個Python web框架,這樣就可以構建API端點了。還需要請求(http://docs.python-requests.org/en/master/),這樣就可以使用API了。
有關的pip安裝命令如下:
$ pip install flask gevent requests pillow
構建你的Keras REST API
Keras REST API獨立於一個名為run_keras_server.py的文件中。為了簡單起見,我們將安裝保存在一個文件中——安裝啟用也可以很容易地模塊化。
在 run_keras_server.py中,你會發現三個函數,即:
· load_model:用於加載訓練好的Keras模型,併為推理做準備。
· prepare_image:這個函數在通過我們的網絡進行預測之前對輸入圖像進行預處理。如果你沒有使用圖像數據,則可能需要考慮將名稱更改為更通用的prepare_datapoint,並應用一些可能需要的縮放/標準化。
· predict:API的實際端點可以將請求中的輸入數據分類,並將結果反饋給客戶端。
# import the necessary packagesfrom keras.applications import ResNet50from keras.preprocessing.image import img_to_arrayfrom keras.applications import imagenet_utilsfrom PIL import Imageimport numpy as npimport flaskimport io
# initialize our Flask application and the Keras modelapp = flask.Flask(__name__)model = None
第一個代碼片段處理導入了所需的程序包,並且對Flask應用程序和模型進行了初始化。
在此,我們定義load_model函數:
def load_model():
# load the pre-trained Keras model (here we are using a model
# pre-trained on ImageNet and provided by Keras, but you can
# substitute in your own networks just as easily)
global model
model = ResNet50(weights="imagenet")
顧名思義,這個方法負責將我們的架構實例化,並從磁盤加載權重。
為了簡單起見,將使用在ImageNet數據集上預先訓練過的ResNet50架構。
如果你正在使用自定義模型,則需要修改此函數以從磁盤加載架構+權重。
在對任何來自客戶端的數據進行預測之前,首先需要準備並預處理數據:
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)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)
# return the processed image
return image
這個函數:
· 接受輸入圖像
· 將模式轉換為RGB(如果需要)
· 將大小調整為224x224像素(ResNet的輸入空間維度)
· 通過平均減法數組和縮放對陣列進行預處理
此外,在通過模型傳遞輸入數據之前,應該根據某一預處理、縮放或標準化來修改這個函數。
現在可以定義predict函數了——該方法會處理對/predict端點的任何請求:
@app.route("/predict", methods=["POST"])def predict():
# initialize the data dictionary that will be returned from the
# view
data = {"success": False}
# ensure an image was properly uploaded to our endpoint
if flask.request.method == "POST":
if flask.request.files.get("image"):
# read the image in PIL format
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))
# preprocess the image and prepare it for classification
image = prepare_image(image, target=(224, 224))
# classify the input image and then initialize the list
# of predictions to return to the client
preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)
data["predictions"] = []
# loop over the results and add them to the list of
# returned predictions
for (imagenetID, label, prob) in results[0]:
r = {"label": label, "probability": float(prob)}
data["predictions"].append(r)
# indicate that the request was a success
data["success"] = True
# return the data dictionary as a JSON response
return flask.jsonify(data)
數據字典用於存儲希望反饋到客戶端的所有數據。現在,它包含一個布爾值,用來表示預測是否成功——還將使用此字典來存儲對傳入數據進行的所有預測的結果。
為了接收輸入的數據,我們會檢查是否:
· 請求方法是POST(支持我們將任意數據發送到端點,包括圖像、JOSN、編碼數據等)
· 在POST期間,圖像被傳遞到“文件”屬性中
然後,將接收到的數據:
· 以PIL格式讀取
· 進行預處理
· 將其通過我們的網絡
· 循環結果,並將其單獨添加到data["predictions"]列表中
· 以JSON格式將響應反饋給客戶端
如果使用的是非圖像數據,則應刪除該請求文件代碼,並解析原始輸入數據,或者使用request.get_json()將輸入數據自動解析為Python字典/對象。
現在只需啟動我們的服務:
# if this is the main thread of execution first load the model and# then start the serverif __name__ == "__main__":
print(("* Loading Keras model and Flask starting server..."
"please wait until server has fully started"))
load_model()
app.run()
首先調用load_model從磁盤加載Keras模型。
對load_model的調用是一個阻止操作——阻止web服務在模型完全加載之前啟動。如果未能確保模型完全載入內存中,在啟動web服務之前也沒有做好推理準備,就可能會遇到以下情況:
1. 請求被髮送到服務器
2.服務器接受請求,預處理數據,然後嘗試將其傳遞到模型中
3.由於模型還未完全加載,腳本將會出錯!
在構建自己的Keras REST APIs時,務必確保插入邏輯,以保證在接受請求前模型就已加載並準備好進行推理。
如何不在REST API中加載Keras模型
你可能想在predict函數中加載模型,如下所示:
...
# ensure an image was properly uploaded to our endpoint
if request.method == "POST":
if request.files.get("image"):
# read the image in PIL format
image = request.files["image"].read()
image = Image.open(io.BytesIO(image))
# preprocess the image and prepare it for classification
image = prepare_image(image, target=(224, 224))
# load the model
model = ResNet50(weights="imagenet")
# classify the input image and then initialize the list
# of predictions to return to the client
preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)
data["predictions"] = []...
該代碼意味著每次有新請求時都將加載模型。這太低效了,甚至會導致系統內存耗盡。
如果嘗試運行上面的代碼,你會注意到API將運行得特別慢(尤其是在模型很大的情況下)——這是由於為每個新請求加載模型的I/O和CPU操作開銷太大所致。
為了瞭解服務器內存是如何因此輕易崩潰的,假設服務器同時收到N個傳入請求。同樣,這意味著將有N個模型同時加載到內存中。同時,如果模型較大(如ResNet),那麼存儲在RAM中的N個模型副本很容易就會耗盡系統內存。
所以,除非你有一個非這樣做不可的理由,否則請儘量避免為每個新的傳入請求加載一個新的模型實例。
注意:這裡我們假定使用的是默認的單線程Flask服務器。如果將其部署到多線程服務器,那麼即使使用本文前面討論的“更正確”的方法,內存中仍會加載多個模型。如果你打算使用專用服務器,如Apache或nginx,則應該考慮使管道更具可擴展性。
啟動你的Keras Rest API
啟動Keras REST API服務很簡單。
打開終端,執行:
$ python run_keras_server.py
Using TensorFlow backend.
* Loading Keras model and Flask starting server...please wait until server has fully started
...
* Running on http://127.0.0.1:5000
從輸出中可以看到,首先加載模型,然後可以啟動Flask服務器。
現在可以通過http://127.0.0.1:5000訪問服務器。
但是,如果將IP地址+端口複製粘貼到瀏覽器中,會出現以下情況:
這是因為在Flask URLs路由中沒有設置索引/主頁。
那麼試著通過瀏覽器訪問/predict端點:
這是因為在Flask URLs路由中沒有設置索引/主頁。
那麼試著通過瀏覽器訪問/predict端點:
出現了“方法不允許”錯誤。該錯誤是由於瀏覽器正在執行GET請求,但/predict只接受一個POST(我們將在下一節中演示如何執行)。
使用cURL測試Keras REST API
在測試和調試Keras REST API時,請考慮使用cURL(https://curl.haxx.se/)(無論如何,cURL都是一個值得去學習如何使用的好工具)。
下圖是我們想要進行分類的圖像——一隻狗,更具體而言,是一隻比格犬:
我們可以使用curl將該圖像傳遞給API,並找出ResNet認為該圖像包含的內容:
$ curl -X POST -F [email protected] 'http://localhost:5000/predict'{
"predictions": [
{
"label": "beagle",
"probability": 0.9901360869407654
},
{
"label": "Walker_hound",
"probability": 0.002396771451458335
},
{
"label": "pot",
"probability": 0.0013951235450804234
},
{
"label": "Brittany_spaniel",
"probability": 0.001283277408219874
},
{
"label": "bluetick",
"probability": 0.0010894243605434895
}
],
"success": true}
-x標誌和POST值表示我們正在執行POST請求。
我們提供-F [email protected]來表示正在提交表單編碼的數據。然後將image鍵設置為dog.jpg文件的內容。在dog.jpg之前提供@意味著我們希望cURL加載圖像的內容並將數據傳遞給請求。
最後的終點是:http://localhost:5000/predict
請注意輸入的圖像是如何以99.01%的置信度被正確地分類為“比格犬”的。餘下的五大預測及其相關概率也包含在Keras API的響應之內。
以編程方式使用Keras REST API
你很可能會向Keras REST API提交數據,然後以某種方式利用反饋的預測——這就要求我們以編程的方式處理來自服務器的響應。
這是一個使用requests Python程序包的簡單過程(http://docs.python-requests.org/en/master/):
# import the necessary packagesimport requests
# initialize the Keras REST API endpoint URL along with the input# image pathKERAS_REST_API_URL = "http://localhost:5000/predict"IMAGE_PATH = "dog.jpg"
# load the input image and construct the payload for the requestimage = open(IMAGE_PATH, "rb").read()payload = {"image": image}
# submit the requestr = requests.post(KERAS_REST_API_URL, files=payload).json()
# ensure the request was successfulif r["success"]:
# loop over the predictions and display them
for (i, result) in enumerate(r["predictions"]):
print("{}. {}: {:.4f}".format(i + 1, result["label"],
result["probability"]))
# otherwise, the request failedelse:
print("Request failed")
KERAS_REST_API_URL指定端點,而IMAGE_PATH是在磁盤上輸入圖像的路徑。
使用IMAGE_PATH加載圖像,然後將payload構建到請求中。
考慮到有效載荷,我們可以使用requests.post調用將數據發佈到端點。在指示requests調用的末尾附加.json() :
1.服務器的響應應該是JSON格式的
2.希望JSON對象能夠自動解析和反序列化
一旦有了請求r的輸出,就可以檢查分類是否成功,然後循環r["predictions"]。
要運行指令simple_request.py,首先要確保run_keras_server.py(即 Flask web服務器)正在運行。然後在一個單獨的框架中執行下列命令:
$ python simple_request.py
1. beagle: 0.9901
2. Walker_hound: 0.0024
3. pot: 0.0014
4. Brittany_spaniel: 0.0013
5. bluetick: 0.0011
我們成功地調用了Keras REST API,並通過Python得到了模型的預測。
注意,本文中的代碼僅用於指導,而非生產級別,也不能在高負載和大量傳入請求的情況下進行擴展。
該方法最好在以下情況下使用:
· 為自己的Keras深度學習模型快速建立一個REST API
· 端點不會受到嚴重影響
閱讀更多 讀芯術 的文章