TensorflowServing(服务器端部署模型)

TensorflowServing(服务器端部署模型)

转载于https://tf.wiki/en/basic/models.html#mlp
写了两个可用的py文件:

训练文件

# -*- coding: utf-8 -*-
"""
Created on Sat Nov  2 17:15:30 2019

@author: MyPC
"""

import tensorflow as tf
import numpy as np
class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的图像默认为uint8(0-255的数字)。以下代码将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
        self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1)        # [10000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)    # [60000]
        self.test_label = self.test_label.astype(np.int32)      # [10000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
        # 从数据集中随机取出batch_size个元素并返回
        index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
        return self.train_data[index, :], self.train_label[index]
		
class MLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.flatten = tf.keras.layers.Flatten()    # Flatten层将除第一维(batch_size)以外的维度展平
        self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
        self.dense2 = tf.keras.layers.Dense(units=10)
    @tf.function(input_signature=[tf.TensorSpec([None, 28, 28, 1], tf.float32)])
    def call(self, inputs):         # [batch_size, 28, 28, 1]
        x = self.flatten(inputs)    # [batch_size, 784]
        x = self.dense1(x)          # [batch_size, 100]
        x = self.dense2(x)          # [batch_size, 10]
        output = tf.nn.softmax(x)
        return output
		
num_epochs = 1
batch_size = 50
learning_rate = 0.001

model = MLP()

data_loader = MNISTLoader()
model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    loss=tf.keras.losses.sparse_categorical_crossentropy,
    metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
model.fit(data_loader.train_data, data_loader.train_label, epochs=num_epochs, batch_size=batch_size)
tf.saved_model.save(model, "saved_with_signature/1", signatures={"call": model.call})

测试文件

import tensorflow as tf
import numpy as np
class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的图像默认为uint8(0-255的数字)。以下代码将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
        self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1)        # [10000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)    # [60000]
        self.test_label = self.test_label.astype(np.int32)      # [10000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
        # 从数据集中随机取出batch_size个元素并返回
        index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
        return self.train_data[index, :], self.train_label[index]

batch_size = 50

model = tf.saved_model.load("saved/1")
data_loader = MNISTLoader()
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
num_batches = int(data_loader.num_test_data // batch_size)
for batch_index in range(num_batches):
    start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
    y_pred = model(data_loader.test_data[start_index: end_index])
    sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
  1. TensorflowServing安装

添加Google的TensorFlow Serving源

echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list

添加gpg key

curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -

更新源后,即可使用 apt-get 安装 TensorFlow Serving

sudo apt-get update
sudo apt-get install tensorflow-model-server

TensorFlow Serving 可以直接读取 SavedModel 格式的模型进行部署(导出模型到 SavedModel 文件的方法见 前文 )。使用以下命令即可:

tensorflow_model_server \
    --rest_api_port=端口号(如8501 \
    --model_name=模型名 \
    --model_base_path="SavedModel格式模型的文件夹绝对地址(不含版本号)"

Keras Sequential 模式模型的部署
由于 Sequential 模式的输入和输出都很固定,因此这种类型的模型很容易部署,无需其他额外操作。例如,要将 前文使用 SavedModel 导出的 MNIST 手写体识别模型 (使用 Keras Sequential 模式建立)以 MLP 的模型名在 8501 端口进行部署,可以直接使用以下命令:

tensorflow_model_server \
    --rest_api_port=8501 \
    --model_name=MLP \
    --model_base_path="/home/.../.../saved"  # 文件夹绝对地址根据自身情况填写,无需加入版本号

自定义 Keras 模型的部署

使用继承 tf.keras.Model 类建立的自定义 Keras 模型的自由度相对更高。因此当使用 TensorFlow Serving 部署模型时,对导出的 SavedModel 文件也有更多的要求:

需要导出到 SavedModel 格式的方法(比如 call )不仅需要使用 @tf.function 修饰,还要在修饰时指定 input_signature 参数,以显式说明输入的形状。该参数传入一个由 tf.TensorSpec 组成的列表,指定每个输入张量的形状和类型。例如,对于 MNIST 手写体数字识别,我们的输入是一个 [None, 28, 28, 1] 的四维张量( None 表示第一维即 Batch Size 的大小不固定),此时我们可以将模型的 call 方法做以下修饰:

class MLP(tf.keras.Model):
    ...

    @tf.function(input_signature=[tf.TensorSpec([None, 28, 28, 1], tf.float32)])
    def call(self, inputs):
        ...

在将模型使用 tf.saved_model.save 导出时,需要通过 signature 参数提供待导出的函数的签名(Signature)。简单说来,由于自定义的模型类里可能有多个方法都需要导出,因此,需要告诉 TensorFlow Serving 每个方法在被客户端调用时分别叫做什么名字。例如,如果我们希望客户端在调用模型时使用 call 这一签名来调用 model.call 方法时,我们可以在导出时传入 signature 参数,以 dict 的键值对形式告知导出的方法对应的签名,代码如下:

model = MLP()
...
tf.saved_model.save(model, "saved_with_signature/1", signatures={"call": model.call})

以上两步均完成后,即可使用以下命令部署:

tensorflow_model_server \
    --rest_api_port=8501 \
    --model_name=MLP \
    --model_base_path="/home/.../.../saved_with_signature"  # 修改为自己模型的绝对地址

在客户端调用以 TensorFlow Serving 部署的模型

TensorFlow Serving 支持以 gRPC 和 RESTful API 调用以 TensorFlow Serving 部署的模型。本手册主要介绍较为通用的 RESTful API 方法。

RESTful API 以标准的 HTTP POST 方法进行交互,请求和回复均为 JSON 对象。为了调用服务器端的模型,我们在客户端向服务器发送以下格式的请求:

服务器 URI: http://服务器地址:端口号/v1/models/模型名:predict

请求内容:

{
    "signature_name": "需要调用的函数签名(Sequential模式不需要)",
    "instances": 输入数据
}

回复为:

{
    "predictions": 返回值
}

Python 客户端示例
以下示例使用 Python 的 Requests 库 (你可能需要使用 pip install requests 安装该库)向本机的 TensorFlow Serving 服务器发送 MNIST 测试集的前 10 幅图像并返回预测结果,同时与测试集的真实标签进行比较。

import json
import numpy as np
import requests
from zh.model.utils import MNISTLoader


data_loader = MNISTLoader()
data = json.dumps({
    "instances": data_loader.test_data[0:3].tolist()
    })
headers = {"content-type": "application/json"}
json_response = requests.post(
    'http://localhost:8501/v1/models/MLP:predict',
    data=data, headers=headers)
predictions = np.array(json.loads(json_response.text)['predictions'])
print(np.argmax(predictions, axis=-1))
print(data_loader.test_label[0:10])

对于自定义的 Keras 模型,在发送的数据中加入 signature_name 键值即可,即将上面代码的 data 建立过程改为

data = json.dumps({
    "signature_name": "call",
    "instances": data_loader.test_data[0:10].tolist()
    })

模型的设计和导入

一 实现一个简单的 MNISTLoader 类来读取 MNIST 数据集数据

class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的图像默认为uint8(0-255的数字)。以下代码将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
        self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1)        # [10000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)    # [60000]
        self.test_label = self.test_label.astype(np.int32)      # [10000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
        # 从数据集中随机取出batch_size个元素并返回
        index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
        return self.train_data[index, :], self.train_label[index]

init()函数
MNIST中的图像默认为uint8(0-255的数字)。以下代码将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
get_batch(self, batch_size)函数
从数据集中随机取出batch_size个元素并返回

模型构建Model construction: tf.keras.Model and tf.keras.layers



class MLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.flatten = tf.keras.layers.Flatten()    # Flatten层将除第一维(batch_size)以外的维度展平
        self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
        self.dense2 = tf.keras.layers.Dense(units=10)

    def call(self, inputs):         # [batch_size, 28, 28, 1]
        x = self.flatten(inputs)    # [batch_size, 784]
        x = self.dense1(x)          # [batch_size, 100]
        x = self.dense2(x)          # [batch_size, 10]
        output = tf.nn.softmax(x)
        return output

三 定义一些超参数

num_epochs = 5
batch_size = 50
learning_rate = 0.001

四 实例化模型和数据读取类,并实例化一个 tf.keras.optimizer 的优化器(这里使用常用的 Adam 优化器):

model = MLP()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)

然后迭代进行以下步骤:

从 DataLoader 中随机取一批训练数据;

将这批数据送入模型,计算出模型的预测值;

将模型预测值与真实值进行比较,计算损失函数(loss)。这里使用 tf.keras.losses 中的交叉熵函数作为损失函数;

计算损失函数关于模型变量的导数;

将求出的导数值传入优化器,使用优化器的 apply_gradients 方法更新模型参数以最小化损失函数(优化器的详细使用方法见 前章 )。

具体代码实现如下:

   num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
    for batch_index in range(num_batches):
        X, y = data_loader.get_batch(batch_size)
        with tf.GradientTape() as tape:
            y_pred = model(X)
            loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
            loss = tf.reduce_mean(loss)
            print("batch %d: loss %f" % (batch_index, loss.numpy()))
        grads = tape.gradient(loss, model.variables)
        optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值