代码详解:构建一个简单的Keras+深度学习REST API
在本教程中,我们将介绍一个简单的方法来获取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
· 端点不会受到严重影响