代码详解:构建一个简单的Keras+深度学习REST API

在本教程中,我们将介绍一个简单的方法来获取Keras模型并将其部署为REST API。本文所介绍的示例将作为你构建自己的深度学习API的模板/起点——你可以扩展代码,根据API端点的可伸缩性和稳定性对其进行定制。

代码详解:构建一个简单的Keras+深度学习REST API

具体而言,我们将了解:

· 如何(以及如何不)将Keras模型加载到内存中,以便有效地进行推理

· 如何使用Flask web框架为我们的API创建端点

· 如何使用我们的模型进行预测,用JSON-ify转换它们,并将结果反馈到客户端

· 如何使用cURL和Python来调用我们的Keras REST API

在本教程结束时,你将能很好地理解创建Keras REST API所需的组件(以最简单的形式)。

请随意使用本指南中提供的代码作为你自己的深度学习REST API起点。

代码详解:构建一个简单的Keras+深度学习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

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时,务必确保插入逻辑,以保证在接受请求前模型就已加载并准备好进行推理。

代码详解:构建一个简单的Keras+深度学习REST API

如何不在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

启动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端点:

代码详解:构建一个简单的Keras+深度学习REST API

这是因为在Flask URLs路由中没有设置索引/主页。

那么试着通过浏览器访问/predict端点:

代码详解:构建一个简单的Keras+深度学习REST API

出现了“方法不允许”错误。该错误是由于浏览器正在执行GET请求,但/predict只接受一个POST(我们将在下一节中演示如何执行)。

代码详解:构建一个简单的Keras+深度学习REST API

使用cURL测试Keras REST API

在测试和调试Keras REST API时,请考虑使用cURL(https://curl.haxx.se/)(无论如何,cURL都是一个值得去学习如何使用的好工具)。

下图是我们想要进行分类的图像——一只狗,更具体而言,是一只比格犬:

代码详解:构建一个简单的Keras+深度学习REST API

我们可以使用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来表示正在提交表单编码的数据。然后将image键设置为dog.jpg文件的内容。在dog.jpg之前提供@意味着我们希望cURL加载图像的内容并将数据传递给请求。

最后的终点是:http://localhost:5000/predict

请注意输入的图像是如何以99.01%的置信度被正确地分类为“比格犬”的。余下的五大预测及其相关概率也包含在Keras API的响应之内。

代码详解:构建一个简单的Keras+深度学习REST 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

· 端点不会受到严重影响

相关推荐