干貨蹦漠!生產(chǎn)環(huán)境中使用Keras椭员、Redis、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

<article class="syl-article-base tt-article-content syl-page-article syl-device-pc" style="box-sizing: border-box; display: block; padding: 0px; text-align: justify; overflow-wrap: break-word; word-break: break-word; overflow: hidden; hyphens: auto; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; line-height: 1.667; font-size: 18px; margin-bottom: 20px;">

今天我們演示如何在生產(chǎn)環(huán)境中使用Keras笛园、Redis隘击、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

項目結(jié)構(gòu)

keras-complete-rest-api
├── helpers.py
├── jemma.png
├── keras_rest_api_app.wsgi
├── run_model_server.py
├── run_web_server.py
├── settings.py
├── simple_request.py
└── stress_test.py

文件解釋:

  • run_web_server.py 包含我們所有的 Flask Web 服務(wù)器代碼——Apache 將在啟動我們的深度學(xué)習(xí) Web 應(yīng)用程序時加載它侍芝。

  • run_model_server.py 將:

  • 從磁盤加載我們的 Keras 模型

  • 不斷輪詢Redis尋找新圖像進(jìn)行分類

  • 對圖像進(jìn)行分類(批量處理以提高效率)

  • 將推理結(jié)果寫回 Redis,以便它們可以通過 Flask 返回給客戶端埋同。

  • settings.py 包含我們深度學(xué)習(xí)生產(chǎn)服務(wù)的所有基于 Python 的設(shè)置州叠,例如 Redis 主機(jī)/端口信息、圖像分類設(shè)置凶赁、圖像隊列名稱等咧栗。

  • helpers.py 包含 run_web_server.py 和 run_model_server.py 都將使用的實用函數(shù)(即 base64 編碼)。

  • keras_rest_api_app.wsgi 包含我們的 WSGI 設(shè)置虱肄,因此我們可以從我們的 Apache 服務(wù)器為 Flask 應(yīng)用程序提供服務(wù)致板。

  • simple_request.py 可用于以編程方式使用我們的深度學(xué)習(xí) API 服務(wù)的結(jié)果。

  • jemma.png 是我家小獵犬的照片咏窿。在調(diào)用 REST API 以驗證它確實有效時斟或,我們將使用她作為示例圖像。

  • 最后翰灾,我們將使用 stress_test.py 來給我們的服務(wù)器施加壓力并在整個過程中測量圖像分類缕粹。

我們在 Flask 服務(wù)器上有一個端點 /predict 。此方法位于 run_web_server.py 中纸淮,將根據(jù)需要計算輸入圖像的分類。圖像預(yù)處理也在 run_web_server.py 中處理亚享。

為了使我們的服務(wù)器做好生產(chǎn)準(zhǔn)備咽块,我從上周的單個腳本中取出了分類過程函數(shù)并將其放置在 run_model_server.py 中。這個腳本非常重要欺税,因為它將加載我們的 Keras 模型并從 Redis 中的圖像隊列中抓取圖像進(jìn)行分類侈沪。結(jié)果被寫回 Redis(/predict 端點和 run_web_server.py 中的相應(yīng)函數(shù)監(jiān)視 Redis 以將結(jié)果發(fā)送回客戶端)。

但是除非我們知道深度學(xué)習(xí) REST API 服務(wù)器的功能和局限性晚凿,否則它有什么好處呢亭罪?

在 stress_test.py 中,我們測試我們的服務(wù)器歼秽。我們將通過啟動 500 個并發(fā)線程來實現(xiàn)這一點应役,這些線程將我們的圖像發(fā)送到服務(wù)器進(jìn)行并行分類。我建議在服務(wù)器 localhost 上運行它以啟動燥筷,然后從異地客戶端運行它箩祥。

構(gòu)建我們的深度學(xué)習(xí)網(wǎng)絡(luò)應(yīng)用

干貨!生產(chǎn)環(huán)境中使用Keras肆氓、Redis袍祖、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

圖 1:使用 Python、Keras谢揪、Redis 和 Flask 構(gòu)建的深度學(xué)習(xí) REST API 服務(wù)器的數(shù)據(jù)流圖蕉陋。

這個項目中使用的幾乎每一行代碼都來自我們之前關(guān)于構(gòu)建可擴(kuò)展深度學(xué)習(xí) REST API 的文章——唯一的變化是我們將一些代碼移動到單獨的文件中捐凭,以促進(jìn)生產(chǎn)環(huán)境中的可擴(kuò)展性。

設(shè)置和配置

# initialize Redis connection settings
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
# initialize constants used to control image spatial dimensions and
# data type
IMAGE_WIDTH = 224
IMAGE_HEIGHT = 224
IMAGE_CHANS = 3
IMAGE_DTYPE = "float32"
# initialize constants used for server queuing
IMAGE_QUEUE = "image_queue"
BATCH_SIZE = 32
SERVER_SLEEP = 0.25
CLIENT_SLEEP = 0.25

在 settings.py 中凳鬓,您將能夠更改服務(wù)器連接柑营、圖像尺寸 + 數(shù)據(jù)類型和服務(wù)器隊列的參數(shù)。

# import the necessary packages
import numpy as np
import base64
import sys
def base64_encode_image(a):
    # base64 encode the input NumPy array
    return base64.b64encode(a).decode("utf-8")
def base64_decode_image(a, dtype, shape):
    # if this is Python 3, we need the extra step of encoding the
    # serialized NumPy string as a byte object
    if sys.version_info.major == 3:
        a = bytes(a, encoding="utf-8")
    # convert the string to a NumPy array using the supplied data
    # type and target shape
    a = np.frombuffer(base64.decodestring(a), dtype=dtype)
    a = a.reshape(shape)
    # return the decoded image
    return a

helpers.py 文件包含兩個函數(shù)——一個用于 base64 編碼村视,另一個用于解碼官套。

編碼是必要的,以便我們可以在 Redis 中序列化 + 存儲我們的圖像蚁孔。 同樣奶赔,解碼是必要的,以便我們可以在預(yù)處理之前將圖像反序列化為 NumPy 數(shù)組格式杠氢。

深度學(xué)習(xí)網(wǎng)絡(luò)服務(wù)器

# import the necessary packages
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.applications.resnet50 import preprocess_input
from PIL import Image
import numpy as np
import settings
import helpers
import flask
import redis
import uuid
import time
import json
import io
# initialize our Flask application and Redis server
app = flask.Flask(__name__)
db = redis.StrictRedis(host=settings.REDIS_HOST,
    port=settings.REDIS_PORT, db=settings.REDIS_DB)
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 = preprocess_input(image)
    # return the processed image
    return image
@app.route("/")
def homepage():
    return "Welcome to the PyImageSearch Keras REST API!"
@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 and prepare it for
            # classification
            image = flask.request.files["image"].read()
            image = Image.open(io.BytesIO(image))
            image = prepare_image(image,
                (settings.IMAGE_WIDTH, settings.IMAGE_HEIGHT))
            # ensure our NumPy array is C-contiguous as well,
            # otherwise we won't be able to serialize it
            image = image.copy(order="C")
            # generate an ID for the classification then add the
            # classification ID + image to the queue
            k = str(uuid.uuid4())
            image = helpers.base64_encode_image(image)
            d = {"id": k, "image": image}
            db.rpush(settings.IMAGE_QUEUE, json.dumps(d))
            # keep looping until our model server returns the output
            # predictions
            while True:
                # attempt to grab the output predictions
                output = db.get(k)
                # check to see if our model has classified the input
                # image
                if output is not None:
                    # add the output predictions to our data
                    # dictionary so we can return it to the client
                    output = output.decode("utf-8")
                    data["predictions"] = json.loads(output)
                    # delete the result from the database and break
                    # from the polling loop
                    db.delete(k)
                    break
                # sleep for a small amount to give the model a chance
                # to classify the input image
                time.sleep(settings.CLIENT_SLEEP)
            # indicate that the request was a success
            data["success"] = True
    # return the data dictionary as a JSON response
    return flask.jsonify(data)
# for debugging purposes, it's helpful to start the Flask testing
# server (don't use this for production
if __name__ == "__main__":
    print("* Starting web service...")
    app.run()

在 run_web_server.py 中站刑,您將看到 predict ,該函數(shù)與我們的 REST API /predict 端點相關(guān)聯(lián)鼻百。

predict 函數(shù)將編碼的圖像推送到 Redis 隊列中绞旅,然后不斷循環(huán)/輪詢,直到它從模型服務(wù)器獲取預(yù)測數(shù)據(jù)温艇。 然后我們對數(shù)據(jù)進(jìn)行 JSON 編碼并指示 Flask 將數(shù)據(jù)發(fā)送回客戶端因悲。

深度學(xué)習(xí)模型服務(wù)器

# import the necessary packages
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.applications.resnet50 import decode_predictions
import numpy as np
import settings
import helpers
import redis
import time
import json
# connect to Redis server
db = redis.StrictRedis(host=settings.REDIS_HOST,
    port=settings.REDIS_PORT, db=settings.REDIS_DB)
def classify_process():
    # 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)
    print("* Loading model...")
    model = ResNet50(weights="imagenet")
    print("* Model loaded")
    # continually pool for new images to classify
    while True:
        # attempt to grab a batch of images from the database, then
        # initialize the image IDs and batch of images themselves
        queue = db.lrange(settings.IMAGE_QUEUE, 0,
            settings.BATCH_SIZE - 1)
        imageIDs = []
        batch = None
        # loop over the queue
        for q in queue:
            # deserialize the object and obtain the input image
            q = json.loads(q.decode("utf-8"))
            image = helpers.base64_decode_image(q["image"],
                settings.IMAGE_DTYPE,
                (1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH,
                    settings.IMAGE_CHANS))
            # check to see if the batch list is None
            if batch is None:
                batch = image
            # otherwise, stack the data
            else:
                batch = np.vstack([batch, image])
            # update the list of image IDs
            imageIDs.append(q["id"])
        # check to see if we need to process the batch
        if len(imageIDs) > 0:
            # classify the batch
            print("* Batch size: {}".format(batch.shape))
            preds = model.predict(batch)
            results = decode_predictions(preds)
            # loop over the image IDs and their corresponding set of
            # results from our model
            for (imageID, resultSet) in zip(imageIDs, results):
                # initialize the list of output predictions
                output = []
                # loop over the results and add them to the list of
                # output predictions
                for (imagenetID, label, prob) in resultSet:
                    r = {"label": label, "probability": float(prob)}
                    output.append(r)
                # store the output predictions in the database, using
                # the image ID as the key so we can fetch the results
                db.set(imageID, json.dumps(output))
            # remove the set of images from our queue
            db.ltrim(settings.IMAGE_QUEUE, len(imageIDs), -1)
        # sleep for a small amount
        time.sleep(settings.SERVER_SLEEP)
# if this is the main thread of execution start the model server
# process
if __name__ == "__main__":
    classify_process()

run_model_server.py 文件包含我們的classify_process 函數(shù)。 這個函數(shù)加載我們的模型勺爱,然后對一批圖像運行預(yù)測晃琳。 這個過程最好在 GPU 上執(zhí)行,但也可以使用 CPU琐鲁。

在這個例子中卫旱,為了簡單起見,我們將使用在 ImageNet 數(shù)據(jù)集上預(yù)訓(xùn)練的 ResNet50围段。 您可以修改classify_process 以利用您自己的深度學(xué)習(xí)模型顾翼。

WSGI 配置

# add our app to the system path
import sys
sys.path.insert(0, "/var/www/html/keras-complete-rest-api")
# import the application and away we go...
from run_web_server import app as application

壓力測試

# import the necessary packages
from threading import Thread
import requests
import time
# initialize the Keras REST API endpoint URL along with the input
# image path
KERAS_REST_API_URL = "http://localhost/predict"
IMAGE_PATH = "jemma.png"
# initialize the number of requests for the stress test along with
# the sleep amount between requests
NUM_REQUESTS = 500
SLEEP_COUNT = 0.05
def call_predict_endpoint(n):
    # 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 sucessful
    if r["success"]:
        print("[INFO] thread {} OK".format(n))
    # otherwise, the request failed
    else:
        print("[INFO] thread {} FAILED".format(n))
# loop over the number of threads
for i in range(0, NUM_REQUESTS):
    # start a new thread to call the API
    t = Thread(target=call_predict_endpoint, args=(i,))
    t.daemon = True
    t.start()
    time.sleep(SLEEP_COUNT)
# insert a long sleep so we can wait until the server is finished
# processing the images
time.sleep(300)

我們的 stress_test.py 腳本將幫助我們測試服務(wù)器并確定其限制。 我總是建議對您的深度學(xué)習(xí) REST API 服務(wù)器進(jìn)行壓力測試奈泪,以便您知道是否(更重要的是适贸,何時)需要添加額外的 GPU、CPU 或 RAM段磨。 此腳本啟動 NUM_REQUESTS 線程和 POST 到 /predict 端點取逾。 這取決于我們的 Flask 網(wǎng)絡(luò)應(yīng)用程序。

編譯安裝Redis

Redis 是一種高效的內(nèi)存數(shù)據(jù)庫苹支,它將充當(dāng)我們的隊列/消息代理砾隅。 獲取和安裝Redis非常簡單:

$ wget http://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
$ cd redis-stable
$ make
$ sudo make install

安裝 Apache Web 服務(wù)器

可以使用其他 Web 服務(wù)器,例如 nginx债蜜,但由于我對 Apache 有更多的經(jīng)驗(因此通常更熟悉 Apache)晴埂,因此我將在此示例中使用 Apache究反。 Apache 可以通過以下方式安裝:

$ sudo apt-get install apache2

如果您使用 Python 3 創(chuàng)建了一個虛擬環(huán)境,您將需要安裝 Python 3 WSGI + Apache 模塊:

$ sudo apt-get install libapache2-mod-wsgi-py3
$ sudo a2enmod wsgi

要驗證是否安裝了 Apache儒洛,請打開瀏覽器并輸入 Web 服務(wù)器的 IP 地址精耐。 如果您看不到服務(wù)器啟動畫面,請確保打開端口 80 和端口 5000琅锻。 就我而言卦停,我服務(wù)器的 IP 地址是 54.187.46.215(你的會有所不同)。 在瀏覽器中輸入這個恼蓬,我看到:

干貨惊完!生產(chǎn)環(huán)境中使用Keras、Redis处硬、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

Sym-link鏈接您的 Flask + 深度學(xué)習(xí)應(yīng)用程序

默認(rèn)情況下小槐,Apache 提供來自 /var/www/html 的內(nèi)容。 我建議創(chuàng)建一個從 /var/www/html 到 Flask Web 應(yīng)用程序的符號鏈接荷辕。 我已將我的深度學(xué)習(xí) + Flask 應(yīng)用程序上傳到名為 keras-complete-rest-api 的目錄中的主目錄:

$ ls ~
keras-complete-rest-api

我可以通過以下方式將其符號鏈接到 /var/www/html:

$ cd /var/www/html/
$ sudo ln -s ~/keras-complete-rest-api keras-complete-rest-api

更新您的 Apache 配置以指向 Flask 應(yīng)用程序

為了將 Apache 配置為指向我們的 Flask 應(yīng)用程序凿跳,我們需要編輯
/etc/apache2/sites-available/000-default.conf 。 在你最喜歡的文本編輯器中打開(這里我將使用 vi ):

$ sudo vi /etc/apache2/sites-available/000-default.conf

在文件的頂部提供您的 WSGIPythonHome(Python bin 目錄的路徑)和 WSGIPythonPath(Python 站點包目錄的路徑)配置:

WSGIPythonHome /home/ubuntu/.virtualenvs/keras_flask/bin
WSGIPythonPath /home/ubuntu/.virtualenvs/keras_flask/lib/python3.5/site-packages
<VirtualHost *:80>
    ...
</VirtualHost>

在 Ubuntu 18.04 上疮方,您可能需要將第一行更改為:

WSGIPythonHome /home/ubuntu/.virtualenvs/keras_flask

由于我們在本示例中使用 Python 虛擬環(huán)境(我將我的虛擬環(huán)境命名為 keras_flask )控嗜,因此我們?yōu)?Python 虛擬環(huán)境提供 bin 和 site-packages 目錄的路徑。 然后在 的正文中案站,在 ServerAdmin 和 DocumentRoot 之后躬审,添加:

<VirtualHost *:80>
    ...

    WSGIDaemonProcess keras_rest_api_app threads=10
    WSGIScriptAlias / /var/www/html/keras-complete-rest-api/keras_rest_api_app.wsgi

    <Directory /var/www/html/keras-complete-rest-api>
        WSGIProcessGroup keras_rest_api_app
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>

    ...
</VirtualHost>

符號鏈接 CUDA 庫(可選,僅限 GPU)

如果您將 GPU 用于深度學(xué)習(xí)并希望利用 CUDA(您為什么不這樣做)蟆盐,不幸的是,Apache 不了解 /usr/local/cuda/lib64 中的 CUDA 的 *.so 庫遭殉。

我不確定什么是“最正確”的方式向 Apache 指示這些 CUDA 庫所在的位置石挂,但“完全破解”解決方案是將所有文件從 /usr/local/cuda/lib64 符號鏈接到 /usr/lib :

$ cd /usr/lib
$ sudo ln -s /usr/local/cuda/lib64/* ./

重新啟動 Apache Web 服務(wù)器

編輯完 Apache 配置文件并可選擇符號鏈接 CUDA 深度學(xué)習(xí)庫后,請務(wù)必通過以下方式重新啟動 Apache 服務(wù)器:

$ sudo service apache2 restart

測試您的 Apache Web 服務(wù)器 + 深度學(xué)習(xí)端點

要測試 Apache 是否已正確配置以提供 Flask + 深度學(xué)習(xí)應(yīng)用程序险污,請刷新您的 Web 瀏覽器:

干貨痹愚!生產(chǎn)環(huán)境中使用Keras、Redis蛔糯、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

您現(xiàn)在應(yīng)該看到文本“歡迎使用 PyImageSearch Keras REST API拯腮!” 在您的瀏覽器中。 一旦你達(dá)到這個階段蚁飒,你的 Flask 深度學(xué)習(xí)應(yīng)用程序就應(yīng)該準(zhǔn)備好了动壤。 綜上所述,如果您遇到任何問題淮逻,請確保參考下一節(jié)……

提示:如果遇到問題琼懊,請監(jiān)控 Apache 錯誤日志

多年來阁簸,我一直在使用 Python + Web 框架,例如 Flask 和 Django哼丈,但在正確配置環(huán)境時仍然會出錯启妹。 雖然我希望有一種防彈的方法來確保一切順利,但事實是醉旦,在此過程中可能會出現(xiàn)一些問題饶米。 好消息是 WSGI 將 Python 事件(包括失敗)記錄到服務(wù)器日志中车胡。 在 Ubuntu 上檬输,Apache 服務(wù)器日志位于 /var/log/apache2/ :

$ ls /var/log/apache2
access.log error.log other_vhosts_access.log

調(diào)試時,我經(jīng)常打開一個運行的終端:

$ tail -f /var/log/apache2/error.log

......所以我可以看到第二個錯誤滾滾而來吨拍。 使用錯誤日志幫助您在服務(wù)器上啟動和運行 Flask褪猛。

啟動您的深度學(xué)習(xí)模型服務(wù)器

您的 Apache 服務(wù)器應(yīng)該已經(jīng)在運行。 如果沒有羹饰,您可以通過以下方式啟動它:

$ sudo service apache2 start

然后伊滋,您將要啟動 Redis 存儲:

$ redis-server

并在單獨的終端中啟動 Keras 模型服務(wù)器:

$ python run_model_server.py
* Loading model...
...
* Model loaded

從那里嘗試向您的深度學(xué)習(xí) API 服務(wù)提交示例圖像:

$ curl -X POST -F image=@jemma.png 'http://localhost/predict'
{
  "predictions": [
    {
      "label": "beagle", 
      "probability": 0.9461532831192017
    }, 
    {
      "label": "bluetick", 
      "probability": 0.031958963721990585
    }, 
    {
      "label": "redbone", 
      "probability": 0.0066171870566904545
    }, 
    {
      "label": "Walker_hound", 
      "probability": 0.003387963864952326
    }, 
    {
      "label": "Greater_Swiss_Mountain_dog", 
      "probability": 0.0025766845792531967
    }
  ], 
  "success": true
}

如果一切正常,您應(yīng)該會收到來自深度學(xué)習(xí) API 模型服務(wù)器的格式化 JSON 輸出队秩,其中包含類別預(yù)測 + 概率笑旺。

干貨!生產(chǎn)環(huán)境中使用Keras馍资、Redis筒主、Flask 和 Apache 進(jìn)行深度學(xué)習(xí)

對您的深度學(xué)習(xí) REST API 進(jìn)行壓力測試

當(dāng)然,這只是一個例子鸟蟹。 讓我們對深度學(xué)習(xí) REST API 進(jìn)行壓力測試乌妙。 打開另一個終端并執(zhí)行以下命令:

$ python stress_test.py 
[INFO] thread 3 OK
[INFO] thread 0 OK
[INFO] thread 1 OK
...
[INFO] thread 497 OK
[INFO] thread 499 OK
[INFO] thread 498 OK

在 run_model_server.py 輸出中,您將開始看到記錄到終端的以下行:

* Batch size: (4, 224, 224, 3)
* Batch size: (9, 224, 224, 3)
* Batch size: (9, 224, 224, 3)
* Batch size: (8, 224, 224, 3)
...
* Batch size: (2, 224, 224, 3)
* Batch size: (10, 224, 224, 3)
* Batch size: (7, 224, 224, 3)

即使每 0.05 秒有一個新請求建钥,我們的批次大小也不會超過每批次約 10-12 張圖像藤韵。 我們的模型服務(wù)器可以輕松處理負(fù)載而不會出汗,并且可以輕松擴(kuò)展到此之外熊经。 如果您確實使服務(wù)器超載(可能是您的批大小太大并且 GPU 內(nèi)存不足并顯示錯誤消息)泽艘,您應(yīng)該停止服務(wù)器,并使用 Redis CLI 清除隊列:

$ redis-cli
> FLUSHALL

從那里您可以調(diào)整 settings.py 和
/etc/apache2/sites-available/000-default.conf 中的設(shè)置镐依。 然后您可以重新啟動服務(wù)器匹涮。

將您自己的深度學(xué)習(xí)模型部署到生產(chǎn)環(huán)境的建議

我能給出的最好建議之一是將您的數(shù)據(jù),尤其是 Redis 服務(wù)器槐壳,靠近 GPU然低。 您可能想啟動一個具有數(shù)百 GB RAM 的巨型 Redis 服務(wù)器來處理多個圖像隊列并為多個 GPU 機(jī)器提供服務(wù)。 這里的問題將是 I/O 延遲和網(wǎng)絡(luò)開銷。

假設(shè) 224 x 224 x 3 圖像表示為 float32 數(shù)組脚翘,32 張圖像的批量大小將是 ~19MB 的數(shù)據(jù)灼卢。 這意味著對于來自模型服務(wù)器的每個批處理請求,Redis 將需要提取 19MB 的數(shù)據(jù)并將其發(fā)送到服務(wù)器来农。 在快速切換上鞋真,這沒什么大不了的,但您應(yīng)該考慮在同一臺服務(wù)器上同時運行模型服務(wù)器和 Redis沃于,以使數(shù)據(jù)靠近 GPU涩咖。

總結(jié)

在今天的博文中,我們學(xué)習(xí)了如何使用 Keras繁莹、Redis檩互、Flask 和 Apache 將深度學(xué)習(xí)模型部署到生產(chǎn)環(huán)境中。 我們在這里使用的大多數(shù)工具都是可以互換的咨演。您可以將 TensorFlow 或 PyTorch 換成 Keras闸昨。可以使用 Django 代替 Flask薄风。 Nginx 可以換成 Apache饵较。

我不建議換掉的唯一工具是 Redis。 Redis 可以說是內(nèi)存數(shù)據(jù)存儲的最佳解決方案遭赂。除非您有不使用 Redis 的特定原因循诉,否則我建議您使用 Redis 進(jìn)行排隊操作。 最后撇他,我們對深度學(xué)習(xí) REST API 進(jìn)行了壓力測試茄猫。

我們向我們的服務(wù)器提交了總共 500 個圖像分類請求,每個請求之間有 0.05 秒的延遲——我們的服務(wù)器沒有分階段(CNN 的批量大小從未超過約 37%)困肩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末划纽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锌畸,更是在濱河造成了極大的恐慌阿浓,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋绽,死亡現(xiàn)場離奇詭異,居然都是意外死亡筋蓖,警方通過查閱死者的電腦和手機(jī)卸耘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘咖,“玉大人蚣抗,你說我怎么就攤上這事。” “怎么了翰铡?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵钝域,是天一觀的道長。 經(jīng)常有香客問我锭魔,道長例证,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任迷捧,我火速辦了婚禮织咧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漠秋。我一直安慰自己笙蒙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布庆锦。 她就那樣靜靜地躺著捅位,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搂抒。 梳的紋絲不亂的頭發(fā)上艇搀,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音燕耿,去河邊找鬼中符。 笑死,一個胖子當(dāng)著我的面吹牛誉帅,可吹牛的內(nèi)容都是我干的淀散。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚜锨,長吁一口氣:“原來是場噩夢啊……” “哼档插!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亚再,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤郭膛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氛悬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體则剃,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年如捅,在試婚紗的時候發(fā)現(xiàn)自己被綠了棍现。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡镜遣,死狀恐怖己肮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤谎僻,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布娄柳,位于F島的核電站,受9級特大地震影響艘绍,放射性物質(zhì)發(fā)生泄漏赤拒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一鞍盗、第九天 我趴在偏房一處隱蔽的房頂上張望需了。 院中可真熱鬧,春花似錦般甲、人聲如沸肋乍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墓造。三九已至,卻和暖如春锚烦,著一層夾襖步出監(jiān)牢的瞬間觅闽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工涮俄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蛉拙,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓彻亲,卻偏偏與公主長得像孕锄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苞尝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容