
掌握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獨立于一個名為run_keras_server.py的文件中。為了簡單起見,我們將安裝保存在一個文件中——安裝啟用也可以很容易地模塊化。
在 run_keras_server.py中,你會發現三個函數,即:
# 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
這個函數:
·此外,在通過模型傳遞輸入數據之前,應該根據某一預處理、縮放或標準化來修改這個函數。
現在可以定義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)
數據字典用于存儲希望反饋到客戶端的所有數據。現在,它包含一個布爾值,用來表示預測是否成功,還將使用此字典來存儲對傳入數據進行的所有預測的結果。
為了接收輸入的數據,我們會檢查是否:
如果使用的是非圖像數據,則應刪除該請求文件代碼,并解析原始輸入數據,或者使用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服務之前也沒有做好推理準備,就可能會遇到以下情況:
在構建自己的Keras REST APIs時,務必確保插入邏輯,以保證在接受請求前模型就已加載并準備好進行推理。
你可能想在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,則應該考慮使管道更具可擴展性。
打開終端,執行:
$ 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(我們將在下一節中演示如何執行)。
在測試和調試Keras REST API時,請考慮使用cURL(https://curl.haxx.se/)(無論如何,cURL都是一個值得去學習如何使用的好工具)。
下圖是我們想要進行分類的圖像——一只狗,更具體而言,是一只比格犬:
我們可以使用curl將該圖像傳遞給API,并找出ResNet認為該圖像包含的內容:
$ curl -X POST -F image=@dog.jpg '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}
我們提供-F image=@dog.jpg來表示正在提交表單編碼的數據。然后將image鍵設置為dog.jpg文件的內容。在dog.jpg之前提供@意味著我們希望cURL加載圖像的內容并將數據傳遞給請求。
最后的終點是:http://localhost:5000/predict
請注意輸入的圖像是如何以99.01%的置信度被正確地分類為“比格犬”的。余下的五大預測及其相關概率也包含在Keras 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")
使用IMAGE_PATH加載圖像,然后將payload構建到請求中??紤]到有效載荷,我們可以使用requests.post調用將數據發布到端點。在指示requests調用的末尾附加.json() :
一旦有了請求r的輸出,就可以檢查分類是否成功,然后循環r[“predictions”]。
要運行指令simple_request.py,首先要確保run_keras_server.py(即 Flask web服務器)正在運行。然后在一個單獨的框架中執行下列命令:
$ python simple_request.py
beagle: 0.9901
Walker_hound: 0.0024
pot: 0.0014
Brittany_spaniel: 0.0013
bluetick: 0.0011
我們成功地調用了Keras REST API,并通過Python得到了模型的預測。
注意,本文中的代碼僅用于指導,而非生產級別,也不能在高負載和大量傳入請求的情況下進行擴展。
該方法最好在以下情況下使用:
本文章轉載微信公眾號@海豚數智科學實驗室