1.1Webサービスと技術フレームワーク
例としてResNet50事前トレーニングモデルを取り上げて、軽量の深層学習モデルの展開を示し、より単純な画像分類RESTAPIを記述しましょう。主な技術フレームワークはKeras + Flask + Redisです。その中で、Kerasはモデルフレームワーク、FlaskはバックエンドWebフレームワーク、Redisは画像をKey-Value形式で保存するためのデータベースです。主要なパッケージバージョン:
tensorflow 1.14
keras 2.2.4
flask 1.1.1
redis 3.3.8
Webサービスについて簡単に説明します。Webアプリケーションの本質は、クライアントがHTTP要求を送信し、サーバーが応答としてHTMLドキュメントを生成し、要求を受信した後にそれをクライアントに返すプロセスにすぎません。ディープラーニングモデルをデプロイする場合、ほとんどの場合、フロントエンドページを作成する必要はありません。フロントエンドページは、通常、RESTAPIの形式で開発呼び出し用に提供されます。では、APIとは何ですか?非常に単純です。URLがHTMLではなく、マシンで直接解析できるデータを返す場合、そのようなURLはAPIと見なすことができます。
最初にRedisサービスを開始します。
redis-server
1.2サービス構成
いくつかの構成パラメーターを定義します。
IMAGE_WIDTH = 224
IMAGE_HEIGHT = 224
IMAGE_CHANS = 3
IMAGE_DTYPE = "float32"
IMAGE_QUEUE = "image_queue"
BATCH_SIZE = 32
SERVER_SLEEP = 0.25
CLIENT_SLEEP = 0.25
入力イメージのサイズ、タイプ、batch_sizeサイズ、およびRedisイメージのキュー名を指定します。
次に、Flaskオブジェクトのインスタンスを作成し、Redisデータベース接続を確立します。
app = flask.Flask(__name__)
db = redis.StrictRedis(host="localhost", port=6379, db=0)
model = None
画像データはnumpy配列としてRedisに直接保存できないため、画像をデータベースに保存する前にシリアル化およびエンコードする必要があり、データベースから取得するときに逆シリアル化およびデコードできます。エンコード機能とデコード機能を別々に定義します。
def base64_encode_image(img):
return base64.b64encode(img).decode("utf-8")
def base64_decode_image(img, dtype, shape):
if sys.version_info.major == 3:
img = bytes(img, encoding="utf-8")
img = np.frombuffer(base64.decodebytes(img), dtype=dtype)
img = img.reshape(shape)
return img
また、予測する画像は簡単な前処理が必要です。前処理機能は次のように定義されています。
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)
# expand image as one batch like shape (1, c, w, h)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)
# return the processed image
return image
1.3予測インターフェースの定義
準備作業が完了したら、次の2つの部分は、モデル予測部分とアプリバックエンド応答部分の主要な2つの部分です。まず、モデル予測関数を次のように定義します。
def classify_process():
# 导入模型
print("* Loading model...")
model = ResNet50(weights="imagenet")
print("* Model loaded")
while True:
# 从数据库中创建预测图像队列
queue = db.lrange(IMAGE_QUEUE, 0, BATCH_SIZE - 1)
imageIDs = []
batch = None
# 遍历队列
for q in queue:
# 获取队列中的图像并反序列化解码
q = json.loads(q.decode("utf-8"))
image = base64_decode_image(q["image"], IMAGE_DTYPE,
(1, IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_CHANS))
# 检查batch列表是否为空
if batch is None:
batch = image
# 合并batch
else:
batch = np.vstack([batch, image])
# 更新图像ID
imageIDs.append(q["id"])
if len(imageIDs) > 0:
print("* Batch size: {}".format(batch.shape))
preds = model.predict(batch)
results = imagenet_utils.decode_predictions(preds)
# 遍历图像ID和预测结果并打印
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)
# 保存结果到数据库
db.set(imageID, json.dumps(output))
# 从队列中删除已预测过的图像
db.ltrim(IMAGE_QUEUE, len(imageIDs), -1)
time.sleep(SERVER_SLEEP)
次に、アプリサービスを定義します。
@app.route("/predict", methods=["POST"])
def predict():
# 初始化数据字典
data = {"success": False}
# 确保图像上传方式正确
if flask.request.method == "POST":
if flask.request.files.get("image"):
# 读取图像数据
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))
image = prepare_image(image, (IMAGE_WIDTH, IMAGE_HEIGHT))
# 将数组以C语言存储顺序存储
image = image.copy(order="C")
# 生成图像ID
k = str(uuid.uuid4())
d = {"id": k, "image": base64_encode_image(image)}
db.rpush(IMAGE_QUEUE, json.dumps(d))
# 运行服务
while True:
# 获取输出结果
output = db.get(k)
if output is not None:
output = output.decode("utf-8")
data["predictions"] = json.loads(output)
db.delete(k)
break
time.sleep(CLIENT_SLEEP)
data["success"] = True
return flask.jsonify(data)
FlaskはPythonデコレータを使用して、要求されたURLを内部でターゲット関数に自動的に関連付けるため、Webサービスをすばやく構築できます。
1.4インターフェーステスト
サービスの設定後、写真を使用して効果をテストできます。
curl -X POST -F [email protected] 'http://127.0.0.1:5000/predict'
モデルから戻る:
予測結果が返されます。
最後に、構築されたサービスでストレステストを実行して、サービスの同時実行性やその他のパフォーマンスを確認し、ストレステストファイルstress_test.pyを次のように定義できます。
from threading import Thread
import requests
import time
# 请求的URL
KERAS_REST_API_URL = "http://127.0.0.1:5000/predict"
# 测试图片
IMAGE_PATH = "test.jpg"
# 并发数
NUM_REQUESTS = 500
# 请求间隔
SLEEP_COUNT = 0.05
def call_predict_endpoint(n):
# 上传图像
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}
# 提交请求
r = requests.post(KERAS_REST_API_URL, files=payload).json()
# 确认请求是否成功
if r["success"]:
print("[INFO] thread {} OK".format(n))
else:
print("[INFO] thread {} FAILED".format(n))
# 多线程进行
for i in range(0, NUM_REQUESTS):
# 创建线程来调用api
t = Thread(target=call_predict_endpoint, args=(i,))
t.daemon = True
t.start()
time.sleep(SLEEP_COUNT)
time.sleep(300)
テスト結果は次のとおりです。