Deploy Machine Learning model in production with Tensorflow Serving

Posted by V on November 10, 2020

Có một bài hướng dẫn khá xịn ở đây nhưng khá dài. Mình sẽ viết 1 đoạn ngắn hơn tập trung vào thực hành serving một model cụ thể.

Hiểu đơn giản khi đưa model lên môi trường product cần serving để:

  • Tách biệt model với codebase của backend
  • Dễ dàng thay đổi phiên bản mới của model, hoặc rollback về phiên bản cũ
  • Serving nhiều model cùng lúc với 1 file config
  • Tăng hiệu năng xử lý
  • Batching

Các bước triển khai

  • Bước 1: convert TF model về định dạng TF Savemodel
  • Bước 2: Pull tensorflow serving docker

Một ví dụ với một model đơn giản

Build/Train
# -*- coding: utf-8 -*-
"""CNNs.ipynb

Automatically generated by Colaboratory.

Original file is located at

    https://colab.research.google.com/drive/1ibfKtpxC_hIhZlPbefCoqpAS7jTdyiFw
"""

import tensorflow as tf

# Load MNIST data
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# Preprocessing

x_train = x_train / 255.0
x_test = x_test / 255.0

# Add one domention to make 3D images

x_train = x_train[...,tf.newaxis]
x_test = x_test[...,tf.newaxis]

# Track the data type

dataType, dataShape = x_train.dtype, x_train.shape
print(f"Data type and shape x_train: {dataType} {dataShape}")
labelType, labelShape = y_train.dtype, y_train.shape
print(f"Data type and shape y_train: {labelType} {labelShape}")

im_list = []
n_samples_to_show = 16
c = 0
for i in range(n_samples_to_show):
  im_list.append(x_train[i])
# Visualization

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure(figsize=(4., 4.))
# Ref: https://matplotlib.org/3.1.1/gallery/axes_grid1/simple_axesgrid.html

grid = ImageGrid(fig, 111,  # similar to subplot(111)
                 nrows_ncols=(4, 4),  # creates 2x2 grid of axes
                 axes_pad=0.1,  # pad between axes in inch.
                 )
# Show image grid

for ax, im in zip(grid, im_list):
    # Iterating over the grid returns the Axes.
    ax.imshow(im[:,:,0], 'gray')
plt.show()

"""## Training"""

# Model building

NUM_CLASSES = 10
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(16, (3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Conv2D(32, (3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='sigmoid')]
    )
import os
checkpoint_path = "training/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Create a callback that saves the model's weights

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
                                                 save_weights_only=True,
                                                 verbose=1)

# Compiling the model with the high-level keras

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])

# Model training

model.fit(x_train, y_train, epochs=5, callbacks=[cp_callback])


"""## Evaluation"""

eval_loss, eval_acc = model.evaluate(x_test,  y_test, verbose=1)
print('Eval accuracy percentage: {:.2f}'.format(eval_acc * 100))
  • Trọng số được lưu dưới dạng checkpoint training/cp.ckpt. Thực hiện bước 1 (convert TF model về định dạng TF Savemodel)
    Build/load model
NUM_CLASSES = 10
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(16, (3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Conv2D(32, (3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='sigmoid')]
    )

model.load_weights(checkpoint_path)

SaveModel là định dạng lưu toàn bộ trọng số và các phép tính toán, nhờ vậy khi sử dụng không cần build lại model bằng code. Muốn lưu model sang dạng này cần biết tensor đầu vào và đầu ra mà chúng ta cần. Vậy, ta xem list tensor của model


[n.name for n in tf.get_default_graph().as_graph_def().node]

output

['conv2d_input',
 'conv2d/kernel/Initializer/random_uniform/shape',
 'conv2d/kernel/Initializer/random_uniform/min',
 'conv2d/kernel/Initializer/random_uniform/max',
 'conv2d/kernel/Initializer/random_uniform/RandomUniform',
 'conv2d/kernel/Initializer/random_uniform/sub',
 'conv2d/kernel/Initializer/random_uniform/mul',
 'conv2d/kernel/Initializer/random_uniform',
 'conv2d/kernel',
 'conv2d/kernel/IsInitialized/VarIsInitializedOp',
...
 'dense_3/bias',
 'dense_3/bias/IsInitialized/VarIsInitializedOp',
 'dense_3/bias/Assign',
 'dense_3/bias/Read/ReadVariableOp',
 'dense_3/MatMul/ReadVariableOp',
 'dense_3/MatMul',
 'dense_3/BiasAdd/ReadVariableOp',
 'dense_3/BiasAdd',
 'dense_3/Sigmoid',
 'Const_2',
 'RestoreV2/tensor_names',
 'RestoreV2/shape_and_slices',
...

Dựa vào model mà chúng ta build có thể nhận ra đầu vào là tensor 'conv2d_input' và đầu ra là 'dense_3/Sigmoid'

Convert sang SaveModel bằng đoạn code sau
graph = tf.get_default_graph()
inputImgs = graph.get_tensor_by_name('conv2d_input:0')
pred_ = graph.get_tensor_by_name('dense_3/Sigmoid:0')

model_version = '1'
export_model_dir = "models/serving/versions"


export_path_base = export_model_dir

export_path = os.path.join(
            tf.compat.as_bytes(export_path_base),
            tf.compat.as_bytes(str(model_version)))
print('Exporting trained model to', export_path)



builder = tf.saved_model.builder.SavedModelBuilder(export_path)

tensor_input = tf.saved_model.utils.build_tensor_info(inputImgs)
tensor_output = tf.saved_model.utils.build_tensor_info(pred_)

prediction_signature = (tf.saved_model.signature_def_utils.build_signature_def(
                inputs={'image': tensor_input},
                outputs={'decoded': tensor_output},
                method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

with tf.Session(graph=tf.Graph()) as sess:
  builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING],
                                      signature_def_map={'predict_classes':prediction_signature,})

builder.save()

Kiểm tra folder models/serving/versions/1 chúng ta đã có TFSaveModel

Bước 2: Pull tensorflow serving docker

Trong thư mục models tạo file batching_parameters.txt
max_batch_size { value: 1024 }
batch_timeout_micros { value: 1000 }
Và file models.config
 model_config_list: {
  config: {
      name: "mobile",
      base_path: "models/serving/versions",
      model_platform: "tensorflow"
  }
}
Cuối cùng là pull docker
#pull tensorflow serving docker
##Step 1
docker pull tensorflow/serving:1.13.0-gpu

##Step 2
#create image # run
sudo nvidia-docker run -d --name serving_base_v3 tensorflow/serving:1.13.0-gpu

##Step 3

sudo docker cp ./serving serving_base_v3:/models/mobile 
sudo docker cp models.config serving_base_v3:/models/models.config 
sudo docker cp batching_parameters.txt serving_base_v3:/models/batching_parameters.txt 
sudo docker commit serving_base_v3 mobile_serving:v1

##Step 4

sudo docker kill serving_base_v3
#
##Step 5

sudo nvidia-docker run -d -p 8500:8500 mobile_serving:v1 --model_config_file=/models/models.config --enable_batching=true --batching_parameters_file=/models/batching_parameters.txt --per_process_gpu_memory_fraction=0.5  

Mọi model bạn đều có thể làm theo các bước trên kể cả phức tạp. Nhưng nếu bạn không biết đầu vào, đầu ra của model thì chịu - đùa thôi - thì thử. Goodluck. code coclab