在本教程中,我们将介绍一种简单的方法来采用Keras模型并将其部署为REST API。
这篇文章中涵盖的示例将作为构建您自己的深度学习API的模板/起点–您将能够扩展代码并根据您的API端点的可扩展性和健壮性对其进行自定义。
具体来说,我们将学习:
如何(以及如何不)将Keras模型加载到内存中,以便可以将其有效地用于推理
如何使用Flask Web框架为我们的API创建端点
如何使用我们的模型进行预测,对它们进行JSON验证并将结果返回给客户端
如何使用cURL和Python调用我们的Keras REST API
在本教程结束时,您将对创建Keras REST API的组件(以最简单的形式)有很好的了解。
随意使用本指南中提供的代码作为您自己的深度学习REST API的起点。
注意:此处介绍的方法仅供参考。它并不意味着要达到生产级别,并且不能在重负载下进行扩展。如果您对利用消息队列和批处理的更高级的Keras REST API感兴趣,请参阅本教程。
配置您的开发环境
我们将假设Keras已在您的计算机上配置和安装。 如果没有,请确保按照官方安装说明安装Keras。
从那里,我们需要安装Flask(及其相关的依赖项),一个Python Web框架,以便我们可以构建API端点。 我们还将需要请求,以便我们也可以使用我们的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并应用您可能需要的任何缩放/规范化。
预测:API的实际端点,它将对来自请求的传入数据进行分类并将结果返回给客户端。
可以在此处找到本教程的完整代码。
# import the necessary packages
from keras.applications import ResNet50
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
# initialize our Flask application and the Keras model
app = 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的输入空间尺寸)
通过均值减法和缩放对数组进行预处理
同样,您应该在将输入数据传递通过模型之前,根据所需的任何预处理,缩放和/或规范化来修改此功能。
现在我们可以定义预测函数了-该方法处理对/
@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方法是POST(使我们能够将任意数据发送到端点,包括图像,JSON,编码数据等)。
在POST期间,图像已传递到files属性中
然后,我们获取传入的数据并:
以PIL格式读取
预处理
通过我们的网络传递
遍历结果并将其分别添加到data [“ predictions”]列表中
以JSON格式将响应返回给客户端
如果您使用的是非图像数据,则应删除request.files代码,然后自己解析原始输入数据,或者利用request.get_json()自动将输入数据解析为Python字典/对象。此外,请考虑阅读以下教程,其中讨论了Flask请求对象的基础。
现在剩下要做的就是启动我们的服务:
# if this is the main thread of execution first load the model and
# then start the server
if __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 API时,请确保插入逻辑以确保在接受请求之前已加载模型并准备进行推理。
如何不在REST API中加载Keras模型
您可能会想将模型加载到预测函数中,如下所示:
...
# 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,则将模型的N个副本存储在RAM中很容易耗尽系统内存。
为此,除非有非常具体的合理理由,否则请避免为每个新的传入请求加载新的模型实例。
警告:我们假设您使用的是默认的单线程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 URL路由中没有设置索引/首页。
而是尝试通过浏览器访问/ predict端点:
并且您将看到“不允许使用方法”错误。 此错误是由于您的浏览器正在执行GET请求,而/ predict仅接受POST(我们将在下一部分中演示如何执行)。
使用cURL测试Keras REST API
在测试和调试Keras REST API时,请考虑使用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
}
-X标志和POST值指示我们正在执行POST请求。
我们提供-F image=@dog.jpg来表示我们正在提交表单编码的数据。 然后将图像键设置为dog.jpg文件的内容。 在dog.jpg之前提供@表示我们希望cURL加载图像的内容并将数据传递给请求。
最后,我们有了端点:http:// localhost:5000 / predict
请注意,如何以99.01%的置信度将输入图像正确分类为“ beagle”。 其余的前5个预测及其相关概率,也包括在我们的Keras API的响应中。
以编程方式使用Keras REST API
您极有可能将数据提交到Keras REST API,然后以某种方式使用返回的预测-这要求我们以编程方式处理服务器的响应。
这是一个使用请求Python包的简单过程:
# import the necessary packages
import requests
# initialize the Keras REST API endpoint URL along with the input
# image path
KERAS_REST_API_URL = "http://localhost:5000/predict"
IMAGE_PATH = "dog.jpg"
# load the input image and construct the payload for the request
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}
# submit the request
r = requests.post(KERAS_REST_API_URL, files=payload).json()
# ensure the request was successful
if 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 failed
else:
print("Request failed")
KERAS_REST_API_URL指定我们的端点,而IMAGE_PATH是驻留在磁盘上的输入映像的路径。
使用IMAGE_PATH,我们加载图像,然后为请求构造有效负载。
给定有效负载后,我们可以使用对request.post的调用将数据发布到我们的端点。 将.json()附加到调用的末尾可指示请求:
服务器的响应应为JSON
我们希望为我们自动解析和反序列化JSON对象
获得请求r的输出后,我们可以检查分类是否成功(或不成功),然后遍历r [“ predictions”]。
要运行execute simple_request.py,请首先确保run_keras_server.py(即Flask Web服务器)当前正在运行。 从那里,在单独的shell中执行以下命令:
$ 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获得了模型的预测。
在这篇文章中,您学习了如何:
使用Flask Web框架将Keras模型包装为REST API
利用cURL将数据发送到API
使用Python和请求包将数据发送到端点并使用结果
他可以在这里找到本教程中介绍的代码,并可用作您自己的Keras REST API的模板-可以随意修改它。
请记住,本文中的代码仅供参考。这并不意味着要达到生产级别并且能够在重负载和大量传入请求下进行扩展。
在以下情况下最好使用此方法:
您需要为Keras深度学习模型快速建立REST API
您的端点不会受到严重打击
如果您对利用消息队列和批处理的更高级的Keras REST API感兴趣,请参阅此博客文章。
如果您对此帖子有任何疑问或评论,请通过PyImageSearch(今天的帖子的作者)与Adrian联系。有关未来主题的建议,请在Twitter上找到Francois。